diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk index 7c86ff81..028bfae0 100644 --- a/build/android/jni/Android.mk +++ b/build/android/jni/Android.mk @@ -78,10 +78,9 @@ ifndef NDEBUG LOCAL_CFLAGS += -g -D_DEBUG -O0 -fno-omit-frame-pointer else +ifeq ($(TARGET_ARCH_ABI),armeabi-v7a-hard) LOCAL_CFLAGS += -mfpu=vfpv3-d16 -D_NDK_MATH_NO_SOFTFP=1 -mfloat-abi=hard -march=armv7-a -Ofast -fno-fast-math -fdata-sections -ffunction-sections -fmodulo-sched -fmodulo-sched-allow-regmoves LOCAL_LDFLAGS = -Wl,--no-warn-mismatch,--gc-sections -lm_hard -# ToDo - disable for x86! - endif ifdef GPROF @@ -109,7 +108,6 @@ LOCAL_C_INCLUDES := \ deps/luajit/src LOCAL_SRC_FILES := \ - jni/src/areastore.cpp \ jni/src/ban.cpp \ jni/src/camera.cpp \ jni/src/cavegen.cpp \ @@ -218,6 +216,7 @@ LOCAL_SRC_FILES := \ jni/src/version.cpp \ jni/src/voxel.cpp \ jni/src/voxelalgorithms.cpp \ + jni/src/util/areastore.cpp \ jni/src/util/auth.cpp \ jni/src/util/base64.cpp \ jni/src/util/directiontables.cpp \ diff --git a/builtin/common/filterlist.lua b/builtin/common/filterlist.lua index 2b24dd3e..7556b2b4 100644 --- a/builtin/common/filterlist.lua +++ b/builtin/common/filterlist.lua @@ -189,7 +189,7 @@ function filterlist.process(self) for k,v in pairs(self.m_raw_list) do if self.m_filtercriteria == nil or self.m_filter_fct(v,self.m_filtercriteria) then - table.insert(self.m_processed_list,v) + self.m_processed_list[#self.m_processed_list + 1] = v end end diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 08a23043..e4653d41 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -2,7 +2,6 @@ -------------------------------------------------------------------------------- -- Localize functions to avoid table lookups (better performance). -local table_insert = table.insert local string_sub, string_find = string.sub, string.find -------------------------------------------------------------------------------- @@ -94,13 +93,13 @@ function dump2(o, name, dumped) -- the form _G["table: 0xFFFFFFF"] keyStr = string.format("_G[%q]", tostring(k)) -- Dump key table - table_insert(t, dump2(k, keyStr, dumped)) + t[#t + 1] = dump2(k, keyStr, dumped) end else keyStr = basic_dump(k) end local vname = string.format("%s[%s]", name, keyStr) - table_insert(t, dump2(v, vname, dumped)) + t[#t + 1] = dump2(v, vname, dumped) end return string.format("%s = {}\n%s", name, table.concat(t)) end @@ -135,7 +134,7 @@ function dump(o, indent, nested, level) local t = {} local dumped_indexes = {} for i, v in ipairs(o) do - table_insert(t, dump(v, indent, nested, level + 1)) + t[#t + 1] = dump(v, indent, nested, level + 1) dumped_indexes[i] = true end for k, v in pairs(o) do @@ -144,7 +143,7 @@ function dump(o, indent, nested, level) k = "["..dump(k, indent, nested, level + 1).."]" end v = dump(v, indent, nested, level + 1) - table_insert(t, k.." = "..v) + t[#t + 1] = k.." = "..v end end nested[o] = nil @@ -177,7 +176,7 @@ function string.split(str, delim, include_empty, max_splits, sep_is_pattern) local s = string_sub(str, pos, np - 1) if include_empty or (s ~= "") then max_splits = max_splits - 1 - table_insert(items, s) + items[#items + 1] = s end pos = npe + 1 until (max_splits == 0) or (pos > (len + 1)) @@ -186,8 +185,8 @@ end -------------------------------------------------------------------------------- function table.indexof(list, val) - for i = 1, #list do - if list[i] == val then + for i, v in ipairs(list) do + if v == val then return i end end @@ -324,7 +323,7 @@ function core.splittext(text,charlimit) local last_line = "" while start ~= nil do if string.len(last_line) + (stop-start) > charlimit then - table_insert(retval, last_line) + retval[#retval + 1] = last_line last_line = "" end @@ -335,7 +334,7 @@ function core.splittext(text,charlimit) last_line = last_line .. string_sub(text, current_idx, stop - 1) if gotnewline then - table_insert(retval, last_line) + retval[#retval + 1] = last_line last_line = "" gotnewline = false end @@ -353,11 +352,11 @@ function core.splittext(text,charlimit) --add last part of text if string.len(last_line) + (string.len(text) - current_idx) > charlimit then - table_insert(retval, last_line) - table_insert(retval, string_sub(text, current_idx)) + retval[#retval + 1] = last_line + retval[#retval + 1] = string_sub(text, current_idx) else last_line = last_line .. " " .. string_sub(text, current_idx) - table_insert(retval, last_line) + retval[#retval + 1] = last_line end return retval @@ -430,14 +429,14 @@ if INIT == "game" then if iswall then core.set_node(pos, {name = wield_name, - param2 = dirs1[fdir+1]}) + param2 = dirs1[fdir + 1]}) elseif isceiling then if orient_flags.force_facedir then core.set_node(pos, {name = wield_name, param2 = 20}) else core.set_node(pos, {name = wield_name, - param2 = dirs2[fdir+1]}) + param2 = dirs2[fdir + 1]}) end else -- place right side up if orient_flags.force_facedir then diff --git a/builtin/common/serialize.lua b/builtin/common/serialize.lua index 90b8b2ad..b2165648 100644 --- a/builtin/common/serialize.lua +++ b/builtin/common/serialize.lua @@ -104,7 +104,7 @@ function core.serialize(x) local i = local_index local_index = local_index + 1 var = "_["..i.."]" - table.insert(local_defs, var.." = "..val) + local_defs[#local_defs + 1] = var.." = "..val dumped[x] = var return var end @@ -135,16 +135,15 @@ function core.serialize(x) local np = nest_points[x] for i, v in ipairs(x) do if not np or not np[i] then - table.insert(vals, dump_or_ref_val(v)) + vals[#vals + 1] = dump_or_ref_val(v) end idx_dumped[i] = true end for k, v in pairs(x) do if (not np or not np[k]) and not idx_dumped[k] then - table.insert(vals, - "["..dump_or_ref_val(k).."] = " - ..dump_or_ref_val(v)) + vals[#vals + 1] = "["..dump_or_ref_val(k).."] = " + ..dump_or_ref_val(v) end end return "{"..table.concat(vals, ", ").."}" @@ -156,9 +155,9 @@ function core.serialize(x) local function dump_nest_points() for parent, vals in pairs(nest_points) do for k, v in pairs(vals) do - table.insert(local_defs, dump_or_ref_val(parent) + local_defs[#local_defs + 1] = dump_or_ref_val(parent) .."["..dump_or_ref_val(k).."] = " - ..dump_or_ref_val(v)) + ..dump_or_ref_val(v) end end end diff --git a/builtin/fstk/buttonbar.lua b/builtin/fstk/buttonbar.lua index df749563..eed16090 100644 --- a/builtin/fstk/buttonbar.lua +++ b/builtin/fstk/buttonbar.lua @@ -145,7 +145,12 @@ local buttonbar_metatable = { if image == nil then image = "" end if tooltip == nil then tooltip = "" end - table.insert(self.buttons,{ name=name, caption=caption, image=image, tooltip=tooltip}) + self.buttons[#self.buttons + 1] = { + name = name, + caption = caption, + image = image, + tooltip = tooltip + } if self.orientation == "horizontal" then if ( (self.btn_size * #self.buttons) + (self.btn_size * 0.05 *2) > self.size.x ) then diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua index f6501156..9e2a3f5d 100644 --- a/builtin/fstk/tabview.lua +++ b/builtin/fstk/tabview.lua @@ -46,7 +46,7 @@ local function add_tab(self,tab) tabdata = {}, } - table.insert(self.tablist,newtab) + self.tablist[#self.tablist + 1] = newtab if self.last_tab_index == #self.tablist then self.current_tab = tab.name diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua index 423eb313..deb811b1 100644 --- a/builtin/game/auth.lua +++ b/builtin/game/auth.lua @@ -20,7 +20,7 @@ function core.privs_to_string(privs, delim) local list = {} for priv, bool in pairs(privs) do if bool then - table.insert(list, priv) + list[#list + 1] = priv end end return table.concat(list, delim) diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 6b4ca0d1..0b197664 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -116,7 +116,7 @@ core.register_chatcommand("help", { local cmds = {} for cmd, def in pairs(core.chatcommands) do if core.check_player_privs(name, def.privs) then - table.insert(cmds, cmd) + cmds[#cmds + 1] = cmd end end table.sort(cmds) @@ -127,7 +127,7 @@ core.register_chatcommand("help", { local cmds = {} for cmd, def in pairs(core.chatcommands) do if core.check_player_privs(name, def.privs) then - table.insert(cmds, format_help_line(cmd, def)) + cmds[#cmds + 1] = format_help_line(cmd, def) end end table.sort(cmds) @@ -135,7 +135,7 @@ core.register_chatcommand("help", { elseif param == "privs" then local privs = {} for priv, def in pairs(core.registered_privileges) do - table.insert(privs, priv .. ": " .. def.description) + privs[#privs + 1] = priv .. ": " .. def.description end table.sort(privs) return true, "Available privileges:\n"..table.concat(privs, "\n") @@ -785,6 +785,13 @@ core.register_chatcommand("time", { end, }) +core.register_chatcommand("days", { + description = "Display day count", + func = function(name, param) + return true, "Current day is " .. core.get_day_count() + end +}) + core.register_chatcommand("shutdown", { description = "shutdown server", privs = {server=true}, diff --git a/builtin/game/features.lua b/builtin/game/features.lua index a5f17e54..2aad458d 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -8,6 +8,7 @@ core.features = { use_texture_alpha = true, no_legacy_abms = true, texture_names_parens = true, + area_store_custom_ids = true, } function core.has_feature(arg) diff --git a/builtin/game/item.lua b/builtin/game/item.lua index c42aff5b..36c2c1a6 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -233,7 +233,8 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2) place_to = {x = under.x, y = under.y, z = under.z} end - if core.is_protected(place_to, placer:get_player_name()) then + if core.is_protected(place_to, placer:get_player_name()) and + not minetest.check_player_privs(placer, "protection_bypass") then core.log("action", placer:get_player_name() .. " tried to place " .. def.name .. " at protected position " @@ -444,7 +445,8 @@ function core.node_dig(pos, node, digger) return end - if core.is_protected(pos, digger:get_player_name()) then + if core.is_protected(pos, digger:get_player_name()) and + not minetest.check_player_privs(digger, "protection_bypass") then core.log("action", digger:get_player_name() .. " tried to dig " .. node.name .. " at protected position " diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index d0e67bd8..4f58710d 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -40,12 +40,12 @@ end) function core.after(after, func, ...) assert(tonumber(time) and type(func) == "function", "Invalid core.after invocation") - table.insert(jobs, { + jobs[#jobs + 1] = { func = func, expire = time + after, arg = {...}, mod_origin = core.get_last_run_mod() - }) + } end function core.check_player_privs(player_or_name, ...) @@ -63,14 +63,14 @@ function core.check_player_privs(player_or_name, ...) -- We were provided with a table like { privA = true, privB = true }. for priv, value in pairs(requested_privs[1]) do if value and not player_privs[priv] then - table.insert(missing_privileges, priv) + missing_privileges[#missing_privileges + 1] = priv end end else -- Only a list, we can process it directly. for key, priv in pairs(requested_privs) do if not player_privs[priv] then - table.insert(missing_privileges, priv) + missing_privileges[#missing_privileges + 1] = priv end end end @@ -96,7 +96,7 @@ function core.get_connected_players() local temp_table = {} for index, value in pairs(player_list) do if value:is_player_connected() then - table.insert(temp_table, value) + temp_table[#temp_table + 1] = value end end return temp_table diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua index 7e6387c7..bd5ead62 100644 --- a/builtin/game/privileges.lua +++ b/builtin/game/privileges.lua @@ -32,6 +32,7 @@ core.register_privilege("settime", "Can use /time") core.register_privilege("privs", "Can modify privileges") core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges") core.register_privilege("server", "Can do server maintenance stuff") +core.register_privilege("protection_bypass", "Can bypass node protection in the world") core.register_privilege("shout", "Can speak in chat") core.register_privilege("ban", "Can ban and unban players") core.register_privilege("kick", "Can kick players") diff --git a/builtin/game/register.lua b/builtin/game/register.lua index ba5f69d6..f330491a 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -11,10 +11,11 @@ local register_alias_raw = core.register_alias_raw core.register_alias_raw = nil -- --- Item / entity / ABM registration functions +-- Item / entity / ABM / LBM registration functions -- core.registered_abms = {} +core.registered_lbms = {} core.registered_entities = {} core.registered_items = {} core.registered_nodes = {} @@ -75,7 +76,14 @@ end function core.register_abm(spec) -- Add to core.registered_abms - core.registered_abms[#core.registered_abms+1] = spec + core.registered_abms[#core.registered_abms + 1] = spec + spec.mod_origin = core.get_current_modname() or "??" +end + +function core.register_lbm(spec) + -- Add to core.registered_lbms + check_modname_prefix(spec.name) + core.registered_lbms[#core.registered_lbms + 1] = spec spec.mod_origin = core.get_current_modname() or "??" end @@ -391,7 +399,7 @@ end local function make_registration() local t = {} local registerfunc = function(func) - table.insert(t, func) + t[#t + 1] = func core.callback_origins[func] = { mod = core.get_current_modname() or "??", name = debug.getinfo(1, "n").name or "??" @@ -467,9 +475,9 @@ end function core.register_on_player_hpchange(func, modifier) if modifier then - table.insert(core.registered_on_player_hpchanges.modifiers, func) + core.registered_on_player_hpchanges.modifiers[#core.registered_on_player_hpchanges.modifiers + 1] = func else - table.insert(core.registered_on_player_hpchanges.loggers, func) + core.registered_on_player_hpchanges.loggers[#core.registered_on_player_hpchanges.loggers + 1] = func end core.callback_origins[func] = { mod = core.get_current_modname() or "??", diff --git a/builtin/mainmenu/common.lua b/builtin/mainmenu/common.lua index 6a9895f6..b44f3cac 100644 --- a/builtin/mainmenu/common.lua +++ b/builtin/mainmenu/common.lua @@ -67,13 +67,13 @@ function order_favorite_list(list) for i=1,#list,1 do local fav = list[i] if is_server_protocol_compat(fav.proto_min, fav.proto_max) then - table.insert(res, fav) + res[#res + 1] = fav end end for i=1,#list,1 do local fav = list[i] if not is_server_protocol_compat(fav.proto_min, fav.proto_max) then - table.insert(res, fav) + res[#res + 1] = fav end end return res diff --git a/builtin/mainmenu/dlg_settings_advanced.lua b/builtin/mainmenu/dlg_settings_advanced.lua index d1c04847..0ad7e565 100644 --- a/builtin/mainmenu/dlg_settings_advanced.lua +++ b/builtin/mainmenu/dlg_settings_advanced.lua @@ -151,8 +151,8 @@ local function parse_setting_line(settings, line, read_all, base_level, allow_se if setting_type == "float" then local default, min, max = remaining_line:match("^" -- first float is required, the last 2 are optional - .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "?" - .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "?" + .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "*" .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .."$") diff --git a/builtin/mainmenu/modmgr.lua b/builtin/mainmenu/modmgr.lua index 01ee882c..40be2543 100644 --- a/builtin/mainmenu/modmgr.lua +++ b/builtin/mainmenu/modmgr.lua @@ -23,7 +23,7 @@ function get_mods(path,retval,modpack) if name:sub(1, 1) ~= "." then local prefix = path .. DIR_DELIM .. name .. DIR_DELIM local toadd = {} - table.insert(retval, toadd) + retval[#retval + 1] = toadd local mod_conf = Settings(prefix .. "mod.conf"):to_table() if mod_conf.name then @@ -412,7 +412,7 @@ function modmgr.preparemodlist(data) for i=1,#global_mods,1 do global_mods[i].typ = "global_mod" - table.insert(retval,global_mods[i]) + retval[#retval + 1] = global_mods[i] end --read game mods @@ -421,7 +421,7 @@ function modmgr.preparemodlist(data) for i=1,#game_mods,1 do game_mods[i].typ = "game_mod" - table.insert(retval,game_mods[i]) + retval[#retval + 1] = game_mods[i] end if data.worldpath == nil then diff --git a/builtin/mainmenu/tab_mods.lua b/builtin/mainmenu/tab_mods.lua index 45c62ced..4843f69a 100644 --- a/builtin/mainmenu/tab_mods.lua +++ b/builtin/mainmenu/tab_mods.lua @@ -78,7 +78,7 @@ local function get_formspec(tabview, name, tabdata) descriptionfile:close() else descriptionlines = {} - table.insert(descriptionlines,fgettext("No mod description available")) + descriptionlines[#descriptionlines + 1] = fgettext("No mod description available") end retval = retval .. diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua index 478dbf91..11180868 100644 --- a/builtin/mainmenu/tab_settings.lua +++ b/builtin/mainmenu/tab_settings.lua @@ -17,110 +17,115 @@ -------------------------------------------------------------------------------- -local leaves_style_labels = { - fgettext("Opaque Leaves"), - fgettext("Simple Leaves"), - fgettext("Fancy Leaves") +local labels = { + leaves = { + fgettext("Opaque Leaves"), + fgettext("Simple Leaves"), + fgettext("Fancy Leaves") + }, + node_highlighting = { + fgettext("Node Outlining"), + fgettext("Node Highlighting") + }, + filters = { + fgettext("No Filter"), + fgettext("Bilinear Filter"), + fgettext("Trilinear Filter") + }, + mipmap = { + fgettext("No Mipmap"), + fgettext("Mipmap"), + fgettext("Mipmap + Aniso. Filter") + }, + antialiasing = { + fgettext("None"), + fgettext("2x"), + fgettext("4x"), + fgettext("8x") + } } -local leaves_style = { - {leaves_style_labels[1] .. "," .. leaves_style_labels[2] .. "," .. leaves_style_labels[3]}, - {"opaque", "simple", "fancy"}, +local dd_options = { + leaves = { + table.concat(labels.leaves, ","), + {"opaque", "simple", "fancy"} + }, + node_highlighting = { + table.concat(labels.node_highlighting, ","), + {"box", "halo"} + }, + filters = { + table.concat(labels.filters, ","), + {"", "bilinear_filter", "trilinear_filter"} + }, + mipmap = { + table.concat(labels.mipmap, ","), + {"", "mip_map", "anisotropic_filter"} + }, + antialiasing = { + table.concat(labels.antialiasing, ","), + {"0", "2", "4", "8"} + } } -local dd_filter_labels = { - fgettext("No Filter"), - fgettext("Bilinear Filter"), - fgettext("Trilinear Filter") -} - -local filters = { - {dd_filter_labels[1] .. "," .. dd_filter_labels[2] .. "," .. dd_filter_labels[3]}, - {"", "bilinear_filter", "trilinear_filter"}, -} - -local dd_mipmap_labels = { - fgettext("No Mipmap"), - fgettext("Mipmap"), - fgettext("Mipmap + Aniso. Filter") -} - -local mipmap = { - {dd_mipmap_labels[1] .. "," .. dd_mipmap_labels[2] .. "," .. dd_mipmap_labels[3]}, - {"", "mip_map", "anisotropic_filter"}, -} - -local function getLeavesStyleSettingIndex() - local style = core.setting_get("leaves_style") - if (style == leaves_style[2][3]) then - return 3 - elseif (style == leaves_style[2][2]) then - return 2 - end - return 1 -end - -local dd_antialiasing_labels = { - fgettext("None"), - fgettext("2x"), - fgettext("4x"), - fgettext("8x"), -} - -local antialiasing = { - {dd_antialiasing_labels[1] .. "," .. dd_antialiasing_labels[2] .. "," .. - dd_antialiasing_labels[3] .. "," .. dd_antialiasing_labels[4]}, - {"0", "2", "4", "8"} -} - -local function getFilterSettingIndex() - if (core.setting_get(filters[2][3]) == "true") then - return 3 - end - if (core.setting_get(filters[2][3]) == "false" and core.setting_get(filters[2][2]) == "true") then - return 2 - end - return 1 -end - -local function getMipmapSettingIndex() - if (core.setting_get(mipmap[2][3]) == "true") then - return 3 - end - if (core.setting_get(mipmap[2][3]) == "false" and core.setting_get(mipmap[2][2]) == "true") then - return 2 - end - return 1 -end - -local function getAntialiasingSettingIndex() - local antialiasing_setting = core.setting_get("fsaa") - for i = 1, #(antialiasing[2]) do - if antialiasing_setting == antialiasing[2][i] then - return i +local getSettingIndex = { + Leaves = function() + local style = core.setting_get("leaves_style") + for idx, name in pairs(dd_options.leaves[2]) do + if style == name then return idx end end + return 1 + end, + NodeHighlighting = function() + local style = core.setting_get("node_highlighting") + for idx, name in pairs(dd_options.node_highlighting[2]) do + if style == name then return idx end + end + return 1 + end, + Filter = function() + if core.setting_get(dd_options.filters[2][3]) == "true" then + return 3 + elseif core.setting_get(dd_options.filters[2][3]) == "false" and + core.setting_get(dd_options.filters[2][2]) == "true" then + return 2 + end + return 1 + end, + Mipmap = function() + if core.setting_get(dd_options.mipmap[2][3]) == "true" then + return 3 + elseif core.setting_get(dd_options.mipmap[2][3]) == "false" and + core.setting_get(dd_options.mipmap[2][2]) == "true" then + return 2 + end + return 1 + end, + Antialiasing = function() + local antialiasing_setting = core.setting_get("fsaa") + for i = 1, #dd_options.antialiasing[2] do + if antialiasing_setting == dd_options.antialiasing[2][i] then + return i + end + end + return 1 end - return 1 -end +} local function antialiasing_fname_to_name(fname) - for i = 1, #(dd_antialiasing_labels) do - if fname == dd_antialiasing_labels[i] then - return antialiasing[2][i] + for i = 1, #labels.antialiasing do + if fname == labels.antialiasing[i] then + return dd_options.antialiasing[2][i] end end return 0 end local function dlg_confirm_reset_formspec(data) - local retval = - "size[8,3]" .. + return "size[8,3]" .. "label[1,1;" .. fgettext("Are you sure to reset your singleplayer world?") .. "]" .. - "button[1,2;2.6,0.5;dlg_reset_singleplayer_confirm;" .. - fgettext("Yes") .. "]" .. - "button[4,2;2.8,0.5;dlg_reset_singleplayer_cancel;" .. - fgettext("No!!!") .. "]" - return retval + "button[1,2;2.6,0.5;dlg_reset_singleplayer_confirm;" .. fgettext("Yes") .. "]" .. + "button[4,2;2.8,0.5;dlg_reset_singleplayer_cancel;" .. fgettext("No") .. "]" end local function dlg_confirm_reset_btnhandler(this, fields, dialogdata) @@ -129,7 +134,7 @@ local function dlg_confirm_reset_btnhandler(this, fields, dialogdata) local worldlist = core.get_worlds() local found_singleplayerworld = false - for i = 1, #worldlist, 1 do + for i = 1, #worldlist do if worldlist[i].name == "singleplayerworld" then found_singleplayerworld = true gamedata.worldindex = i @@ -141,12 +146,10 @@ local function dlg_confirm_reset_btnhandler(this, fields, dialogdata) end core.create_world("singleplayerworld", 1) - worldlist = core.get_worlds() - found_singleplayerworld = false - for i = 1, #worldlist, 1 do + for i = 1, #worldlist do if worldlist[i].name == "singleplayerworld" then found_singleplayerworld = true gamedata.worldindex = i @@ -170,41 +173,12 @@ local function showconfirm_reset(tabview) new_dlg:show() end -local function gui_scale_to_scrollbar() - local current_value = tonumber(core.setting_get("gui_scaling")) - - if (current_value == nil) or current_value < 0.25 then - return 0 - end - if current_value <= 1.25 then - return ((current_value - 0.25)/ 1.0) * 700 - end - if current_value <= 6 then - return ((current_value -1.25) * 100) + 700 - end - - return 1000 -end - -local function scrollbar_to_gui_scale(value) - value = tonumber(value) - - if (value <= 700) then - return ((value / 700) * 1.0) + 0.25 - end - if (value <= 1000) then - return ((value - 700) / 100) + 1.25 - end - - return 1 -end - local function formspec(tabview, name, tabdata) local tab_string = - "box[0,0;3.5,4.3;#999999]" .. - "checkbox[0.25,0;cb_smooth_lighting;" .. fgettext("Smooth Lighting") - .. ";" .. dump(core.setting_getbool("smooth_lighting")) .. "]" .. - "checkbox[0.25,0.5;cb_particles;" .. fgettext("Enable Particles") .. ";" + "box[0,0;3.5,4.5;#999999]" .. + "checkbox[0.25,0;cb_smooth_lighting;" .. fgettext("Smooth Lighting") .. ";" + .. dump(core.setting_getbool("smooth_lighting")) .. "]" .. + "checkbox[0.25,0.5;cb_particles;" .. fgettext("Particles") .. ";" .. dump(core.setting_getbool("enable_particles")) .. "]" .. "checkbox[0.25,1;cb_3d_clouds;" .. fgettext("3D Clouds") .. ";" .. dump(core.setting_getbool("enable_3d_clouds")) .. "]" .. @@ -212,20 +186,20 @@ local function formspec(tabview, name, tabdata) .. dump(core.setting_getbool("opaque_water")) .. "]" .. "checkbox[0.25,2.0;cb_connected_glass;" .. fgettext("Connected Glass") .. ";" .. dump(core.setting_getbool("connected_glass")) .. "]" .. - "checkbox[0.25,2.5;cb_node_highlighting;" .. fgettext("Node Highlighting") .. ";" - .. dump(core.setting_getbool("enable_node_highlighting")) .. "]" .. - "dropdown[0.25,3.4;3.3;dd_leaves_style;" .. leaves_style[1][1] .. ";" - .. getLeavesStyleSettingIndex() .. "]" .. + "dropdown[0.25,2.8;3.3;dd_node_highlighting;" .. dd_options.node_highlighting[1] .. ";" + .. getSettingIndex.NodeHighlighting() .. "]" .. + "dropdown[0.25,3.6;3.3;dd_leaves_style;" .. dd_options.leaves[1] .. ";" + .. getSettingIndex.Leaves() .. "]" .. "box[3.75,0;3.75,3.45;#999999]" .. "label[3.85,0.1;" .. fgettext("Texturing:") .. "]" .. - "dropdown[3.85,0.55;3.85;dd_filters;" .. filters[1][1] .. ";" - .. getFilterSettingIndex() .. "]" .. - "dropdown[3.85,1.35;3.85;dd_mipmap;" .. mipmap[1][1] .. ";" - .. getMipmapSettingIndex() .. "]" .. + "dropdown[3.85,0.55;3.85;dd_filters;" .. dd_options.filters[1] .. ";" + .. getSettingIndex.Filter() .. "]" .. + "dropdown[3.85,1.35;3.85;dd_mipmap;" .. dd_options.mipmap[1] .. ";" + .. getSettingIndex.Mipmap() .. "]" .. "label[3.85,2.15;" .. fgettext("Antialiasing:") .. "]" .. - "dropdown[3.85,2.6;3.85;dd_antialiasing;" .. antialiasing[1][1] .. ";" - .. getAntialiasingSettingIndex() .. "]" .. - "box[7.75,0;4,4;#999999]" .. + "dropdown[3.85,2.6;3.85;dd_antialiasing;" .. dd_options.antialiasing[1] .. ";" + .. getSettingIndex.Antialiasing() .. "]" .. + "box[7.75,0;4,4.4;#999999]" .. "checkbox[8,0;cb_shaders;" .. fgettext("Shaders") .. ";" .. dump(core.setting_getbool("enable_shaders")) .. "]" @@ -236,37 +210,40 @@ local function formspec(tabview, name, tabdata) if core.setting_get("touchscreen_threshold") ~= nil then tab_string = tab_string .. - "label[4.3,4.1;" .. fgettext("Touchthreshold (px)") .. "]" .. - "dropdown[3.85,4.55;3.85;dd_touchthreshold;0,10,20,30,40,50;" .. - ((tonumber(core.setting_get("touchscreen_threshold"))/10)+1) .. "]" + "label[4.3,4.1;" .. fgettext("Touchthreshold (px)") .. "]" .. + "dropdown[3.85,4.55;3.85;dd_touchthreshold;0,10,20,30,40,50;" .. + ((tonumber(core.setting_get("touchscreen_threshold")) / 10) + 1) .. "]" end if core.setting_getbool("enable_shaders") then tab_string = tab_string .. - "checkbox[8,0.5;cb_bumpmapping;" .. fgettext("Bumpmapping") .. ";" - .. dump(core.setting_getbool("enable_bumpmapping")) .. "]" .. - "checkbox[8,1.0;cb_generate_normalmaps;" .. fgettext("Generate Normalmaps") .. ";" - .. dump(core.setting_getbool("generate_normalmaps")) .. "]" .. - "checkbox[8,1.5;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";" - .. dump(core.setting_getbool("enable_parallax_occlusion")) .. "]" .. - "checkbox[8,2.0;cb_waving_water;" .. fgettext("Waving Water") .. ";" - .. dump(core.setting_getbool("enable_waving_water")) .. "]" .. - "checkbox[8,2.5;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" - .. dump(core.setting_getbool("enable_waving_leaves")) .. "]" .. - "checkbox[8,3.0;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" - .. dump(core.setting_getbool("enable_waving_plants")) .. "]" + "checkbox[8,0.5;cb_bumpmapping;" .. fgettext("Bump Mapping") .. ";" + .. dump(core.setting_getbool("enable_bumpmapping")) .. "]" .. + "checkbox[8,1;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";" + .. dump(core.setting_getbool("tone_mapping")) .. "]" .. + "checkbox[8,1.5;cb_generate_normalmaps;" .. fgettext("Normal Mapping") .. ";" + .. dump(core.setting_getbool("generate_normalmaps")) .. "]" .. + "checkbox[8,2;cb_parallax;" .. fgettext("Parallax Occlusion") .. ";" + .. dump(core.setting_getbool("enable_parallax_occlusion")) .. "]" .. + "checkbox[8,2.5;cb_waving_water;" .. fgettext("Waving Water") .. ";" + .. dump(core.setting_getbool("enable_waving_water")) .. "]" .. + "checkbox[8,3;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" + .. dump(core.setting_getbool("enable_waving_leaves")) .. "]" .. + "checkbox[8,3.5;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" + .. dump(core.setting_getbool("enable_waving_plants")) .. "]" else tab_string = tab_string .. - "tablecolumns[color;text]" .. - "tableoptions[background=#00000000;highlight=#00000000;border=false]" .. - "table[8.33,0.7;3.5,4;shaders;" .. - "#888888," .. fgettext("Bumpmapping") .. "," .. - "#888888," .. fgettext("Generate Normalmaps") .. "," .. - "#888888," .. fgettext("Parallax Occlusion") .. "," .. - "#888888," .. fgettext("Waving Water") .. "," .. - "#888888," .. fgettext("Waving Leaves") .. "," .. - "#888888," .. fgettext("Waving Plants") .. "," .. - ";1]" + "tablecolumns[color;text]" .. + "tableoptions[background=#00000000;highlight=#00000000;border=false]" .. + "table[8.33,0.7;3.5,4;shaders;" .. + "#888888," .. fgettext("Bump Mapping") .. "," .. + "#888888," .. fgettext("Tone Mapping") .. "," .. + "#888888," .. fgettext("Normal Mapping") .. "," .. + "#888888," .. fgettext("Parallax Occlusion") .. "," .. + "#888888," .. fgettext("Waving Water") .. "," .. + "#888888," .. fgettext("Waving Leaves") .. "," .. + "#888888," .. fgettext("Waving Plants") .. "," .. + ";1]" end return tab_string @@ -283,7 +260,6 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) --mm_texture.update("singleplayer", current_game()) return true end - if fields["cb_smooth_lighting"] then core.setting_set("smooth_lighting", fields["cb_smooth_lighting"]) return true @@ -304,13 +280,9 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) core.setting_set("connected_glass", fields["cb_connected_glass"]) return true end - if fields["cb_node_highlighting"] then - core.setting_set("enable_node_highlighting", fields["cb_node_highlighting"]) - return true - end if fields["cb_shaders"] then - if (core.setting_get("video_driver") == "direct3d8" - or core.setting_get("video_driver") == "direct3d9") then + if (core.setting_get("video_driver") == "direct3d8" or + core.setting_get("video_driver") == "direct3d9") then core.setting_set("enable_shaders", "false") gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.") else @@ -320,9 +292,15 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) end if fields["cb_bumpmapping"] then core.setting_set("enable_bumpmapping", fields["cb_bumpmapping"]) + return true + end + if fields["cb_tonemapping"] then + core.setting_set("tone_mapping", fields["cb_tonemapping"]) + return true end if fields["cb_generate_normalmaps"] then core.setting_set("generate_normalmaps", fields["cb_generate_normalmaps"]) + return true end if fields["cb_parallax"] then core.setting_set("enable_parallax_occlusion", fields["cb_parallax"]) @@ -339,17 +317,6 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) core.setting_set("enable_waving_plants", fields["cb_waving_plants"]) return true end - - if fields["sb_gui_scaling"] then - local event = core.explode_scrollbar_event(fields["sb_gui_scaling"]) - - if event.type == "CHG" then - local tosave = string.format("%.2f",scrollbar_to_gui_scale(event.value)) - core.setting_set("gui_scaling", tosave) - return true - end - end - if fields["btn_change_keys"] then core.show_keys_menu() return true @@ -366,38 +333,40 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) --Note dropdowns have to be handled LAST! local ddhandled = false - if fields["dd_leaves_style"] == leaves_style_labels[1] then - core.setting_set("leaves_style", leaves_style[2][1]) - ddhandled = true - elseif fields["dd_leaves_style"] == leaves_style_labels[2] then - core.setting_set("leaves_style", leaves_style[2][2]) - ddhandled = true - elseif fields["dd_leaves_style"] == leaves_style_labels[3] then - core.setting_set("leaves_style", leaves_style[2][3]) - ddhandled = true + for i = 1, #labels.leaves do + if fields["dd_leaves_style"] == labels.leaves[i] then + core.setting_set("leaves_style", dd_options.leaves[2][i]) + ddhandled = true + end end - if fields["dd_filters"] == dd_filter_labels[1] then + for i = 1, #labels.node_highlighting do + if fields["dd_node_highlighting"] == labels.node_highlighting[i] then + core.setting_set("node_highlighting", dd_options.node_highlighting[2][i]) + ddhandled = true + end + end + if fields["dd_filters"] == labels.filters[1] then core.setting_set("bilinear_filter", "false") core.setting_set("trilinear_filter", "false") ddhandled = true - elseif fields["dd_filters"] == dd_filter_labels[2] then + elseif fields["dd_filters"] == labels.filters[2] then core.setting_set("bilinear_filter", "true") core.setting_set("trilinear_filter", "false") ddhandled = true - elseif fields["dd_filters"] == dd_filter_labels[3] then + elseif fields["dd_filters"] == labels.filters[3] then core.setting_set("bilinear_filter", "false") core.setting_set("trilinear_filter", "true") ddhandled = true end - if fields["dd_mipmap"] == dd_mipmap_labels[1] then + if fields["dd_mipmap"] == labels.mipmap[1] then core.setting_set("mip_map", "false") core.setting_set("anisotropic_filter", "false") ddhandled = true - elseif fields["dd_mipmap"] == dd_mipmap_labels[2] then + elseif fields["dd_mipmap"] == labels.mipmap[2] then core.setting_set("mip_map", "true") core.setting_set("anisotropic_filter", "false") ddhandled = true - elseif fields["dd_mipmap"] == dd_mipmap_labels[3] then + elseif fields["dd_mipmap"] == labels.mipmap[3] then core.setting_set("mip_map", "true") core.setting_set("anisotropic_filter", "true") ddhandled = true @@ -408,7 +377,7 @@ local function handle_settings_buttons(this, fields, tabname, tabdata) ddhandled = true end if fields["dd_touchthreshold"] then - core.setting_set("touchscreen_threshold",fields["dd_touchthreshold"]) + core.setting_set("touchscreen_threshold", fields["dd_touchthreshold"]) ddhandled = true end diff --git a/builtin/mainmenu/tab_texturepacks.lua b/builtin/mainmenu/tab_texturepacks.lua index dedd1809..86c2403d 100644 --- a/builtin/mainmenu/tab_texturepacks.lua +++ b/builtin/mainmenu/tab_texturepacks.lua @@ -20,7 +20,7 @@ local function filter_texture_pack_list(list) local retval = {} for _, item in ipairs(list) do if item ~= "base" then - table.insert(retval, item) + retval[#retval + 1] = item end end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 493cedd6..c1157b16 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -554,6 +554,14 @@ fallback_font_shadow_alpha (Fallback font shadow alpha) int 128 0 255 # Path to save screenshots at. screenshot_path (Screenshot folder) path +# Format of screenshots. +screenshot_format (Screenshot format) enum png png,jpg,bmp,pcx,ppm,tga + +# Screenshot quality. Only used for JPEG format. +# 1 means worst quality; 100 means best quality. +# Use 0 for default quality. +screenshot_quality (Screenshot quality) int 0 0 100 + [**Advanced] # Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens. @@ -800,7 +808,7 @@ liquid_update (Liquid update tick) float 1.0 # Name of map generator to be used when creating a new world. # Creating a world in the main menu will override this. -mg_name (Mapgen name) enum v6 v5,v6,v7,flat,fractal,singlenode +mg_name (Mapgen name) enum v6 v5,v6,v7,flat,valleys,fractal,singlenode # Water surface level of the world. water_level (Water level) int 1 @@ -819,8 +827,10 @@ map_generation_limit (Map generation limit) int 31000 0 31000 # Global map generation attributes. # In Mapgen v6 the 'decorations' flag controls all decorations except trees # and junglegrass, in all other mapgens this flag controls all decorations. +# The default flags set in the engine are: caves, light, decorations +# The flags string modifies the engine defaults. # Flags that are not specified in the flag string are not modified from the default. -# Flags starting with "no" are used to explicitly disable them. +# Flags starting with 'no' are used to explicitly disable them. mg_flags (Mapgen flags) flags caves,dungeons,light,decorations caves,dungeons,light,decorations,nocaves,nodungeons,nolight,nodecorations [**Advanced] @@ -875,9 +885,11 @@ mgv5_np_cave2 (Mapgen v5 cave2 noise parameters) noise_params 0, 12, (50, 50, 50 [***Mapgen v6] # Map generation attributes specific to Mapgen v6. -# When snowbiomes are enabled jungles are enabled and the jungles flag is ignored. +# When snowbiomes are enabled jungles are automatically enabled, the 'jungles' flag is ignored. +# The default flags set in the engine are: biomeblend, mudflow +# The flags string modifies the engine defaults. # Flags that are not specified in the flag string are not modified from the default. -# Flags starting with "no" are used to explicitly disable them. +# Flags starting with 'no' are used to explicitly disable them. mgv6_spflags (Mapgen v6 flags) flags jungles,biomeblend,mudflow,snowbiomes,trees jungles,biomeblend,mudflow,snowbiomes,flat,trees,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees # Controls size of deserts and beaches in Mapgen v6. @@ -900,9 +912,11 @@ mgv6_np_apple_trees (Mapgen v6 apple trees noise parameters) noise_params 0, 1, [***Mapgen v7] # Map generation attributes specific to Mapgen v7. -# 'ridges' are the rivers. +# The 'ridges' flag controls the rivers. +# The default flags set in the engine are: mountains, ridges +# The flags string modifies the engine defaults. # Flags that are not specified in the flag string are not modified from the default. -# Flags starting with "no" are used to explicitly disable them. +# Flags starting with 'no' are used to explicitly disable them. mgv7_spflags (Mapgen v7 flags) flags mountains,ridges mountains,ridges,nomountains,noridges mgv7_np_terrain_base (Mapgen v7 terrain base noise parameters) noise_params 4, 70, (600, 600, 600), 82341, 5, 0.6, 2.0 @@ -920,9 +934,11 @@ mgv7_np_cave2 (Mapgen v7 cave2 noise parameters) noise_params 0, 12, (100, 100, [***Mapgen flat] # Map generation attributes specific to Mapgen flat. -# Occasional lakes and hills added to the flat world. +# Occasional lakes and hills can be added to the flat world. +# The default flags set in the engine are: none +# The flags string modifies the engine defaults. # Flags that are not specified in the flag string are not modified from the default. -# Flags starting with "no" are used to explicitly disable them. +# Flags starting with 'no' are used to explicitly disable them. mgflat_spflags (Mapgen flat flags) flags lakes,hills,,nolakes,nohills # Y of flat ground. @@ -1027,10 +1043,13 @@ mgfractal_np_cave2 (Mapgen fractal cave2 noise parameters) noise_params 0, 12, ( [****General] # Map generation attributes specific to Mapgen Valleys. +# 'altitude_chill' makes higher elevations colder, which may cause biome issues. +# 'humid_rivers' modifies the humidity around rivers and in areas where water would tend to pool, +# it may interfere with delicately adjusted biomes. +# The default flags set in the engine are: altitude_chill, humid_rivers +# The flags string modifies the engine defaults. # Flags that are not specified in the flag string are not modified from the default. -# Flags starting with "no" are used to explicitly disable them. -# "altitude_chill" makes higher elevations colder, which may cause biome issues. -# "humid_rivers" modifies the humidity around rivers and in areas where water would tend to pool. It may interfere with delicately adjusted biomes. +# Flags starting with 'no' are used to explicitly disable them. mg_valleys_spflags (Valleys C Flags) flags altitude_chill,humid_rivers altitude_chill,noaltitude_chill,humid_rivers,nohumid_rivers # The altitude at which temperature drops by 20C diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 10b0f2e3..2a93b83c 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -617,6 +617,18 @@ A nodebox is defined as any of: wall_bottom = box, wall_side = box } + { + -- A node that has optional boxes depending on neighbouring nodes' + -- presence and type. See also `connects_to`. + type = "connected", + fixed = box OR {box1, box2, ...} + connect_top = box OR {box1, box2, ...} + connect_bottom = box OR {box1, box2, ...} + connect_front = box OR {box1, box2, ...} + connect_left = box OR {box1, box2, ...} + connect_back = box OR {box1, box2, ...} + connect_right = box OR {box1, box2, ...} + } A `box` is defined as: @@ -1793,6 +1805,7 @@ Call these functions only at load time! * `minetest.register_entity(name, prototype table)` * `minetest.register_abm(abm definition)` +* `minetest.register_lbm(lbm definition)` * `minetest.register_node(name, node definition)` * `minetest.register_tool(name, item definition)` * `minetest.register_craftitem(name, item definition)` @@ -1997,6 +2010,8 @@ and `minetest.auth_reload` call the authetification handler. * `val` is between `0` and `1`; `0` for midnight, `0.5` for midday * `minetest.get_timeofday()` * `minetest.get_gametime()`: returns the time, in seconds, since the world was created +* `minetest.get_day_count()`: returns number days elapsed since world was created, + * accounting for time changes. * `minetest.find_node_near(pos, radius, nodenames)`: returns pos or `nil` * `radius`: using a maximum metric * `nodenames`: e.g. `{"ignore", "group:tree"}` or `"default:dirt"` @@ -2726,8 +2741,7 @@ If you chose the parameter-less constructor, a fast implementation will be autom * `get_area(id, include_borders, include_data)`: returns the area with the id `id`. (optional) Boolean values `include_borders` and `include_data` control what's copied. * `get_areas_for_pos(pos, include_borders, include_data)`: returns all areas that contain the position `pos`. (optional) Boolean values `include_borders` and `include_data` control what's copied. * `get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data)`: returns all areas that contain all nodes inside the area specified by `edge1` and `edge2` (inclusive). If `accept_overlap` is true, also areas are returned that have nodes in common with the specified area. (optional) Boolean values `include_borders` and `include_data` control what's copied. -* `insert_area(edge1, edge2, data)`: inserts an area into the store. Returns the id if successful, nil otherwise. The (inclusive) positions `edge1` and `edge2` describe the area, `data` -is a string stored with the area. +* `insert_area(edge1, edge2, data, [id])`: inserts an area into the store. Returns the new area's ID, or nil if the insertion failed. The (inclusive) positions `edge1` and `edge2` describe the area. `data` is a string stored with the area. If passed, `id` will be used as the internal area ID, it must be a unique number between 0 and 2^32-2. If you use the `id` parameter you must always use it, or insertions are likely to fail due to conflicts. * `reserve(count)`: reserves resources for at most `count` many contained areas. Only needed for efficiency, and only some implementations profit. * `remove_area(id)`: removes the area with the given id from the store, returns success. * `set_cache_params(params)`: sets params for the included prefiltering cache. Calling invalidates the cache, so that its elements have to be newly generated. @@ -2737,6 +2751,10 @@ is a string stored with the area. block_radius = number, -- the radius (in nodes) of the areas the cache generates prefiltered lists for, minimum 16, default 64 limit = number, -- the cache's size, minimum 20, default 1000 } +* `to_string()`: Experimental. Returns area store serialized as a (binary) string. +* `to_file(filename)`: Experimental. Like `to_string()`, but writes the data to a file. +* `from_string(str)`: Experimental. Deserializes string and loads it into the AreaStore. Returns success and, optionally, an error message. +* `from_file(filename)`: Experimental. Like `from_string()`, but reads the data from a file. ### `ItemStack` An `ItemStack` is a stack of items. @@ -3305,6 +3323,21 @@ Definition tables action = func(pos, node, active_object_count, active_object_count_wider), } +### LBM (LoadingBlockModifier) definition (`register_lbm`) + + { + name = "modname:replace_legacy_door", + nodenames = {"default:lava_source"}, + -- ^ List of node names to trigger the LBM on. + -- Also non-registered nodes will work. + -- Groups (as of group:groupname) will work as well. + run_at_every_load = false, + -- ^ Whether to run the LBM's action every time a block gets loaded, + -- and not just for blocks that were saved last time before LBMs were + -- introduced to the world. + action = func(pos, node), + } + ### Item definition (`register_node`, `register_craftitem`, `register_tool`) { @@ -3440,6 +3473,12 @@ Definition tables light_source = 0, -- Amount of light emitted by node damage_per_second = 0, -- If player is inside node, this damage is caused node_box = {type="regular"}, -- See "Node boxes" + connects_to = nodenames, --[[ + * Used for nodebox nodes with the type == "connected" + * Specifies to what neighboring nodes connections will be drawn + * e.g. `{"group:fence", "default:wood"}` or `"default:stone"` ]] + connect_sides = { "top", "bottom", "front", "left", "back", "right" }, --[[ + ^ Tells connected nodebox nodes to connect only to these sides of this node. ]] mesh = "model", selection_box = {type="regular"}, -- See "Node boxes" --[[ ^ If drawtype "nodebox" is used and selection_box is nil, then node_box is used. ]] diff --git a/multicraft.conf.example b/multicraft.conf.example index 411fd399..54bd4287 100644 --- a/multicraft.conf.example +++ b/multicraft.conf.example @@ -653,6 +653,16 @@ # type: path # screenshot_path = +# Format of screenshots. +# type: enum values: png, jpg, bmp, pcx, ppm, tga +# screenshot_format = png + +# Screenshot quality. Only used for JPEG format. +# 1 means worst quality; 100 means best quality. +# Use 0 for default quality. +# type: int min: 0 max: 100 +# screenshot_quality = 0 + ### Advanced # Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens. @@ -986,7 +996,7 @@ # Name of map generator to be used when creating a new world. # Creating a world in the main menu will override this. -# type: enum values: v5, v6, v7, flat, fractal, singlenode +# type: enum values: v5, v6, v7, flat, valleys, fractal, singlenode # mg_name = v6 # Water surface level of the world. @@ -1009,8 +1019,10 @@ # Global map generation attributes. # In Mapgen v6 the 'decorations' flag controls all decorations except trees # and junglegrass, in all other mapgens this flag controls all decorations. +# The default flags set in the engine are: caves, light, decorations +# The flags string modifies the engine defaults. # Flags that are not specified in the flag string are not modified from the default. -# Flags starting with "no" are used to explicitly disable them. +# Flags starting with 'no' are used to explicitly disable them. # type: flags possible values: caves, dungeons, light, decorations, nocaves, nodungeons, nolight, nodecorations # mg_flags = caves,dungeons,light,decorations @@ -1077,9 +1089,11 @@ #### Mapgen v6 # Map generation attributes specific to Mapgen v6. -# When snowbiomes are enabled jungles are enabled and the jungles flag is ignored. +# When snowbiomes are enabled jungles are automatically enabled, the 'jungles' flag is ignored. +# The default flags set in the engine are: biomeblend, mudflow +# The flags string modifies the engine defaults. # Flags that are not specified in the flag string are not modified from the default. -# Flags starting with "no" are used to explicitly disable them. +# Flags starting with 'no' are used to explicitly disable them. # type: flags possible values: jungles, biomeblend, mudflow, snowbiomes, flat, trees, nojungles, nobiomeblend, nomudflow, nosnowbiomes, noflat, notrees # mgv6_spflags = jungles,biomeblend,mudflow,snowbiomes,trees @@ -1127,9 +1141,11 @@ #### Mapgen v7 # Map generation attributes specific to Mapgen v7. -# 'ridges' are the rivers. +# The 'ridges' flag controls the rivers. +# The default flags set in the engine are: mountains, ridges +# The flags string modifies the engine defaults. # Flags that are not specified in the flag string are not modified from the default. -# Flags starting with "no" are used to explicitly disable them. +# Flags starting with 'no' are used to explicitly disable them. # type: flags possible values: mountains, ridges, nomountains, noridges # mgv7_spflags = mountains,ridges @@ -1169,9 +1185,11 @@ #### Mapgen flat # Map generation attributes specific to Mapgen flat. -# Occasional lakes and hills added to the flat world. +# Occasional lakes and hills can be added to the flat world. +# The default flags set in the engine are: none +# The flags string modifies the engine defaults. # Flags that are not specified in the flag string are not modified from the default. -# Flags starting with "no" are used to explicitly disable them. +# Flags starting with 'no' are used to explicitly disable them. # type: flags possible values: lakes, hills, , nolakes, nohills # mgflat_spflags = @@ -1303,10 +1321,13 @@ ##### General # Map generation attributes specific to Mapgen Valleys. +# 'altitude_chill' makes higher elevations colder, which may cause biome issues. +# 'humid_rivers' modifies the humidity around rivers and in areas where water would tend to pool, +# it may interfere with delicately adjusted biomes. +# The default flags set in the engine are: altitude_chill, humid_rivers +# The flags string modifies the engine defaults. # Flags that are not specified in the flag string are not modified from the default. -# Flags starting with "no" are used to explicitly disable them. -# "altitude_chill" makes higher elevations colder, which may cause biome issues. -# "humid_rivers" modifies the humidity around rivers and in areas where water would tend to pool. It may interfere with delicately adjusted biomes. +# Flags starting with 'no' are used to explicitly disable them. # type: flags possible values: altitude_chill, noaltitude_chill, humid_rivers, nohumid_rivers # mg_valleys_spflags = altitude_chill,humid_rivers diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a43e1c29..9e85911c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -348,7 +348,6 @@ add_subdirectory(unittest) add_subdirectory(util) set(common_SRCS - areastore.cpp ban.cpp cavegen.cpp chat.cpp diff --git a/src/client.cpp b/src/client.cpp index bd91f535..e9d7ea68 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1813,9 +1813,12 @@ void Client::makeScreenshot(IrrlichtDevice *device) + DIR_DELIM + std::string("screenshot_") + std::string(timetstamp_c); - std::string filename_ext = ".png"; + std::string filename_ext = "." + g_settings->get("screenshot_format"); std::string filename; + u32 quality = (u32)g_settings->getS32("screenshot_quality"); + quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255; + // Try to find a unique filename unsigned serial = 0; @@ -1837,7 +1840,7 @@ void Client::makeScreenshot(IrrlichtDevice *device) raw_image->copyTo(image); std::ostringstream sstr; - if (driver->writeImageToFile(image, filename.c_str())) { + if (driver->writeImageToFile(image, filename.c_str(), quality)) { sstr << "Saved screenshot to '" << filename << "'"; } else { sstr << "Failed to save screenshot '" << filename << "'"; diff --git a/src/clientiface.cpp b/src/clientiface.cpp index bcd9b3aa..21be98af 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -370,17 +370,21 @@ queue_full_break: void RemoteClient::GotBlock(v3s16 p) { - if(m_blocks_sending.find(p) != m_blocks_sending.end()) - m_blocks_sending.erase(p); - else - { - m_excess_gotblocks++; + if (m_blocks_modified.find(p) == m_blocks_modified.end()) { + if (m_blocks_sending.find(p) != m_blocks_sending.end()) + m_blocks_sending.erase(p); + else + m_excess_gotblocks++; + + m_blocks_sent.insert(p); } - m_blocks_sent.insert(p); } void RemoteClient::SentBlock(v3s16 p) { + if (m_blocks_modified.find(p) != m_blocks_modified.end()) + m_blocks_modified.erase(p); + if(m_blocks_sending.find(p) == m_blocks_sending.end()) m_blocks_sending[p] = 0.0; else @@ -397,6 +401,7 @@ void RemoteClient::SetBlockNotSent(v3s16 p) m_blocks_sending.erase(p); if(m_blocks_sent.find(p) != m_blocks_sent.end()) m_blocks_sent.erase(p); + m_blocks_modified.insert(p); } void RemoteClient::SetBlocksNotSent(std::map &blocks) @@ -409,6 +414,7 @@ void RemoteClient::SetBlocksNotSent(std::map &blocks) i != blocks.end(); ++i) { v3s16 p = i->first; + m_blocks_modified.insert(p); if(m_blocks_sending.find(p) != m_blocks_sending.end()) m_blocks_sending.erase(p); diff --git a/src/clientiface.h b/src/clientiface.h index 8d5db1cf..b8302a69 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -394,6 +394,16 @@ private: */ std::map m_blocks_sending; + /* + Blocks that have been modified since last sending them. + These blocks will not be marked as sent, even if the + client reports it has received them to account for blocks + that are being modified while on the line. + + List of block positions. + */ + std::set m_blocks_modified; + /* Count of excess GotBlocks(). There is an excess amount because the client sometimes diff --git a/src/collision.cpp b/src/collision.cpp index 34ccc146..e9722f75 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -185,6 +185,13 @@ bool wouldCollideWithCeiling( return false; } +static inline void getNeighborConnectingFace(v3s16 p, INodeDefManager *nodedef, + Map *map, MapNode n, int v, int *neighbors) +{ + MapNode n2 = map->getNodeNoEx(p); + if (nodedef->nodeboxConnects(n, n2, v)) + *neighbors |= v; +} collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, f32 pos_max_d, const aabb3f &box_0, @@ -261,12 +268,41 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, // Object collides into walkable nodes any_position_valid = true; - const ContentFeatures &f = gamedef->getNodeDefManager()->get(n); + INodeDefManager *nodedef = gamedef->getNodeDefManager(); + const ContentFeatures &f = nodedef->get(n); if(f.walkable == false) continue; int n_bouncy_value = itemgroup_get(f.groups, "bouncy"); - std::vector nodeboxes = n.getCollisionBoxes(gamedef->ndef()); + int neighbors = 0; + if (f.drawtype == NDT_NODEBOX && f.node_box.type == NODEBOX_CONNECTED) { + v3s16 p2 = p; + + p2.Y++; + getNeighborConnectingFace(p2, nodedef, map, n, 1, &neighbors); + + p2 = p; + p2.Y--; + getNeighborConnectingFace(p2, nodedef, map, n, 2, &neighbors); + + p2 = p; + p2.Z--; + getNeighborConnectingFace(p2, nodedef, map, n, 4, &neighbors); + + p2 = p; + p2.X--; + getNeighborConnectingFace(p2, nodedef, map, n, 8, &neighbors); + + p2 = p; + p2.Z++; + getNeighborConnectingFace(p2, nodedef, map, n, 16, &neighbors); + + p2 = p; + p2.X++; + getNeighborConnectingFace(p2, nodedef, map, n, 32, &neighbors); + } + std::vector nodeboxes; + n.getCollisionBoxes(gamedef->ndef(), &nodeboxes, neighbors); for(std::vector::iterator i = nodeboxes.begin(); i != nodeboxes.end(); ++i) diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index 8f5ed5ba..4e8ab134 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -163,6 +163,14 @@ void makeCuboid(MeshCollector *collector, const aabb3f &box, } } +static inline void getNeighborConnectingFace(v3s16 p, INodeDefManager *nodedef, + MeshMakeData *data, MapNode n, int v, int *neighbors) +{ + MapNode n2 = data->m_vmanip.getNodeNoEx(p); + if (nodedef->nodeboxConnects(n, n2, v)) + *neighbors |= v; +} + /* TODO: Fix alpha blending for special nodes Currently only the last element rendered is blended correct @@ -1501,7 +1509,38 @@ void mapblock_mesh_generate_special(MeshMakeData *data, v3f pos = intToFloat(p, BS); - std::vector boxes = n.getNodeBoxes(nodedef); + int neighbors = 0; + + // locate possible neighboring nodes to connect to + if (f.node_box.type == NODEBOX_CONNECTED) { + v3s16 p2 = p; + + p2.Y++; + getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 1, &neighbors); + + p2 = p; + p2.Y--; + getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 2, &neighbors); + + p2 = p; + p2.Z--; + getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 4, &neighbors); + + p2 = p; + p2.X--; + getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 8, &neighbors); + + p2 = p; + p2.Z++; + getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 16, &neighbors); + + p2 = p; + p2.X++; + getNeighborConnectingFace(blockpos_nodes + p2, nodedef, data, n, 32, &neighbors); + } + + std::vector boxes; + n.getNodeBoxes(nodedef, &boxes, neighbors); for(std::vector::iterator i = boxes.begin(); i != boxes.end(); ++i) diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index bcc0e059..0f438ba7 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -124,6 +124,8 @@ void set_default_settings(Settings *settings) settings->setDefault("invert_mouse", "false"); settings->setDefault("enable_clouds", "true"); settings->setDefault("screenshot_path", "."); + settings->setDefault("screenshot_format", "png"); + settings->setDefault("screenshot_quality", "0"); settings->setDefault("view_bobbing_amount", "1.0"); settings->setDefault("fall_bobbing_amount", "1.0"); settings->setDefault("enable_3d_clouds", "true"); diff --git a/src/environment.cpp b/src/environment.cpp index 3dbcff09..3031b631 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -48,6 +48,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" +#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:" + Environment::Environment(): m_time_of_day_speed(0), m_time_of_day(9000), @@ -206,6 +208,8 @@ void Environment::setDayNightRatioOverride(bool enable, u32 value) void Environment::setTimeOfDay(u32 time) { MutexAutoLock lock(this->m_time_lock); + if (m_time_of_day > time) + m_day_count++; m_time_of_day = time; m_time_of_day_f = (float)time / 24000.0; } @@ -236,8 +240,10 @@ void Environment::stepTimeOfDay(float dtime) bool sync_f = false; if (units > 0) { // Sync at overflow - if (m_time_of_day + units >= 24000) + if (m_time_of_day + units >= 24000) { sync_f = true; + m_day_count++; + } m_time_of_day = (m_time_of_day + units) % 24000; if (sync_f) m_time_of_day_f = (float)m_time_of_day / 24000.0; @@ -254,6 +260,13 @@ void Environment::stepTimeOfDay(float dtime) } } +u32 Environment::getDayCount() +{ + // Atomic counter + return m_day_count; +} + + /* ABMWithState */ @@ -270,6 +283,223 @@ ABMWithState::ABMWithState(ActiveBlockModifier *abm_): timer = myrand_range(minval, maxval); } +/* + LBMManager +*/ + +void LBMContentMapping::deleteContents() +{ + for (std::vector::iterator it = lbm_list.begin(); + it != lbm_list.end(); ++it) { + delete *it; + } +} + +void LBMContentMapping::addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef) +{ + // Add the lbm_def to the LBMContentMapping. + // Unknown names get added to the global NameIdMapping. + INodeDefManager *nodedef = gamedef->ndef(); + + lbm_list.push_back(lbm_def); + + for (std::set::const_iterator it = lbm_def->trigger_contents.begin(); + it != lbm_def->trigger_contents.end(); ++it) { + std::set c_ids; + bool found = nodedef->getIds(*it, c_ids); + if (!found) { + content_t c_id = gamedef->allocateUnknownNodeId(*it); + if (c_id == CONTENT_IGNORE) { + // Seems it can't be allocated. + warningstream << "Could not internalize node name \"" << *it + << "\" while loading LBM \"" << lbm_def->name << "\"." << std::endl; + continue; + } + c_ids.insert(c_id); + } + + for (std::set::const_iterator iit = + c_ids.begin(); iit != c_ids.end(); ++iit) { + content_t c_id = *iit; + map[c_id].push_back(lbm_def); + } + } +} + +const std::vector * + LBMContentMapping::lookup(content_t c) const +{ + container_map::const_iterator it = map.find(c); + if (it == map.end()) + return NULL; + // This first dereferences the iterator, returning + // a std::vector + // reference, then we convert it to a pointer. + return &(it->second); +} + +LBMManager::~LBMManager() +{ + for (std::map::iterator it = + m_lbm_defs.begin(); it != m_lbm_defs.end(); ++it) { + delete it->second; + } + for (lbm_lookup_map::iterator it = m_lbm_lookup.begin(); + it != m_lbm_lookup.end(); ++it) { + (it->second).deleteContents(); + } +} + +void LBMManager::addLBMDef(LoadingBlockModifierDef *lbm_def) +{ + // Precondition, in query mode the map isn't used anymore + FATAL_ERROR_IF(m_query_mode == true, + "attempted to modify LBMManager in query mode"); + + if (!string_allowed(lbm_def->name, LBM_NAME_ALLOWED_CHARS)) { + throw ModError("Error adding LBM \"" + lbm_def->name + + "\": Does not follow naming conventions: " + "Only chararacters [a-z0-9_:] are allowed."); + } + + m_lbm_defs[lbm_def->name] = lbm_def; +} + +void LBMManager::loadIntroductionTimes(const std::string ×, + IGameDef *gamedef, u32 now) +{ + m_query_mode = true; + + // name -> time map. + // Storing it in a map first instead of + // handling the stuff directly in the loop + // removes all duplicate entries. + // TODO make this std::unordered_map + std::map introduction_times; + + /* + The introduction times string consists of name~time entries, + with each entry terminated by a semicolon. The time is decimal. + */ + + size_t idx = 0; + size_t idx_new; + while ((idx_new = times.find(";", idx)) != std::string::npos) { + std::string entry = times.substr(idx, idx_new - idx); + std::vector components = str_split(entry, '~'); + if (components.size() != 2) + throw SerializationError("Introduction times entry \"" + + entry + "\" requires exactly one '~'!"); + const std::string &name = components[0]; + u32 time = from_string(components[1]); + introduction_times[name] = time; + idx = idx_new + 1; + } + + // Put stuff from introduction_times into m_lbm_lookup + for (std::map::const_iterator it = introduction_times.begin(); + it != introduction_times.end(); ++it) { + const std::string &name = it->first; + u32 time = it->second; + + std::map::iterator def_it = + m_lbm_defs.find(name); + if (def_it == m_lbm_defs.end()) { + // This seems to be an LBM entry for + // an LBM we haven't loaded. Discard it. + continue; + } + LoadingBlockModifierDef *lbm_def = def_it->second; + if (lbm_def->run_at_every_load) { + // This seems to be an LBM entry for + // an LBM that runs at every load. + // Don't add it just yet. + continue; + } + + m_lbm_lookup[time].addLBM(lbm_def, gamedef); + + // Erase the entry so that we know later + // what elements didn't get put into m_lbm_lookup + m_lbm_defs.erase(name); + } + + // Now also add the elements from m_lbm_defs to m_lbm_lookup + // that weren't added in the previous step. + // They are introduced first time to this world, + // or are run at every load (introducement time hardcoded to U32_MAX). + + LBMContentMapping &lbms_we_introduce_now = m_lbm_lookup[now]; + LBMContentMapping &lbms_running_always = m_lbm_lookup[U32_MAX]; + + for (std::map::iterator it = + m_lbm_defs.begin(); it != m_lbm_defs.end(); ++it) { + if (it->second->run_at_every_load) { + lbms_running_always.addLBM(it->second, gamedef); + } else { + lbms_we_introduce_now.addLBM(it->second, gamedef); + } + } + + // Clear the list, so that we don't delete remaining elements + // twice in the destructor + m_lbm_defs.clear(); +} + +std::string LBMManager::createIntroductionTimesString() +{ + // Precondition, we must be in query mode + FATAL_ERROR_IF(m_query_mode == false, + "attempted to query on non fully set up LBMManager"); + + std::ostringstream oss; + for (lbm_lookup_map::iterator it = m_lbm_lookup.begin(); + it != m_lbm_lookup.end(); ++it) { + u32 time = it->first; + std::vector &lbm_list = it->second.lbm_list; + for (std::vector::iterator iit = lbm_list.begin(); + iit != lbm_list.end(); ++iit) { + // Don't add if the LBM runs at every load, + // then introducement time is hardcoded + // and doesn't need to be stored + if ((*iit)->run_at_every_load) + continue; + oss << (*iit)->name << "~" << time << ";"; + } + } + return oss.str(); +} + +void LBMManager::applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp) +{ + // Precondition, we need m_lbm_lookup to be initialized + FATAL_ERROR_IF(m_query_mode == false, + "attempted to query on non fully set up LBMManager"); + v3s16 pos_of_block = block->getPosRelative(); + v3s16 pos; + MapNode n; + content_t c; + lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp); + for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++) + for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++) + for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) + { + n = block->getNodeNoEx(pos); + c = n.getContent(); + for (LBMManager::lbm_lookup_map::const_iterator iit = it; + iit != m_lbm_lookup.end(); ++iit) { + const std::vector *lbm_list = + iit->second.lookup(c); + if (!lbm_list) + continue; + for (std::vector::const_iterator iit = + lbm_list->begin(); iit != lbm_list->end(); ++iit) { + (*iit)->trigger(env, pos + pos_of_block, n); + } + } + } +} + /* ActiveBlockList */ @@ -505,6 +735,10 @@ void ServerEnvironment::saveMeta() args.setU64("game_time", m_game_time); args.setU64("time_of_day", getTimeOfDay()); args.setU64("last_clear_objects_time", m_last_clear_objects_time); + args.setU64("lbm_introduction_times_version", 1); + args.set("lbm_introduction_times", + m_lbm_mgr.createIntroductionTimesString()); + args.setU64("day_count", m_day_count); args.writeLines(ss); ss<<"EnvArgsEnd\n"; @@ -542,19 +776,35 @@ void ServerEnvironment::loadMeta() throw SerializationError("Couldn't load env meta game_time"); } - try { - setTimeOfDay(args.getU64("time_of_day")); - } catch (SettingNotFoundException &e) { - // This is not as important - setTimeOfDay(9000); - } + setTimeOfDay(args.exists("time_of_day") ? + // set day to morning by default + args.getU64("time_of_day") : 9000); - try { - m_last_clear_objects_time = args.getU64("last_clear_objects_time"); - } catch (SettingNotFoundException &e) { + m_last_clear_objects_time = args.exists("last_clear_objects_time") ? // If missing, do as if clearObjects was never called - m_last_clear_objects_time = 0; + args.getU64("last_clear_objects_time") : 0; + + std::string lbm_introduction_times = ""; + try { + u64 ver = args.getU64("lbm_introduction_times_version"); + if (ver == 1) { + lbm_introduction_times = args.get("lbm_introduction_times"); + } else { + infostream << "ServerEnvironment::loadMeta(): Non-supported" + << " introduction time version " << ver << std::endl; + } + } catch (SettingNotFoundException &e) { + // No problem, this is expected. Just continue with an empty string } + m_lbm_mgr.loadIntroductionTimes(lbm_introduction_times, m_gamedef, m_game_time); + + m_day_count = args.exists("day_count") ? + args.getU64("day_count") : 0; +} + +void ServerEnvironment::loadDefaultMeta() +{ + m_lbm_mgr.loadIntroductionTimes("", m_gamedef, m_game_time); } struct ActiveABM @@ -770,6 +1020,9 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) // Activate stored objects activateObjects(block, dtime_s); + /* Handle LoadingBlockModifiers */ + m_lbm_mgr.applyLBMs(this, block, stamp); + // Run node timers std::map elapsed_timers = block->m_node_timers.step((float)dtime_s); @@ -795,6 +1048,11 @@ void ServerEnvironment::addActiveBlockModifier(ActiveBlockModifier *abm) m_abms.push_back(ABMWithState(abm)); } +void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm) +{ + m_lbm_mgr.addLBMDef(lbm); +} + bool ServerEnvironment::setNode(v3s16 p, const MapNode &n) { INodeDefManager *ndef = m_gamedef->ndef(); diff --git a/src/environment.h b/src/environment.h index dd91f4f0..8cb51fef 100644 --- a/src/environment.h +++ b/src/environment.h @@ -95,6 +95,8 @@ public: void setDayNightRatioOverride(bool enable, u32 value); + u32 getDayCount(); + // counter used internally when triggering ABMs u32 m_added_objects; @@ -117,6 +119,9 @@ protected: // Overriding the day-night ratio is useful for custom sky visuals bool m_enable_day_night_ratio_override; u32 m_day_night_ratio_override; + // Days from the server start, accounts for time shift + // in game (e.g. /time or bed usage) + Atomic m_day_count; /* * Above: values managed by m_time_lock */ @@ -139,7 +144,7 @@ private: }; /* - Active block modifier interface. + {Active, Loading} block modifier interface. These are fed into ServerEnvironment at initialization time; ServerEnvironment handles deleting them. @@ -177,6 +182,77 @@ struct ABMWithState ABMWithState(ActiveBlockModifier *abm_); }; +struct LoadingBlockModifierDef +{ + // Set of contents to trigger on + std::set trigger_contents; + std::string name; + bool run_at_every_load; + + virtual ~LoadingBlockModifierDef() {} + virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){}; +}; + +struct LBMContentMapping +{ + typedef std::map > container_map; + container_map map; + + std::vector lbm_list; + + // Needs to be separate method (not inside destructor), + // because the LBMContentMapping may be copied and destructed + // many times during operation in the lbm_lookup_map. + void deleteContents(); + void addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef); + const std::vector *lookup(content_t c) const; +}; + +class LBMManager +{ +public: + LBMManager(): + m_query_mode(false) + {} + + ~LBMManager(); + + // Don't call this after loadIntroductionTimes() ran. + void addLBMDef(LoadingBlockModifierDef *lbm_def); + + void loadIntroductionTimes(const std::string ×, + IGameDef *gamedef, u32 now); + + // Don't call this before loadIntroductionTimes() ran. + std::string createIntroductionTimesString(); + + // Don't call this before loadIntroductionTimes() ran. + void applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp); + + // Warning: do not make this std::unordered_map, order is relevant here + typedef std::map lbm_lookup_map; + +private: + // Once we set this to true, we can only query, + // not modify + bool m_query_mode; + + // For m_query_mode == false: + // The key of the map is the LBM def's name. + // TODO make this std::unordered_map + std::map m_lbm_defs; + + // For m_query_mode == true: + // The key of the map is the LBM def's first introduction time. + lbm_lookup_map m_lbm_lookup; + + // Returns an iterator to the LBMs that were introduced + // after the given time. This is guaranteed to return + // valid values for everything + lbm_lookup_map::const_iterator getLBMsIntroducedAfter(u32 time) + { return m_lbm_lookup.lower_bound(time); } +}; + /* List of active blocks, used by ServerEnvironment */ @@ -254,6 +330,9 @@ public: */ void saveMeta(); void loadMeta(); + // to be called instead of loadMeta if + // env_meta.txt doesn't exist (e.g. new world) + void loadDefaultMeta(); /* External ActiveObject interface @@ -312,11 +391,12 @@ public: void activateBlock(MapBlock *block, u32 additional_dtime=0); /* - ActiveBlockModifiers + {Active,Loading}BlockModifiers ------------------------------------------- */ void addActiveBlockModifier(ActiveBlockModifier *abm); + void addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm); /* Other stuff @@ -427,6 +507,7 @@ private: u32 m_last_clear_objects_time; // Active block modifiers std::vector m_abms; + LBMManager m_lbm_mgr; // An interval for generally sending object positions and stuff float m_recommended_send_interval; // Estimate for general maximum lag as determined by server. diff --git a/src/game.cpp b/src/game.cpp index 57d447a1..2eca7850 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -358,7 +358,9 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio if (!isPointableNode(n, client, liquids_pointable)) { continue; } - std::vector boxes = n.getSelectionBoxes(nodedef); + + std::vector boxes; + n.getSelectionBoxes(nodedef, &boxes); v3s16 np(x, y, z); v3f npf = intToFloat(np, BS); @@ -389,7 +391,8 @@ PointedThing getPointedThing(Client *client, Hud *hud, const v3f &player_positio f32 d = 0.001 * BS; MapNode n = map.getNodeNoEx(pointed_pos); v3f npf = intToFloat(pointed_pos, BS); - std::vector boxes = n.getSelectionBoxes(nodedef); + std::vector boxes; + n.getSelectionBoxes(nodedef, &boxes); f32 face_min_distance = 1000 * BS; for (std::vector::const_iterator i = boxes.begin(); diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp index 261b5e30..45815d99 100644 --- a/src/guiChatConsole.cpp +++ b/src/guiChatConsole.cpp @@ -122,6 +122,8 @@ void GUIChatConsole::openConsole(f32 height) m_desired_height_fraction = height; m_desired_height = height * m_screensize.Y; reformatConsole(); + m_animate_time_old = getTimeMs(); + IGUIElement::setVisible(true); Environment->setFocus(this); m_menumgr->createdMenu(this); } @@ -243,6 +245,11 @@ void GUIChatConsole::animate(u32 msec) { // animate the console height s32 goal = m_open ? m_desired_height : 0; + + // Set invisible if close animation finished (reset by openConsole) + if (!m_open && m_height == 0) + IGUIElement::setVisible(false); + if (m_height != goal) { s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0); @@ -628,3 +635,13 @@ bool GUIChatConsole::OnEvent(const SEvent& event) return Parent ? Parent->OnEvent(event) : false; } +void GUIChatConsole::setVisible(bool visible) +{ + m_open = visible; + IGUIElement::setVisible(visible); + if (!visible) { + m_height = 0; + recalculateConsolePosition(); + } +} + diff --git a/src/guiChatConsole.h b/src/guiChatConsole.h index 1400a66a..98fafcff 100644 --- a/src/guiChatConsole.h +++ b/src/guiChatConsole.h @@ -77,6 +77,8 @@ public: virtual bool OnEvent(const SEvent& event); + virtual void setVisible(bool visible); + private: void reformatConsole(); void recalculateConsolePosition(); diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 736de3c6..8ec95ef9 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -183,7 +183,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, */ if (control.sneak && m_sneak_node_exists && !(fly_allowed && g_settings->getBool("free_move")) && !in_liquid && - physics_override_sneak) { + physics_override_sneak && !got_teleported) { f32 maxd = 0.5 * BS + sneak_max; v3f lwn_f = intToFloat(m_sneak_node, BS); position.X = rangelim(position.X, lwn_f.X-maxd, lwn_f.X+maxd); @@ -204,6 +204,9 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, } } + if (got_teleported) + got_teleported = false; + // this shouldn't be hardcoded but transmitted from server float player_stepheight = touching_ground ? (BS*0.6) : (BS*0.2); @@ -310,7 +313,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, if (sneak_node_found) { f32 cb_max = 0; MapNode n = map->getNodeNoEx(m_sneak_node); - std::vector nodeboxes = n.getCollisionBoxes(nodemgr); + std::vector nodeboxes; + n.getCollisionBoxes(nodemgr, &nodeboxes); for (std::vector::iterator it = nodeboxes.begin(); it != nodeboxes.end(); ++it) { aabb3f box = *it; diff --git a/src/mapgen_flat.cpp b/src/mapgen_flat.cpp index 6b900bc8..40fd7103 100644 --- a/src/mapgen_flat.cpp +++ b/src/mapgen_flat.cpp @@ -573,6 +573,12 @@ void MapgenFlat::generateCaves(s16 max_stone_y) for (s16 y = node_max.Y + 1; y >= node_min.Y - 1; y--, index3d -= ystride, vm->m_area.add_y(em, vi, -1)) { + // Don't excavate the overgenerated stone at node_max.Y + 1, + // this creates a 'roof' over the tunnel, preventing light in + // tunnels at mapchunk borders when generating mapchunks upwards. + if (y > node_max.Y) + continue; + content_t c = vm->m_data[vi].getContent(); if (c == CONTENT_AIR || c == biome->c_water_top || c == biome->c_water) { diff --git a/src/mapgen_fractal.cpp b/src/mapgen_fractal.cpp index 5c2c0aab..91c58295 100644 --- a/src/mapgen_fractal.cpp +++ b/src/mapgen_fractal.cpp @@ -701,6 +701,12 @@ void MapgenFractal::generateCaves(s16 max_stone_y) for (s16 y = node_max.Y + 1; y >= node_min.Y - 1; y--, index3d -= ystride, vm->m_area.add_y(em, vi, -1)) { + // Don't excavate the overgenerated stone at node_max.Y + 1, + // this creates a 'roof' over the tunnel, preventing light in + // tunnels at mapchunk borders when generating mapchunks upwards. + if (y > node_max.Y) + continue; + content_t c = vm->m_data[vi].getContent(); if (c == CONTENT_AIR || c == biome->c_water_top || c == biome->c_water) { diff --git a/src/mapgen_v5.cpp b/src/mapgen_v5.cpp index 5bc45189..fcd0407c 100644 --- a/src/mapgen_v5.cpp +++ b/src/mapgen_v5.cpp @@ -519,6 +519,12 @@ void MapgenV5::generateCaves(int max_stone_y) for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { u32 vi = vm->m_area.index(node_min.X, y, z); for (s16 x = node_min.X; x <= node_max.X; x++, vi++, index++) { + // Don't excavate the overgenerated stone at node_max.Y + 1, + // this creates a 'roof' over the tunnel, preventing light in + // tunnels at mapchunk borders when generating mapchunks upwards. + if (y > node_max.Y) + continue; + float d1 = contour(noise_cave1->result[index]); float d2 = contour(noise_cave2->result[index]); if (d1 * d2 > 0.125f) { diff --git a/src/mapgen_v7.cpp b/src/mapgen_v7.cpp index 32953237..0a6d0e5d 100644 --- a/src/mapgen_v7.cpp +++ b/src/mapgen_v7.cpp @@ -883,6 +883,12 @@ void MapgenV7::generateCaves(s16 max_stone_y) for (s16 y = node_max.Y + 1; y >= node_min.Y - 1; y--, index3d -= ystride, vm->m_area.add_y(em, vi, -1)) { + // Don't excavate the overgenerated stone at node_max.Y + 1, + // this creates a 'roof' over the tunnel, preventing light in + // tunnels at mapchunk borders when generating mapchunks upwards. + if (y > node_max.Y) + continue; + content_t c = vm->m_data[vi].getContent(); if (c == CONTENT_AIR || c == biome->c_water_top || c == biome->c_water) { diff --git a/src/mapgen_valleys.cpp b/src/mapgen_valleys.cpp index e9b1d391..c60c5fbf 100644 --- a/src/mapgen_valleys.cpp +++ b/src/mapgen_valleys.cpp @@ -931,6 +931,12 @@ void MapgenValleys::generateCaves(s16 max_stone_y) for (s16 y = node_max.Y + 1; y >= node_min.Y - 1; y--, index_3d -= ystride, vm->m_area.add_y(em, index_data, -1)) { + // Don't excavate the overgenerated stone at node_max.Y + 1, + // this creates a 'roof' over the tunnel, preventing light in + // tunnels at mapchunk borders when generating mapchunks upwards. + if (y > node_max.Y) + continue; + float terrain = noise_terrain_height->result[index_2d]; // Saves some time. diff --git a/src/mapnode.cpp b/src/mapnode.cpp index d51d6d03..486dd1f1 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -214,12 +214,12 @@ void MapNode::rotateAlongYAxis(INodeDefManager *nodemgr, Rotation rot) } } -static std::vector transformNodeBox(const MapNode &n, - const NodeBox &nodebox, INodeDefManager *nodemgr) +void transformNodeBox(const MapNode &n, const NodeBox &nodebox, + INodeDefManager *nodemgr, std::vector *p_boxes, u8 neighbors = 0) { - std::vector boxes; - if(nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED) - { + std::vector &boxes = *p_boxes; + + if (nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED) { const std::vector &fixed = nodebox.fixed; int facedir = n.getFaceDir(nodemgr); u8 axisdir = facedir>>2; @@ -395,32 +395,71 @@ static std::vector transformNodeBox(const MapNode &n, boxes.push_back(box); } } + else if (nodebox.type == NODEBOX_CONNECTED) + { + size_t boxes_size = boxes.size(); + boxes_size += nodebox.fixed.size(); + if (neighbors & 1) + boxes_size += nodebox.connect_top.size(); + if (neighbors & 2) + boxes_size += nodebox.connect_bottom.size(); + if (neighbors & 4) + boxes_size += nodebox.connect_front.size(); + if (neighbors & 8) + boxes_size += nodebox.connect_left.size(); + if (neighbors & 16) + boxes_size += nodebox.connect_back.size(); + if (neighbors & 32) + boxes_size += nodebox.connect_right.size(); + boxes.reserve(boxes_size); + +#define BOXESPUSHBACK(c) do { \ + for (std::vector::const_iterator \ + it = (c).begin(); \ + it != (c).end(); ++it) \ + (boxes).push_back(*it); \ + } while (0) + + BOXESPUSHBACK(nodebox.fixed); + + if (neighbors & 1) + BOXESPUSHBACK(nodebox.connect_top); + if (neighbors & 2) + BOXESPUSHBACK(nodebox.connect_bottom); + if (neighbors & 4) + BOXESPUSHBACK(nodebox.connect_front); + if (neighbors & 8) + BOXESPUSHBACK(nodebox.connect_left); + if (neighbors & 16) + BOXESPUSHBACK(nodebox.connect_back); + if (neighbors & 32) + BOXESPUSHBACK(nodebox.connect_right); + } else // NODEBOX_REGULAR { boxes.push_back(aabb3f(-BS/2,-BS/2,-BS/2,BS/2,BS/2,BS/2)); } - return boxes; } -std::vector MapNode::getNodeBoxes(INodeDefManager *nodemgr) const +void MapNode::getNodeBoxes(INodeDefManager *nodemgr, std::vector *boxes, u8 neighbors) { const ContentFeatures &f = nodemgr->get(*this); - return transformNodeBox(*this, f.node_box, nodemgr); + transformNodeBox(*this, f.node_box, nodemgr, boxes, neighbors); } -std::vector MapNode::getCollisionBoxes(INodeDefManager *nodemgr) const +void MapNode::getCollisionBoxes(INodeDefManager *nodemgr, std::vector *boxes, u8 neighbors) { const ContentFeatures &f = nodemgr->get(*this); if (f.collision_box.fixed.empty()) - return transformNodeBox(*this, f.node_box, nodemgr); + transformNodeBox(*this, f.node_box, nodemgr, boxes, neighbors); else - return transformNodeBox(*this, f.collision_box, nodemgr); + transformNodeBox(*this, f.collision_box, nodemgr, boxes, neighbors); } -std::vector MapNode::getSelectionBoxes(INodeDefManager *nodemgr) const +void MapNode::getSelectionBoxes(INodeDefManager *nodemgr, std::vector *boxes) { const ContentFeatures &f = nodemgr->get(*this); - return transformNodeBox(*this, f.selection_box, nodemgr); + transformNodeBox(*this, f.selection_box, nodemgr, boxes); } u8 MapNode::getMaxLevel(INodeDefManager *nodemgr) const diff --git a/src/mapnode.h b/src/mapnode.h index 0a6d1c51..72ac3463 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -240,17 +240,17 @@ struct MapNode /* Gets list of node boxes (used for rendering (NDT_NODEBOX)) */ - std::vector getNodeBoxes(INodeDefManager *nodemgr) const; + void getNodeBoxes(INodeDefManager *nodemgr, std::vector *boxes, u8 neighbors = 0); /* Gets list of selection boxes */ - std::vector getSelectionBoxes(INodeDefManager *nodemgr) const; + void getSelectionBoxes(INodeDefManager *nodemg, std::vector *boxes); /* Gets list of collision boxes */ - std::vector getCollisionBoxes(INodeDefManager *nodemgr) const; + void getCollisionBoxes(INodeDefManager *nodemgr, std::vector *boxes, u8 neighbors = 0); /* Liquid helpers diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 2e1ec230..36a3e32a 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -552,6 +552,7 @@ void Client::handleCommand_MovePlayer(NetworkPacket* pkt) *pkt >> pos >> pitch >> yaw; + player->got_teleported = true; player->setPosition(pos); infostream << "Client got TOCLIENT_MOVE_PLAYER" diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 843fb7dd..2c505851 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -135,6 +135,7 @@ with this program; if not, write to the Free Software Foundation, Inc., PROTOCOL_VERSION 27: backface_culling: backwards compatibility for playing with newer client on pre-27 servers. + Add nodedef v3 - connected nodeboxes */ #define LATEST_PROTOCOL_VERSION 27 diff --git a/src/nodedef.cpp b/src/nodedef.cpp index b1fe47c8..21aaa084 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "exceptions.h" #include "debug.h" #include "gamedef.h" +#include "mapnode.h" #include // Used in applyTextureOverrides() /* @@ -48,44 +49,91 @@ void NodeBox::reset() wall_top = aabb3f(-BS/2, BS/2-BS/16., -BS/2, BS/2, BS/2, BS/2); wall_bottom = aabb3f(-BS/2, -BS/2, -BS/2, BS/2, -BS/2+BS/16., BS/2); wall_side = aabb3f(-BS/2, -BS/2, -BS/2, -BS/2+BS/16., BS/2, BS/2); + // no default for other parts + connect_top.clear(); + connect_bottom.clear(); + connect_front.clear(); + connect_left.clear(); + connect_back.clear(); + connect_right.clear(); } void NodeBox::serialize(std::ostream &os, u16 protocol_version) const { - int version = protocol_version >= 21 ? 2 : 1; + int version = 1; + if (protocol_version >= 27) + version = 3; + else if (protocol_version >= 21) + version = 2; writeU8(os, version); - if (version == 1 && type == NODEBOX_LEVELED) - writeU8(os, NODEBOX_FIXED); - else - writeU8(os, type); + switch (type) { + case NODEBOX_LEVELED: + case NODEBOX_FIXED: + if (version == 1) + writeU8(os, NODEBOX_FIXED); + else + writeU8(os, type); - if(type == NODEBOX_FIXED || type == NODEBOX_LEVELED) - { writeU16(os, fixed.size()); - for(std::vector::const_iterator + for (std::vector::const_iterator i = fixed.begin(); i != fixed.end(); ++i) { writeV3F1000(os, i->MinEdge); writeV3F1000(os, i->MaxEdge); } - } - else if(type == NODEBOX_WALLMOUNTED) - { + break; + case NODEBOX_WALLMOUNTED: + writeU8(os, type); + writeV3F1000(os, wall_top.MinEdge); writeV3F1000(os, wall_top.MaxEdge); writeV3F1000(os, wall_bottom.MinEdge); writeV3F1000(os, wall_bottom.MaxEdge); writeV3F1000(os, wall_side.MinEdge); writeV3F1000(os, wall_side.MaxEdge); + break; + case NODEBOX_CONNECTED: + if (version <= 2) { + // send old clients nodes that can't be walked through + // to prevent abuse + writeU8(os, NODEBOX_FIXED); + + writeU16(os, 1); + writeV3F1000(os, v3f(-BS/2, -BS/2, -BS/2)); + writeV3F1000(os, v3f(BS/2, BS/2, BS/2)); + } else { + writeU8(os, type); + +#define WRITEBOX(box) do { \ + writeU16(os, (box).size()); \ + for (std::vector::const_iterator \ + i = (box).begin(); \ + i != (box).end(); ++i) { \ + writeV3F1000(os, i->MinEdge); \ + writeV3F1000(os, i->MaxEdge); \ + }; } while (0) + + WRITEBOX(fixed); + WRITEBOX(connect_top); + WRITEBOX(connect_bottom); + WRITEBOX(connect_front); + WRITEBOX(connect_left); + WRITEBOX(connect_back); + WRITEBOX(connect_right); + } + break; + default: + writeU8(os, type); + break; } } void NodeBox::deSerialize(std::istream &is) { int version = readU8(is); - if(version < 1 || version > 2) + if (version < 1 || version > 3) throw SerializationError("unsupported NodeBox version"); reset(); @@ -112,6 +160,26 @@ void NodeBox::deSerialize(std::istream &is) wall_side.MinEdge = readV3F1000(is); wall_side.MaxEdge = readV3F1000(is); } + else if (type == NODEBOX_CONNECTED) + { +#define READBOXES(box) do { \ + count = readU16(is); \ + (box).reserve(count); \ + while (count--) { \ + v3f min = readV3F1000(is); \ + v3f max = readV3F1000(is); \ + (box).push_back(aabb3f(min, max)); }; } while (0) + + u16 count; + + READBOXES(fixed); + READBOXES(connect_top); + READBOXES(connect_bottom); + READBOXES(connect_front); + READBOXES(connect_left); + READBOXES(connect_back); + READBOXES(connect_right); + } } /* @@ -261,6 +329,9 @@ void ContentFeatures::reset() sound_footstep = SimpleSoundSpec(); sound_dig = SimpleSoundSpec("__group"); sound_dug = SimpleSoundSpec(); + connects_to.clear(); + connects_to_ids.clear(); + connect_sides = 0; } void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const @@ -328,6 +399,11 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const os<::const_iterator i = connects_to_ids.begin(); + i != connects_to_ids.end(); ++i) + writeU16(os, *i); + writeU8(os, connect_sides); } void ContentFeatures::deSerialize(std::istream &is) @@ -402,6 +478,11 @@ void ContentFeatures::deSerialize(std::istream &is) mesh = deSerializeString(is); collision_box.deSerialize(is); floodable = readU8(is); + u16 connects_to_size = readU16(is); + connects_to_ids.clear(); + for (u16 i = 0; i < connects_to_size; i++) + connects_to_ids.insert(readU16(is)); + connect_sides = readU8(is); }catch(SerializationError &e) {}; } @@ -419,7 +500,7 @@ public: inline virtual const ContentFeatures& get(const MapNode &n) const; virtual bool getId(const std::string &name, content_t &result) const; virtual content_t getId(const std::string &name) const; - virtual void getIds(const std::string &name, std::set &result) const; + virtual bool getIds(const std::string &name, std::set &result) const; virtual const ContentFeatures& get(const std::string &name) const; content_t allocateId(); virtual content_t set(const std::string &name, const ContentFeatures &def); @@ -439,6 +520,8 @@ public: virtual bool cancelNodeResolveCallback(NodeResolver *nr); virtual void runNodeResolveCallbacks(); virtual void resetNodeResolveState(); + virtual void mapNodeboxConnections(); + virtual bool nodeboxConnects(MapNode from, MapNode to, u8 connect_face); private: void addNameIdMapping(content_t i, std::string name); @@ -603,22 +686,23 @@ content_t CNodeDefManager::getId(const std::string &name) const } -void CNodeDefManager::getIds(const std::string &name, +bool CNodeDefManager::getIds(const std::string &name, std::set &result) const { //TimeTaker t("getIds", NULL, PRECISION_MICRO); if (name.substr(0,6) != "group:") { content_t id = CONTENT_IGNORE; - if(getId(name, id)) + bool exists = getId(name, id); + if (exists) result.insert(id); - return; + return exists; } std::string group = name.substr(6); std::map::const_iterator i = m_group_to_items.find(group); if (i == m_group_to_items.end()) - return; + return true; const GroupItems &items = i->second; for (GroupItems::const_iterator j = items.begin(); @@ -627,6 +711,7 @@ void CNodeDefManager::getIds(const std::string &name, result.insert((*j).first); } //printf("getIds: %dus\n", t.stop()); + return true; } @@ -1436,6 +1521,57 @@ void CNodeDefManager::resetNodeResolveState() m_pending_resolve_callbacks.clear(); } +void CNodeDefManager::mapNodeboxConnections() +{ + for (u32 i = 0; i < m_content_features.size(); i++) { + ContentFeatures *f = &m_content_features[i]; + if ((f->drawtype != NDT_NODEBOX) || (f->node_box.type != NODEBOX_CONNECTED)) + continue; + for (std::vector::iterator it = f->connects_to.begin(); + it != f->connects_to.end(); ++it) { + getIds(*it, f->connects_to_ids); + } + } +} + +bool CNodeDefManager::nodeboxConnects(MapNode from, MapNode to, u8 connect_face) +{ + const ContentFeatures &f1 = get(from); + + if ((f1.drawtype != NDT_NODEBOX) || (f1.node_box.type != NODEBOX_CONNECTED)) + return false; + + // lookup target in connected set + if (f1.connects_to_ids.find(to.param0) == f1.connects_to_ids.end()) + return false; + + const ContentFeatures &f2 = get(to); + + if ((f2.drawtype == NDT_NODEBOX) && (f2.node_box.type == NODEBOX_CONNECTED)) + // ignores actually looking if back connection exists + return (f2.connects_to_ids.find(from.param0) != f2.connects_to_ids.end()); + + // does to node declare usable faces? + if (f2.connect_sides > 0) { + if ((f2.param_type_2 == CPT2_FACEDIR) && (connect_face >= 4)) { + static const u8 rot[33 * 4] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 32, 16, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4 - back + 8, 4, 32, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 - right + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 16, 8, 4, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - front + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 32, 16, 8, 4 // 32 - left + }; + return (f2.connect_sides & rot[(connect_face * 4) + to.param2]); + } + return (f2.connect_sides & connect_face); + } + // the target is just a regular node, so connect no matter back connection + return true; +} //// //// NodeResolver diff --git a/src/nodedef.h b/src/nodedef.h index c8c2423d..e87226a7 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -80,6 +80,7 @@ enum NodeBoxType NODEBOX_FIXED, // Static separately defined box(es) NODEBOX_WALLMOUNTED, // Box for wall mounted nodes; (top, bottom, side) NODEBOX_LEVELED, // Same as fixed, but with dynamic height from param2. for snow, ... + NODEBOX_CONNECTED, // optionally draws nodeboxes if a neighbor node attaches }; struct NodeBox @@ -92,6 +93,13 @@ struct NodeBox aabb3f wall_top; aabb3f wall_bottom; aabb3f wall_side; // being at the -X side + // NODEBOX_CONNECTED + std::vector connect_top; + std::vector connect_bottom; + std::vector connect_front; + std::vector connect_left; + std::vector connect_back; + std::vector connect_right; NodeBox() { reset(); } @@ -263,12 +271,17 @@ struct ContentFeatures bool legacy_facedir_simple; // Set to true if wall_mounted used to be set to true bool legacy_wallmounted; + // for NDT_CONNECTED pairing + u8 connect_sides; // Sound properties SimpleSoundSpec sound_footstep; SimpleSoundSpec sound_dig; SimpleSoundSpec sound_dug; + std::vector connects_to; + std::set connects_to_ids; + /* Methods */ @@ -303,7 +316,8 @@ public: virtual bool getId(const std::string &name, content_t &result) const=0; virtual content_t getId(const std::string &name) const=0; // Allows "group:name" in addition to regular node names - virtual void getIds(const std::string &name, std::set &result) + // returns false if node name not found, true otherwise + virtual bool getIds(const std::string &name, std::set &result) const=0; virtual const ContentFeatures &get(const std::string &name) const=0; @@ -313,6 +327,7 @@ public: virtual void pendNodeResolve(NodeResolver *nr)=0; virtual bool cancelNodeResolveCallback(NodeResolver *nr)=0; + virtual bool nodeboxConnects(const MapNode from, const MapNode to, u8 connect_face)=0; }; class IWritableNodeDefManager : public INodeDefManager { @@ -327,7 +342,7 @@ public: // If not found, returns CONTENT_IGNORE virtual content_t getId(const std::string &name) const=0; // Allows "group:name" in addition to regular node names - virtual void getIds(const std::string &name, std::set &result) + virtual bool getIds(const std::string &name, std::set &result) const=0; // If not found, returns the features of CONTENT_UNKNOWN virtual const ContentFeatures &get(const std::string &name) const=0; @@ -367,6 +382,7 @@ public: virtual bool cancelNodeResolveCallback(NodeResolver *nr)=0; virtual void runNodeResolveCallbacks()=0; virtual void resetNodeResolveState()=0; + virtual void mapNodeboxConnections()=0; }; IWritableNodeDefManager *createNodeDefManager(); diff --git a/src/player.cpp b/src/player.cpp index f4d1048f..c5669cd1 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Player::Player(IGameDef *gamedef, const char *name): + got_teleported(false), touching_ground(false), in_liquid(false), in_liquid_stable(false), diff --git a/src/player.h b/src/player.h index 5cf6994f..3abd507c 100644 --- a/src/player.h +++ b/src/player.h @@ -318,6 +318,7 @@ public: // Use a function, if isDead can be defined by other conditions bool isDead() { return hp == 0; } + bool got_teleported; bool touching_ground; // This oscillates so that the player jumps a bit above the surface bool in_liquid; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index f7ebbafa..98debb2d 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -535,6 +535,46 @@ ContentFeatures read_content_features(lua_State *L, int index) f.node_box = read_nodebox(L, -1); lua_pop(L, 1); + lua_getfield(L, index, "connects_to"); + if (lua_istable(L, -1)) { + int table = lua_gettop(L); + lua_pushnil(L); + while (lua_next(L, table) != 0) { + // Value at -1 + f.connects_to.push_back(lua_tostring(L, -1)); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + lua_getfield(L, index, "connect_sides"); + if (lua_istable(L, -1)) { + int table = lua_gettop(L); + lua_pushnil(L); + while (lua_next(L, table) != 0) { + // Value at -1 + std::string side(lua_tostring(L, -1)); + // Note faces are flipped to make checking easier + if (side == "top") + f.connect_sides |= 2; + else if (side == "bottom") + f.connect_sides |= 1; + else if (side == "front") + f.connect_sides |= 16; + else if (side == "left") + f.connect_sides |= 32; + else if (side == "back") + f.connect_sides |= 4; + else if (side == "right") + f.connect_sides |= 8; + else + warningstream << "Unknown value for \"connect_sides\": " + << side << std::endl; + lua_pop(L, 1); + } + } + lua_pop(L, 1); + lua_getfield(L, index, "selection_box"); if(lua_istable(L, -1)) f.selection_box = read_nodebox(L, -1); @@ -627,25 +667,31 @@ NodeBox read_nodebox(lua_State *L, int index) nodebox.type = (NodeBoxType)getenumfield(L, index, "type", ScriptApiNode::es_NodeBoxType, NODEBOX_REGULAR); - lua_getfield(L, index, "fixed"); - if(lua_istable(L, -1)) - nodebox.fixed = read_aabb3f_vector(L, -1, BS); - lua_pop(L, 1); +#define NODEBOXREAD(n, s) \ + do { \ + lua_getfield(L, index, (s)); \ + if (lua_istable(L, -1)) \ + (n) = read_aabb3f(L, -1, BS); \ + lua_pop(L, 1); \ + } while (0) - lua_getfield(L, index, "wall_top"); - if(lua_istable(L, -1)) - nodebox.wall_top = read_aabb3f(L, -1, BS); - lua_pop(L, 1); - - lua_getfield(L, index, "wall_bottom"); - if(lua_istable(L, -1)) - nodebox.wall_bottom = read_aabb3f(L, -1, BS); - lua_pop(L, 1); - - lua_getfield(L, index, "wall_side"); - if(lua_istable(L, -1)) - nodebox.wall_side = read_aabb3f(L, -1, BS); - lua_pop(L, 1); +#define NODEBOXREADVEC(n, s) \ + do { \ + lua_getfield(L, index, (s)); \ + if (lua_istable(L, -1)) \ + (n) = read_aabb3f_vector(L, -1, BS); \ + lua_pop(L, 1); \ + } while (0) + NODEBOXREADVEC(nodebox.fixed, "fixed"); + NODEBOXREAD(nodebox.wall_top, "wall_top"); + NODEBOXREAD(nodebox.wall_bottom, "wall_bottom"); + NODEBOXREAD(nodebox.wall_side, "wall_side"); + NODEBOXREADVEC(nodebox.connect_top, "connect_top"); + NODEBOXREADVEC(nodebox.connect_bottom, "connect_bottom"); + NODEBOXREADVEC(nodebox.connect_front, "connect_front"); + NODEBOXREADVEC(nodebox.connect_left, "connect_left"); + NODEBOXREADVEC(nodebox.connect_back, "connect_back"); + NODEBOXREADVEC(nodebox.connect_right, "connect_right"); } return nodebox; } diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 186f6e32..38b629c1 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -83,6 +83,7 @@ public: protected: friend class LuaABM; + friend class LuaLBM; friend class InvRef; friend class ObjectRef; friend class NodeMetaRef; diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 82c67182..62fc1d5d 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" void ScriptApiEnv::environment_OnGenerated(v3s16 minp, v3s16 maxp, - u32 blockseed) + u32 blockseed) { SCRIPTAPI_PRECHECKHEADER @@ -44,7 +44,7 @@ void ScriptApiEnv::environment_OnGenerated(v3s16 minp, v3s16 maxp, void ScriptApiEnv::environment_Step(float dtime) { SCRIPTAPI_PRECHECKHEADER - //infostream<<"scriptapi_environment_step"< trigger_contents; - lua_getfield(L, current_abm, "nodenames"); - if(lua_istable(L, -1)){ - int table = lua_gettop(L); - lua_pushnil(L); - while(lua_next(L, table) != 0){ - // key at index -2 and value at index -1 - luaL_checktype(L, -1, LUA_TSTRING); - trigger_contents.insert(lua_tostring(L, -1)); - // removes value, keeps key for next iteration - lua_pop(L, 1); - } - } else if(lua_isstring(L, -1)){ + std::set trigger_contents; + lua_getfield(L, current_abm, "nodenames"); + if (lua_istable(L, -1)) { + int table = lua_gettop(L); + lua_pushnil(L); + while (lua_next(L, table)) { + // key at index -2 and value at index -1 + luaL_checktype(L, -1, LUA_TSTRING); trigger_contents.insert(lua_tostring(L, -1)); + // removes value, keeps key for next iteration + lua_pop(L, 1); } - lua_pop(L, 1); - - std::set required_neighbors; - lua_getfield(L, current_abm, "neighbors"); - if(lua_istable(L, -1)){ - int table = lua_gettop(L); - lua_pushnil(L); - while(lua_next(L, table) != 0){ - // key at index -2 and value at index -1 - luaL_checktype(L, -1, LUA_TSTRING); - required_neighbors.insert(lua_tostring(L, -1)); - // removes value, keeps key for next iteration - lua_pop(L, 1); - } - } else if(lua_isstring(L, -1)){ - required_neighbors.insert(lua_tostring(L, -1)); - } - lua_pop(L, 1); - - float trigger_interval = 10.0; - getfloatfield(L, current_abm, "interval", trigger_interval); - - int trigger_chance = 50; - getintfield(L, current_abm, "chance", trigger_chance); - - bool simple_catch_up = true; - getboolfield(L, current_abm, "catch_up", simple_catch_up); - - LuaABM *abm = new LuaABM(L, id, trigger_contents, required_neighbors, - trigger_interval, trigger_chance, simple_catch_up); - - env->addActiveBlockModifier(abm); - - // removes value, keeps key for next iteration - lua_pop(L, 1); + } else if (lua_isstring(L, -1)) { + trigger_contents.insert(lua_tostring(L, -1)); } + lua_pop(L, 1); + + std::set required_neighbors; + lua_getfield(L, current_abm, "neighbors"); + if (lua_istable(L, -1)) { + int table = lua_gettop(L); + lua_pushnil(L); + while (lua_next(L, table)) { + // key at index -2 and value at index -1 + luaL_checktype(L, -1, LUA_TSTRING); + required_neighbors.insert(lua_tostring(L, -1)); + // removes value, keeps key for next iteration + lua_pop(L, 1); + } + } else if (lua_isstring(L, -1)) { + required_neighbors.insert(lua_tostring(L, -1)); + } + lua_pop(L, 1); + + float trigger_interval = 10.0; + getfloatfield(L, current_abm, "interval", trigger_interval); + + int trigger_chance = 50; + getintfield(L, current_abm, "chance", trigger_chance); + + bool simple_catch_up = true; + getboolfield(L, current_abm, "catch_up", simple_catch_up); + + LuaABM *abm = new LuaABM(L, id, trigger_contents, required_neighbors, + trigger_interval, trigger_chance, simple_catch_up); + + env->addActiveBlockModifier(abm); + + // removes value, keeps key for next iteration + lua_pop(L, 1); + } + lua_pop(L, 1); + + // Get core.registered_lbms + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_lbms"); + int registered_lbms = lua_gettop(L); + + if (!lua_istable(L, registered_lbms)) { + lua_pop(L, 1); + throw LuaError("core.registered_lbms was not a lua table, as expected."); + } + + lua_pushnil(L); + while (lua_next(L, registered_lbms)) { + // key at index -2 and value at index -1 + int id = lua_tonumber(L, -2); + int current_lbm = lua_gettop(L); + + std::set trigger_contents; + lua_getfield(L, current_lbm, "nodenames"); + if (lua_istable(L, -1)) { + int table = lua_gettop(L); + lua_pushnil(L); + while (lua_next(L, table)) { + // key at index -2 and value at index -1 + luaL_checktype(L, -1, LUA_TSTRING); + trigger_contents.insert(lua_tostring(L, -1)); + // removes value, keeps key for next iteration + lua_pop(L, 1); + } + } else if (lua_isstring(L, -1)) { + trigger_contents.insert(lua_tostring(L, -1)); + } + lua_pop(L, 1); + + std::string name; + getstringfield(L, current_lbm, "name", name); + + bool run_at_every_load = getboolfield_default(L, current_lbm, + "run_at_every_load", false); + + LuaLBM *lbm = new LuaLBM(L, id, trigger_contents, name, + run_at_every_load); + + env->addLoadingBlockModifierDef(lbm); + + // removes value, keeps key for next iteration + lua_pop(L, 1); } lua_pop(L, 1); } diff --git a/src/script/cpp_api/s_env.h b/src/script/cpp_api/s_env.h index 78c85962..f1e40a0a 100644 --- a/src/script/cpp_api/s_env.h +++ b/src/script/cpp_api/s_env.h @@ -26,7 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., class ServerEnvironment; struct ScriptCallbackState; -class ScriptApiEnv : virtual public ScriptApiBase { +class ScriptApiEnv : virtual public ScriptApiBase +{ public: // Called on environment step void environment_Step(float dtime); @@ -35,7 +36,7 @@ public: void environment_OnGenerated(v3s16 minp, v3s16 maxp, u32 blockseed); // Called on player event - void player_event(ServerActiveObject *player, std::string type); + void player_event(ServerActiveObject *player, const std::string &type); // Called after emerge of a block queued from core.emerge_area() void on_emerge_area_completion(v3s16 blockpos, int action, diff --git a/src/script/cpp_api/s_node.cpp b/src/script/cpp_api/s_node.cpp index fb620fd7..78b93acd 100644 --- a/src/script/cpp_api/s_node.cpp +++ b/src/script/cpp_api/s_node.cpp @@ -82,6 +82,7 @@ struct EnumString ScriptApiNode::es_NodeBoxType[] = {NODEBOX_FIXED, "fixed"}, {NODEBOX_WALLMOUNTED, "wallmounted"}, {NODEBOX_LEVELED, "leveled"}, + {NODEBOX_CONNECTED, "connected"}, {0, NULL}, }; diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h index b19368c7..944b9571 100644 --- a/src/script/cpp_api/s_security.h +++ b/src/script/cpp_api/s_security.h @@ -25,9 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CHECK_SECURE_PATH(L, path) \ if (!ScriptApiSecurity::checkPath(L, path)) { \ - lua_pushstring(L, (std::string("Attempt to access external file ") + \ - path + " with mod security on.").c_str()); \ - lua_error(L); \ + throw LuaError(std::string("Attempt to access external file ") + \ + path + " with mod security on."); \ } #define CHECK_SECURE_PATH_OPTIONAL(L, path) \ if (ScriptApiSecurity::isSecure(L)) { \ diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp index f022c01e..af021cc0 100644 --- a/src/script/lua_api/l_areastore.cpp +++ b/src/script/lua_api/l_areastore.cpp @@ -23,11 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_converter.h" #include "cpp_api/s_security.h" #include "irr_v3d.h" -#include "areastore.h" +#include "util/areastore.h" #include "filesys.h" -#ifndef ANDROID - #include "cmake_config.h" -#endif #include static inline void get_data_and_border_flags(lua_State *L, u8 start_i, @@ -73,6 +70,22 @@ static inline void push_areas(lua_State *L, const std::vector &areas, } } +// Deserializes value and handles errors +static int deserialization_helper(lua_State *L, AreaStore *as, + std::istream &is) +{ + try { + as->deserialize(is); + } catch (const SerializationError &e) { + lua_pushboolean(L, false); + lua_pushstring(L, e.what()); + return 2; + } + + lua_pushboolean(L, true); + return 1; +} + // garbage collector int LuaAreaStore::gc_object(lua_State *L) { @@ -151,7 +164,7 @@ int LuaAreaStore::l_get_areas_in_area(lua_State *L) return 1; } -// insert_area(edge1, edge2, data) +// insert_area(edge1, edge2, data, id) int LuaAreaStore::l_insert_area(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -159,26 +172,18 @@ int LuaAreaStore::l_insert_area(lua_State *L) LuaAreaStore *o = checkobject(L, 1); AreaStore *ast = o->as; - Area a; - - a.minedge = check_v3s16(L, 2); - a.maxedge = check_v3s16(L, 3); - - a.extremifyEdges(); - a.id = ast->getFreeId(a.minedge, a.maxedge); - - if (a.id == AREA_ID_INVALID) { - // couldn't get free id - lua_pushnil(L); - return 1; - } + Area a(check_v3s16(L, 2), check_v3s16(L, 3)); size_t d_len; const char *data = luaL_checklstring(L, 4, &d_len); a.data = std::string(data, d_len); - ast->insertArea(a); + if (lua_isnumber(L, 5)) + a.id = lua_tonumber(L, 5); + + if (!ast->insertArea(&a)) + return 0; lua_pushnumber(L, a.id); return 1; @@ -231,17 +236,15 @@ int LuaAreaStore::l_set_cache_params(lua_State *L) return 0; } -#if 0 // to_string() int LuaAreaStore::l_to_string(lua_State *L) { NO_MAP_LOCK_REQUIRED; LuaAreaStore *o = checkobject(L, 1); - AreaStore *ast = o->as; std::ostringstream os(std::ios_base::binary); - ast->serialize(os); + o->as->serialize(os); std::string str = os.str(); lua_pushlstring(L, str.c_str(), str.length()); @@ -272,16 +275,12 @@ int LuaAreaStore::l_from_string(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaAreaStore *o = checkobject(L, 1); - AreaStore *ast = o->as; size_t len; const char *str = luaL_checklstring(L, 2, &len); std::istringstream is(std::string(str, len), std::ios::binary); - bool success = ast->deserialize(is); - - lua_pushboolean(L, success); - return 1; + return deserialization_helper(L, o->as, is); } // from_file(filename) @@ -290,26 +289,17 @@ int LuaAreaStore::l_from_file(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaAreaStore *o = checkobject(L, 1); - AreaStore *ast = o->as; const char *filename = luaL_checkstring(L, 2); CHECK_SECURE_PATH_OPTIONAL(L, filename); std::ifstream is(filename, std::ios::binary); - bool success = ast->deserialize(is); - - lua_pushboolean(L, success); - return 1; + return deserialization_helper(L, o->as, is); } -#endif LuaAreaStore::LuaAreaStore() { -#if USE_SPATIAL - this->as = new SpatialAreaStore(); -#else - this->as = new VectorAreaStore(); -#endif + this->as = AreaStore::getOptimalImplementation(); } LuaAreaStore::LuaAreaStore(const std::string &type) @@ -395,9 +385,9 @@ const luaL_reg LuaAreaStore::methods[] = { luamethod(LuaAreaStore, reserve), luamethod(LuaAreaStore, remove_area), luamethod(LuaAreaStore, set_cache_params), - /* luamethod(LuaAreaStore, to_string), + luamethod(LuaAreaStore, to_string), luamethod(LuaAreaStore, to_file), luamethod(LuaAreaStore, from_string), - luamethod(LuaAreaStore, from_file),*/ + luamethod(LuaAreaStore, from_file), {0,0} }; diff --git a/src/script/lua_api/l_areastore.h b/src/script/lua_api/l_areastore.h index 0ff3dd06..3e5615f9 100644 --- a/src/script/lua_api/l_areastore.h +++ b/src/script/lua_api/l_areastore.h @@ -17,15 +17,14 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef L_AREASTORE_H_ -#define L_AREASTORE_H_ +#ifndef L_AREA_STORE_H_ +#define L_AREA_STORE_H_ #include "lua_api/l_base.h" -#include "areastore.h" -/* - AreaStore - */ + +class AreaStore; + class LuaAreaStore : public ModApiBase { private: @@ -44,11 +43,11 @@ private: static int l_set_cache_params(lua_State *L); - /* static int l_to_string(lua_State *L); + static int l_to_string(lua_State *L); static int l_to_file(lua_State *L); static int l_from_string(lua_State *L); - static int l_from_file(lua_State *L); */ + static int l_from_file(lua_State *L); public: AreaStore *as; @@ -66,4 +65,4 @@ public: static void Register(lua_State *L); }; -#endif /* L_AREASTORE_H_ */ +#endif // L_AREA_STORE_H_ diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index e412754e..5c14a3ff 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -90,6 +90,46 @@ void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, lua_pop(L, 1); // Pop error handler } +void LuaLBM::trigger(ServerEnvironment *env, v3s16 p, MapNode n) +{ + GameScripting *scriptIface = env->getScriptIface(); + scriptIface->realityCheck(); + + lua_State *L = scriptIface->getStack(); + sanity_check(lua_checkstack(L, 20)); + StackUnroller stack_unroller(L); + + int error_handler = PUSH_ERROR_HANDLER(L); + + // Get registered_lbms + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_lbms"); + luaL_checktype(L, -1, LUA_TTABLE); + lua_remove(L, -2); // Remove core + + // Get registered_lbms[m_id] + lua_pushnumber(L, m_id); + lua_gettable(L, -2); + FATAL_ERROR_IF(lua_isnil(L, -1), "Entry with given id not found in registered_lbms table"); + lua_remove(L, -2); // Remove registered_lbms + + scriptIface->setOriginFromTable(-1); + + // Call action + luaL_checktype(L, -1, LUA_TTABLE); + lua_getfield(L, -1, "action"); + luaL_checktype(L, -1, LUA_TFUNCTION); + lua_remove(L, -2); // Remove registered_lbms[m_id] + push_v3s16(L, p); + pushnode(L, n, env->getGameDef()->ndef()); + + int result = lua_pcall(L, 2, 0, error_handler); + if (result) + scriptIface->scriptError(result, "LuaLBM::trigger"); + + lua_pop(L, 1); // Pop error handler +} + void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param) { ScriptCallbackState *state = (ScriptCallbackState *)param; @@ -521,6 +561,15 @@ int ModApiEnvMod::l_get_timeofday(lua_State *L) return 1; } +// get_day_count() -> int +int ModApiEnvMod::l_get_day_count(lua_State *L) +{ + GET_ENV_PTR; + + lua_pushnumber(L, env->getDayCount()); + return 1; +} + // get_gametime() int ModApiEnvMod::l_get_gametime(lua_State *L) { @@ -1015,6 +1064,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(set_timeofday); API_FCT(get_timeofday); API_FCT(get_gametime); + API_FCT(get_day_count); API_FCT(find_node_near); API_FCT(find_nodes_in_area); API_FCT(find_nodes_in_area_under_air); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 005192a7..430d69a5 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -113,6 +113,9 @@ private: // get_gametime() static int l_get_gametime(lua_State *L); + // get_day_count() -> int + static int l_get_day_count(lua_State *L); + // find_node_near(pos, radius, nodenames) -> pos or nil // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" static int l_find_node_near(lua_State *L); @@ -220,6 +223,24 @@ public: u32 active_object_count, u32 active_object_count_wider); }; +class LuaLBM : public LoadingBlockModifierDef +{ +private: + int m_id; +public: + LuaLBM(lua_State *L, int id, + const std::set &trigger_contents, + const std::string &name, + bool run_at_every_load): + m_id(id) + { + this->run_at_every_load = run_at_every_load; + this->trigger_contents = trigger_contents; + this->name = name; + } + virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n); +}; + struct ScriptCallbackState { GameScripting *script; int callback_ref; diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index a6df965b..9301a07e 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serialization.h" #include "json/json.h" #include "cpp_api/s_security.h" -#include "areastore.h" #include "porting.h" #include "debug.h" #include "log.h" @@ -77,8 +76,7 @@ int ModApiUtil::l_get_us_time(lua_State *L) #define CHECK_SECURE_SETTING(L, name) \ if (ScriptApiSecurity::isSecure(L) && \ name.compare(0, 7, "secure.") == 0) { \ - lua_pushliteral(L, "Attempt to set secure setting."); \ - lua_error(L); \ + throw LuaError("Attempt to set secure setting."); \ } // setting_set(name, value) diff --git a/src/server.cpp b/src/server.cpp index 49e78d58..0bfeb045 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -320,6 +320,9 @@ Server::Server( // Perform pending node name resolutions m_nodedef->runNodeResolveCallbacks(); + // unmap node names for connected nodeboxes + m_nodedef->mapNodeboxConnections(); + // init the recipe hashes to speed up crafting m_craftdef->initHashes(this); @@ -344,10 +347,11 @@ Server::Server( servermap->addEventReceiver(this); // If file exists, load environment metadata - if(fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) - { - infostream<<"Server: Loading environment metadata"<loadMeta(); + } else { + m_env->loadDefaultMeta(); } // Add some test ActiveBlockModifiers to environment @@ -2560,7 +2564,7 @@ void Server::DenyAccessVerCompliant(u16 peer_id, u16 proto_ver, AccessDeniedCode const std::string &str_reason, bool reconnect) { if (proto_ver >= 25) { - SendAccessDenied(peer_id, reason, str_reason); + SendAccessDenied(peer_id, reason, str_reason, reconnect); } else { std::wstring wreason = utf8_to_wide( reason == SERVER_ACCESSDENIED_CUSTOM_STRING ? str_reason : diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp index d8e08521..f115bc01 100644 --- a/src/settings_translation_file.cpp +++ b/src/settings_translation_file.cpp @@ -272,6 +272,10 @@ fake_function() { gettext("Fallback font shadow alpha"); gettext("Screenshot folder"); gettext("Path to save screenshots at."); + gettext("Screenshot format"); + gettext("Format of screenshots."); + gettext("Screenshot quality"); + gettext("Screenshot quality. Only used for JPEG format.\n1 means worst quality; 100 means best quality.\nUse 0 for default quality."); gettext("Advanced"); gettext("DPI"); gettext("Adjust dpi configuration to your screen (non X11/Android only) e.g. for 4k screens."); @@ -421,7 +425,7 @@ fake_function() { gettext("Map generation limit"); gettext("Where the map generator stops.\nPlease note:\n- Limited to 31000 (setting above has no effect)\n- The map generator works in groups of 80x80x80 nodes (5x5x5 MapBlocks).\n- Those groups have an offset of -32, -32 nodes from the origin.\n- Only groups which are within the map_generation_limit are generated"); gettext("Mapgen flags"); - gettext("Global map generation attributes.\nIn Mapgen v6 the 'decorations' flag controls all decorations except trees\nand junglegrass, in all other mapgens this flag controls all decorations.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with \"no\" are used to explicitly disable them."); + gettext("Global map generation attributes.\nIn Mapgen v6 the 'decorations' flag controls all decorations except trees\nand junglegrass, in all other mapgens this flag controls all decorations.\nThe default flags set in the engine are: caves, light, decorations\nThe flags string modifies the engine defaults.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); gettext("Advanced"); gettext("Chunk size"); gettext("Size of chunks to be generated at once by mapgen, stated in mapblocks (16 nodes)."); @@ -448,7 +452,7 @@ fake_function() { gettext("Mapgen v5 cave2 noise parameters"); gettext("Mapgen v6"); gettext("Mapgen v6 flags"); - gettext("Map generation attributes specific to Mapgen v6.\nWhen snowbiomes are enabled jungles are enabled and the jungles flag is ignored.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with \"no\" are used to explicitly disable them."); + gettext("Map generation attributes specific to Mapgen v6.\nWhen snowbiomes are enabled jungles are automatically enabled, the 'jungles' flag is ignored.\nThe default flags set in the engine are: biomeblend, mudflow\nThe flags string modifies the engine defaults.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); gettext("Mapgen v6 desert frequency"); gettext("Controls size of deserts and beaches in Mapgen v6.\nWhen snowbiomes are enabled 'mgv6_freq_desert' is ignored."); gettext("Mapgen v6 beach frequency"); @@ -465,7 +469,7 @@ fake_function() { gettext("Mapgen v6 apple trees noise parameters"); gettext("Mapgen v7"); gettext("Mapgen v7 flags"); - gettext("Map generation attributes specific to Mapgen v7.\n'ridges' are the rivers.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with \"no\" are used to explicitly disable them."); + gettext("Map generation attributes specific to Mapgen v7.\nThe 'ridges' flag controls the rivers.\nThe default flags set in the engine are: mountains, ridges\nThe flags string modifies the engine defaults.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); gettext("Mapgen v7 terrain base noise parameters"); gettext("Mapgen v7 terrain altitude noise parameters"); gettext("Mapgen v7 terrain persistation noise parameters"); @@ -479,7 +483,7 @@ fake_function() { gettext("Mapgen v7 cave2 noise parameters"); gettext("Mapgen flat"); gettext("Mapgen flat flags"); - gettext("Map generation attributes specific to Mapgen flat.\nOccasional lakes and hills added to the flat world.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with \"no\" are used to explicitly disable them."); + gettext("Map generation attributes specific to Mapgen flat.\nOccasional lakes and hills can be added to the flat world.\nThe default flags set in the engine are: none\nThe flags string modifies the engine defaults.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); gettext("Mapgen flat ground level"); gettext("Y of flat ground."); gettext("Mapgen flat large cave depth"); @@ -523,7 +527,7 @@ fake_function() { gettext("Mapgen Valleys"); gettext("General"); gettext("Valleys C Flags"); - gettext("Map generation attributes specific to Mapgen Valleys.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with \"no\" are used to explicitly disable them.\n\"altitude_chill\" makes higher elevations colder, which may cause biome issues.\n\"humid_rivers\" modifies the humidity around rivers and in areas where water would tend to pool. It may interfere with delicately adjusted biomes."); + gettext("Map generation attributes specific to Mapgen Valleys.\n'altitude_chill' makes higher elevations colder, which may cause biome issues.\n'humid_rivers' modifies the humidity around rivers and in areas where water would tend to pool,\nit may interfere with delicately adjusted biomes.\nThe default flags set in the engine are: altitude_chill, humid_rivers\nThe flags string modifies the engine defaults.\nFlags that are not specified in the flag string are not modified from the default.\nFlags starting with 'no' are used to explicitly disable them."); gettext("Altitude Chill"); gettext("The altitude at which temperature drops by 20C"); gettext("Large cave depth"); diff --git a/src/unittest/test_areastore.cpp b/src/unittest/test_areastore.cpp index ef661f65..6a7879a0 100644 --- a/src/unittest/test_areastore.cpp +++ b/src/unittest/test_areastore.cpp @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "test.h" -#include "areastore.h" +#include "util/areastore.h" class TestAreaStore : public TestBase { public: @@ -31,6 +31,7 @@ public: void genericStoreTest(AreaStore *store); void testVectorStore(); void testSpatialStore(); + void testSerialization(); }; static TestAreaStore g_test_instance; @@ -41,6 +42,7 @@ void TestAreaStore::runTests(IGameDef *gamedef) #if USE_SPATIAL TEST(testSpatialStore); #endif + TEST(testSerialization); } //////////////////////////////////////////////////////////////////////////////// @@ -62,18 +64,15 @@ void TestAreaStore::testSpatialStore() void TestAreaStore::genericStoreTest(AreaStore *store) { Area a(v3s16(-10, -3, 5), v3s16(0, 29, 7)); - a.id = 1; Area b(v3s16(-5, -2, 5), v3s16(0, 28, 6)); - b.id = 2; Area c(v3s16(-7, -3, 6), v3s16(-1, 27, 7)); - c.id = 3; std::vector res; UASSERTEQ(size_t, store->size(), 0); store->reserve(2); // sic - store->insertArea(a); - store->insertArea(b); - store->insertArea(c); + store->insertArea(&a); + store->insertArea(&b); + store->insertArea(&c); UASSERTEQ(size_t, store->size(), 3); store->getAreasForPos(&res, v3s16(-1, 0, 6)); @@ -81,20 +80,18 @@ void TestAreaStore::genericStoreTest(AreaStore *store) res.clear(); store->getAreasForPos(&res, v3s16(0, 0, 7)); UASSERTEQ(size_t, res.size(), 1); - UASSERTEQ(u32, res[0]->id, 1); res.clear(); - store->removeArea(1); + store->removeArea(a.id); store->getAreasForPos(&res, v3s16(0, 0, 7)); UASSERTEQ(size_t, res.size(), 0); res.clear(); - store->insertArea(a); + store->insertArea(&a); store->getAreasForPos(&res, v3s16(0, 0, 7)); UASSERTEQ(size_t, res.size(), 1); - UASSERTEQ(u32, res[0]->id, 1); res.clear(); store->getAreasInArea(&res, v3s16(-10, -3, 5), v3s16(0, 29, 7), false); @@ -109,21 +106,57 @@ void TestAreaStore::genericStoreTest(AreaStore *store) UASSERTEQ(size_t, res.size(), 3); res.clear(); - store->removeArea(1); - store->removeArea(2); - store->removeArea(3); + store->removeArea(a.id); + store->removeArea(b.id); + store->removeArea(c.id); Area d(v3s16(-100, -300, -200), v3s16(-50, -200, -100)); - d.id = 4; d.data = "Hi!"; - store->insertArea(d); + store->insertArea(&d); store->getAreasForPos(&res, v3s16(-75, -250, -150)); UASSERTEQ(size_t, res.size(), 1); - UASSERTEQ(u32, res[0]->id, 4); UASSERTEQ(u16, res[0]->data.size(), 3); UASSERT(strncmp(res[0]->data.c_str(), "Hi!", 3) == 0); res.clear(); - store->removeArea(4); + store->removeArea(d.id); } + +void TestAreaStore::testSerialization() +{ + VectorAreaStore store; + + Area a(v3s16(-1, 0, 1), v3s16(0, 1, 2)); + a.data = "Area A"; + store.insertArea(&a); + + Area b(v3s16(123, 456, 789), v3s16(32000, 100, 10)); + b.data = "Area B"; + store.insertArea(&b); + + std::ostringstream os; + store.serialize(os); + std::string str = os.str(); + + std::string str_wanted("\x00" // Version + "\x00\x02" // Count + "\xFF\xFF\x00\x00\x00\x01" // Area A min edge + "\x00\x00\x00\x01\x00\x02" // Area A max edge + "\x00\x06" // Area A data length + "Area A" // Area A data + "\x00\x7B\x00\x64\x00\x0A" // Area B min edge (last two swapped with max edge for sorting) + "\x7D\x00\x01\xC8\x03\x15" // Area B max edge (^) + "\x00\x06" // Area B data length + "Area B", // Area B data + 1 + 2 + + 6 + 6 + 2 + 6 + + 6 + 6 + 2 + 6); + UASSERTEQ(std::string, str, str_wanted); + + std::istringstream is(str); + store.deserialize(is); + + UASSERTEQ(size_t, store.size(), 4); // deserialize() doesn't clear the store +} + diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 33900a43..0e7cbad0 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -1,4 +1,5 @@ set(UTIL_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/areastore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/auth.cpp ${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp ${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp diff --git a/src/areastore.cpp b/src/util/areastore.cpp similarity index 64% rename from src/areastore.cpp rename to src/util/areastore.cpp index b75c05ca..1c807620 100644 --- a/src/areastore.cpp +++ b/src/util/areastore.cpp @@ -17,7 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "areastore.h" +#include "util/areastore.h" #include "util/serialize.h" #include "util/container.h" @@ -44,97 +44,70 @@ with this program; if not, write to the Free Software Foundation, Inc., AST_OVERLAPS_IN_DIMENSION((amine), (amaxe), (b), Y) && \ AST_OVERLAPS_IN_DIMENSION((amine), (amaxe), (b), Z)) -u16 AreaStore::size() const -{ - return areas_map.size(); -} -u32 AreaStore::getFreeId(v3s16 minedge, v3s16 maxedge) +AreaStore *AreaStore::getOptimalImplementation() { - int keep_on = 100; - while (keep_on--) { - m_highest_id++; - // Handle overflows, we dont want to return 0 - if (m_highest_id == AREA_ID_INVALID) - m_highest_id++; - if (areas_map.find(m_highest_id) == areas_map.end()) - return m_highest_id; - } - // search failed - return AREA_ID_INVALID; +#if USE_SPATIAL + return new SpatialAreaStore(); +#else + return new VectorAreaStore(); +#endif } const Area *AreaStore::getArea(u32 id) const { - const Area *res = NULL; - std::map::const_iterator itr = areas_map.find(id); - if (itr != areas_map.end()) { - res = &itr->second; - } - return res; + AreaMap::const_iterator it = areas_map.find(id); + if (it == areas_map.end()) + return NULL; + return &it->second; } -#if 0 -Currently, serialisation is commented out. This is because of multiple reasons: -1. Why do we store the areastore into a file, why not into the database? -2. We don't use libspatial's serialisation, but we should, or perhaps not, because - it would remove the ability to switch. Perhaps write migration routines? -3. Various things need fixing, e.g. the size is serialized as - c++ implementation defined size_t -bool AreaStore::deserialize(std::istream &is) -{ - u8 ver = readU8(is); - if (ver != 1) - return false; - u16 count_areas = readU16(is); - for (u16 i = 0; i < count_areas; i++) { - // deserialize an area - Area a; - a.id = readU32(is); - a.minedge = readV3S16(is); - a.maxedge = readV3S16(is); - a.datalen = readU16(is); - a.data = new char[a.datalen]; - is.read((char *) a.data, a.datalen); - insertArea(a); - } - return true; -} - - -static bool serialize_area(void *ostr, Area *a) -{ - std::ostream &os = *((std::ostream *) ostr); - writeU32(os, a->id); - writeV3S16(os, a->minedge); - writeV3S16(os, a->maxedge); - writeU16(os, a->datalen); - os.write(a->data, a->datalen); - - return false; -} - - void AreaStore::serialize(std::ostream &os) const { - // write initial data - writeU8(os, 1); // serialisation version - writeU16(os, areas_map.size()); //DANGER: not platform independent - forEach(&serialize_area, &os); + writeU8(os, 0); // Serialisation version + + // TODO: Compression? + writeU16(os, areas_map.size()); + for (AreaMap::const_iterator it = areas_map.begin(); + it != areas_map.end(); ++it) { + const Area &a = it->second; + writeV3S16(os, a.minedge); + writeV3S16(os, a.maxedge); + writeU16(os, a.data.size()); + os.write(a.data.data(), a.data.size()); + } } -#endif +void AreaStore::deserialize(std::istream &is) +{ + u8 ver = readU8(is); + if (ver != 0) + throw SerializationError("Unknown AreaStore " + "serialization version!"); + + u16 num_areas = readU16(is); + for (u32 i = 0; i < num_areas; ++i) { + Area a; + a.minedge = readV3S16(is); + a.maxedge = readV3S16(is); + u16 data_len = readU16(is); + char *data = new char[data_len]; + is.read(data, data_len); + a.data = std::string(data, data_len); + insertArea(&a); + } +} void AreaStore::invalidateCache() { - if (cache_enabled) { + if (m_cache_enabled) { m_res_cache.invalidate(); } } void AreaStore::setCacheParams(bool enabled, u8 block_radius, size_t limit) { - cache_enabled = enabled; + m_cache_enabled = enabled; m_cacheblock_radius = MYMAX(block_radius, 16); m_res_cache.setLimit(MYMAX(limit, 20)); invalidateCache(); @@ -163,7 +136,7 @@ void AreaStore::cacheMiss(void *data, const v3s16 &mpos, std::vector *de void AreaStore::getAreasForPos(std::vector *result, v3s16 pos) { - if (cache_enabled) { + if (m_cache_enabled) { v3s16 mblock = getContainerPos(pos, m_cacheblock_radius); const std::vector *pre_list = m_res_cache.lookupCache(mblock); @@ -185,42 +158,41 @@ void AreaStore::getAreasForPos(std::vector *result, v3s16 pos) //// -void VectorAreaStore::insertArea(const Area &a) +bool VectorAreaStore::insertArea(Area *a) { - areas_map[a.id] = a; - m_areas.push_back(&(areas_map[a.id])); + if (a->id == U32_MAX) + a->id = getNextId(); + std::pair res = + areas_map.insert(std::make_pair(a->id, *a)); + if (!res.second) + // ID is not unique + return false; + m_areas.push_back(&res.first->second); invalidateCache(); -} - -void VectorAreaStore::reserve(size_t count) -{ - m_areas.reserve(count); + return true; } bool VectorAreaStore::removeArea(u32 id) { - std::map::iterator itr = areas_map.find(id); - if (itr != areas_map.end()) { - size_t msiz = m_areas.size(); - for (size_t i = 0; i < msiz; i++) { - Area * b = m_areas[i]; - if (b->id == id) { - areas_map.erase(itr); - m_areas.erase(m_areas.begin() + i); - invalidateCache(); - return true; - } + AreaMap::iterator it = areas_map.find(id); + if (it == areas_map.end()) + return false; + Area *a = &it->second; + for (std::vector::iterator v_it = m_areas.begin(); + v_it != m_areas.end(); ++v_it) { + if (*v_it == a) { + m_areas.erase(v_it); + break; } - // we should never get here, it means we did find it in map, - // but not in the vector } - return false; + areas_map.erase(it); + invalidateCache(); + return true; } void VectorAreaStore::getAreasForPosImpl(std::vector *result, v3s16 pos) { - size_t msiz = m_areas.size(); - for (size_t i = 0; i < msiz; i++) { + for (size_t i = 0; i < m_areas.size(); ++i) { Area *b = m_areas[i]; if (AST_CONTAINS_PT(b, pos)) { result->push_back(b); @@ -231,9 +203,8 @@ void VectorAreaStore::getAreasForPosImpl(std::vector *result, v3s16 pos) void VectorAreaStore::getAreasInArea(std::vector *result, v3s16 minedge, v3s16 maxedge, bool accept_overlap) { - size_t msiz = m_areas.size(); - for (size_t i = 0; i < msiz; i++) { - Area * b = m_areas[i]; + for (size_t i = 0; i < m_areas.size(); ++i) { + Area *b = m_areas[i]; if (accept_overlap ? AST_AREAS_OVERLAP(minedge, maxedge, b) : AST_CONTAINS_AREA(minedge, maxedge, b)) { result->push_back(b); @@ -241,19 +212,6 @@ void VectorAreaStore::getAreasInArea(std::vector *result, } } -#if 0 -bool VectorAreaStore::forEach(bool (*callback)(void *args, Area *a), void *args) const -{ - size_t msiz = m_areas.size(); - for (size_t i = 0; i < msiz; i++) { - if (callback(args, m_areas[i])) { - return true; - } - } - return false; -} -#endif - #if USE_SPATIAL static inline SpatialIndex::Region get_spatial_region(const v3s16 minedge, @@ -273,11 +231,16 @@ static inline SpatialIndex::Point get_spatial_point(const v3s16 pos) } -void SpatialAreaStore::insertArea(const Area &a) +bool SpatialAreaStore::insertArea(Area *a) { - areas_map[a.id] = a; - m_tree->insertData(0, NULL, get_spatial_region(a.minedge, a.maxedge), a.id); + if (a->id == U32_MAX) + a->id = getNextId(); + if (!areas_map.insert(std::make_pair(a->id, *a)).second) + // ID is not unique + return false; + m_tree->insertData(0, NULL, get_spatial_region(a->minedge, a->maxedge), a->id); invalidateCache(); + return true; } bool SpatialAreaStore::removeArea(u32 id) @@ -287,6 +250,7 @@ bool SpatialAreaStore::removeArea(u32 id) Area *a = &itr->second; bool result = m_tree->deleteData(get_spatial_region(a->minedge, a->maxedge), id); + areas_map.erase(itr); invalidateCache(); return result; } else { @@ -312,14 +276,6 @@ void SpatialAreaStore::getAreasInArea(std::vector *result, } } -#if 0 -bool SpatialAreaStore::forEach(bool (*callback)(void *args, Area *a), void *args) const -{ - // TODO ?? (this is only needed for serialisation, but libspatial has its own serialisation) - return false; -} -#endif - SpatialAreaStore::~SpatialAreaStore() { delete m_tree; diff --git a/src/areastore.h b/src/util/areastore.h similarity index 58% rename from src/areastore.h rename to src/util/areastore.h index a08da507..d49546fd 100644 --- a/src/areastore.h +++ b/src/util/areastore.h @@ -17,8 +17,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef AREASTORE_H_ -#define AREASTORE_H_ +#ifndef AREA_STORE_H_ +#define AREA_STORE_H_ #include "irr_v3d.h" #include "noise.h" // for PcgRandom @@ -36,141 +36,147 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #endif -#define AST_EXTREMIFY(min, max, pa, pb) \ - (min).X = MYMIN((pa).X, (pb).X); \ - (min).Y = MYMIN((pa).Y, (pb).Y); \ - (min).Z = MYMIN((pa).Z, (pb).Z); \ - (max).X = MYMAX((pa).X, (pb).X); \ - (max).Y = MYMAX((pa).Y, (pb).Y); \ - (max).Z = MYMAX((pa).Z, (pb).Z); - -#define AREA_ID_INVALID 0 struct Area { - Area(const v3s16 &minedge, const v3s16 &maxedge) + Area() : id(U32_MAX) {} + Area(const v3s16 &mine, const v3s16 &maxe) : + id(U32_MAX), minedge(mine), maxedge(maxe) { - this->minedge = minedge; - this->maxedge = maxedge; - } - - Area() {} - - void extremifyEdges() - { - v3s16 nminedge; - v3s16 nmaxedge; - - AST_EXTREMIFY(nminedge, nmaxedge, minedge, maxedge) - - maxedge = nmaxedge; - minedge = nminedge; + sortBoxVerticies(minedge, maxedge); } u32 id; - v3s16 minedge; - v3s16 maxedge; + v3s16 minedge, maxedge; std::string data; }; -std::vector get_areastore_typenames(); class AreaStore { -protected: - // TODO change to unordered_map when we can - std::map areas_map; - void invalidateCache(); - virtual void getAreasForPosImpl(std::vector *result, v3s16 pos) = 0; - bool cache_enabled; // don't write to this from subclasses, only read. public: - virtual void insertArea(const Area &a) = 0; + AreaStore() : + m_cache_enabled(true), + m_cacheblock_radius(64), + m_res_cache(1000, &cacheMiss, this), + m_next_id(0) + {} + + virtual ~AreaStore() {} + + static AreaStore *getOptimalImplementation(); + virtual void reserve(size_t count) {}; + size_t size() const { return areas_map.size(); } + + /// Add an area to the store. + /// Updates the area's ID if it hasn't already been set. + /// @return Whether the area insertion was successful. + virtual bool insertArea(Area *a) = 0; + + /// Removes an area from the store by ID. + /// @return Whether the area was in the store and removed. virtual bool removeArea(u32 id) = 0; + + /// Finds areas that the passed position is contained in. + /// Stores output in passed vector. void getAreasForPos(std::vector *result, v3s16 pos); + + /// Finds areas that are completely contained inside the area defined + /// by the passed edges. If @p accept_overlap is true this finds any + /// areas that intersect with the passed area at any point. virtual void getAreasInArea(std::vector *result, v3s16 minedge, v3s16 maxedge, bool accept_overlap) = 0; -#if 0 - // calls a passed function for every stored area, until the - // callback returns true. If that happens, it returns true, - // if the search is exhausted, it returns false - virtual bool forEach(bool (*callback)(void *args, Area *a), void *args) const = 0; -#endif - - virtual ~AreaStore() - {} - - AreaStore() : - cache_enabled(true), - m_cacheblock_radius(64), - m_res_cache(1000, &cacheMiss, this), - m_highest_id(0) - { - } - + /// Sets cache parameters. void setCacheParams(bool enabled, u8 block_radius, size_t limit); - u32 getFreeId(v3s16 minedge, v3s16 maxedge); + /// Returns a pointer to the area coresponding to the passed ID, + /// or NULL if it doesn't exist. const Area *getArea(u32 id) const; - u16 size() const; -#if 0 - bool deserialize(std::istream &is); - void serialize(std::ostream &is) const; -#endif -private: - static void cacheMiss(void *data, const v3s16 &mpos, std::vector *dest); - u8 m_cacheblock_radius; // if you modify this, call invalidateCache() - LRUCache > m_res_cache; - u32 m_highest_id; + /// Serializes the store's areas to a binary ostream. + void serialize(std::ostream &is) const; + + /// Deserializes the Areas from a binary istream. + /// This does not currently clear the AreaStore before adding the + /// areas, making it possible to deserialize multiple serialized + /// AreaStores. + void deserialize(std::istream &is); + +protected: + /// Invalidates the getAreasForPos cache. + /// Call after adding or removing an area. + void invalidateCache(); + + /// Implementation of getAreasForPos. + /// getAreasForPos calls this if the cache is disabled. + virtual void getAreasForPosImpl(std::vector *result, v3s16 pos) = 0; + + /// Returns the next area ID and increments it. + u32 getNextId() { return m_next_id++; } + + // Note: This can't be an unordered_map, since all + // references would be invalidated on rehash. + typedef std::map AreaMap; + AreaMap areas_map; + +private: + /// Called by the cache when a value isn't found in the cache. + static void cacheMiss(void *data, const v3s16 &mpos, std::vector *dest); + + bool m_cache_enabled; + /// Range, in nodes, of the getAreasForPos cache. + /// If you modify this, call invalidateCache() + u8 m_cacheblock_radius; + LRUCache > m_res_cache; + + u32 m_next_id; }; class VectorAreaStore : public AreaStore { -protected: - virtual void getAreasForPosImpl(std::vector *result, v3s16 pos); public: - virtual void insertArea(const Area &a); - virtual void reserve(size_t count); + virtual void reserve(size_t count) { m_areas.reserve(count); } + virtual bool insertArea(Area *a); virtual bool removeArea(u32 id); virtual void getAreasInArea(std::vector *result, v3s16 minedge, v3s16 maxedge, bool accept_overlap); - // virtual bool forEach(bool (*callback)(void *args, Area *a), void *args) const; + +protected: + virtual void getAreasForPosImpl(std::vector *result, v3s16 pos); + private: std::vector m_areas; }; + #if USE_SPATIAL class SpatialAreaStore : public AreaStore { -protected: - virtual void getAreasForPosImpl(std::vector *result, v3s16 pos); public: SpatialAreaStore(); - virtual void insertArea(const Area &a); + virtual ~SpatialAreaStore(); + + virtual bool insertArea(Area *a); virtual bool removeArea(u32 id); virtual void getAreasInArea(std::vector *result, v3s16 minedge, v3s16 maxedge, bool accept_overlap); - // virtual bool forEach(bool (*callback)(void *args, Area *a), void *args) const; - virtual ~SpatialAreaStore(); +protected: + virtual void getAreasForPosImpl(std::vector *result, v3s16 pos); + private: SpatialIndex::ISpatialIndex *m_tree; SpatialIndex::IStorageManager *m_storagemanager; class VectorResultVisitor : public SpatialIndex::IVisitor { - private: - SpatialAreaStore *m_store; - std::vector *m_result; public: - VectorResultVisitor(std::vector *result, SpatialAreaStore *store) - { - m_store = store; - m_result = result; - } + VectorResultVisitor(std::vector *result, SpatialAreaStore *store) : + m_store(store), + m_result(result) + {} + ~VectorResultVisitor() {} - virtual void visitNode(const SpatialIndex::INode &in) - { - } + virtual void visitNode(const SpatialIndex::INode &in) {} virtual void visitData(const SpatialIndex::IData &in) { @@ -187,10 +193,12 @@ private: visitData(*(v[i])); } - ~VectorResultVisitor() {} + private: + SpatialAreaStore *m_store; + std::vector *m_result; }; }; -#endif +#endif // USE_SPATIAL -#endif /* AREASTORE_H_ */ +#endif // AREA_STORE_H_ diff --git a/src/util/string.h b/src/util/string.h index 6826b731..49aeb520 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -299,15 +299,6 @@ inline s32 mystoi(const std::string &str, s32 min, s32 max) } -/// Returns a 64-bit value represented by the string \p str (decimal). -inline s64 stoi64(const std::string &str) -{ - std::stringstream tmp(str); - s64 t; - tmp >> t; - return t; -} - // MSVC2010 includes it's own versions of these //#if !defined(_MSC_VER) || _MSC_VER < 1600 @@ -346,9 +337,22 @@ inline float mystof(const std::string &str) #define stoi mystoi #define stof mystof +/// Returns a value represented by the string \p val. +template +inline T from_string(const std::string &str) +{ + std::stringstream tmp(str); + T t; + tmp >> t; + return t; +} + +/// Returns a 64-bit signed value represented by the string \p str (decimal). +inline s64 stoi64(const std::string &str) { return from_string(str); } + // TODO: Replace with C++11 std::to_string. -/// Returns A string representing the value \p val. +/// Returns a string representing the value \p val. template inline std::string to_string(T val) {