- name: Install deps
run: |
source ./util/ci/common.sh
- install_linux_deps --old-irr clang-9
+ install_linux_deps clang-9
- name: Build prometheus-cpp
run: |
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- - name: Install luarocks
- run: |
- sudo apt-get update && sudo apt-get install -y luarocks
+ - uses: leafo/gh-actions-lua@v9
+ with:
+ luaVersion: "5.1.5"
+ - uses: leafo/gh-actions-luarocks@v4
- name: Install luarocks tools
run: |
# IrrlichtMtConfig.cmake
message(FATAL_ERROR "Could not find IrrlichtMtConfig.cmake in ${IRRLICHTMT_BUILD_DIR}/cmake.")
endif()
-# This is done here so that relative search paths are more reasonable
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/lib/irrlichtmt")
message(STATUS "Using user-provided IrrlichtMt at subdirectory 'lib/irrlichtmt'")
if(BUILD_CLIENT)
include(MinetestFindIrrlichtHeaders)
if(NOT IRRLICHT_INCLUDE_DIR)
- message(FATAL_ERROR "Irrlicht or IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
+ message(FATAL_ERROR "IrrlichtMt headers are required to build the server, but none found.\n${explanation_msg}")
endif()
- message(STATUS "Found Irrlicht headers: ${IRRLICHT_INCLUDE_DIR}")
+ message(STATUS "Found IrrlichtMt headers: ${IRRLICHT_INCLUDE_DIR}")
add_library(IrrlichtMt::IrrlichtMt INTERFACE IMPORTED)
# Note that we can't use target_include_directories() since that doesn't work for IMPORTED targets before CMake 3.11
set_target_properties(IrrlichtMt::IrrlichtMt PROPERTIES
- You can disable the client build by specifying `-DBUILD_CLIENT=FALSE`.
- You can select between Release and Debug build by `-DCMAKE_BUILD_TYPE=<Debug or Release>`.
- Debug build is slower, but gives much more useful output in a debugger.
-- If you build a bare server you don't need to have the Irrlicht or IrrlichtMt library installed.
- - In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlicht/include`.
+- If you build a bare server you don't need to compile IrrlichtMt, just the headers suffice.
+ - In that case use `-DIRRLICHT_INCLUDE_DIR=/some/where/irrlichtmt/include`.
- Minetest will use the IrrlichtMt package that is found first, given by the following order:
1. Specified `IRRLICHTMT_BUILD_DIR` CMake variable
local F = core.formspec_escape
local S = core.get_translator("__builtin")
-local check_player_privs = core.check_player_privs
-- CHAT COMMANDS FORMSPEC
.. "any entry in the list.").. "\n" ..
S("Double-click to copy the entry to the chat history.")
+ local privs = core.get_player_privs(name)
for i, data in ipairs(mod_cmds) do
rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. ","
for j, cmds in ipairs(data[2]) do
- local has_priv = check_player_privs(name, cmds[2].privs)
+ local has_priv = privs[cmds[2].privs]
rows[#rows + 1] = ("%s,1,%s,%s"):format(
has_priv and COLOR_GREEN or COLOR_GRAY,
cmds[1], F(cmds[2].params))
--------------------------------------------------------------------------------
function string:trim()
- return (self:gsub("^%s*(.-)%s*$", "%1"))
+ return self:match("^%s*(.-)%s*$")
end
--------------------------------------------------------------------------------
return math.ceil(x - 0.5)
end
-
+local formspec_escapes = {
+ ["\\"] = "\\\\",
+ ["["] = "\\[",
+ ["]"] = "\\]",
+ [";"] = "\\;",
+ [","] = "\\,"
+}
function core.formspec_escape(text)
- if text ~= nil then
- text = string.gsub(text,"\\","\\\\")
- text = string.gsub(text,"%]","\\]")
- text = string.gsub(text,"%[","\\[")
- text = string.gsub(text,";","\\;")
- text = string.gsub(text,",","\\,")
- end
- return text
+ -- Use explicit character set instead of dot here because it doubles the performance
+ return text and text:gsub("[\\%[%];,]", formspec_escapes)
end
return as_table and {text} or text
end
- for word in text:gmatch('%S+') do
- local cur_length = #table.concat(line, ' ')
- if cur_length > 0 and cur_length + #word + 1 >= max_length then
+ local line_length = 0
+ for word in text:gmatch("%S+") do
+ if line_length > 0 and line_length + #word + 1 >= max_length then
-- word wouldn't fit on current line, move to next line
- table.insert(result, table.concat(line, ' '))
- line = {}
+ table.insert(result, table.concat(line, " "))
+ line = {word}
+ line_length = #word
+ else
+ table.insert(line, word)
+ line_length = line_length + 1 + #word
end
- table.insert(line, word)
end
- table.insert(result, table.concat(line, ' '))
- return as_table and result or table.concat(result, '\n')
+ table.insert(result, table.concat(line, " "))
+ return as_table and result or table.concat(result, "\n")
end
--------------------------------------------------------------------------------
return nil
end
- local x, y, z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
- if x and y and z then
- x = tonumber(x)
- y = tonumber(y)
- z = tonumber(z)
- return vector.new(x, y, z)
- end
- x, y, z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
+ value = value:match("^%((.-)%)$") or value -- strip parentheses
+
+ local x, y, z = value:trim():match("^([%d.-]+)[,%s]%s*([%d.-]+)[,%s]%s*([%d.-]+)$")
if x and y and z then
x = tonumber(x)
y = tonumber(y)
z = tonumber(z)
return vector.new(x, y, z)
end
+
return nil
end
--------------------------------------------------------------------------------
-function core.string_to_area(value)
- local p1, p2 = unpack(value:split(") ("))
- if p1 == nil or p2 == nil then
- return nil
- end
- p1 = core.string_to_pos(p1 .. ")")
- p2 = core.string_to_pos("(" .. p2)
- if p1 == nil or p2 == nil then
- return nil
+do
+ local rel_num_cap = "(~?-?%d*%.?%d*)" -- may be overly permissive as this will be tonumber'ed anyways
+ local num_delim = "[,%s]%s*"
+ local pattern = "^" .. table.concat({rel_num_cap, rel_num_cap, rel_num_cap}, num_delim) .. "$"
+
+ local function parse_area_string(pos, relative_to)
+ local pp = {}
+ pp.x, pp.y, pp.z = pos:trim():match(pattern)
+ return core.parse_coordinates(pp.x, pp.y, pp.z, relative_to)
end
- return p1, p2
-end
+ function core.string_to_area(value, relative_to)
+ local p1, p2 = value:match("^%((.-)%)%s*%((.-)%)$")
+ if not p1 then
+ return
+ end
-local function test_string_to_area()
- local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2, 4, -12.53)")
- assert(p1.x == 10.0 and p1.y == 5 and p1.z == -2)
- assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)
+ p1 = parse_area_string(p1, relative_to)
+ p2 = parse_area_string(p2, relative_to)
- p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53")
- assert(p1 == nil and p2 == nil)
+ if p1 == nil or p2 == nil then
+ return
+ end
- p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53")
- assert(p1 == nil and p2 == nil)
+ return p1, p2
+ end
end
-test_string_to_area()
-
--------------------------------------------------------------------------------
function table.copy(t, seen)
local n = {}
return number ~= number
end
+--[[ Helper function for parsing an optionally relative number
+of a chat command parameter, using the chat command tilde notation.
+
+Parameters:
+* arg: String snippet containing the number; possible values:
+ * "<number>": return as number
+ * "~<number>": return relative_to + <number>
+ * "~": return relative_to
+ * Anything else will return `nil`
+* relative_to: Number to which the `arg` number might be relative to
+
+Returns:
+A number or `nil`, depending on `arg.
+
+Examples:
+* `core.parse_relative_number("5", 10)` returns 5
+* `core.parse_relative_number("~5", 10)` returns 15
+* `core.parse_relative_number("~", 10)` returns 10
+]]
+function core.parse_relative_number(arg, relative_to)
+ if not arg then
+ return nil
+ elseif arg == "~" then
+ return relative_to
+ elseif string.sub(arg, 1, 1) == "~" then
+ local number = tonumber(string.sub(arg, 2))
+ if not number then
+ return nil
+ end
+ if core.is_nan(number) or number == math.huge or number == -math.huge then
+ return nil
+ end
+ return relative_to + number
+ else
+ local number = tonumber(arg)
+ if core.is_nan(number) or number == math.huge or number == -math.huge then
+ return nil
+ end
+ return number
+ end
+end
+
+--[[ Helper function to parse coordinates that might be relative
+to another position; supports chat command tilde notation.
+Intended to be used in chat command parameter parsing.
+
+Parameters:
+* x, y, z: Parsed x, y, and z coordinates as strings
+* relative_to: Position to which to compare the position
+
+Syntax of x, y and z:
+* "<number>": return as number
+* "~<number>": return <number> + player position on this axis
+* "~": return player position on this axis
+
+Returns: a vector or nil for invalid input or if player does not exist
+]]
+function core.parse_coordinates(x, y, z, relative_to)
+ if not relative_to then
+ x, y, z = tonumber(x), tonumber(y), tonumber(z)
+ return x and y and z and { x = x, y = y, z = z }
+ end
+ local rx = core.parse_relative_number(x, relative_to.x)
+ local ry = core.parse_relative_number(y, relative_to.y)
+ local rz = core.parse_relative_number(z, relative_to.z)
+ return rx and ry and rz and { x = rx, y = ry, z = rz }
+end
+
function core.inventorycube(img1, img2, img3)
img2 = img2 or img1
img3 = img3 or img1
end)
end)
+describe("area parsing", function()
+ describe("valid inputs", function()
+ it("accepts absolute numbers", function()
+ local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2 4 -12.53)")
+ assert(p1.x == 10 and p1.y == 5 and p1.z == -2)
+ assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)
+ end)
+
+ it("accepts relative numbers", function()
+ local p1, p2 = core.string_to_area("(1,2,3) (~5,~-5,~)", {x=10,y=10,z=10})
+ assert(type(p1) == "table" and type(p2) == "table")
+ assert(p1.x == 1 and p1.y == 2 and p1.z == 3)
+ assert(p2.x == 15 and p2.y == 5 and p2.z == 10)
+
+ p1, p2 = core.string_to_area("(1 2 3) (~5 ~-5 ~)", {x=10,y=10,z=10})
+ assert(type(p1) == "table" and type(p2) == "table")
+ assert(p1.x == 1 and p1.y == 2 and p1.z == 3)
+ assert(p2.x == 15 and p2.y == 5 and p2.z == 10)
+ end)
+ end)
+ describe("invalid inputs", function()
+ it("rejects too few numbers", function()
+ local p1, p2 = core.string_to_area("(1,1) (1,1,1,1)", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+ end)
+
+ it("rejects too many numbers", function()
+ local p1, p2 = core.string_to_area("(1,1,1,1) (1,1,1,1)", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+ end)
+
+ it("rejects nan & inf", function()
+ local p1, p2 = core.string_to_area("(1,1,1) (1,1,nan)", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(1,1,1) (1,1,~nan)", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(1,1,1) (1,~nan,1)", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(1,1,1) (1,1,inf)", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(1,1,1) (1,1,~inf)", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(1,1,1) (1,~inf,1)", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(nan,nan,nan) (nan,nan,nan)", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(nan,nan,nan) (nan,nan,nan)")
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(inf,inf,inf) (-inf,-inf,-inf)", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(inf,inf,inf) (-inf,-inf,-inf)")
+ assert(p1 == nil and p2 == nil)
+ end)
+
+ it("rejects words", function()
+ local p1, p2 = core.string_to_area("bananas", {x=1,y=1,z=1})
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("bananas", "foobar")
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("bananas")
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(bananas,bananas,bananas)")
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(bananas,bananas,bananas) (bananas,bananas,bananas)")
+ assert(p1 == nil and p2 == nil)
+ end)
+
+ it("requires parenthesis & valid numbers", function()
+ local p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53")
+ assert(p1 == nil and p2 == nil)
+
+ p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53")
+ assert(p1 == nil and p2 == nil)
+ end)
+ end)
+end)
+
describe("table", function()
it("indexof()", function()
assert.equal(1, table.indexof({"foo", "bar"}, "foo"))
assert.equal(-1, table.indexof({"foo", "bar"}, "baz"))
end)
end)
+
+describe("formspec_escape", function()
+ it("escapes", function()
+ assert.equal(nil, core.formspec_escape(nil))
+ assert.equal("", core.formspec_escape(""))
+ assert.equal("\\[Hello\\\\\\[", core.formspec_escape("[Hello\\["))
+ end)
+end)
return false, S("Unable to get position of player @1.", player_name)
end
else
- p1, p2 = core.string_to_area(str)
- if p1 == nil then
+ local player = core.get_player_by_name(player_name)
+ local relpos
+ if player then
+ relpos = player:get_pos()
+ end
+ p1, p2 = core.string_to_area(str, relpos)
+ if p1 == nil or p2 == nil then
return false, S("Incorrect area format. "
.. "Expected: (x1,y1,z1) (x2,y2,z2)")
end
description = S("Teleport to position or player"),
privs = {teleport=true},
func = function(name, param)
+ local player = core.get_player_by_name(name)
+ local relpos
+ if player then
+ relpos = player:get_pos()
+ end
local p = {}
- p.x, p.y, p.z = param:match("^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
- p = vector.apply(p, tonumber)
- if p.x and p.y and p.z then
+ p.x, p.y, p.z = string.match(param, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
+ p = core.parse_coordinates(p.x, p.y, p.z, relpos)
+ if p and p.x and p.y and p.z then
return teleport_to_pos(name, p)
end
"other players (missing privilege: @1).", "bring")
local teleportee_name
+ p = {}
teleportee_name, p.x, p.y, p.z = param:match(
- "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
+ "^([^ ]+) +([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
+ if teleportee_name then
+ local teleportee = core.get_player_by_name(teleportee_name)
+ if not teleportee then
+ return
+ end
+ relpos = teleportee:get_pos()
+ p = core.parse_coordinates(p.x, p.y, p.z, relpos)
+ end
p = vector.apply(p, tonumber)
+
if teleportee_name and p.x and p.y and p.z then
if not has_bring_priv then
return false, missing_bring_msg
description = S("Spawn entity at given (or your) position"),
privs = {give=true, interact=true},
func = function(name, param)
- local entityname, p = string.match(param, "^([^ ]+) *(.*)$")
+ local entityname, pstr = string.match(param, "^([^ ]+) *(.*)$")
if not entityname then
return false, S("EntityName required.")
end
if not core.registered_entities[entityname] then
return false, S("Cannot spawn an unknown entity.")
end
- if p == "" then
+ local p
+ if pstr == "" then
p = player:get_pos()
else
- p = core.string_to_pos(p)
- if p == nil then
+ p = {}
+ p.x, p.y, p.z = string.match(pstr, "^([%d.~-]+)[, ] *([%d.~-]+)[, ] *([%d.~-]+)$")
+ local relpos = player:get_pos()
+ p = core.parse_coordinates(p.x, p.y, p.z, relpos)
+ if not (p and p.x and p.y and p.z) then
return false, S("Invalid parameters (@1).", param)
end
end
end,
})
+local function get_time(timeofday)
+ local time = math.floor(timeofday * 1440)
+ local minute = time % 60
+ local hour = (time - minute) / 60
+ return time, hour, minute
+end
+
core.register_chatcommand("time", {
params = S("[<0..23>:<0..59> | <0..24000>]"),
description = S("Show or set time of day"),
return false, S("You don't have permission to run "
.. "this command (missing privilege: @1).", "settime")
end
- local hour, minute = param:match("^(%d+):(%d+)$")
- if not hour then
- local new_time = tonumber(param) or -1
+ local relative, negative, hour, minute = param:match("^(~?)(%-?)(%d+):(%d+)$")
+ if not relative then -- checking the first capture against nil suffices
+ local new_time = core.parse_relative_number(param, core.get_timeofday() * 24000)
+ if not new_time then
+ new_time = tonumber(param) or -1
+ else
+ new_time = new_time % 24000
+ end
if new_time ~= new_time or new_time < 0 or new_time > 24000 then
return false, S("Invalid time (must be between 0 and 24000).")
end
core.log("action", name .. " sets time to " .. new_time)
return true, S("Time of day changed.")
end
+ local new_time
hour = tonumber(hour)
minute = tonumber(minute)
- if hour < 0 or hour > 23 then
- return false, S("Invalid hour (must be between 0 and 23 inclusive).")
- elseif minute < 0 or minute > 59 then
- return false, S("Invalid minute (must be between 0 and 59 inclusive).")
+ if relative == "" then
+ if hour < 0 or hour > 23 then
+ return false, S("Invalid hour (must be between 0 and 23 inclusive).")
+ elseif minute < 0 or minute > 59 then
+ return false, S("Invalid minute (must be between 0 and 59 inclusive).")
+ end
+ new_time = (hour * 60 + minute) / 1440
+ else
+ if minute < 0 or minute > 59 then
+ return false, S("Invalid minute (must be between 0 and 59 inclusive).")
+ end
+ local current_time = core.get_timeofday()
+ if negative == "-" then -- negative time
+ hour, minute = -hour, -minute
+ end
+ new_time = (current_time + (hour * 60 + minute) / 1440) % 1
+ local _
+ _, hour, minute = get_time(new_time)
end
- core.set_timeofday((hour * 60 + minute) / 1440)
+ core.set_timeofday(new_time)
core.log("action", ("%s sets time to %d:%02d"):format(name, hour, minute))
return true, S("Time of day changed.")
end,
return true, S("Ban list: @1", ban_list)
end
end
+ if core.is_singleplayer() then
+ return false, S("You cannot ban players in singleplayer!")
+ end
if not core.get_player_by_name(param) then
return false, S("Player is not online.")
end
fgettext("Low"),
fgettext("Medium"),
fgettext("High"),
- fgettext("Ultra High")
+ fgettext("Very High")
}
}
.. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" ..
"checkbox[8.25,2;cb_waving_plants;" .. fgettext("Waving Plants") .. ";"
.. dump(core.settings:get_bool("enable_waving_plants")) .. "]"..
- "label[8.25,3.0;" .. fgettext("Dynamic shadows: ") .. "]" ..
- "dropdown[8.25,3.5;3.5;dd_shadows;" .. dd_options.shadow_levels[1] .. ";"
+ "label[8.25,2.8;" .. fgettext("Dynamic shadows:") .. "]" ..
+ "label[8.25,3.2;" .. fgettext("(game support required)") .. "]" ..
+ "dropdown[8.25,3.7;3.5;dd_shadows;" .. dd_options.shadow_levels[1] .. ";"
.. getSettingIndex.ShadowMapping() .. "]"
else
tab_string = tab_string ..
core.settings:set("enable_dynamic_shadows", "false")
else
local shadow_presets = {
- [2] = { 55, 512, "true", 0, "false" },
- [3] = { 82, 1024, "true", 1, "false" },
- [4] = { 240, 2048, "true", 1, "false" },
- [5] = { 240, 2048, "true", 2, "true" },
- [6] = { 300, 4096, "true", 2, "true" },
+ [2] = { 62, 512, "true", 0, "false" },
+ [3] = { 93, 1024, "true", 0, "false" },
+ [4] = { 140, 2048, "true", 1, "false" },
+ [5] = { 210, 4096, "true", 2, "true" },
+ [6] = { 300, 8192, "true", 2, "true" },
}
local s = shadow_presets[table.indexof(labels.shadow_levels, fields["dd_shadows"])]
if s then
-# Locate Irrlicht or IrrlichtMt headers on system.
+# Locate IrrlichtMt headers on system.
-foreach(libname IN ITEMS IrrlichtMt Irrlicht)
- string(TOLOWER "${libname}" libname2)
-
- find_path(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h
- DOC "Path to the directory with IrrlichtMt includes"
- PATHS
- /usr/local/include/${libname2}
- /usr/include/${libname2}
- /system/develop/headers/${libname2} #Haiku
- PATH_SUFFIXES "include/${libname2}"
- )
-
- if(IRRLICHT_INCLUDE_DIR)
- break()
- endif()
-endforeach()
+find_path(IRRLICHT_INCLUDE_DIR NAMES irrlicht.h
+ DOC "Path to the directory with IrrlichtMt includes"
+ PATHS
+ /usr/local/include/irrlichtmt
+ /usr/include/irrlichtmt
+ /system/develop/headers/irrlichtmt #Haiku
+ PATH_SUFFIXES "include/irrlichtmt"
+)
# Handholding for users
if(IRRLICHT_INCLUDE_DIR AND (NOT IS_DIRECTORY "${IRRLICHT_INCLUDE_DIR}" OR
max_drop_level=1,
groupcaps={
crumbly={maxlevel=2, uses=20, times={[1]=1.60, [2]=1.20, [3]=0.80}}
- }
+ },
damage_groups = {fleshy=2},
}
* `minetest.string_to_pos(string)`: returns a position or `nil`
* Same but in reverse.
* If the string can't be parsed to a position, nothing is returned.
-* `minetest.string_to_area("(X1, Y1, Z1) (X2, Y2, Z2)")`: returns two positions
+* `minetest.string_to_area("(X1, Y1, Z1) (X2, Y2, Z2)", relative_to)`:
+ * returns two positions
* Converts a string representing an area box into two positions
+ * X1, Y1, ... Z2 are coordinates
+ * `relative_to`: Optional. If set to a position, each coordinate
+ can use the tilde notation for relative positions
+ * Tilde notation: "~": Relative coordinate
+ "~<number>": Relative coordinate plus <number>
+ * Example: `minetest.string_to_area("(1,2,3) (~5,~-5,~)", {x=10,y=10,z=10})`
+ returns `{x=1,y=2,z=3}, {x=15,y=5,z=10}`
* `minetest.formspec_escape(string)`: returns a string
* escapes the characters "[", "]", "\", "," and ";", which can not be used
in formspecs.
* `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness)
* `get_lighting()`: returns the current state of lighting for the player.
* Result is a table with the same fields as `light_definition` in `set_lighting`.
+* `respawn()`: Respawns the player using the same mechanism as the death screen,
+ including calling on_respawnplayer callbacks.
`PcgRandom`
-----------
spawn_by = "default:water",
-- Node (or list of nodes) that the decoration only spawns next to.
- -- Checks two horizontal planes of 8 neighbouring nodes (including
- -- diagonal neighbours), one plane level with the 'place_on' node and a
- -- plane one node above that.
+ -- Checks the 8 neighbouring nodes on the same Y, and also the ones
+ -- at Y+1, excluding both center nodes.
num_spawn_by = 1,
-- Number of spawn_by nodes that must be surrounding the decoration
.B \-\-random\-input
Enable random user input, for testing (client only)
.TP
-.B \-\-videomodes
-List available video modes (client only)
.TP
.B \-\-speedtests
Run speed tests
--- /dev/null
+local S = minetest.get_translator("testitems")
+
+--
+-- Texture overlays for items
+--
+
+-- For the global overlay color test
+local GLOBAL_COLOR_ARG = "orange"
+
+-- Punch handler to set random color with "color" argument in item metadata
+local overlay_on_use = function(itemstack, user, pointed_thing)
+ local meta = itemstack:get_meta()
+ local color = math.random(0x0, 0xFFFFFF)
+ local colorstr = string.format("#%06x", color)
+ meta:set_string("color", colorstr)
+ minetest.log("action", "[testitems] Color of "..itemstack:get_name().." changed to "..colorstr)
+ return itemstack
+end
+-- Place handler to clear item metadata color
+local overlay_on_place = function(itemstack, user, pointed_thing)
+ local meta = itemstack:get_meta()
+ meta:set_string("color", "")
+ return itemstack
+end
+
+minetest.register_craftitem("testitems:overlay_meta", {
+ description = S("Texture Overlay Test Item, Meta Color") .. "\n" ..
+ S("Image must be a square with rainbow cross (inventory and wield)") .. "\n" ..
+ S("Item meta color must only change square color") .. "\n" ..
+ S("Punch: Set random color") .. "\n" ..
+ S("Place: Clear color"),
+ -- Base texture: A grayscale square (can be colorized)
+ inventory_image = "testitems_overlay_base.png",
+ wield_image = "testitems_overlay_base.png",
+ -- Overlay: A rainbow cross (NOT to be colorized!)
+ inventory_overlay = "testitems_overlay_overlay.png",
+ wield_overlay = "testitems_overlay_overlay.png",
+
+ on_use = overlay_on_use,
+ on_place = overlay_on_place,
+ on_secondary_use = overlay_on_place,
+})
+minetest.register_craftitem("testitems:overlay_global", {
+ description = S("Texture Overlay Test Item, Global Color") .. "\n" ..
+ S("Image must be an orange square with rainbow cross (inventory and wield)"),
+ -- Base texture: A grayscale square (to be colorized)
+ inventory_image = "testitems_overlay_base.png",
+ wield_image = "testitems_overlay_base.png",
+ -- Overlay: A rainbow cross (NOT to be colorized!)
+ inventory_overlay = "testitems_overlay_overlay.png",
+ wield_overlay = "testitems_overlay_overlay.png",
+ color = GLOBAL_COLOR_ARG,
+})
+
+
--- /dev/null
+name = testitems
+description = Test mod to test misc. items that are neither tools nor nodes
dofile(path.."/liquids.lua")
dofile(path.."/light.lua")
dofile(path.."/textures.lua")
+dofile(path.."/overlays.lua")
--- /dev/null
+local S = minetest.get_translator("testnodes")
+
+minetest.register_node("testnodes:overlay", {
+ description = S("Texture Overlay Test Node") .. "\n" ..
+ S("Uncolorized"),
+ tiles = {{name = "testnodes_overlayable.png"}},
+ overlay_tiles = {{name = "testnodes_overlay.png"}},
+ groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_color_all", {
+ description = S("Texture Overlay Test Node, Colorized") .. "\n" ..
+ S("param2 changes color"),
+ tiles = {{name = "testnodes_overlayable.png"}},
+ overlay_tiles = {{name = "testnodes_overlay.png"}},
+ paramtype2 = "color",
+ palette = "testnodes_palette_full.png",
+
+
+ groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_color_overlay", {
+ description = S("Texture Overlay Test Node, Colorized Overlay") .. "\n" ..
+ S("param2 changes color of overlay"),
+ tiles = {{name = "testnodes_overlayable.png", color="white"}},
+ overlay_tiles = {{name = "testnodes_overlay.png"}},
+ paramtype2 = "color",
+ palette = "testnodes_palette_full.png",
+
+
+ groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_color_overlayed", {
+ description = S("Texture Overlay Test Node, Colorized Base") .. "\n" ..
+ S("param2 changes color of base texture"),
+ tiles = {{name = "testnodes_overlayable.png"}},
+ overlay_tiles = {{name = "testnodes_overlay.png", color="white"}},
+ paramtype2 = "color",
+ palette = "testnodes_palette_full.png",
+
+
+ groups = { dig_immediate = 2 },
+})
+
+local global_overlay_color = "#FF2000"
+minetest.register_node("testnodes:overlay_global", {
+ description = S("Texture Overlay Test Node, Global Color") .. "\n" ..
+ S("Global color = @1", global_overlay_color),
+ tiles = {{name = "testnodes_overlayable.png"}},
+ overlay_tiles = {{name = "testnodes_overlay.png"}},
+ color = global_overlay_color,
+
+
+ groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_global_color_all", {
+ description = S("Texture Overlay Test Node, Global Color + Colorized") .. "\n" ..
+ S("Global color = @1", global_overlay_color) .. "\n" ..
+ S("param2 changes color"),
+ tiles = {{name = "testnodes_overlayable.png"}},
+ overlay_tiles = {{name = "testnodes_overlay.png"}},
+ color = global_overlay_color,
+ paramtype2 = "color",
+ palette = "testnodes_palette_full.png",
+
+
+ groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_global_color_overlay", {
+ description = S("Texture Overlay Test Node, Global Color + Colorized Overlay") .. "\n" ..
+ S("Global color = @1", global_overlay_color) .. "\n" ..
+ S("param2 changes color of overlay"),
+ tiles = {{name = "testnodes_overlayable.png", color=global_overlay_color}},
+ overlay_tiles = {{name = "testnodes_overlay.png"}},
+ color = global_overlay_color,
+ paramtype2 = "color",
+ palette = "testnodes_palette_full.png",
+
+
+ groups = { dig_immediate = 2 },
+})
+minetest.register_node("testnodes:overlay_global_color_overlayed", {
+ description = S("Texture Overlay Test Node, Global Color + Colorized Base") .. "\n" ..
+ S("Global color = @1", global_overlay_color) .. "\n" ..
+ S("param2 changes color of base texture"),
+ tiles = {{name = "testnodes_overlayable.png"}},
+ overlay_tiles = {{name = "testnodes_overlay.png", color=global_overlay_color}},
+ color = global_overlay_color,
+ paramtype2 = "color",
+ palette = "testnodes_palette_full.png",
+
+
+ groups = { dig_immediate = 2 },
+})
assert(vector.check(found_pos))
end
unittests.register("test_v3s16_metatable", test_v3s16_metatable, {map=true})
+
+local function test_clear_meta(_, pos)
+ local ref = core.get_meta(pos)
+
+ for way = 1, 3 do
+ ref:set_string("foo", "bar")
+ assert(ref:contains("foo"))
+
+ if way == 1 then
+ ref:from_table({})
+ elseif way == 2 then
+ ref:from_table(nil)
+ else
+ ref:set_string("foo", "")
+ end
+
+ assert(#core.find_nodes_with_meta(pos, pos) == 0, "clearing failed " .. way)
+ end
+end
+unittests.register("test_clear_meta", test_clear_meta, {map=true})
if(PostgreSQL_INCLUDE_DIR AND PostgreSQL_LIBRARY)
set(PostgreSQL_FOUND TRUE)
set(PostgreSQL_INCLUDE_DIRS ${PostgreSQL_INCLUDE_DIR})
+ set(PostgreSQL_LIBRARIES ${PostgreSQL_LIBRARY})
endif()
else()
find_package(PostgreSQL)
{
int num_processed_meshes = 0;
std::vector<v3s16> blocks_to_ack;
+ bool force_update_shadows = false;
while (!m_mesh_update_thread.m_queue_out.empty())
{
num_processed_meshes++;
if (is_empty)
delete r.mesh;
- else
+ else {
// Replace with the new mesh
block->mesh = r.mesh;
+ force_update_shadows = true;
+ }
}
} else {
delete r.mesh;
if (num_processed_meshes > 0)
g_profiler->graphAdd("num_processed_meshes", num_processed_meshes);
+
+ auto shadow_renderer = RenderingEngine::get_shadow_renderer();
+ if (shadow_renderer && force_update_shadows)
+ shadow_renderer->setForceUpdateShadowMap();
}
/*
m_access_denied = true;
if (timeout)
m_access_denied_reason = gettext("Connection timed out.");
- else
+ else if (m_access_denied_reason.empty())
m_access_denied_reason = gettext("Connection aborted (protocol error?).");
}
scene::SMeshBuffer *buf = new scene::SMeshBuffer();
buf->Material = material;
- switch (p.layer.material_type) {
- // list of transparent materials taken from tile.h
- case TILE_MATERIAL_ALPHA:
- case TILE_MATERIAL_LIQUID_TRANSPARENT:
- case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
- {
- buf->append(&p.vertices[0], p.vertices.size(),
- &p.indices[0], 0);
-
- MeshTriangle t;
- t.buffer = buf;
- for (u32 i = 0; i < p.indices.size(); i += 3) {
- t.p1 = p.indices[i];
- t.p2 = p.indices[i + 1];
- t.p3 = p.indices[i + 2];
- t.updateAttributes();
- m_transparent_triangles.push_back(t);
- }
+ if (p.layer.isTransparent()) {
+ buf->append(&p.vertices[0], p.vertices.size(), nullptr, 0);
+
+ MeshTriangle t;
+ t.buffer = buf;
+ m_transparent_triangles.reserve(p.indices.size() / 3);
+ for (u32 i = 0; i < p.indices.size(); i += 3) {
+ t.p1 = p.indices[i];
+ t.p2 = p.indices[i + 1];
+ t.p3 = p.indices[i + 2];
+ t.updateAttributes();
+ m_transparent_triangles.push_back(t);
}
- break;
- default:
+ } else {
buf->append(&p.vertices[0], p.vertices.size(),
&p.indices[0], p.indices.size());
- break;
}
mesh->addMeshBuffer(buf);
buf->drop();
#endif
-v2u32 RenderingEngine::getDisplaySize()
-{
- IrrlichtDevice *nulldevice = createDevice(video::EDT_NULL);
-
- core::dimension2d<u32> deskres =
- nulldevice->getVideoModeList()->getDesktopResolution();
- nulldevice->drop();
-
- return deskres;
-}
-
#else // __ANDROID__
float RenderingEngine::getDisplayDensity()
{
return porting::getDisplayDensity();
}
-v2u32 RenderingEngine::getDisplaySize()
-{
- return porting::getDisplaySize();
-}
#endif // __ANDROID__
static const VideoDriverInfo &getVideoDriverInfo(irr::video::E_DRIVER_TYPE type);
static float getDisplayDensity();
- static v2u32 getDisplaySize();
bool setupTopLevelWindow(const std::string &name);
void setupTopLevelXorgWindow(const std::string &name);
using m4f = core::matrix4;
+static v3f quantizeDirection(v3f direction, float step)
+{
+
+ float yaw = std::atan2(direction.Z, direction.X);
+ float pitch = std::asin(direction.Y); // assume look is normalized
+
+ yaw = std::floor(yaw / step) * step;
+ pitch = std::floor(pitch / step) * step;
+
+ return v3f(std::cos(yaw)*std::cos(pitch), std::sin(pitch), std::sin(yaw)*std::cos(pitch));
+}
+
void DirectionalLight::createSplitMatrices(const Camera *cam)
{
+ const float DISTANCE_STEP = BS * 2.0; // 2 meters
v3f newCenter;
v3f look = cam->getDirection();
+ look = quantizeDirection(look, M_PI / 12.0); // 15 degrees
// camera view tangents
float tanFovY = tanf(cam->getFovY() * 0.5f);
// adjusted camera positions
v3f cam_pos_world = cam->getPosition();
+ cam_pos_world = v3f(
+ floor(cam_pos_world.X / DISTANCE_STEP) * DISTANCE_STEP,
+ floor(cam_pos_world.Y / DISTANCE_STEP) * DISTANCE_STEP,
+ floor(cam_pos_world.Z / DISTANCE_STEP) * DISTANCE_STEP);
v3f cam_pos_scene = v3f(cam_pos_world.X - cam->getOffset().X * BS,
cam_pos_world.Y - cam->getOffset().Y * BS,
cam_pos_world.Z - cam->getOffset().Z * BS);
v3f boundVec = (cam_pos_scene + farCorner * sfFar) - center_scene;
float radius = boundVec.getLength();
float length = radius * 3.0f;
- v3f eye_displacement = direction * length;
+ v3f eye_displacement = quantizeDirection(direction, M_PI / 2880 /*15 seconds*/) * length;
// we must compute the viewmat with the position - the camera offset
// but the future_frustum position must be the actual world position
// detect if SM should be regenerated
for (DirectionalLight &light : m_light_list) {
- if (light.should_update_map_shadow) {
+ if (light.should_update_map_shadow || m_force_update_shadow_map) {
light.should_update_map_shadow = false;
m_current_frame = 0;
reset_sm_texture = true;
// should put some gl* fn here
- if (m_current_frame < m_map_shadow_update_frames) {
+ if (m_current_frame < m_map_shadow_update_frames || m_force_update_shadow_map) {
m_driver->setRenderTarget(shadowMapTargetTexture, reset_sm_texture, true,
video::SColor(255, 255, 255, 255));
renderShadowMap(shadowMapTargetTexture, light);
// Render transparent part in one pass.
// This is also handled in ClientMap.
- if (m_current_frame == m_map_shadow_update_frames - 1) {
+ if (m_current_frame == m_map_shadow_update_frames - 1 || m_force_update_shadow_map) {
if (m_shadow_map_colored) {
m_driver->setRenderTarget(0, false, false);
m_driver->setRenderTarget(shadowMapTextureColors,
++m_current_frame;
// pass finished, swap textures and commit light changes
- if (m_current_frame == m_map_shadow_update_frames) {
+ if (m_current_frame == m_map_shadow_update_frames || m_force_update_shadow_map) {
if (shadowMapClientMapFuture != nullptr)
std::swap(shadowMapClientMapFuture, shadowMapClientMap);
for (DirectionalLight &light : m_light_list)
light.commitFrustum();
}
+ m_force_update_shadow_map = false;
}
}
m_driver->setTransform(video::ETS_WORLD,
map_node->getAbsoluteTransformation());
- map_node->renderMapShadows(m_driver, material, pass, m_current_frame, m_map_shadow_update_frames);
+ int frame = m_force_update_shadow_map ? 0 : m_current_frame;
+ int total_frames = m_force_update_shadow_map ? 1 : m_map_shadow_update_frames;
+
+ map_node->renderMapShadows(m_driver, material, pass, frame, total_frames);
break;
}
}
void removeNodeFromShadowList(scene::ISceneNode *node);
void update(video::ITexture *outputTarget = nullptr);
+ void setForceUpdateShadowMap() { m_force_update_shadow_map = true; }
void drawDebug();
video::ITexture *get_texture()
bool m_shadows_enabled;
bool m_shadows_supported;
bool m_shadow_map_colored;
+ bool m_force_update_shadow_map;
u8 m_map_shadow_update_frames; /* Use this number of frames to update map shaodw */
u8 m_current_frame{0}; /* Current frame */
f32 m_perspective_bias_xy;
return retval;
m_jni_field_name = field_name;
- /*~ Imperative, as in "Enter/type in text".
- Don't forget the space. */
- std::string message = gettext("Enter ");
std::string label = wide_to_utf8(getLabelByID(hovered->getID()));
if (label.empty())
label = "text";
- message += strgettext(label) + ":";
+ /*~ Imperative, as in "Type in text" */
+ std::string message = fmtgettext("Enter %s:");
// single line text input
int type = 2;
void Logger::addOutput(ILogOutput *out, LogLevel lev)
{
+ MutexAutoLock lock(m_mutex);
m_outputs[lev].push_back(out);
m_has_outputs[lev] = true;
}
void Logger::addOutputMasked(ILogOutput *out, LogLevelMask mask)
{
+ MutexAutoLock lock(m_mutex);
for (size_t i = 0; i < LL_MAX; i++) {
if (mask & LOGLEVEL_TO_MASKLEVEL(i)) {
m_outputs[i].push_back(out);
void Logger::addOutputMaxLevel(ILogOutput *out, LogLevel lev)
{
+ MutexAutoLock lock(m_mutex);
assert(lev < LL_MAX);
for (size_t i = 0; i <= lev; i++) {
m_outputs[i].push_back(out);
LogLevelMask Logger::removeOutput(ILogOutput *out)
{
+ MutexAutoLock lock(m_mutex);
LogLevelMask ret_mask = 0;
for (size_t i = 0; i < LL_MAX; i++) {
std::vector<ILogOutput *>::iterator it;
default: break;
}
}
-
- m_buffer.push(color.append(line));
+ MutexAutoLock lock(m_buffer_mutex);
+ m_buffer.emplace(color.append(line));
}
#if !defined(_WIN32) // POSIX
#include <unistd.h>
#endif
+#include "threading/mutex_auto_lock.h"
#include "util/basic_macros.h"
#include "util/stream.h"
#include "irrlichttypes.h"
void clear()
{
+ MutexAutoLock lock(m_buffer_mutex);
m_buffer = std::queue<std::string>();
}
bool empty() const
{
+ MutexAutoLock lock(m_buffer_mutex);
return m_buffer.empty();
}
std::string get()
{
- if (empty())
+ MutexAutoLock lock(m_buffer_mutex);
+ if (m_buffer.empty())
return "";
- std::string s = m_buffer.front();
+ std::string s = std::move(m_buffer.front());
m_buffer.pop();
return s;
}
private:
+ // g_logger serializes calls to logRaw() with a mutex, but that
+ // doesn't prevent get() / clear() from being called on top of it.
+ // This mutex prevents that.
+ mutable std::mutex m_buffer_mutex;
std::queue<std::string> m_buffer;
Logger &m_logger;
};
return sector;
}
- std::map<v2s16, MapSector*>::iterator n = m_sectors.find(p);
+ auto n = m_sectors.find(p);
if (n == m_sectors.end())
return NULL;
std::set<MapEventReceiver*> m_event_receivers;
- std::map<v2s16, MapSector*> m_sectors;
+ std::unordered_map<v2s16, MapSector*> m_sectors;
// Be sure to set this to NULL when the cached sector is deleted
MapSector *m_sector_cache = nullptr;
/*
Serialization
*/
+
// List relevant id-name pairs for ids in the block using nodedef
-// Renumbers the content IDs (starting at 0 and incrementing
-// use static memory requires about 65535 * sizeof(int) ram in order to be
-// sure we can handle all content ids. But it's absolutely worth it as it's
-// a speedup of 4 for one of the major time consuming functions on storing
-// mapblocks.
-static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1];
+// Renumbers the content IDs (starting at 0 and incrementing)
static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes,
const NodeDefManager *nodedef)
{
- memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
-
- std::set<content_t> unknown_contents;
+ // The static memory requires about 65535 * sizeof(int) RAM in order to be
+ // sure we can handle all content ids. But it's absolutely worth it as it's
+ // a speedup of 4 for one of the major time consuming functions on storing
+ // mapblocks.
+ thread_local std::unique_ptr<content_t[]> mapping;
+ static_assert(sizeof(content_t) == 2, "content_t must be 16-bit");
+ if (!mapping)
+ mapping = std::make_unique<content_t[]>(USHRT_MAX + 1);
+
+ memset(mapping.get(), 0xFF, (USHRT_MAX + 1) * sizeof(content_t));
+
+ std::unordered_set<content_t> unknown_contents;
content_t id_counter = 0;
for (u32 i = 0; i < MapBlock::nodecount; i++) {
content_t global_id = nodes[i].getContent();
content_t id = CONTENT_IGNORE;
// Try to find an existing mapping
- if (getBlockNodeIdMapping_mapping[global_id] != 0xFFFF) {
- id = getBlockNodeIdMapping_mapping[global_id];
- }
- else
- {
+ if (mapping[global_id] != 0xFFFF) {
+ id = mapping[global_id];
+ } else {
// We have to assign a new mapping
id = id_counter++;
- getBlockNodeIdMapping_mapping[global_id] = id;
+ mapping[global_id] = id;
const ContentFeatures &f = nodedef->get(global_id);
const std::string &name = f.name;
<< "Name for node id " << unknown_content << " not known" << std::endl;
}
}
+
// Correct ids in the block to match nodedef based on names.
// Unknown ones are added to nodedef.
// Will not update itself to match id-name pairs in nodedef.
std::vector<aabb3f> &boxes = *p_boxes;
if (nodebox.type == NODEBOX_FIXED || nodebox.type == NODEBOX_LEVELED) {
- const std::vector<aabb3f> &fixed = nodebox.fixed;
+ const auto &fixed = nodebox.fixed;
int facedir = n.getFaceDir(nodemgr, true);
u8 axisdir = facedir>>2;
facedir&=0x03;
+
+ boxes.reserve(boxes.size() + fixed.size());
for (aabb3f box : fixed) {
if (nodebox.type == NODEBOX_LEVELED)
box.MaxEdge.Y = (-0.5f + n.getLevel(nodemgr) / 64.0f) * BS;
{
size_t boxes_size = boxes.size();
boxes_size += nodebox.fixed.size();
+ const auto &c = nodebox.getConnected();
+
if (neighbors & 1)
- boxes_size += nodebox.connect_top.size();
+ boxes_size += c.connect_top.size();
else
- boxes_size += nodebox.disconnected_top.size();
+ boxes_size += c.disconnected_top.size();
if (neighbors & 2)
- boxes_size += nodebox.connect_bottom.size();
+ boxes_size += c.connect_bottom.size();
else
- boxes_size += nodebox.disconnected_bottom.size();
+ boxes_size += c.disconnected_bottom.size();
if (neighbors & 4)
- boxes_size += nodebox.connect_front.size();
+ boxes_size += c.connect_front.size();
else
- boxes_size += nodebox.disconnected_front.size();
+ boxes_size += c.disconnected_front.size();
if (neighbors & 8)
- boxes_size += nodebox.connect_left.size();
+ boxes_size += c.connect_left.size();
else
- boxes_size += nodebox.disconnected_left.size();
+ boxes_size += c.disconnected_left.size();
if (neighbors & 16)
- boxes_size += nodebox.connect_back.size();
+ boxes_size += c.connect_back.size();
else
- boxes_size += nodebox.disconnected_back.size();
+ boxes_size += c.disconnected_back.size();
if (neighbors & 32)
- boxes_size += nodebox.connect_right.size();
+ boxes_size += c.connect_right.size();
else
- boxes_size += nodebox.disconnected_right.size();
+ boxes_size += c.disconnected_right.size();
if (neighbors == 0)
- boxes_size += nodebox.disconnected.size();
+ boxes_size += c.disconnected.size();
if (neighbors < 4)
- boxes_size += nodebox.disconnected_sides.size();
+ boxes_size += c.disconnected_sides.size();
boxes.reserve(boxes_size);
BOXESPUSHBACK(nodebox.fixed);
if (neighbors & 1) {
- BOXESPUSHBACK(nodebox.connect_top);
+ BOXESPUSHBACK(c.connect_top);
} else {
- BOXESPUSHBACK(nodebox.disconnected_top);
+ BOXESPUSHBACK(c.disconnected_top);
}
if (neighbors & 2) {
- BOXESPUSHBACK(nodebox.connect_bottom);
+ BOXESPUSHBACK(c.connect_bottom);
} else {
- BOXESPUSHBACK(nodebox.disconnected_bottom);
+ BOXESPUSHBACK(c.disconnected_bottom);
}
if (neighbors & 4) {
- BOXESPUSHBACK(nodebox.connect_front);
+ BOXESPUSHBACK(c.connect_front);
} else {
- BOXESPUSHBACK(nodebox.disconnected_front);
+ BOXESPUSHBACK(c.disconnected_front);
}
if (neighbors & 8) {
- BOXESPUSHBACK(nodebox.connect_left);
+ BOXESPUSHBACK(c.connect_left);
} else {
- BOXESPUSHBACK(nodebox.disconnected_left);
+ BOXESPUSHBACK(c.disconnected_left);
}
if (neighbors & 16) {
- BOXESPUSHBACK(nodebox.connect_back);
+ BOXESPUSHBACK(c.connect_back);
} else {
- BOXESPUSHBACK(nodebox.disconnected_back);
+ BOXESPUSHBACK(c.disconnected_back);
}
if (neighbors & 32) {
- BOXESPUSHBACK(nodebox.connect_right);
+ BOXESPUSHBACK(c.connect_right);
} else {
- BOXESPUSHBACK(nodebox.disconnected_right);
+ BOXESPUSHBACK(c.disconnected_right);
}
if (neighbors == 0) {
- BOXESPUSHBACK(nodebox.disconnected);
+ BOXESPUSHBACK(c.disconnected);
}
if (neighbors < 4) {
- BOXESPUSHBACK(nodebox.disconnected_sides);
+ BOXESPUSHBACK(c.disconnected_sides);
}
}
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();
- disconnected_top.clear();
- disconnected_bottom.clear();
- disconnected_front.clear();
- disconnected_left.clear();
- disconnected_back.clear();
- disconnected_right.clear();
- disconnected.clear();
- disconnected_sides.clear();
+ connected.reset();
}
void NodeBox::serialize(std::ostream &os, u16 protocol_version) const
writeV3F32(os, wall_side.MinEdge);
writeV3F32(os, wall_side.MaxEdge);
break;
- case NODEBOX_CONNECTED:
+ case NODEBOX_CONNECTED: {
writeU8(os, type);
#define WRITEBOX(box) \
writeV3F32(os, i.MaxEdge); \
};
+ const auto &c = getConnected();
+
WRITEBOX(fixed);
- WRITEBOX(connect_top);
- WRITEBOX(connect_bottom);
- WRITEBOX(connect_front);
- WRITEBOX(connect_left);
- WRITEBOX(connect_back);
- WRITEBOX(connect_right);
- WRITEBOX(disconnected_top);
- WRITEBOX(disconnected_bottom);
- WRITEBOX(disconnected_front);
- WRITEBOX(disconnected_left);
- WRITEBOX(disconnected_back);
- WRITEBOX(disconnected_right);
- WRITEBOX(disconnected);
- WRITEBOX(disconnected_sides);
+ WRITEBOX(c.connect_top);
+ WRITEBOX(c.connect_bottom);
+ WRITEBOX(c.connect_front);
+ WRITEBOX(c.connect_left);
+ WRITEBOX(c.connect_back);
+ WRITEBOX(c.connect_right);
+ WRITEBOX(c.disconnected_top);
+ WRITEBOX(c.disconnected_bottom);
+ WRITEBOX(c.disconnected_front);
+ WRITEBOX(c.disconnected_left);
+ WRITEBOX(c.disconnected_back);
+ WRITEBOX(c.disconnected_right);
+ WRITEBOX(c.disconnected);
+ WRITEBOX(c.disconnected_sides);
break;
+ }
default:
writeU8(os, type);
break;
u16 count;
+ auto &c = getConnected();
+
READBOXES(fixed);
- READBOXES(connect_top);
- READBOXES(connect_bottom);
- READBOXES(connect_front);
- READBOXES(connect_left);
- READBOXES(connect_back);
- READBOXES(connect_right);
- READBOXES(disconnected_top);
- READBOXES(disconnected_bottom);
- READBOXES(disconnected_front);
- READBOXES(disconnected_left);
- READBOXES(disconnected_back);
- READBOXES(disconnected_right);
- READBOXES(disconnected);
- READBOXES(disconnected_sides);
+ READBOXES(c.connect_top);
+ READBOXES(c.connect_bottom);
+ READBOXES(c.connect_front);
+ READBOXES(c.connect_left);
+ READBOXES(c.connect_back);
+ READBOXES(c.connect_right);
+ READBOXES(c.disconnected_top);
+ READBOXES(c.disconnected_bottom);
+ READBOXES(c.disconnected_front);
+ READBOXES(c.disconnected_left);
+ READBOXES(c.disconnected_back);
+ READBOXES(c.disconnected_right);
+ READBOXES(c.disconnected);
+ READBOXES(c.disconnected_sides);
}
}
drowning = 0;
light_source = 0;
damage_per_second = 0;
- node_box = NodeBox();
- selection_box = NodeBox();
- collision_box = NodeBox();
+ node_box.reset();
+ selection_box.reset();
+ collision_box.reset();
waving = 0;
legacy_facedir_simple = false;
legacy_wallmounted = false;
solidness = 0;
visual_solidness = 1;
} else {
- drawtype = NDT_NORMAL;
- solidness = 2;
+ if (waving >= 1) {
+ // waving nodes must make faces so there are no gaps
+ drawtype = NDT_ALLFACES;
+ solidness = 0;
+ visual_solidness = 1;
+ } else {
+ drawtype = NDT_NORMAL;
+ solidness = 2;
+ }
for (TileDef &td : tdef)
td.name += std::string("^[noalpha");
}
{
ContentFeatures f;
f.name = "unknown";
- TileDef unknownTile;
- unknownTile.name = "unknown_node.png";
for (int t = 0; t < 6; t++)
- f.tiledef[t] = unknownTile;
+ f.tiledef[t].name = "unknown_node.png";
// Insert directly into containers
content_t c = CONTENT_UNKNOWN;
m_content_features[c] = f;
break;
}
case NODEBOX_CONNECTED: {
+ const auto &c = nodebox.getConnected();
// Add all possible connected boxes
- boxVectorUnion(nodebox.fixed, box_union);
- boxVectorUnion(nodebox.connect_top, box_union);
- boxVectorUnion(nodebox.connect_bottom, box_union);
- boxVectorUnion(nodebox.connect_front, box_union);
- boxVectorUnion(nodebox.connect_left, box_union);
- boxVectorUnion(nodebox.connect_back, box_union);
- boxVectorUnion(nodebox.connect_right, box_union);
- boxVectorUnion(nodebox.disconnected_top, box_union);
- boxVectorUnion(nodebox.disconnected_bottom, box_union);
- boxVectorUnion(nodebox.disconnected_front, box_union);
- boxVectorUnion(nodebox.disconnected_left, box_union);
- boxVectorUnion(nodebox.disconnected_back, box_union);
- boxVectorUnion(nodebox.disconnected_right, box_union);
- boxVectorUnion(nodebox.disconnected, box_union);
- boxVectorUnion(nodebox.disconnected_sides, box_union);
+ boxVectorUnion(nodebox.fixed, box_union);
+ boxVectorUnion(c.connect_top, box_union);
+ boxVectorUnion(c.connect_bottom, box_union);
+ boxVectorUnion(c.connect_front, box_union);
+ boxVectorUnion(c.connect_left, box_union);
+ boxVectorUnion(c.connect_back, box_union);
+ boxVectorUnion(c.connect_right, box_union);
+ boxVectorUnion(c.disconnected_top, box_union);
+ boxVectorUnion(c.disconnected_bottom, box_union);
+ boxVectorUnion(c.disconnected_front, box_union);
+ boxVectorUnion(c.disconnected_left, box_union);
+ boxVectorUnion(c.disconnected_back, box_union);
+ boxVectorUnion(c.disconnected_right, box_union);
+ boxVectorUnion(c.disconnected, box_union);
+ boxVectorUnion(c.disconnected_sides, box_union);
break;
}
default: {
NODEBOX_CONNECTED, // optionally draws nodeboxes if a neighbor node attaches
};
-struct NodeBox
+struct NodeBoxConnected
{
- enum NodeBoxType type;
- // NODEBOX_REGULAR (no parameters)
- // NODEBOX_FIXED
- std::vector<aabb3f> fixed;
- // NODEBOX_WALLMOUNTED
- aabb3f wall_top;
- aabb3f wall_bottom;
- aabb3f wall_side; // being at the -X side
- // NODEBOX_CONNECTED
std::vector<aabb3f> connect_top;
std::vector<aabb3f> connect_bottom;
std::vector<aabb3f> connect_front;
std::vector<aabb3f> disconnected_right;
std::vector<aabb3f> disconnected;
std::vector<aabb3f> disconnected_sides;
+};
+
+struct NodeBox
+{
+ enum NodeBoxType type;
+ // NODEBOX_REGULAR (no parameters)
+ // NODEBOX_FIXED
+ std::vector<aabb3f> fixed;
+ // NODEBOX_WALLMOUNTED
+ aabb3f wall_top;
+ aabb3f wall_bottom;
+ aabb3f wall_side; // being at the -X side
+ // NODEBOX_CONNECTED
+ // (kept externally to not bloat the structure)
+ std::shared_ptr<NodeBoxConnected> connected;
NodeBox()
{ reset(); }
+ ~NodeBox() = default;
+
+ inline NodeBoxConnected &getConnected() {
+ if (!connected)
+ connected = std::make_shared<NodeBoxConnected>();
+ return *connected;
+ }
+ inline const NodeBoxConnected &getConnected() const {
+ assert(connected);
+ return *connected;
+ }
void reset();
void serialize(std::ostream &os, u16 protocol_version) const;
// up down right left back front
TileSpec tiles[6];
// Special tiles
- // - Currently used for flowing liquids
TileSpec special_tiles[CF_SPECIAL_COUNT];
u8 solidness; // Used when choosing which face is drawn
u8 visual_solidness; // When solidness=0, this tells how it looks like
*/
inline const ContentFeatures& get(content_t c) const {
return
- c < m_content_features.size() ?
+ (c < m_content_features.size() && !m_content_features[c].name.empty()) ?
m_content_features[c] : m_content_features[CONTENT_UNKNOWN];
}
push_aabb3f(L, box.wall_side);
lua_setfield(L, -2, "wall_side");
break;
- case NODEBOX_CONNECTED:
+ case NODEBOX_CONNECTED: {
lua_pushstring(L, "connected");
lua_setfield(L, -2, "type");
- push_box(L, box.connect_top);
+ const auto &c = box.getConnected();
+ push_box(L, c.connect_top);
lua_setfield(L, -2, "connect_top");
- push_box(L, box.connect_bottom);
+ push_box(L, c.connect_bottom);
lua_setfield(L, -2, "connect_bottom");
- push_box(L, box.connect_front);
+ push_box(L, c.connect_front);
lua_setfield(L, -2, "connect_front");
- push_box(L, box.connect_back);
+ push_box(L, c.connect_back);
lua_setfield(L, -2, "connect_back");
- push_box(L, box.connect_left);
+ push_box(L, c.connect_left);
lua_setfield(L, -2, "connect_left");
- push_box(L, box.connect_right);
+ push_box(L, c.connect_right);
lua_setfield(L, -2, "connect_right");
+ // half the boxes are missing here?
break;
+ }
default:
FATAL_ERROR("Invalid box.type");
break;
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");
- NODEBOXREADVEC(nodebox.disconnected_top, "disconnected_top");
- NODEBOXREADVEC(nodebox.disconnected_bottom, "disconnected_bottom");
- NODEBOXREADVEC(nodebox.disconnected_front, "disconnected_front");
- NODEBOXREADVEC(nodebox.disconnected_left, "disconnected_left");
- NODEBOXREADVEC(nodebox.disconnected_back, "disconnected_back");
- NODEBOXREADVEC(nodebox.disconnected_right, "disconnected_right");
- NODEBOXREADVEC(nodebox.disconnected, "disconnected");
- NODEBOXREADVEC(nodebox.disconnected_sides, "disconnected_sides");
+
+ if (nodebox.type == NODEBOX_CONNECTED) {
+ auto &c = nodebox.getConnected();
+ NODEBOXREADVEC(c.connect_top, "connect_top");
+ NODEBOXREADVEC(c.connect_bottom, "connect_bottom");
+ NODEBOXREADVEC(c.connect_front, "connect_front");
+ NODEBOXREADVEC(c.connect_left, "connect_left");
+ NODEBOXREADVEC(c.connect_back, "connect_back");
+ NODEBOXREADVEC(c.connect_right, "connect_right");
+ NODEBOXREADVEC(c.disconnected_top, "disconnected_top");
+ NODEBOXREADVEC(c.disconnected_bottom, "disconnected_bottom");
+ NODEBOXREADVEC(c.disconnected_front, "disconnected_front");
+ NODEBOXREADVEC(c.disconnected_left, "disconnected_left");
+ NODEBOXREADVEC(c.disconnected_back, "disconnected_back");
+ NODEBOXREADVEC(c.disconnected_right, "disconnected_right");
+ NODEBOXREADVEC(c.disconnected, "disconnected");
+ NODEBOXREADVEC(c.disconnected_sides, "disconnected_sides");
+ }
return nodebox;
}
"gethook",
"traceback",
"getinfo",
- "getmetatable",
- "setmetatable",
"upvalueid",
"sethook",
"debug",
// be done
if (f.name == "ignore")
return 0;
+ // This would break everything
+ if (f.name.empty())
+ throw LuaError("Cannot register node with empty name");
content_t id = ndef->set(f.name, f);
Metadata* NodeMetaRef::getmeta(bool auto_create)
{
if (m_is_local)
- return m_meta;
+ return m_local_meta;
NodeMetadata *meta = m_env->getMap().getNodeMetadata(m_p);
if (meta == NULL && auto_create) {
void NodeMetaRef::reportMetadataChange(const std::string *name)
{
SANITY_CHECK(!m_is_local);
- // NOTE: This same code is in rollback_interface.cpp
// Inform other things that the metadata has changed
- NodeMetadata *meta = dynamic_cast<NodeMetadata*>(m_meta);
+ NodeMetadata *meta = dynamic_cast<NodeMetadata*>(getmeta(false));
+
+ // If the metadata is now empty, get rid of it
+ if (meta && meta->empty()) {
+ clearMeta();
+ meta = nullptr;
+ }
MapEditEvent event;
event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
}
NodeMetaRef::NodeMetaRef(Metadata *meta):
- m_meta(meta),
- m_is_local(true)
+ m_is_local(true),
+ m_local_meta(meta)
{
}
class NodeMetaRef : public MetaDataRef {
private:
+ bool m_is_local = false;
+ // Set for server metadata
v3s16 m_p;
ServerEnvironment *m_env = nullptr;
- Metadata *m_meta = nullptr;
- bool m_is_local = false;
+ // Set for client metadata
+ Metadata *m_local_meta = nullptr;
static const char className[];
static const luaL_Reg methodsServer[];
return 1;
}
+// respawn(self)
+int ObjectRef::l_respawn(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+ ObjectRef *ref = checkobject(L, 1);
+ RemotePlayer *player = getplayer(ref);
+ if (player == nullptr)
+ return 0;
+
+ getServer(L)->RespawnPlayer(player->getPeerId());
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+
ObjectRef::ObjectRef(ServerActiveObject *object):
m_object(object)
{}
luamethod(ObjectRef, set_minimap_modes),
luamethod(ObjectRef, set_lighting),
luamethod(ObjectRef, get_lighting),
+ luamethod(ObjectRef, respawn),
+
{0,0}
};
// get_lighting(self)
static int l_get_lighting(lua_State *L);
+
+ // respawn(self)
+ static int l_respawn(lua_State *L);
};
#include "log.h"
-/* This protects:
- * 'secure.*' settings from being set
- * some mapgen settings from being set
+/* This protects the following from being set:
+ * 'secure.*' settings
+ * some security-relevant settings
+ * (better solution pending)
+ * some mapgen settings
* (not security-criticial, just to avoid messing up user configs)
*/
#define CHECK_SETTING_SECURITY(L, name) \
static inline int checkSettingSecurity(lua_State* L, const std::string &name)
{
if (ScriptApiSecurity::isSecure(L) && name.compare(0, 7, "secure.") == 0)
- throw LuaError("Attempt to set secure setting.");
+ throw LuaError("Attempted to set secure setting.");
bool is_mainmenu = false;
#ifndef SERVER
return -1;
}
+ const char *disallowed[] = {
+ "main_menu_script", "shader_path", "texture_path", "screenshot_path",
+ "serverlist_file", "serverlist_url", "map-dir", "contentdb_url",
+ };
+ if (!is_mainmenu) {
+ for (const char *name2 : disallowed) {
+ if (name == name2)
+ throw LuaError("Attempted to set disallowed setting.");
+ }
+ }
+
return 0;
}
// We'll log the amount of each
Profiler prof;
- std::list<v3s16> node_meta_updates;
+ std::unordered_set<v3s16> node_meta_updates;
while (!m_unsent_map_edit_queue.empty()) {
MapEditEvent* event = m_unsent_map_edit_queue.front();
case MEET_BLOCK_NODE_METADATA_CHANGED: {
prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1);
if (!event->is_private_change) {
- // Don't send the change yet. Collect them to eliminate dupes.
- node_meta_updates.remove(event->p);
- node_meta_updates.push_back(event->p);
+ node_meta_updates.emplace(event->p);
}
if (MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(
}
// Send all metadata updates
- if (node_meta_updates.size())
+ if (!node_meta_updates.empty())
sendMetadataChanged(node_meta_updates);
}
}
}
-void Server::sendMetadataChanged(const std::list<v3s16> &meta_updates, float far_d_nodes)
+void Server::sendMetadataChanged(const std::unordered_set<v3s16> &positions, float far_d_nodes)
{
- float maxd = far_d_nodes * BS;
NodeMetadataList meta_updates_list(false);
- std::vector<session_t> clients = m_clients.getClientIDs();
+ std::ostringstream os(std::ios::binary);
+ std::vector<session_t> clients = m_clients.getClientIDs();
ClientInterface::AutoLock clientlock(m_clients);
for (session_t i : clients) {
if (!client)
continue;
- ServerActiveObject *player = m_env->getActiveObject(i);
- v3f player_pos = player ? player->getBasePosition() : v3f();
+ ServerActiveObject *player = getPlayerSAO(i);
+ v3s16 player_pos;
+ if (player)
+ player_pos = floatToInt(player->getBasePosition(), BS);
- for (const v3s16 &pos : meta_updates) {
+ for (const v3s16 pos : positions) {
NodeMetadata *meta = m_env->getMap().getNodeMetadata(pos);
if (!meta)
continue;
v3s16 block_pos = getNodeBlockPos(pos);
- if (!client->isBlockSent(block_pos) || (player &&
- player_pos.getDistanceFrom(intToFloat(pos, BS)) > maxd)) {
+ if (!client->isBlockSent(block_pos) ||
+ player_pos.getDistanceFrom(pos) > far_d_nodes) {
client->SetBlockNotSent(block_pos);
continue;
}
continue;
// Send the meta changes
- std::ostringstream os(std::ios::binary);
+ os.str("");
meta_updates_list.serialize(os, client->serialization_version, false, true, true);
- std::ostringstream oss(std::ios::binary);
- compressZlib(os.str(), oss);
+ std::string raw = os.str();
+ os.str("");
+ compressZlib(raw, os);
- NetworkPacket pkt(TOCLIENT_NODEMETA_CHANGED, 0);
- pkt.putLongString(oss.str());
- m_clients.send(i, 0, &pkt, true);
+ NetworkPacket pkt(TOCLIENT_NODEMETA_CHANGED, 0, i);
+ pkt.putLongString(os.str());
+ Send(&pkt);
meta_updates_list.clear();
}
<< playersao->getPlayer()->getName()
<< " respawns" << std::endl;
- playersao->setHP(playersao->accessObjectProperties()->hp_max,
+ const auto *prop = playersao->accessObjectProperties();
+ playersao->setHP(prop->hp_max,
PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN));
- playersao->setBreath(playersao->accessObjectProperties()->breath_max);
+ playersao->setBreath(prop->breath_max);
bool repositioned = m_script->on_respawnplayer(playersao);
if (!repositioned) {
void setLighting(RemotePlayer *player, const Lighting &lighting);
+ void RespawnPlayer(session_t peer_id);
+
/* con::PeerHandler implementation. */
void peerAdded(con::Peer *peer);
void deletingPeer(con::Peer *peer, bool timeout);
std::unordered_set<session_t> waiting_players;
};
- // the standard library does not implement std::hash for pairs so we have this:
+ // The standard library does not implement std::hash for pairs so we have this:
struct SBCHash {
size_t operator() (const std::pair<v3s16, u16> &p) const {
- return (((size_t) p.first.X) << 48) | (((size_t) p.first.Y) << 32) |
- (((size_t) p.first.Z) << 16) | ((size_t) p.second);
+ return std::hash<v3s16>()(p.first) ^ p.second;
}
};
std::unordered_set<u16> *far_players = nullptr,
float far_d_nodes = 100, bool remove_metadata = true);
- void sendMetadataChanged(const std::list<v3s16> &meta_updates,
+ void sendMetadataChanged(const std::unordered_set<v3s16> &positions,
float far_d_nodes = 100);
// Environment and Connection must be locked when called
*/
void HandlePlayerDeath(PlayerSAO* sao, const PlayerHPChangeReason &reason);
- void RespawnPlayer(session_t peer_id);
void DeleteClient(session_t peer_id, ClientDeletionReason reason);
void UpdateCrafting(RemotePlayer *player);
bool checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what);
if (result.did_punch) {
setHP((s32)getHP() - result.damage,
PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
-
- // create message and add to list
- sendPunchCommand();
}
}
- if (getHP() == 0 && !isGone()) {
- clearParentAttachment();
- clearChildAttachments();
- m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
- markForRemoval();
- }
-
actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
", hp=" << puncher->getHP() << ") punched " <<
getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
{
m_hp = rangelim(hp, 0, U16_MAX);
+
+ sendPunchCommand();
+
+ if (m_hp == 0 && !isGone()) {
+ clearParentAttachment();
+ clearChildAttachments();
+ if (m_registered) {
+ ServerActiveObject *killer = nullptr;
+ if (reason.type == PlayerHPChangeReason::PLAYER_PUNCH)
+ killer = reason.object;
+ m_env->getScriptIface()->luaentity_on_death(m_id, killer);
+ }
+ markForRemoval();
+ }
}
u16 LuaEntitySAO::getHP() const
{
}
~LuaEntitySAO();
+
ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_LUAENTITY; }
ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; }
virtual void addedToEnvironment(u32 dtime_s);
void step(float dtime, bool send_recommended);
std::string getClientInitializationData(u16 protocol_version);
+
bool isStaticAllowed() const { return m_prop.static_save; }
bool shouldUnload() const { return true; }
void getStaticData(std::string *result) const;
+
u32 punch(v3f dir, const ToolCapabilities *toolcap = nullptr,
ServerActiveObject *puncher = nullptr,
float time_from_last_punch = 1000000.0f,
u16 initial_wear = 0);
+
void rightClick(ServerActiveObject *clicker);
+
void setPos(const v3f &pos);
void moveTo(v3f pos, bool continuous);
float getMinimumSavedMovement();
+
std::string getDescription();
+
void setHP(s32 hp, const PlayerHPChangeReason &reason);
u16 getHP() const;
/*
Update m_list
*/
- m_list.clear();
- for (v3s16 p : newlist) {
- m_list.insert(p);
- }
+ m_list = std::move(newlist);
}
/*
/* Add object to environment */
addActiveObject(playersao);
+ // Update active blocks asap so objects in those blocks appear on the client
+ m_force_update_active_blocks = true;
+
return playersao;
}
/*
Manage active block list
*/
- if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
+ if (m_active_blocks_mgmt_interval.step(dtime, m_cache_active_block_mgmt_interval) ||
+ m_force_update_active_blocks) {
ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
+
/*
Get player block positions
*/
std::vector<PlayerSAO*> players;
- for (RemotePlayer *player: m_players) {
+ players.reserve(m_players.size());
+ for (RemotePlayer *player : m_players) {
// Ignore disconnected players
if (player->getPeerId() == PEER_ID_INEXISTENT)
continue;
m_active_blocks.update(players, active_block_range, active_object_range,
blocks_removed, blocks_added);
- m_active_block_gauge->set(m_active_blocks.size());
-
/*
Handle removed blocks
*/
for (const v3s16 &p: blocks_added) {
MapBlock *block = m_map->getBlockOrEmerge(p);
if (!block) {
- m_active_blocks.m_list.erase(p);
- m_active_blocks.m_abm_list.erase(p);
+ // TODO: The blocks removed here will only be picked up again
+ // on the next cycle. To minimize the latency of objects being
+ // activated we could remember the blocks pending activating
+ // and activate them instantly as soon as they're loaded.
+ m_active_blocks.remove(p);
continue;
}
activateBlock(block);
}
+
+ // Some blocks may be removed again by the code above so do this here
+ m_active_block_gauge->set(m_active_blocks.size());
}
+ m_force_update_active_blocks = false;
/*
Mess around in active blocks
m_list.clear();
}
+ void remove(v3s16 p) {
+ m_list.erase(p);
+ m_abm_list.erase(p);
+ }
+
std::set<v3s16> m_list;
std::set<v3s16> m_abm_list;
+ // list of blocks that are always active, not modified by this class
std::set<v3s16> m_forceloaded_list;
};
IntervalLimiter m_object_management_interval;
// List of active blocks
ActiveBlockList m_active_blocks;
- IntervalLimiter m_active_blocks_management_interval;
+ bool m_force_update_active_blocks = false;
+ IntervalLimiter m_active_blocks_mgmt_interval;
IntervalLimiter m_active_block_modifier_interval;
IntervalLimiter m_active_blocks_nodemetadata_interval;
// Whether the variables below have been read from file yet
if [[ "$1" == "--no-irr" ]]; then
shift
- elif [[ "$1" == "--old-irr" ]]; then
- shift
- pkgs+=(libirrlicht-dev)
else
wget "https://github.com/minetest/irrlicht/releases/download/1.9.0mt6/ubuntu-bionic.tar.gz"
sudo tar -xaf ubuntu-bionic.tar.gz -C /usr/local