This commit is contained in:
Bruno Rybársky 2024-02-27 13:48:48 +01:00
commit c990427d07
271 changed files with 5403 additions and 2290 deletions

@ -15,11 +15,10 @@ body:
label: Minetest version
description: |
Paste the Minetest version below.
If you are on a devel version, please add a git commit hash.
You can use `minetest --version` to find it.
You can also refer to the "About" tab of the menu.
If you are on a dev version, please also indicate the git commit hash.
Refer to the "About" tab of the menu or run `minetest --version` on the command line.
placeholder: |
Example:
Example:
Minetest 5.7.0-dev-ca13c51 (Linux)
Using Irrlicht 1.9.0mt9
Using LuaJIT 2.1.0-beta3
@ -33,59 +32,53 @@ body:
render: "true"
validations:
required: true
- type: input
attributes:
label: Active renderer
description: For graphical and input-related issues. You can find these in the About tab in the mainmenu.
placeholder: "Example: OpenGL 4.6.0"
validations:
required: false
- type: input
attributes:
label: Irrlicht device
description:
description:
placeholder: "Example: X11"
validations:
required: false
- type: input
attributes:
label: Operating system and version
description: It is recommended to upgrade your operating system to see if the problem still exists.
description: It is recommended to upgrade your operating system to see if the problem persists.
placeholder: "Example: Ubuntu 22.04"
validations:
required: true
- type: input
attributes:
label: CPU model
description: Usually found in system settings.
placeholder: "Example: Intel i5-2410M (4) @ 2.900GHz"
description: Usually found in OS/system settings.
placeholder: "Example: Intel Core i5-2410M"
validations:
required: false
- type: markdown
attributes:
value: The GPU model and OpenGL version can be omitted if the bug is not a graphical issue.
value: The GPU model and renderer can be omitted if the bug is not a graphical issue.
- type: input
attributes:
label: GPU model
description: Usually found in system settings.
placeholder: "Example: NVIDA GeForce RTX 4090"
description: Usually found in OS/system settings.
placeholder: "Example: NVIDIA GeForce GTX 1660"
validations:
required: false
- type: input
attributes:
label: OpenGL version
placeholder: "Example: 4.6"
label: Active renderer
description: You can find this in the "About" tab in the main menu.
placeholder: "Example: OpenGL 4.6.0"
validations:
required: false
- type: textarea
attributes:
attributes:
label: Summary
description: Describe your problem here.
validations:
required: true
- type: textarea
attributes:
attributes:
label: Steps to reproduce
description: Explain how the problem has happened, providing a minimal test (i.e. a code snippet reduced to the bone) where possible.
description: Explain how the problem has happened, providing a minimal test (e.g. a minimized code snippet) where possible.
validations:
required: true

@ -27,7 +27,7 @@ jobs:
build:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install deps
run: |
sudo apt-get update
@ -35,22 +35,22 @@ jobs:
- name: Build with Gradle
run: cd android; ./gradlew assemblerelease
- name: Save armeabi artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Minetest-armeabi-v7a.apk
path: android/app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned.apk
- name: Save arm64 artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Minetest-arm64-v8a.apk
path: android/app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
- name: Save x86 artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Minetest-x86.apk
path: android/app/build/outputs/apk/release/app-x86-release-unsigned.apk
- name: Save x86_64 artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Minetest-x86_64.apk
path: android/app/build/outputs/apk/release/app-x86_64-release-unsigned.apk

@ -30,7 +30,7 @@ jobs:
clang_tidy:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install deps
run: |
source ./util/ci/common.sh

@ -37,7 +37,7 @@ jobs:
gcc_7:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install deps
run: |
source ./util/ci/common.sh
@ -58,7 +58,7 @@ jobs:
gcc_12:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install deps
run: |
source ./util/ci/common.sh
@ -82,11 +82,11 @@ jobs:
clang_7:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-7 valgrind
install_linux_deps clang-7 llvm
- name: Build
run: |
@ -94,20 +94,17 @@ jobs:
env:
CC: clang-7
CXX: clang++-7
CMAKE_FLAGS: '-DCMAKE_C_FLAGS="-fsanitize=address" -DCMAKE_CXX_FLAGS="-fsanitize=address"'
- name: Unittest
run: |
./bin/minetest --run-unittests
- name: Valgrind
run: |
valgrind --leak-check=full --leak-check-heuristics=all --undef-value-errors=no --error-exitcode=9 ./bin/minetest --run-unittests
# Current clang version
clang_14:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install deps
run: |
source ./util/ci/common.sh
@ -133,7 +130,7 @@ jobs:
name: "clang_9 (PROMETHEUS=1)"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install deps
run: |
source ./util/ci/common.sh
@ -159,7 +156,7 @@ jobs:
name: "Docker image"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build docker image
run: |
docker build . -t minetest:latest

@ -19,7 +19,7 @@ jobs:
name: "Compile and run multiplayer tests"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install deps
run: |
source ./util/ci/common.sh
@ -43,11 +43,11 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: leafo/gh-actions-lua@v9
- uses: actions/checkout@v4
- uses: leafo/gh-actions-lua@v10
with:
luaVersion: "5.1.5"
- uses: leafo/gh-actions-luarocks@v4
- uses: leafo/gh-actions-luarocks@v4.3.0
- name: Install LuaJIT
run: |

@ -19,10 +19,10 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.11
@ -36,13 +36,13 @@ jobs:
./build.sh
- name: Setup Pages
uses: actions/configure-pages@v3
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
uses: actions/upload-pages-artifact@v3
with:
path: 'public/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
uses: actions/deploy-pages@v4

@ -25,7 +25,7 @@ jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install deps
run: |
source ./util/ci/common.sh
@ -56,7 +56,7 @@ jobs:
cd build
cpack -G ZIP -B macos
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: minetest-macos
path: ./build/macos/*.zip

@ -32,17 +32,17 @@ jobs:
name: "MinGW cross-compiler (32-bit)"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install compiler
run: |
sudo apt-get update && sudo apt-get install -y gettext
sudo ./util/buildbot/download_toolchain.sh i686 /usr
sudo ./util/buildbot/download_toolchain.sh /usr
- name: Build
run: |
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh B
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: mingw32
path: B/build/*.zip
@ -52,17 +52,17 @@ jobs:
name: "MinGW cross-compiler (64-bit)"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install compiler
run: |
sudo apt-get update && sudo apt-get install -y gettext
sudo ./util/buildbot/download_toolchain.sh x86_64 /usr
sudo ./util/buildbot/download_toolchain.sh /usr
- name: Build
run: |
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh B
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: mingw64
path: B/build/*.zip
@ -74,7 +74,7 @@ jobs:
env:
VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50
# 2023.10.19
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry sdl2
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp sdl2
strategy:
fail-fast: false
matrix:
@ -95,7 +95,7 @@ jobs:
# Enable it, when working on the installer.
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Checkout IrrlichtMt
run: |
@ -145,7 +145,7 @@ jobs:
env:
TYPE: ${{matrix.type}}
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
path: .\Package\

3
.gitignore vendored

@ -92,11 +92,8 @@ cmake_install.cmake
CMakeCache.txt
CPackConfig.cmake
CPackSourceConfig.cmake
src/test_config.h
src/cmake_config.h
src/cmake_config_githash.h
src/unittest/test_world/world.mt
games/devtest/mods/testnodes/textures/testnodes_generated_*.png
/locale/
.directory
*.cbp

@ -17,6 +17,7 @@ read_globals = {
"VoxelArea",
"profiler",
"Settings",
"PerlinNoise", "PerlinNoiseMap",
string = {fields = {"split", "trim"}},
table = {fields = {"copy", "getn", "indexof", "insert_all"}},
@ -70,7 +71,6 @@ files["builtin/mainmenu"] = {
read_globals = {
"PLATFORM",
"TOUCHSCREEN_GUI",
},
}
@ -81,9 +81,3 @@ files["builtin/common/tests"] = {
"assert",
},
}
files["builtin/fstk"] = {
read_globals = {
"TOUCHSCREEN_GUI",
},
}

@ -1,11 +1,4 @@
cmake_minimum_required(VERSION 3.5)
# Set policies up to 3.9 since we want to enable the IPO option
if(${CMAKE_VERSION} VERSION_LESS 3.9)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.9)
endif()
cmake_minimum_required(VERSION 3.12)
# This can be read from ${PROJECT_NAME} after project() is called
project(minetest)
@ -44,6 +37,25 @@ set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests")
set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks")
set(BUILD_DOCUMENTATION TRUE CACHE BOOL "Build documentation")
set(DEFAULT_ENABLE_LTO TRUE)
# by default don't enable on Debug builds to get faster builds
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DEFAULT_ENABLE_LTO FALSE)
endif()
#### LTO testing list ####
# - Linux: seems to work always
# - win32/msvc: works
# - win32/gcc: fails to link
# - win32/clang: works
# - macOS on x86: seems to be fine
# - macOS on ARM: crashes, see <https://github.com/minetest/minetest/issues/14397>
# Note: since CMake has no easy architecture detection disabling for Mac entirely
#### ####
if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE)
set(DEFAULT_ENABLE_LTO FALSE)
endif()
set(ENABLE_LTO ${DEFAULT_ENABLE_LTO} CACHE BOOL "Use Link Time Optimization")
set(DEFAULT_RUN_IN_PLACE FALSE)
if(WIN32)
set(DEFAULT_RUN_IN_PLACE TRUE)
@ -140,7 +152,7 @@ elseif(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt)
endif()
message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
set(TARGET_VER_S 1.9.0mt14)
set(TARGET_VER_S 1.9.0mt15)
string(REPLACE "mt" "." TARGET_VER ${TARGET_VER_S})
if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER})
message(FATAL_ERROR "At least IrrlichtMt ${TARGET_VER_S} is required to build")
@ -149,6 +161,20 @@ elseif(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt)
endif()
endif()
if (ENABLE_LTO OR CMAKE_INTERPROCEDURAL_OPTIMIZATION)
include(CheckIPOSupported)
check_ipo_supported(RESULT lto_supported OUTPUT lto_output)
if(lto_supported)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
message(STATUS "LTO/IPO is enabled")
else()
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION FALSE)
message(STATUS "LTO/IPO was requested but is not supported by the compiler: ${lto_output}")
endif()
else()
message(STATUS "LTO/IPO is not enabled")
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}")
message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_VERSION}. "

@ -1,8 +1,8 @@
ARG DOCKER_IMAGE=alpine:3.16
ARG DOCKER_IMAGE=alpine:3.19
FROM $DOCKER_IMAGE AS dev
ENV IRRLICHT_VERSION master
ENV SPATIALINDEX_VERSION 1.9.3
ENV SPATIALINDEX_VERSION master
ENV LUAJIT_VERSION v2.1
RUN apk add --no-cache git build-base cmake curl-dev zlib-dev zstd-dev \
@ -10,7 +10,7 @@ RUN apk add --no-cache git build-base cmake curl-dev zlib-dev zstd-dev \
gmp-dev jsoncpp-dev ninja ca-certificates
WORKDIR /usr/src/
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp && \
cd prometheus-cpp && \
cmake -B build \
-DCMAKE_INSTALL_PREFIX=/usr/local \
@ -29,9 +29,9 @@ RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
cd /usr/src/ && \
git clone --recursive https://luajit.org/git/luajit.git -b ${LUAJIT_VERSION} && \
cd luajit && \
make && make install && \
make amalg && make install && \
cd /usr/src/ && \
git clone --depth=1 https://github.com/minetest/irrlicht/ -b ${IRRLICHT_VERSION} && \
git clone --depth=1 https://github.com/minetest/irrlicht -b ${IRRLICHT_VERSION} && \
cp -r irrlicht/include /usr/include/irrlichtmt
FROM dev as builder
@ -56,13 +56,12 @@ RUN cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SERVER=TRUE \
-DENABLE_PROMETHEUS=TRUE \
-DBUILD_UNITTESTS=FALSE \
-DBUILD_UNITTESTS=FALSE -DBUILD_BENCHMARKS=FALSE \
-DBUILD_CLIENT=FALSE \
-GNinja && \
cmake --build build && \
cmake --install build
ARG DOCKER_IMAGE=alpine:3.16
FROM $DOCKER_IMAGE AS runtime
RUN apk add --no-cache curl gmp libstdc++ libgcc libpq jsoncpp zstd-libs \

@ -7,6 +7,7 @@ dofile(clientpath .. "register.lua")
dofile(commonpath .. "after.lua")
dofile(commonpath .. "mod_storage.lua")
dofile(commonpath .. "chatcommands.lua")
dofile(commonpath .. "information_formspecs.lua")
dofile(clientpath .. "chatcommands.lua")
dofile(clientpath .. "death_formspec.lua")
dofile(clientpath .. "misc.lua")

@ -89,7 +89,7 @@ local function do_help_cmd(name, param)
if #args > 1 then
return false, S("Too many arguments, try using just /help <command>")
end
local use_gui = INIT ~= "client" and core.get_player_by_name(name)
local use_gui = INIT == "client" or core.get_player_by_name(name)
use_gui = use_gui and not opts:find("t")
if #args == 0 and not use_gui then
@ -163,8 +163,8 @@ end
if INIT == "client" then
core.register_chatcommand("help", {
params = core.gettext("[all | <cmd>]"),
description = core.gettext("Get help for commands"),
params = core.gettext("[all | <cmd>] [-t]"),
description = core.gettext("Get help for commands (-t: output in chat)"),
func = function(param)
return do_help_cmd(nil, param)
end,

@ -61,15 +61,20 @@ local function build_chatcommands_formspec(name, sel, copy)
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 = INIT == "client" or check_player_privs(name, 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))
if sel == #rows then
description = cmds[2].description
if copy then
core.chat_send_player(name, S("Command: @1 @2",
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params))
local msg = S("Command: @1 @2",
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params)
if INIT == "client" then
core.display_chat_message(msg)
else
core.chat_send_player(name, msg)
end
end
end
end
@ -111,26 +116,46 @@ end
-- DETAILED CHAT COMMAND INFORMATION
if INIT == "client" then
core.register_on_formspec_input(function(formname, fields)
if formname ~= "__builtin:help_cmds" or fields.quit then
return
end
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "__builtin:help_cmds" or fields.quit then
return
end
local event = core.explode_table_event(fields.list)
if event.type ~= "INV" then
core.show_formspec("__builtin:help_cmds",
build_chatcommands_formspec(nil, event.row, event.type == "DCL"))
end
end)
else
core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "__builtin:help_cmds" or fields.quit then
return
end
local event = core.explode_table_event(fields.list)
if event.type ~= "INV" then
local name = player:get_player_name()
core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name, event.row, event.type == "DCL"))
end
end)
local event = core.explode_table_event(fields.list)
if event.type ~= "INV" then
local name = player:get_player_name()
core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name, event.row, event.type == "DCL"))
end
end)
end
function core.show_general_help_formspec(name)
core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name))
if INIT == "client" then
core.show_formspec("__builtin:help_cmds",
build_chatcommands_formspec(name))
else
core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name))
end
end
function core.show_privs_help_formspec(name)
core.show_formspec(name, "__builtin:help_privs",
build_privs_formspec(name))
if INIT ~= "client" then
function core.show_privs_help_formspec(name)
core.show_formspec(name, "__builtin:help_privs",
build_privs_formspec(name))
end
end

61
builtin/emerge/env.lua Normal file

@ -0,0 +1,61 @@
-- Reimplementations of some environment function on vmanips, since this is
-- what the emerge environment operates on
-- core.vmanip = <VoxelManip> -- set by C++
function core.set_node(pos, node)
return core.vmanip:set_node_at(pos, node)
end
function core.bulk_set_node(pos_list, node)
local vm = core.vmanip
local set_node_at = vm.set_node_at
for _, pos in ipairs(pos_list) do
if not set_node_at(vm, pos, node) then
return false
end
end
return true
end
core.add_node = core.set_node
-- we don't deal with metadata currently
core.swap_node = core.set_node
function core.remove_node(pos)
return core.vmanip:set_node_at(pos, {name="air"})
end
function core.get_node(pos)
return core.vmanip:get_node_at(pos)
end
function core.get_node_or_nil(pos)
local node = core.vmanip:get_node_at(pos)
return node.name ~= "ignore" and node
end
function core.get_perlin(seed, octaves, persist, spread)
local params
if type(seed) == "table" then
params = table.copy(seed)
else
assert(type(seed) == "number")
params = {
seed = seed,
octaves = octaves,
persist = persist,
spread = {x=spread, y=spread, z=spread},
}
end
params.seed = core.get_seed(params.seed) -- add mapgen seed
return PerlinNoise(params)
end
function core.get_perlin_map(params, size)
local params2 = table.copy(params)
params2.seed = core.get_seed(params.seed) -- add mapgen seed
return PerlinNoiseMap(params2, size)
end

21
builtin/emerge/init.lua Normal file

@ -0,0 +1,21 @@
local gamepath = core.get_builtin_path() .. "game" .. DIR_DELIM
local commonpath = core.get_builtin_path() .. "common" .. DIR_DELIM
local epath = core.get_builtin_path() .. "emerge" .. DIR_DELIM
local builtin_shared = {}
-- Import parts shared with "game" environment
dofile(gamepath .. "constants.lua")
assert(loadfile(commonpath .. "item_s.lua"))(builtin_shared)
dofile(gamepath .. "misc_s.lua")
dofile(gamepath .. "features.lua")
dofile(gamepath .. "voxelarea.lua")
-- Now for our own stuff
assert(loadfile(commonpath .. "register.lua"))(builtin_shared)
assert(loadfile(epath .. "register.lua"))(builtin_shared)
dofile(epath .. "env.lua")
builtin_shared.cache_content_ids()
core.log("info", "Initialized emerge Lua environment")

@ -0,0 +1,54 @@
local builtin_shared = ...
-- Copy all the registration tables over
do
local all = assert(core.transferred_globals)
core.transferred_globals = nil
all.registered_nodes = {}
all.registered_craftitems = {}
all.registered_tools = {}
for k, v in pairs(all.registered_items) do
-- Disable further modification
setmetatable(v, {__newindex = {}})
-- Reassemble the other tables
if v.type == "node" then
getmetatable(v).__index = all.nodedef_default
all.registered_nodes[k] = v
elseif v.type == "craft" then
getmetatable(v).__index = all.craftitemdef_default
all.registered_craftitems[k] = v
elseif v.type == "tool" then
getmetatable(v).__index = all.tooldef_default
all.registered_tools[k] = v
else
getmetatable(v).__index = all.noneitemdef_default
end
end
for k, v in pairs(all) do
core[k] = v
end
end
-- For tables that are indexed by item name:
-- If table[X] does not exist, default to table[core.registered_aliases[X]]
local alias_metatable = {
__index = function(t, name)
return rawget(t, core.registered_aliases[name])
end
}
setmetatable(core.registered_items, alias_metatable)
setmetatable(core.registered_nodes, alias_metatable)
setmetatable(core.registered_craftitems, alias_metatable)
setmetatable(core.registered_tools, alias_metatable)
--
-- Callbacks
--
local make_registration = builtin_shared.make_registration
core.registered_on_mods_loaded, core.register_on_mods_loaded = make_registration()
core.registered_on_generateds, core.register_on_generated = make_registration()
core.registered_on_shutdown, core.register_on_shutdown = make_registration()

@ -18,7 +18,9 @@
local BASE_SPACING = 0.1
local SCROLL_BTN_WIDTH = TOUCHSCREEN_GUI and 0.8 or 0.5
local function get_scroll_btn_width()
return core.settings:get_bool("enable_touch") and 0.8 or 0.5
end
local function buttonbar_formspec(self)
if self.hidden then
@ -39,7 +41,7 @@ local function buttonbar_formspec(self)
-- The number of buttons per page is always calculated as if the scroll
-- buttons were visible.
local avail_space = self.size.x - 2*BASE_SPACING - 2*SCROLL_BTN_WIDTH
local avail_space = self.size.x - 2*BASE_SPACING - 2*get_scroll_btn_width()
local btns_per_page = math.floor((avail_space - BASE_SPACING) / (btn_size + BASE_SPACING))
self.num_pages = math.ceil(#self.buttons / btns_per_page)
@ -55,7 +57,7 @@ local function buttonbar_formspec(self)
local btn_start_x = self.pos.x + btn_spacing
if show_scroll_btns then
btn_start_x = btn_start_x + BASE_SPACING + SCROLL_BTN_WIDTH
btn_start_x = btn_start_x + BASE_SPACING + get_scroll_btn_width()
end
for i = first_btn, first_btn + btns_per_page - 1 do
@ -80,7 +82,7 @@ local function buttonbar_formspec(self)
y = self.pos.y + BASE_SPACING,
}
local btn_next_pos = {
x = self.pos.x + self.size.x - BASE_SPACING - SCROLL_BTN_WIDTH,
x = self.pos.x + self.size.x - BASE_SPACING - get_scroll_btn_width(),
y = self.pos.y + BASE_SPACING,
}
@ -88,11 +90,11 @@ local function buttonbar_formspec(self)
self.btn_prev_name, self.btn_next_name))
table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;<]",
btn_prev_pos.x, btn_prev_pos.y, SCROLL_BTN_WIDTH, btn_size,
btn_prev_pos.x, btn_prev_pos.y, get_scroll_btn_width(), btn_size,
self.btn_prev_name))
table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;>]",
btn_next_pos.x, btn_next_pos.y, SCROLL_BTN_WIDTH, btn_size,
btn_next_pos.x, btn_next_pos.y, get_scroll_btn_width(), btn_size,
self.btn_next_name))
end

@ -79,6 +79,9 @@ core.register_entity(":__builtin:falling_node", {
-- Cache whether we're supposed to float on water
self.floats = core.get_item_group(node.name, "float") ~= 0
-- Save liquidtype for falling water
self.liquidtype = def.liquidtype
-- Set entity visuals
if def.drawtype == "torchlike" or def.drawtype == "signlike" then
local textures
@ -294,9 +297,17 @@ core.register_entity(":__builtin:falling_node", {
end
-- Decide if we're replacing the node or placing on top
-- This condition is very similar to the check in core.check_single_for_falling(p)
local np = vector.copy(bcp)
if bcd and bcd.buildable_to and
(not self.floats or bcd.liquidtype == "none") then
if bcd and bcd.buildable_to
and -- Take "float" group into consideration:
(
-- Fall through non-liquids
not self.floats or bcd.liquidtype == "none" or
-- Only let sources fall through flowing liquids
(self.floats and self.liquidtype ~= "none" and bcd.liquidtype ~= "source")
) then
core.remove_node(bcp)
else
np.y = np.y + 1
@ -307,7 +318,7 @@ core.register_entity(":__builtin:falling_node", {
local nd = core.registered_nodes[n2.name]
-- If it's not air or liquid, remove node and replace it with
-- it's drops
if n2.name ~= "air" and (not nd or nd.liquidtype == "none") then
if n2.name ~= "air" and (not nd or nd.liquidtype ~= "source") then
if nd and nd.buildable_to == false then
nd.on_dig(np, n2, nil)
-- If it's still there, it might be protected
@ -555,11 +566,19 @@ function core.check_single_for_falling(p)
local success, _ = convert_to_falling_node(p, n)
return success
end
local d_falling = core.registered_nodes[n.name]
local do_float = core.get_item_group(n.name, "float") > 0
-- Otherwise only if the bottom node is considered "fall through"
if not same and
(not d_bottom.walkable or d_bottom.buildable_to) and
(core.get_item_group(n.name, "float") == 0 or
d_bottom.liquidtype == "none") then
(not d_bottom.walkable or d_bottom.buildable_to)
and -- Take "float" group into consideration:
(
-- Fall through non-liquids
not do_float or d_bottom.liquidtype == "none" or
-- Only let sources fall through flowing liquids
(do_float and d_falling.liquidtype == "source" and d_bottom.liquidtype ~= "source")
) then
local success, _ = convert_to_falling_node(p, n)
return success
end

@ -35,6 +35,8 @@ core.features = {
wallmounted_rotate = true,
item_specific_pointabilities = true,
blocking_pointability_type = true,
dynamic_add_media_startup = true,
dynamic_add_media_filepath = true,
}
function core.has_feature(arg)

@ -237,8 +237,8 @@ end
core.dynamic_media_callbacks = {}
-- Transfer of certain globals into async environment
-- see builtin/async/game.lua for the other side
-- Transfer of certain globals into seconday Lua environments
-- see builtin/async/game.lua or builtin/emerge/register.lua for the unpacking
local function copy_filtering(t, seen)
if type(t) == "userdata" or type(t) == "function" then
@ -261,6 +261,9 @@ function core.get_globals_to_transfer()
local all = {
registered_items = copy_filtering(core.registered_items),
registered_aliases = core.registered_aliases,
registered_biomes = core.registered_biomes,
registered_ores = core.registered_ores,
registered_decorations = core.registered_decorations,
nodedef_default = copy_filtering(core.nodedef_default),
craftitemdef_default = copy_filtering(core.craftitemdef_default),

@ -24,6 +24,13 @@ local bar_definitions = {
size = {x = 24, y = 24},
offset = {x = 25, y= -(48 + 24 + 16)},
},
minimap = {
type = "minimap",
position = {x = 1, y = 0},
alignment = {x = -1, y = 1},
offset = {x = -10, y = 10},
size = {x = 256 , y = 256},
},
}
local hud_ids = {}
@ -92,6 +99,16 @@ local function update_builtin_statbars(player)
end, name, hud.id_breathbar)
hud.id_breathbar = nil
end
-- Don't add a minimap for clients which already have it hardcoded in C++.
local show_minimap = flags.minimap and
minetest.get_player_information(name).protocol_version >= 44
if show_minimap and not hud.id_minimap then
hud.id_minimap = player:hud_add(bar_definitions.minimap)
elseif not show_minimap and hud.id_minimap then
player:hud_remove(hud.id_minimap)
hud.id_minimap = nil
end
end
local function cleanup_builtin_statbars(player)
@ -138,8 +155,7 @@ local function player_event_handler(player,eventname)
end
function core.hud_replace_builtin(hud_name, definition)
if type(definition) ~= "table" or
(definition.type or definition.hud_elem_type) ~= "statbar" then
if type(definition) ~= "table" then
return false
end
@ -175,6 +191,20 @@ function core.hud_replace_builtin(hud_name, definition)
return true
end
if hud_name == "minimap" then
bar_definitions.minimap = definition
for name, ids in pairs(hud_ids) do
local player = core.get_player_by_name(name)
if player and ids.id_minimap then
player:hud_remove(ids.id_minimap)
ids.id_minimap = nil
update_builtin_statbars(player)
end
end
return true
end
return false
end

@ -31,8 +31,6 @@ minetest = core
-- Load other files
local scriptdir = core.get_builtin_path()
local gamepath = scriptdir .. "game" .. DIR_DELIM
local clientpath = scriptdir .. "client" .. DIR_DELIM
local commonpath = scriptdir .. "common" .. DIR_DELIM
local asyncpath = scriptdir .. "async" .. DIR_DELIM
@ -42,7 +40,7 @@ dofile(commonpath .. "serialize.lua")
dofile(commonpath .. "misc_helpers.lua")
if INIT == "game" then
dofile(gamepath .. "init.lua")
dofile(scriptdir .. "game" .. DIR_DELIM .. "init.lua")
assert(not core.get_http_api)
elseif INIT == "mainmenu" then
local mm_script = core.settings:get("main_menu_script")
@ -67,7 +65,9 @@ elseif INIT == "async" then
elseif INIT == "async_game" then
dofile(asyncpath .. "game.lua")
elseif INIT == "client" then
dofile(clientpath .. "init.lua")
dofile(scriptdir .. "client" .. DIR_DELIM .. "init.lua")
elseif INIT == "emerge" then
dofile(scriptdir .. "emerge" .. DIR_DELIM .. "init.lua")
else
error(("Unrecognized builtin initialization type %s!"):format(tostring(INIT)))
end

@ -154,7 +154,9 @@ local function start_install(package, reason)
if conf_path then
local conf = Settings(conf_path)
conf:set("title", package.title)
if not conf:get("title") then
conf:set("title", package.title)
end
if not name_is_title then
conf:set("name", package.name)
end
@ -642,8 +644,21 @@ local function fetch_pkgs()
end
end
local languages
local current_language = core.get_language()
if current_language ~= "" then
languages = { current_language, "en;q=0.8" }
else
languages = { "en" }
end
local http = core.get_http_api()
local response = http.fetch_sync({ url = url })
local response = http.fetch_sync({
url = url,
extra_headers = {
"Accept-Language: " .. table.concat(languages, ", ")
},
})
if not response.succeeded then
return
end
@ -898,7 +913,7 @@ local function get_info_formspec(text)
return table.concat({
"formspec_version[6]",
"size[15.75,9.5]",
TOUCHSCREEN_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]",
core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]",
"label[4,4.35;", text, "]",
"container[0,", H - 0.8 - 0.375, "]",
@ -928,7 +943,7 @@ function store.get_formspec(dlgdata)
local formspec = {
"formspec_version[6]",
"size[15.75,9.5]",
TOUCHSCREEN_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]",
core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]",
"style[status,downloading,queued;border=false]",
@ -1175,8 +1190,8 @@ end
function store.handle_events(event)
if event == "DialogShow" then
-- On mobile, don't show the "MINETEST" header behind the dialog.
mm_game_theme.set_engine(TOUCHSCREEN_GUI)
-- On touchscreen, don't show the "MINETEST" header behind the dialog.
mm_game_theme.set_engine(core.settings:get_bool("enable_touch"))
-- If the store is already loaded, auto-install packages here.
do_auto_install()

@ -150,6 +150,8 @@ function pkgmgr.get_mods(path, virtual_path, listing, modpack)
toadd.virtual_path = mod_virtual_path
toadd.type = "mod"
pkgmgr.update_translations({ toadd })
-- Check modpack.txt
-- Note: modpack.conf is already checked above
local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt")
@ -189,6 +191,8 @@ function pkgmgr.get_texture_packs()
load_texture_packs(txtpath_system, retval)
end
pkgmgr.update_translations(retval)
table.sort(retval, function(a, b)
return a.title:lower() < b.title:lower()
end)
@ -775,6 +779,29 @@ function pkgmgr.update_gamelist()
table.sort(pkgmgr.games, function(a, b)
return a.title:lower() < b.title:lower()
end)
pkgmgr.update_translations(pkgmgr.games)
end
--------------------------------------------------------------------------------
function pkgmgr.update_translations(list)
for _, item in ipairs(list) do
local info = core.get_content_info(item.path)
assert(info.path)
assert(info.textdomain)
assert(not item.is_translated)
item.is_translated = true
if info.title and info.title ~= "" then
item.title = core.get_content_translation(info.path, info.textdomain,
core.translate(info.textdomain, info.title))
end
if info.description and info.description ~= "" then
item.description = core.get_content_translation(info.path, info.textdomain,
core.translate(info.textdomain, info.description))
end
end
end
--------------------------------------------------------------------------------

@ -70,6 +70,8 @@ local flag_checkboxes = {
{ "trees", fgettext("Trees and jungle grass") },
{ "flat", fgettext("Flat terrain") },
{ "mudflow", fgettext("Mud flow"), fgettext("Terrain surface erosion") },
{ "temples", fgettext("Desert temples"),
fgettext("Different dungeon variant generated in desert biomes (only if dungeons enabled)") },
-- Biome settings are in mgv6_biomes below
},
}
@ -279,7 +281,7 @@ local function create_world_formspec(dialogdata)
end
local retval =
"size[12.25,7,true]" ..
"size[12.25,7.4,true]" ..
-- Left side
"container[0,0]"..
@ -321,8 +323,10 @@ local function create_world_formspec(dialogdata)
"container_end[]"..
-- Menu buttons
"button[3.25,6.5;3,0.5;world_create_confirm;" .. fgettext("Create") .. "]" ..
"button[6.25,6.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]"
"container[0,6.9]"..
"button[3.25,0;3,0.5;world_create_confirm;" .. fgettext("Create") .. "]" ..
"button[6.25,0;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" ..
"container_end[]"
return retval

@ -316,8 +316,8 @@ local function check_requirements(name, requires)
local special = {
android = PLATFORM == "Android",
desktop = PLATFORM ~= "Android",
touchscreen_gui = TOUCHSCREEN_GUI,
keyboard_mouse = not TOUCHSCREEN_GUI,
touchscreen_gui = core.settings:get_bool("enable_touch"),
keyboard_mouse = not core.settings:get_bool("enable_touch"),
shaders_support = shaders_support,
shaders = core.settings:get_bool("enable_shaders") and shaders_support,
opengl = video_driver == "opengl",
@ -449,13 +449,14 @@ local function get_formspec(dialogdata)
local extra_h = 1 -- not included in tabsize.height
local tabsize = {
width = TOUCHSCREEN_GUI and 16.5 or 15.5,
height = TOUCHSCREEN_GUI and (10 - extra_h) or 12,
width = core.settings:get_bool("enable_touch") and 16.5 or 15.5,
height = core.settings:get_bool("enable_touch") and (10 - extra_h) or 12,
}
local scrollbar_w = TOUCHSCREEN_GUI and 0.6 or 0.4
local scrollbar_w = core.settings:get_bool("enable_touch") and 0.6 or 0.4
local left_pane_width = TOUCHSCREEN_GUI and 4.5 or 4.25
local left_pane_width = core.settings:get_bool("enable_touch") and 4.5 or 4.25
local left_pane_padding = 0.25
local search_width = left_pane_width + scrollbar_w - (0.75 * 2)
local back_w = 3
@ -468,7 +469,7 @@ local function get_formspec(dialogdata)
local fs = {
"formspec_version[6]",
"size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]",
TOUCHSCREEN_GUI and "padding[0.01,0.01]" or "",
core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "",
"bgcolor[#0000]",
-- HACK: this is needed to allow resubmitting the same formspec
@ -516,9 +517,9 @@ local function get_formspec(dialogdata)
y = y + 0.82
end
fs[#fs + 1] = ("box[0,%f;%f,0.8;%s]"):format(
y, left_pane_width, other_page.id == page_id and "#467832FF" or "#3339")
y, left_pane_width-left_pane_padding, other_page.id == page_id and "#467832FF" or "#3339")
fs[#fs + 1] = ("button[0,%f;%f,0.8;page_%s;%s]")
:format(y, left_pane_width, other_page.id, fgettext(other_page.title))
:format(y, left_pane_width-left_pane_padding, other_page.id, fgettext(other_page.title))
y = y + 0.82
end
@ -641,11 +642,22 @@ local function buttonhandler(this, fields)
local value = core.is_yes(fields.show_advanced)
core.settings:set_bool("show_advanced", value)
write_settings_early()
end
-- enable_touch is a checkbox in a setting component. We handle this
-- setting differently so we can hide/show pages using the next if-statement
if fields.enable_touch ~= nil then
local value = core.is_yes(fields.enable_touch)
core.settings:set_bool("enable_touch", value)
write_settings_early()
end
if fields.show_advanced ~= nil or fields.enable_touch ~= nil then
local suggested_page_id = update_filtered_pages(dialogdata.query)
dialogdata.components = nil
if not filtered_page_by_id[dialogdata.page_id] then
dialogdata.components = nil
dialogdata.leftscroll = 0
dialogdata.rightscroll = 0

@ -114,12 +114,13 @@ local function get_formspec(tabview, name, tabdata)
modscreenshot = defaulttexturedir .. "no_screenshot.png"
end
local info = core.get_content_info(selected_pkg.path)
local desc = fgettext("No package description available")
if info.description and info.description:trim() ~= "" then
desc = core.formspec_escape(info.description)
if selected_pkg.description and selected_pkg.description:trim() ~= "" then
desc = core.formspec_escape(selected_pkg.description)
end
local info = core.get_content_info(selected_pkg.path)
local title_and_name
if selected_pkg.type == "game" then
title_and_name = selected_pkg.name

@ -94,7 +94,7 @@ function singleplayer_refresh_gamebar()
local btnbar = buttonbar_create(
"game_button_bar",
TOUCHSCREEN_GUI and {x = 0, y = 7.25} or {x = 0, y = 7.475},
core.settings:get_bool("enable_touch") and {x = 0, y = 7.25} or {x = 0, y = 7.475},
{x = 15.5, y = 1.25},
"#000000",
game_buttonbar_button_handler)

@ -168,6 +168,11 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false
[*Touchscreen]
# Enables touchscreen mode, allowing you to play the game with a touchscreen.
#
# Requires: !android
enable_touch (Enable touchscreen) bool true
# The length in pixels it takes for touchscreen interaction to start.
#
# Requires: touchscreen_gui
@ -422,7 +427,8 @@ anisotropic_filter (Anisotropic filtering) bool false
#
# * None - No antialiasing (default)
#
# * FSAA - Hardware-provided full-screen antialiasing (incompatible with shaders)
# * FSAA - Hardware-provided full-screen antialiasing
# (incompatible with Post Processing and Undersampling)
# A.K.A multi-sample antialiasing (MSAA)
# Smoothens out block edges but does not affect the insides of textures.
# A restart is required to change this option.
@ -577,12 +583,17 @@ shadow_sky_body_orbit_tilt (Sky Body Orbit Tilt) float 0.0 -60.0 60.0
[**Post Processing]
# Enables the post processing pipeline.
#
# Requires: shaders
enable_post_processing (Enable Post Processing) bool true
# Enables Hable's 'Uncharted 2' filmic tone mapping.
# Simulates the tone curve of photographic film and how this approximates the
# appearance of high dynamic range images. Mid-range contrast is slightly
# enhanced, highlights and shadows are gradually compressed.
#
# Requires: shaders
# Requires: shaders, enable_post_processing
tone_mapping (Filmic tone mapping) bool false
# Enable automatic exposure correction
@ -590,14 +601,14 @@ tone_mapping (Filmic tone mapping) bool false
# automatically adjust to the brightness of the scene,
# simulating the behavior of human eye.
#
# Requires: shaders
# Requires: shaders, enable_post_processing
enable_auto_exposure (Enable Automatic Exposure) bool false
# Set the exposure compensation in EV units.
# Value of 0.0 (default) means no exposure compensation.
# Range: from -1 to 1.0
#
# Requires: shaders, enable_auto_exposure
# Requires: shaders, enable_post_processing, enable_auto_exposure
exposure_compensation (Exposure compensation) float 0.0 -1.0 1.0
# Apply dithering to reduce color banding artifacts.
@ -608,7 +619,7 @@ exposure_compensation (Exposure compensation) float 0.0 -1.0 1.0
# With OpenGL ES, dithering only works if the shader supports high
# floating-point precision and it may have a higher performance impact.
#
# Requires: shaders
# Requires: shaders, enable_post_processing
debanding (Enable Debanding) bool true
[**Bloom]
@ -616,7 +627,7 @@ debanding (Enable Debanding) bool true
# Set to true to enable bloom effect.
# Bright colors will bleed over the neighboring objects.
#
# Requires: shaders
# Requires: shaders, enable_post_processing
enable_bloom (Enable Bloom) bool false
# Set to true to render debugging breakdown of the bloom effect.
@ -624,32 +635,32 @@ enable_bloom (Enable Bloom) bool false
# top-left - processed base image, top-right - final image
# bottom-left - raw base image, bottom-right - bloom texture.
#
# Requires: shaders, enable_bloom
# Requires: shaders, enable_post_processing, enable_bloom
enable_bloom_debug (Enable Bloom Debug) bool false
# Defines how much bloom is applied to the rendered image
# Smaller values make bloom more subtle
# Range: from 0.01 to 1.0, default: 0.05
#
# Requires: shaders, enable_bloom
# Requires: shaders, enable_post_processing, enable_bloom
bloom_intensity (Bloom Intensity) float 0.05 0.01 1.0
# Defines the magnitude of bloom overexposure.
# Range: from 0.1 to 10.0, default: 1.0
#
# Requires: shaders, enable_bloom
# Requires: shaders, enable_post_processing, enable_bloom
bloom_strength_factor (Bloom Strength Factor) float 1.0 0.1 10.0
# Logical value that controls how far the bloom effect spreads
# from the bright objects.
# Range: from 0.1 to 8, default: 1
#
# Requires: shaders, enable_bloom
# Requires: shaders, enable_post_processing, enable_bloom
bloom_radius (Bloom Radius) float 1 0.1 8
# Set to true to enable volumetric lighting effect (a.k.a. "Godrays").
#
# Requires: shaders, enable_bloom
# Requires: shaders, enable_post_processing, enable_bloom
enable_volumetric_lighting (Volumetric lighting) bool false
[*Audio]
@ -1089,7 +1100,8 @@ mgv5_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2
# The 'snowbiomes' flag enables the new 5 biome system.
# When the 'snowbiomes' flag is enabled jungles are automatically enabled and
# the 'jungles' flag is ignored.
mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees jungles,biomeblend,mudflow,snowbiomes,flat,trees,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees
# The 'temples' flag disables generation of desert temples. Normal dungeons will appear instead.
mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees,temples jungles,biomeblend,mudflow,snowbiomes,flat,trees,temples,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees,notemples
# Deserts occur when np_biome exceeds this value.
# When the 'snowbiomes' flag is enabled, this is ignored.
@ -2028,9 +2040,10 @@ ask_reconnect_on_crash (Ask to reconnect after crash) bool false
[**Server/Env Performance]
# Length of a server tick and the interval at which objects are generally updated over
# network, stated in seconds.
dedicated_server_step (Dedicated server step) float 0.09 0.0
# Length of a server tick (the interval at which everything is generally updated),
# stated in seconds.
# Does not apply to sessions hosted from the client menu.
dedicated_server_step (Dedicated server step) float 0.09 0.0 1.0
# Whether players are shown to clients without any range limit.
# Deprecated, use the setting player_transfer_distance instead.
@ -2100,8 +2113,7 @@ liquid_update (Liquid update tick) float 1.0 0.001
# At this distance the server will aggressively optimize which blocks are sent to
# clients.
# Small values potentially improve performance a lot, at the expense of visible
# rendering glitches (some blocks will not be rendered under water and in caves,
# as well as sometimes on land).
# rendering glitches (some blocks might not be rendered correctly in caves).
# Setting this to a value greater than max_block_send_distance disables this
# optimization.
# Stated in MapBlocks (16 nodes).

@ -28,6 +28,7 @@ General options and their default values:
ENABLE_REDIS=ON - Build with libhiredis; Enables use of Redis map backend
ENABLE_SPATIAL=ON - Build with LibSpatial; Speeds up AreaStores
ENABLE_SOUND=ON - Build with OpenAL, libogg & libvorbis; in-game sounds
ENABLE_LTO=<varies> - Build with IPO/LTO optimizations (smaller and more efficient than regular build)
ENABLE_LUAJIT=ON - Build with LuaJIT (much faster than non-JIT Lua)
ENABLE_PROMETHEUS=OFF - Build with Prometheus metrics exporter (listens on tcp/30000 by default)
ENABLE_SYSTEM_GMP=ON - Use GMP from system (much faster than bundled mini-gmp)
@ -37,7 +38,7 @@ General options and their default values:
INSTALL_DEVTEST=FALSE - Whether the Development Test game should be installed alongside Minetest
USE_GPROF=FALSE - Enable profiling using GProf
VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
ENABLE_TOUCH=FALSE - Enable Touchscreen support (requires support by IrrlichtMt)
ENABLE_TOUCH=FALSE - Enable touchscreen support by default (requires support by IrrlichtMt)
Library specific options:

@ -8,7 +8,7 @@
Install dependencies with homebrew:
```
brew install cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd gettext
brew install cmake freetype gettext gmp hiredis jpeg-turbo jsoncpp leveldb libogg libpng libvorbis luajit zstd gettext
```
## Download

@ -14,7 +14,7 @@ It is highly recommended to use vcpkg as package manager.
After you successfully built vcpkg you can easily install the required libraries:
```powershell
vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry gettext sdl2 --triplet x64-windows
vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp gettext sdl2 --triplet x64-windows
```
- **Don't forget about IrrlichtMt.** The easiest way is to clone it to `lib/irrlichtmt`:

@ -61,7 +61,8 @@ The game directory can contain the following files:
* `game.conf`, with the following keys:
* `title`: Required, a human-readable title to address the game, e.g. `title = Minetest Game`.
* `name`: (Deprecated) same as title.
* `description`: Short description to be shown in the content tab
* `description`: Short description to be shown in the content tab.
See [Translating content meta](#translating-content-meta).
* `allowed_mapgens = <comma-separated mapgens>`
e.g. `allowed_mapgens = v5,v6,flat`
Mapgens not in this list are removed from the list of mapgens for the
@ -87,10 +88,11 @@ The game directory can contain the following files:
`enable_damage`, `creative_mode`, `enable_server`.
* `map_persistent`: Specifies whether newly created worlds should use
a persistent map backend. Defaults to `true` (= "sqlite3")
* `author`: The author of the game. It only appears when downloaded from
ContentDB.
* `author`: The author's ContentDB username.
* `release`: Ignore this: Should only ever be set by ContentDB, as it is
an internal ID used to track versions.
* `textdomain`: Textdomain used to translate description. Defaults to game id.
See [Translating content meta](#translating-content-meta).
* `minetest.conf`:
Used to set default settings when running this game.
* `settingtypes.txt`:
@ -156,13 +158,14 @@ The file is a key-value store of modpack details.
* `name`: The modpack name. Allows Minetest to determine the modpack name even
if the folder is wrongly named.
* `title`: A human-readable title to address the modpack. See [Translating content meta](#translating-content-meta).
* `description`: Description of mod to be shown in the Mods tab of the main
menu.
* `author`: The author of the modpack. It only appears when downloaded from
ContentDB.
menu. See [Translating content meta](#translating-content-meta).
* `author`: The author's ContentDB username.
* `release`: Ignore this: Should only ever be set by ContentDB, as it is an
internal ID used to track versions.
* `title`: A human-readable title to address the modpack.
* `textdomain`: Textdomain used to translate title and description. Defaults to modpack name.
See [Translating content meta](#translating-content-meta).
Note: to support 0.4.x, please also create an empty modpack.txt file.
@ -201,17 +204,18 @@ A `Settings` file that provides meta information about the mod.
* `name`: The mod name. Allows Minetest to determine the mod name even if the
folder is wrongly named.
* `title`: A human-readable title to address the mod. See [Translating content meta](#translating-content-meta).
* `description`: Description of mod to be shown in the Mods tab of the main
menu.
menu. See [Translating content meta](#translating-content-meta).
* `depends`: A comma separated list of dependencies. These are mods that must be
loaded before this mod.
* `optional_depends`: A comma separated list of optional dependencies.
Like a dependency, but no error if the mod doesn't exist.
* `author`: The author of the mod. It only appears when downloaded from
ContentDB.
* `author`: The author's ContentDB username.
* `release`: Ignore this: Should only ever be set by ContentDB, as it is an
internal ID used to track versions.
* `title`: A human-readable title to address the mod.
* `textdomain`: Textdomain used to translate title and description. Defaults to modname.
See [Translating content meta](#translating-content-meta).
### `screenshot.png`
@ -506,8 +510,8 @@ Example:
* `<w>`: width
* `<h>`: height
* `<x>`: x position
* `<y>`: y position
* `<x>`: x position, negative numbers allowed
* `<y>`: y position, negative numbers allowed
* `<file>`: texture to combine
Creates a texture of size `<w>` times `<h>` and blits the listed files to their
@ -613,13 +617,13 @@ Creates an inventorycube with `grass.png`, `dirt.png^grass_side.png` and
* `<y>`: y position
* `<color>`: a `ColorString`.
Creates a texture of the given size and color, optionally with an <x>,<y>
Creates a texture of the given size and color, optionally with an `<x>,<y>`
position. An alpha value may be specified in the `Colorstring`.
The optional <x>,<y> position is only used if the [fill is being overlaid
The optional `<x>,<y>` position is only used if the `[fill` is being overlaid
onto another texture with '^'.
When [fill is overlaid onto another texture it will not upscale or change
When `[fill` is overlaid onto another texture it will not upscale or change
the resolution of the texture, the base texture will determine the output
resolution.
@ -2167,6 +2171,8 @@ to games.
Negative damage values are discarded as no damage.
* `falling_node`: if there is no walkable block under the node it will fall
* `float`: the node will not fall through liquids (`liquidtype ~= "none"`)
* A liquid source with `groups = {falling_node = 1, float = 1}`
will fall through flowing liquids.
* `level`: Can be used to give an additional sense of progression in the game.
* A larger level will cause e.g. a weapon of a lower level make much less
damage, and get worn out much faster, or not be able to get drops
@ -4133,6 +4139,46 @@ the table returned by `minetest.get_player_information(name)`.
IMPORTANT: This functionality should only be used for sorting, filtering or similar purposes.
You do not need to use this to get translated strings to show up on the client.
Translating content meta
------------------------
You can translate content meta, such as `title` and `description`, by placing
translations in a `locale/DOMAIN.LANG.tr` file. The textdomain defaults to the
content name, but can be customised using `textdomain` in the content's .conf.
### Mods and Texture Packs
Say you have a mod called `mymod` with a short description in mod.conf:
```
description = This is the short description
```
Minetest will look for translations in the `mymod` textdomain as there's no
textdomain specified in mod.conf. For example, `mymod/locale/mymod.fr.tr`:
```
# textdomain:mymod
This is the short description=Voici la description succincte
```
### Games and Modpacks
For games and modpacks, Minetest will look for the textdomain in all mods.
Say you have a game called `mygame` with the following game.conf:
```
description = This is the game's short description
textdomain = mygame
```
Minetest will then look for the textdomain `mygame` in all mods, for example,
`mygame/mods/anymod/locale/mygame.fr.tr`. Note that it is still recommended that your
textdomain match the mod name, but this isn't required.
Perlin noise
============
@ -4677,6 +4723,7 @@ differences:
into it; it's not necessary to call `VoxelManip:read_from_map()`.
Note that the region of map it has loaded is NOT THE SAME as the `minp`, `maxp`
parameters of `on_generated()`. Refer to `minetest.get_mapgen_object` docs.
Once you're done you still need to call `VoxelManip:write_to_map()`
* The `on_generated()` callbacks of some mods may place individual nodes in the
generated area using non-VoxelManip map modification methods. Because the
@ -4873,10 +4920,10 @@ Mapgen objects
==============
A mapgen object is a construct used in map generation. Mapgen objects can be
used by an `on_generate` callback to speed up operations by avoiding
used by an `on_generated` callback to speed up operations by avoiding
unnecessary recalculations, these can be retrieved using the
`minetest.get_mapgen_object()` function. If the requested Mapgen object is
unavailable, or `get_mapgen_object()` was called outside of an `on_generate()`
unavailable, or `get_mapgen_object()` was called outside of an `on_generated`
callback, `nil` is returned.
The following Mapgen objects are currently available:
@ -4908,12 +4955,14 @@ generated chunk by the current mapgen.
### `gennotify`
Returns a table mapping requested generation notification types to arrays of
positions at which the corresponding generated structures are located within
the current chunk. To enable the capture of positions of interest to be recorded
call `minetest.set_gen_notify()` first.
Returns a table. You need to announce your interest in a specific
field by calling `minetest.set_gen_notify()` *before* map generation happens.
Possible fields of the returned table are:
* key = string: generation notification type
* value = list of positions (usually)
* Exceptions are denoted in the listing below.
Available generation notification types:
* `dungeon`: bottom center position of dungeon rooms
* `temple`: as above but for desert temples (mgv6 only)
@ -4921,7 +4970,12 @@ Possible fields of the returned table are:
* `cave_end`
* `large_cave_begin`
* `large_cave_end`
* `decoration#id` (see below)
* `custom`: data originating from [Mapgen environment] (Lua API)
* This is a table.
* key = user-defined ID (string)
* value = arbitrary Lua value
* `decoration#id`: decorations
* (see below)
Decorations have a key in the format of `"decoration#id"`, where `id` is the
numeric unique decoration ID as returned by `minetest.get_decoration_id()`.
@ -5302,6 +5356,10 @@ Utilities
item_specific_pointabilities = true,
-- Nodes `pointable` property can be `"blocking"` (5.9.0)
blocking_pointability_type = true,
-- dynamic_add_media can be called at startup when leaving callback as `nil` (5.9.0)
dynamic_add_media_startup = true,
-- dynamic_add_media supports `filename` and `filedata` parameters (5.9.0)
dynamic_add_media_filepath = true,
}
```
@ -5426,6 +5484,9 @@ Utilities
* `minetest.sha1(data, [raw])`: returns the sha1 hash of data
* `data`: string of data to hash
* `raw`: return raw bytes instead of hex digits, default: false
* `minetest.sha256(data, [raw])`: returns the sha256 hash of data
* `data`: string of data to hash
* `raw`: return raw bytes instead of hex digits, default: false
* `minetest.colorspec_to_colorstring(colorspec)`: Converts a ColorSpec to a
ColorString. If the ColorSpec is invalid, returns `nil`.
* `colorspec`: The ColorSpec to convert
@ -5445,7 +5506,7 @@ Utilities
You can use `colorspec_to_bytes` to generate raw RGBA values.
Palettes are not supported at the moment.
You may use this to procedurally generate textures during server init.
* `minetest.urlencode(str)`: Encodes non-unreserved URI characters by a
* `minetest.urlencode(str)`: Encodes reserved URI characters by a
percent sign followed by two hex digits. See
[RFC 3986, section 2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3).
@ -5581,8 +5642,10 @@ Call these functions only at load time!
* `minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing))`
* Called when a node is punched
* `minetest.register_on_generated(function(minp, maxp, blockseed))`
* Called after generating a piece of world. Modifying nodes inside the area
is a bit faster than usual.
* Called after generating a piece of world between `minp` and `maxp`.
* **Avoid using this** whenever possible. As with other callbacks this blocks
the main thread and introduces noticable latency.
Consider [Mapgen environment] for an alternative.
* `minetest.register_on_newplayer(function(ObjectRef))`
* Called when a new player enters the world for the first time
* `minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage))`
@ -5948,8 +6011,11 @@ Environment access
* `minetest.add_entity(pos, name, [staticdata])`: Spawn Lua-defined entity at
position.
* Returns `ObjectRef`, or `nil` if failed
* Entities with `static_save = true` can be added also
to unloaded and non-generated blocks.
* `minetest.add_item(pos, item)`: Spawn item
* Returns `ObjectRef`, or `nil` if failed
* Items can be added also to unloaded and non-generated blocks.
* `minetest.get_player_by_name(name)`: Get an `ObjectRef` to a player
* `minetest.get_objects_inside_radius(pos, radius)`: returns a list of
ObjectRefs.
@ -5995,20 +6061,18 @@ Environment access
* `minetest.get_voxel_manip([pos1, pos2])`
* Return voxel manipulator object.
* Loads the manipulator from the map if positions are passed.
* `minetest.set_gen_notify(flags, {deco_ids})`
* `minetest.set_gen_notify(flags, [deco_ids], [custom_ids])`
* Set the types of on-generate notifications that should be collected.
* `flags` is a flag field with the available flags:
* dungeon
* temple
* cave_begin
* cave_end
* large_cave_begin
* large_cave_end
* decoration
* The second parameter is a list of IDs of decorations which notification
* `flags`: flag field, see [`gennotify`] for available generation notification types.
* The following parameters are optional:
* `deco_ids` is a list of IDs of decorations which notification
is requested for.
* `custom_ids` is a list of user-defined IDs (strings) which are
requested. By convention these should be the mod name with an optional
colon and specifier added, e.g. `"default"` or `"default:dungeon_loot"`
* `minetest.get_gen_notify()`
* Returns a flagstring and a table with the `deco_id`s.
* Returns a flagstring, a table with the `deco_id`s and a table with
user-defined IDs.
* `minetest.get_decoration_id(decoration_name)`
* Returns the decoration ID number for the provided decoration name string,
or `nil` on failure.
@ -6180,6 +6244,17 @@ Environment access
* increase level of leveled node by level, default `level` equals `1`
* if `totallevel > maxlevel`, returns rest (`total-max`)
* `level` must be between -127 and 127
* `minetest.get_node_boxes(box_type, pos, [node])`
* `box_type` must be `"node_box"`, `"collision_box"` or `"selection_box"`.
* `pos` must be a node position.
* `node` can be a table in the form `{name=string, param1=number, param2=number}`.
If `node` is `nil`, the actual node at `pos` is used instead.
* Resolves any facedir-rotated boxes, connected boxes and the like into
actual boxes.
* Returns a list of boxes in the form
`{{x1, y1, z1, x2, y2, z2}, {x1, y1, z1, x2, y2, z2}, ...}`. Coordinates
are relative to `pos`.
* See also: [Node boxes](#node-boxes)
* `minetest.fix_light(pos1, pos2)`: returns `true`/`false`
* resets the light in a cuboid-shaped part of
the map and removes lighting bugs.
@ -6544,7 +6619,6 @@ Class instances that can be transferred between environments:
Functions:
* Standalone helpers such as logging, filesystem, encoding,
hashing or compression APIs
* `minetest.request_insecure_environment` (same restrictions apply)
Variables:
* `minetest.settings`
@ -6553,6 +6627,85 @@ Variables:
* with all functions and userdata values replaced by `true`, calling any
callbacks here is obviously not possible
Mapgen environment
------------------
The engine runs the map generator on separate threads, each of these also has
a Lua environment. Its primary purpose is to allow mods to operate on newly
generated parts of the map to e.g. generate custom structures.
Internally it is referred to as "emerge environment".
Refer to [Async environment] for the usual disclaimer on what environment isolation entails.
The map generator threads, which also contain the above mentioned Lua environment,
are initialized after all mods have been loaded by the server. After that the
registered scripts (not all mods!) - see below - are run during initialization of
the mapgen environment. After that only callbacks happen. The mapgen env
does not have a global step or timer.
* `minetest.register_mapgen_script(path)`:
* Register a path to a Lua file to be imported when a mapgen environment
is initialized. Run in order of registration.
### List of APIs exclusive to the mapgen env
* `minetest.register_on_generated(function(vmanip, minp, maxp, blockseed))`
* Called after the engine mapgen finishes a chunk but before it is written to
the map.
* Chunk data resides in `vmanip`. Other parts of the map are not accessible.
The area of the chunk if comprised of `minp` and `maxp`, note that is smaller
than the emerged area of the VoxelManip.
Note: calling `read_from_map()` or `write_to_map()` on the VoxelManipulator object
is not necessary and is disallowed.
* `blockseed`: 64-bit seed number used for this chunk
* `minetest.save_gen_notify(id, data)`
* Saves data for retrieval using the gennotify mechanism (see [Mapgen objects]).
* Data is bound to the chunk that is currently being processed, so this function
only makes sense inside the `on_generated` callback.
* `id`: user-defined ID (a string)
By convention these should be the mod name with an optional
colon and specifier added, e.g. `"default"` or `"default:dungeon_loot"`
* `data`: any Lua object (will be serialized, no userdata allowed)
* returns `true` if the data was remembered. That is if `minetest.set_gen_notify`
was called with the same user-defined ID before.
### List of APIs available in the mapgen env
Classes:
* `AreaStore`
* `ItemStack`
* `PerlinNoise`
* `PerlinNoiseMap`
* `PseudoRandom`
* `PcgRandom`
* `SecureRandom`
* `VoxelArea`
* `VoxelManip`
* only given by callbacks; cannot access rest of map
* `Settings`
Functions:
* Standalone helpers such as logging, filesystem, encoding,
hashing or compression APIs
* `minetest.get_biome_id`, `get_biome_name`, `get_heat`, `get_humidity`,
`get_biome_data`, `get_mapgen_object`, `get_mapgen_params`, `get_mapgen_edges`,
`get_mapgen_setting`, `get_noiseparams`, `get_decoration_id` and more
* `minetest.get_node`, `set_node`, `find_node_near`, `find_nodes_in_area`,
`spawn_tree` and similar
* these only operate on the current chunk (if inside a callback)
Variables:
* `minetest.settings`
* `minetest.registered_items`, `registered_nodes`, `registered_tools`,
`registered_craftitems` and `registered_aliases`
* with all functions and userdata values replaced by `true`, calling any
callbacks here is obviously not possible
* `minetest.registered_biomes`, `registered_ores`, `registered_decorations`
Note that node metadata does not exist in the mapgen env, we suggest deferring
setting any metadata you need to the `on_generated` callback in the regular env.
You can use the gennotify mechanism to transfer this information.
Server
------
@ -6583,11 +6736,15 @@ Server
* Returns boolean indicating success (false if player nonexistent)
* `minetest.dynamic_add_media(options, callback)`
* `options`: table containing the following parameters
* `filepath`: path to a media file on the filesystem
* `filename`: name the media file will be usable as
(optional if `filepath` present)
* `filepath`: path to the file on the filesystem [*]
* `filedata`: the data of the file to be sent [*]
* `to_player`: name of the player the media should be sent to instead of
all players (optional)
* `ephemeral`: boolean that marks the media as ephemeral,
it will not be cached on the client (optional, default false)
* Exactly one of the paramters marked [*] must be specified.
* `callback`: function with arguments `name`, which is a player name
* Pushes the specified media file to client(s). (details below)
The file must be a supported image, sound or model format.
@ -6605,6 +6762,9 @@ Server
name twice is not possible/guaranteed to work. An exception to this is the
use of `to_player` to send the same, already existent file to multiple
chosen players.
* You can also call this at startup time. In that case `callback` MUST
be `nil` and you cannot use `ephemeral` or `to_player`, as these logically
do not make sense.
* Clients will attempt to fetch files added this way via remote media,
this can make transfer of bigger files painless (if set up). Nevertheless
it is advised not to use dynamic media for big media files.
@ -6786,7 +6946,7 @@ Misc.
(regardless of online status)
* `minetest.hud_replace_builtin(name, hud_definition)`
* Replaces definition of a builtin hud element
* `name`: `"breath"` or `"health"`
* `name`: `"breath"`, `"health"` or `"minimap"`
* `hud_definition`: definition to replace builtin definition
* `minetest.parse_relative_number(arg, relative_to)`: returns number or nil
* Helper function for chat commands.
@ -7054,10 +7214,6 @@ Global tables
* Map of registered decoration definitions, indexed by the `name` field.
* If `name` is nil, the key is the object handle returned by
`minetest.register_decoration`.
* `minetest.registered_schematics`
* Map of registered schematic definitions, indexed by the `name` field.
* If `name` is nil, the key is the object handle returned by
`minetest.register_schematic`.
* `minetest.registered_chatcommands`
* Map of registered chat command definitions, indexed by name
* `minetest.registered_privileges`
@ -7273,6 +7429,8 @@ an itemstring, a table or `nil`.
the item breaks after `max_uses` times
* Valid `max_uses` range is [0,65536]
* Does nothing if item is not a tool or if `max_uses` is 0
* `get_wear_bar_params()`: returns the wear bar parameters of the item,
or nil if none are defined for this item type or in the stack's meta
* `add_item(item)`: returns leftover `ItemStack`
* Put some item or stack onto this stack
* `item_fits(item)`: returns `true` if item or stack can be fully added to
@ -7314,6 +7472,10 @@ Can be obtained via `item:get_meta()`.
* Overrides the item's tool capabilities
* A nil value will clear the override data and restore the original
behavior.
* `set_wear_bar_params([wear_bar_params])`
* Overrides the item's wear bar parameters (see "Wear Bar Color" section)
* A nil value will clear the override data and restore the original
behavior.
`MetaDataRef`
-------------
@ -8397,11 +8559,14 @@ Player properties need to be saved manually.
-- If `rotate = false`, the selection box will not rotate with the object itself, remaining fixed to the axes.
-- If `rotate = true`, it will match the object's rotation and any attachment rotations.
-- Raycasts use the selection box and object's rotation, but do *not* obey attachment rotations.
-- For server-side raycasts to work correctly,
-- the selection box should extend at most 5 units in each direction.
pointable = true,
-- Can be `true` if it is pointable, `false` if it can be pointed through,
-- or `"blocking"` if it is pointable but not selectable.
-- Clients older than 5.9.0 interpret `pointable = "blocking"` as `pointable = true`.
-- Can be overridden by the `pointabilities` of the held item.
visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem" / "item",
@ -8814,6 +8979,19 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- fallback behavior.
},
-- Set wear bar color of the tool by setting color stops and blend mode
-- See "Wear Bar Color" section for further explanation including an example
wear_color = {
-- interpolation mode: 'constant' or 'linear'
-- (nil defaults to 'constant')
blend = "linear",
color_stops = {
[0.0] = "#ff0000",
[0.5] = "#ffff00",
[1.0] = "#00ff00",
}
},
node_placement_prediction = nil,
-- If nil and item is node, prediction is made automatically.
-- If nil and item is not a node, no prediction is made.
@ -9015,6 +9193,7 @@ Used by `minetest.register_node`.
pointable = true,
-- Can be `true` if it is pointable, `false` if it can be pointed through,
-- or `"blocking"` if it is pointable but not selectable.
-- Clients older than 5.9.0 interpret `pointable = "blocking"` as `pointable = true`.
-- Can be overridden by the `pointabilities` of the held item.
-- A client may be able to point non-pointable nodes, since it isn't checked server-side.
@ -9380,6 +9559,46 @@ Used by `minetest.register_node`.
}
```
Wear Bar Color
--------------
'Wear Bar' is a property of items that defines the coloring
of the bar that appears under damaged tools.
If it is absent, the default behavior of green-yellow-red is
used.
### Wear bar colors definition
#### Syntax
```lua
{
-- 'constant' or 'linear'
-- (nil defaults to 'constant')
blend = "linear",
color_stops = {
[0.0] = "#ff0000",
[0.5] = "slateblue",
[1.0] = {r=0, g=255, b=0, a=150},
}
}
```
#### Blend mode `blend`
* `linear`: blends smoothly between each defined color point.
* `constant`: each color starts at its defined point, and continues up to the next point
#### Color stops `color_stops`
Specified as `ColorSpec` color values assigned to `float` durability keys.
"Durability" is defined as `1 - (wear / 65535)`.
#### Shortcut usage
Wear bar color can also be specified as a single `ColorSpec` instead of a table.
Crafting recipes
----------------

@ -323,6 +323,7 @@ Package - content which is downloadable from the content db, may or may not be i
description = "description",
author = "author",
path = "path/to/content",
textdomain = "textdomain", -- textdomain to translate title / description with
depends = {"mod", "names"}, -- mods only
optional_depends = {"mod", "names"}, -- mods only
}
@ -340,6 +341,13 @@ Package - content which is downloadable from the content db, may or may not be i
error_message = "", -- message or nil
}
```
* `core.get_content_translation(path, domain, string)`
* Translates `string` using `domain` in content directory at `path`.
* Textdomains will be found by looking through all locale folders.
* String should contain translation markup from `core.translate(textdomain, ...)`.
* Ex: `core.get_content_translation("mods/mymod", "mymod", core.translate("mymod", "Hello World"))`
will translate "Hello World" into the current user's language
using `mods/mymod/locale/mymod.fr.tr`.
Logging
-------

@ -119,7 +119,7 @@ Display an interactive terminal over ncurses during execution.
.SH ENVIRONMENT
.TP
.B MINETEST_SUBGAME_PATH
.B MINETEST_GAME_PATH
Colon delimited list of directories to search for games.
.TP
.B MINETEST_MOD_PATH

@ -25,8 +25,14 @@ texture pack. The name must not be “base”.
### `texture_pack.conf`
A key-value config file with the following keys:
* `title` - human readable title
* `name`: The texture pack name. Allows Minetest to determine the texture pack name even if
the folder is wrongly named.
* `title` - human-readable title
* `description` - short description, shown in the content tab
* `author`: The author's ContentDB username.
* `textdomain`: Textdomain used to translate title and description.
Defaults to the texture pack name.
See [Translating content meta](lua_api.md#translating-content-meta).
### `description.txt`
**Deprecated**, you should use texture_pack.conf instead.
@ -205,7 +211,8 @@ Here are targets you can choose from:
Nodes support all targets, but other items only support 'inventory'
and 'wield'.
¹ : `N` is an integer [0,255]. Sets align_style = "world" and scale = N on the tile, refer to lua_api.md for details.
¹ : `N` is an integer [0,255]. Sets align_style = "world" and scale = N on the tile,
refer to lua_api.md for details.
### Using the special targets

@ -420,36 +420,141 @@ minetest.register_tool("basetools:dagger_steel", {
}
})
-- Test tool uses and punch_attack_uses
local uses = { 1, 2, 3, 5, 10, 50, 100, 1000, 10000, 65535 }
for i=1, #uses do
local u = uses[i]
local ustring
if i == 1 then
ustring = u.."-Use"
else
ustring = u.."-Uses"
end
local color = string.format("#FF00%02X", math.floor(((i-1)/#uses) * 255))
minetest.register_tool("basetools:pick_uses_"..string.format("%05d", u), {
-- Test tool uses, punch_attack_uses, and wear bar coloring
local tool_params = {
{uses = 1},
{uses = 2},
{uses = 3},
{
uses = 5,
wear_color = "#5865f2",
wear_description = "Solid color: #5865f2",
},
{
uses = 10,
wear_color = "slateblue",
wear_description = "Solid color: slateblue",
},
{
uses = 50,
wear_color = {
color_stops = {
[0] = "red",
[0.5] = "yellow",
[1.0] = "blue"
},
blend = "linear"
},
wear_description = "Ranges from blue to yellow to red",
},
{
uses = 100,
wear_color = {
color_stops = {
[0] = "#ffff00",
[0.2] = "#ff00ff",
[0.3] = "#ffff00",
[0.45] = "#c0ffee",
[0.6] = {r=255, g=255, b=0, a=100}, -- continues until the end
},
blend = "constant"
},
wear_description = "Misc. colors, constant interpolation",
},
{uses = 1e3},
{uses = 1e4},
{uses = 65535},
}
for i, params in ipairs(tool_params) do
local uses = params.uses
local ustring = uses.."-Use"..(uses == 1 and "" or "s")
local color = string.format("#FF00%02X", math.floor(((i-1)/#tool_params) * 255))
minetest.register_tool("basetools:pick_uses_"..string.format("%05d", uses), {
description = ustring.." Pickaxe".."\n"..
"Digs cracky=3",
"Digs cracky=3"..
(params.wear_description and "\n".."Wear bar: " .. params.wear_description or ""),
inventory_image = "basetools_usespick.png^[colorize:"..color..":127",
tool_capabilities = {
max_drop_level=0,
groupcaps={
cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=u, maxlevel=0}
cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=uses, maxlevel=0}
},
},
wear_color = params.wear_color
})
minetest.register_tool("basetools:sword_uses_"..string.format("%05d", u), {
minetest.register_tool("basetools:sword_uses_"..string.format("%05d", uses), {
description = ustring.." Sword".."\n"..
"Damage: fleshy=1",
inventory_image = "basetools_usessword.png^[colorize:"..color..":127",
tool_capabilities = {
damage_groups = {fleshy=1},
punch_attack_uses = u,
punch_attack_uses = uses,
},
})
end
minetest.register_chatcommand("wear_color", {
params = "[idx]",
description = "Set wear bar color override",
func = function(player_name, param)
local player = minetest.get_player_by_name(player_name)
if not player then return end
local wear_color = nil
local wear_desc = "Reset override"
if param ~= "" then
local params = tool_params[tonumber(param)]
if not params then
return false, "idx out of bounds"
end
wear_color = params.wear_color
wear_desc = "Set override: "..(params.wear_description or "Default behavior")
end
local tool = player:get_wielded_item()
if tool:get_count() == 0 then
return false, "Tool not found"
end
tool:get_meta():set_wear_bar_params(wear_color)
player:set_wielded_item(tool)
return true, wear_desc
end
})
-- Punch handler to set random color & wear
local wear_on_use = function(itemstack, user, pointed_thing)
local meta = itemstack:get_meta()
local color = math.random(0, 0xFFFFFF)
local colorstr = string.format("#%06x", color)
meta:set_wear_bar_params(colorstr)
minetest.log("action", "[basetool] Wear bar color of "..itemstack:get_name().." changed to "..colorstr)
itemstack:set_wear(math.random(0, 65535))
return itemstack
end
-- Place handler to clear item metadata color
local wear_on_place = function(itemstack, user, pointed_thing)
local meta = itemstack:get_meta()
meta:set_wear_bar_params(nil)
return itemstack
end
minetest.register_tool("basetools:random_wear_bar", {
description = "Wear Bar Color Test\n" ..
"Punch: Set random color & wear\n" ..
"Place: Clear color",
-- Base texture: A grayscale square (can be colorized)
inventory_image = "basetools_usespick.png^[colorize:#FFFFFF:127",
tool_capabilities = {
max_drop_level=0,
groupcaps={
cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=1000, maxlevel=0}
},
},
on_use = wear_on_use,
on_place = wear_on_place,
on_secondary_use = wear_on_place,
})

@ -9,7 +9,7 @@ for d=0, 8 do
end
minetest.register_node("testnodes:rliquid_"..d, {
description = "Test Liquid Source, Range "..d..
tt_normal,
tt_normal .. "\n" .. "(falling & floating node)",
drawtype = "liquid",
tiles = {"testnodes_liquidsource_r"..d..".png"},
special_tiles = {
@ -25,6 +25,8 @@ for d=0, 8 do
liquid_alternative_flowing = "testnodes:rliquid_flowing_"..d,
liquid_alternative_source = "testnodes:rliquid_"..d,
liquid_range = d,
-- Also use these nodes to test falling, floating liquid source nodes
groups = {float = 1, falling_node = 1},
})
minetest.register_node("testnodes:rliquid_flowing_"..d, {

@ -149,15 +149,28 @@ fractal = nil
frac_emb = nil
checker = nil
local textures_path = minetest.get_modpath( minetest.get_current_modname() ) .. "/textures/"
minetest.safe_file_write(
textures_path .. "testnodes_generated_mb.png",
do
-- we used to write the textures to our mod folder. in order to avoid
-- duplication errors delete them if they still exist.
local path = core.get_modpath(core.get_current_modname()) .. "/textures/"
os.remove(path .. "testnodes_generated_mb.png")
os.remove(path .. "testnodes_generated_ck.png")
end
local textures_path = core.get_worldpath() .. "/"
core.safe_file_write(
textures_path .. "testnodes1.png",
encode_and_check(512, 512, "rgb", data_mb)
)
minetest.safe_file_write(
textures_path .. "testnodes_generated_ck.png",
encode_and_check(512, 512, "gray", data_ck)
)
local png_ck = encode_and_check(512, 512, "gray", data_ck)
core.dynamic_add_media({
filename = "testnodes_generated_mb.png",
filepath = textures_path .. "testnodes1.png"
})
core.dynamic_add_media({
filename = "testnodes_generated_ck.png",
filedata = png_ck,
})
minetest.register_node("testnodes:generated_png_mb", {
description = S("Generated Mandelbrot PNG Test Node"),
@ -200,6 +213,8 @@ minetest.register_node("testnodes:generated_png_dst_emb", {
groups = { dig_immediate = 2 },
})
png_ck = nil
png_emb = nil
data_emb = nil
data_mb = nil
data_ck = nil

@ -6,6 +6,7 @@ testtools = {}
dofile(minetest.get_modpath("testtools") .. "/light.lua")
dofile(minetest.get_modpath("testtools") .. "/privatizer.lua")
dofile(minetest.get_modpath("testtools") .. "/particles.lua")
dofile(minetest.get_modpath("testtools") .. "/node_box_visualizer.lua")
local pointabilities_nodes = {
nodes = {

@ -0,0 +1,79 @@
local S = minetest.get_translator("testtools")
minetest.register_entity("testtools:visual_box", {
initial_properties = {
visual = "cube",
textures = {
"blank.png", "blank.png", "blank.png",
"blank.png", "blank.png", "blank.png",
},
use_texture_alpha = true,
physical = false,
pointable = false,
static_save = false,
},
on_activate = function(self)
self.timestamp = minetest.get_us_time() + 5000000
end,
on_step = function(self)
if minetest.get_us_time() >= self.timestamp then
self.object:remove()
end
end,
})
local BOX_TYPES = {"node_box", "collision_box", "selection_box"}
local DEFAULT_BOX_TYPE = "selection_box"
local function visualizer_on_use(itemstack, user, pointed_thing)
if pointed_thing.type ~= "node" then
return
end
local meta = itemstack:get_meta()
local box_type = meta:get("box_type") or DEFAULT_BOX_TYPE
local result = minetest.get_node_boxes(box_type, pointed_thing.under)
local t = "testtools_visual_" .. box_type .. ".png"
for _, box in ipairs(result) do
local box_min = pointed_thing.under + vector.new(box[1], box[2], box[3])
local box_max = pointed_thing.under + vector.new(box[4], box[5], box[6])
local box_center = (box_min + box_max) / 2
local obj = minetest.add_entity(box_center, "testtools:visual_box")
if not obj then
break
end
obj:set_properties({
textures = {t, t, t, t, t, t},
-- Add a small offset to avoid Z-fighting.
visual_size = vector.add(box_max - box_min, 0.01),
})
end
end
local function visualizer_on_place(itemstack, placer, pointed_thing)
local meta = itemstack:get_meta()
local prev_value = meta:get("box_type") or DEFAULT_BOX_TYPE
local prev_index = table.indexof(BOX_TYPES, prev_value)
assert(prev_index ~= -1)
local new_value = BOX_TYPES[(prev_index % #BOX_TYPES) + 1]
meta:set_string("box_type", new_value)
minetest.chat_send_player(placer:get_player_name(), S("[Node Box Visualizer] box_type = @1", new_value))
return itemstack
end
minetest.register_tool("testtools:node_box_visualizer", {
description = S("Node Box Visualizer") .. "\n" ..
S("Punch: Show node/collision/selection boxes of the pointed node") .. "\n" ..
S("Place: Change selected box type (default: selection box)"),
inventory_image = "testtools_node_box_visualizer.png",
groups = { testtool = 1, disable_repair = 1 },
on_use = visualizer_on_use,
on_place = visualizer_on_place,
on_secondary_use = visualizer_on_place,
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

@ -0,0 +1,32 @@
core.log("info", "Hello World")
local function do_tests()
assert(core == minetest)
-- stuff that should not be here
assert(not core.get_player_by_name)
assert(not core.object_refs)
-- stuff that should be here
assert(core.register_on_generated)
assert(core.get_node)
assert(core.spawn_tree)
assert(ItemStack)
local meta = ItemStack():get_meta()
assert(type(meta) == "userdata")
assert(type(meta.set_tool_capabilities) == "function")
assert(core.registered_items[""])
assert(core.save_gen_notify)
-- alias handling
assert(core.registered_items["unittests:steel_ingot_alias"].name ==
"unittests:steel_ingot")
-- fallback to item defaults
assert(core.registered_items["unittests:description_test"].on_place == true)
end
-- there's no (usable) communcation path between mapgen and the regular env
-- so we just run the test unconditionally
do_tests()
core.register_on_generated(function(vm, pos1, pos2, blockseed)
local n = tonumber(core.get_mapgen_setting("chunksize")) * 16 - 1
assert(pos2:subtract(pos1) == vector.new(n, n, n))
end)

@ -77,6 +77,29 @@ local function test_metadata(meta)
assert(not meta:equals(compare_meta))
end
local function test_metadata_compat(meta)
-- key/value removal using set_string (undocumented, deprecated way)
meta:set_string("key", "value")
assert(meta:get_string("key") == "value")
meta:set_string("key", nil) -- ignore warning
assert(meta:to_table().fields["key"] == nil)
-- undocumented but supported consequence of Lua's
-- automatic string <--> number cast
meta:set_string("key", 2)
assert(meta:get_string("key") == "2")
-- from_table with non-string keys (supported)
local values = meta:to_table()
values.fields["new"] = 420
meta:from_table(values)
assert(meta:get_int("new") == 420)
values.fields["new"] = nil
meta:from_table(values)
assert(meta:get("new") == nil)
end
local storage_a = core.get_mod_storage()
local storage_b = core.get_mod_storage()
local function test_mod_storage()
@ -86,7 +109,9 @@ end
unittests.register("test_mod_storage", test_mod_storage)
local function test_item_metadata()
test_metadata(ItemStack("unittest:coal_lump"):get_meta())
local meta = ItemStack("unittest:coal_lump"):get_meta()
test_metadata(meta)
test_metadata_compat(meta)
end
unittests.register("test_item_metadata", test_item_metadata)

@ -1,3 +1,6 @@
core.register_mapgen_script(core.get_modpath(core.get_current_modname()) ..
DIR_DELIM .. "inside_mapgen_env.lua")
local function test_pseudo_random()
-- We have comprehensive unit tests in C++, this is just to make sure the API code isn't messing up
local gen1 = PseudoRandom(13)
@ -108,6 +111,13 @@ unittests.register("test_punch_node", function(_, pos)
-- currently failing: assert(on_punch_called)
end, {map=true})
local function test_hashing()
local input = "hello\000world"
assert(core.sha1(input) == "f85b420f1e43ebf88649dfcab302b898d889606c")
assert(core.sha256(input) == "b206899bc103669c8e7b36de29d73f95b46795b508aa87d612b2ce84bfb29df2")
end
unittests.register("test_hashing", test_hashing)
local function test_compress()
-- This text should be compressible, to make sure the results are... normal
local text = "The\000 icey canoe couldn't move very well on the\128 lake. The\000 ice was too stiff and the icey canoe's paddles simply wouldn't punch through."
@ -130,6 +140,12 @@ local function test_compress()
end
unittests.register("test_compress", test_compress)
local function test_urlencode()
-- checks that API code handles null bytes
assert(core.urlencode("foo\000bar!") == "foo%00bar%21")
end
unittests.register("test_urlencode", test_urlencode)
local function test_game_info()
local info = minetest.get_game_info()
local game_conf = Settings(info.path .. "/game.conf")
@ -204,3 +220,30 @@ local function test_on_mapblocks_changed(cb, player, pos)
end
end
unittests.register("test_on_mapblocks_changed", test_on_mapblocks_changed, {map=true, async=true})
local function test_gennotify_api()
local DECO_ID = 123
local UD_ID = "unittests:dummy"
-- the engine doesn't check if the id is actually valid, maybe it should
core.set_gen_notify({decoration=true}, {DECO_ID})
core.set_gen_notify({custom=true}, nil, {UD_ID})
local flags, deco, custom = core.get_gen_notify()
local function ff(flag)
return (" " .. flags .. " "):match("[ ,]" .. flag .. "[ ,]") ~= nil
end
assert(ff("decoration"), "'decoration' flag missing")
assert(ff("custom"), "'custom' flag missing")
assert(table.indexof(deco, DECO_ID) > 0)
assert(table.indexof(custom, UD_ID) > 0)
core.set_gen_notify({decoration=false, custom=false})
flags, deco, custom = core.get_gen_notify()
assert(not ff("decoration") and not ff("custom"))
assert(#deco == 0, "deco ids not empty")
assert(#custom == 0, "custom ids not empty")
end
unittests.register("test_gennotify_api", test_gennotify_api)

@ -210,6 +210,29 @@ minetest.register_chatcommand("dump_item", {
end,
})
minetest.register_chatcommand("dump_itemdef", {
params = "",
description = "Prints a dump of the wielded item's definition in table form",
func = function(name, param)
local player = minetest.get_player_by_name(name)
local str = dump(player:get_wielded_item():get_definition())
print(str)
return true, str
end,
})
minetest.register_chatcommand("dump_wear_bar", {
params = "",
description = "Prints a dump of the wielded item's wear bar parameters in table form",
func = function(name, param)
local player = minetest.get_player_by_name(name)
local item = player:get_wielded_item()
local str = dump(item:get_wear_bar_params())
print(str)
return true, str
end,
})
core.register_chatcommand("set_saturation", {
params = "<saturation>",
description = "Set the saturation for current player.",

@ -1 +1 @@
Subproject commit 85081d6fe0c422cf47f74714ee25562715528aa2
Subproject commit 154064522ef548c019ce66131ea654642e663a42

@ -1 +1 @@
1.9.0mt14
1.9.0mt15

@ -109,9 +109,10 @@ if(BUILD_CLIENT AND ENABLE_SOUND)
endif()
endif()
option(ENABLE_TOUCH "Enable Touchscreen support" FALSE)
option(ENABLE_TOUCH "Enable touchscreen by default" FALSE)
if(ENABLE_TOUCH)
add_definitions(-DHAVE_TOUCHSCREENGUI)
message(STATUS "Touchscreen support enabled by default.")
add_definitions(-DENABLE_TOUCH)
endif()
if(BUILD_CLIENT)

@ -46,7 +46,7 @@ enum ActiveObjectType {
struct ActiveObjectMessage
{
ActiveObjectMessage(u16 id_, bool reliable_=true, const std::string &data_ = "") :
ActiveObjectMessage(u16 id_, bool reliable_=true, std::string_view data_ = "") :
id(id_),
reliable(reliable_),
datastring(data_)

@ -577,6 +577,7 @@ void ChatPrompt::historyNext()
void ChatPrompt::nickCompletion(const std::set<std::string> &names, bool backwards)
{
const std::wstring_view line(getLineRef());
// Two cases:
// (a) m_nick_completion_start == m_nick_completion_end == 0
// Then no previous nick completion is active.
@ -586,7 +587,6 @@ void ChatPrompt::nickCompletion(const std::set<std::string> &names, bool backwar
// m_nick_completion_start..m_nick_completion_end are the
// interval where the originally used prefix was. Cycle
// through the list of completions of that prefix.
const std::wstring &line = getLineRef();
u32 prefix_start = m_nick_completion_start;
u32 prefix_end = m_nick_completion_end;
bool initial = (prefix_end == 0);
@ -601,7 +601,7 @@ void ChatPrompt::nickCompletion(const std::set<std::string> &names, bool backwar
if (prefix_start == prefix_end)
return;
}
std::wstring prefix = line.substr(prefix_start, prefix_end - prefix_start);
auto prefix = line.substr(prefix_start, prefix_end - prefix_start);
// find all names that start with the selected prefix
std::vector<std::wstring> completions;
@ -624,7 +624,7 @@ void ChatPrompt::nickCompletion(const std::set<std::string> &names, bool backwar
{
while (word_end < line.size() && !iswspace(line[word_end]))
++word_end;
std::wstring word = line.substr(prefix_start, word_end - prefix_start);
auto word = line.substr(prefix_start, word_end - prefix_start);
// cycle through completions
for (u32 i = 0; i < completions.size(); ++i)
@ -640,7 +640,7 @@ void ChatPrompt::nickCompletion(const std::set<std::string> &names, bool backwar
}
}
}
std::wstring replacement = completions[replacement_index];
const auto &replacement = completions[replacement_index];
if (word_end < line.size() && iswspace(line[word_end]))
++word_end;

@ -138,8 +138,8 @@ void Camera::notifyFovChange()
// Returns the fractional part of x
inline f32 my_modf(f32 x)
{
double dummy;
return modf(x, &dummy);
float dummy;
return std::modf(x, &dummy);
}
void Camera::step(f32 dtime)
@ -407,10 +407,10 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
f32 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0;
f32 bobknob = 1.2;
f32 bobtmp = sin(pow(bobfrac, bobknob) * M_PI);
f32 bobtmp = std::sin(std::pow(bobfrac, bobknob) * M_PI);
v3f bobvec = v3f(
0.3 * bobdir * sin(bobfrac * M_PI),
0.3 * bobdir * std::sin(bobfrac * M_PI),
-0.28 * bobtmp * bobtmp,
0.);
@ -531,11 +531,9 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
addArmInertia(yaw);
// Position the wielded item
//v3f wield_position = v3f(45, -35, 65);
v3f wield_position = v3f(m_wieldmesh_offset.X, m_wieldmesh_offset.Y, 65);
//v3f wield_rotation = v3f(-100, 120, -100);
v3f wield_rotation = v3f(-100, 120, -100);
wield_position.Y += fabs(m_wield_change_timer)*320 - 40;
wield_position.Y += std::abs(m_wield_change_timer)*320 - 40;
if(m_digging_anim < 0.05 || m_digging_anim > 0.5)
{
f32 frac = 1.0;
@ -543,33 +541,29 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
frac = 2.0 * (m_digging_anim - 0.5);
// This value starts from 1 and settles to 0
f32 ratiothing = std::pow((1.0f - tool_reload_ratio), 0.5f);
//f32 ratiothing2 = pow(ratiothing, 0.5f);
f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0;
wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f);
//wield_position.Z += frac * 5.0 * ratiothing2;
wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f);
wield_rotation.Y += frac * 70.0 * pow(ratiothing2, 1.4f);
//wield_rotation.X -= frac * 15.0 * pow(ratiothing2, 1.4f);
//wield_rotation.Z += frac * 15.0 * pow(ratiothing2, 1.0f);
wield_position.Y -= frac * 25.0f * std::pow(ratiothing2, 1.7f);
wield_position.X -= frac * 35.0f * std::pow(ratiothing2, 1.1f);
wield_rotation.Y += frac * 70.0f * std::pow(ratiothing2, 1.4f);
}
if (m_digging_button != -1)
{
f32 digfrac = m_digging_anim;
wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI);
wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI);
wield_position.X -= 50 * std::sin(std::pow(digfrac, 0.8f) * M_PI);
wield_position.Y += 24 * std::sin(digfrac * 1.8 * M_PI);
wield_position.Z += 25 * 0.5;
// Euler angles are PURE EVIL, so why not use quaternions?
core::quaternion quat_begin(wield_rotation * core::DEGTORAD);
core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD);
core::quaternion quat_slerp;
quat_slerp.slerp(quat_begin, quat_end, sin(digfrac * M_PI));
quat_slerp.slerp(quat_begin, quat_end, std::sin(digfrac * M_PI));
quat_slerp.toEuler(wield_rotation);
wield_rotation *= core::RADTODEG;
} else {
f32 bobfrac = my_modf(m_view_bobbing_anim);
wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0;
wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0;
wield_position.X -= std::sin(bobfrac*M_PI*2.0) * 3.0;
wield_position.Y += std::sin(my_modf(bobfrac*2.0)*M_PI) * 3.0;
}
m_wieldnode->setPosition(wield_position);
m_wieldnode->setRotation(wield_rotation);
@ -584,8 +578,8 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
// view bobbing is enabled and free_move is off,
// start (or continue) the view bobbing animation.
const v3f &speed = player->getSpeed();
const bool movement_XZ = hypot(speed.X, speed.Z) > BS;
const bool movement_Y = fabs(speed.Y) > BS;
const bool movement_XZ = std::hypot(speed.X, speed.Z) > BS;
const bool movement_Y = std::abs(speed.Y) > BS;
const bool walking = movement_XZ && player->touching_ground;
const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid;

@ -1806,7 +1806,7 @@ struct TextureUpdateArgs {
void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress)
{
TextureUpdateArgs* targs = (TextureUpdateArgs*) args;
u16 cur_percent = ceil(progress / (double) max_progress * 100.);
u16 cur_percent = std::ceil(progress / max_progress * 100.f);
// update the loading menu -- if necessary
bool do_draw = false;
@ -1971,21 +1971,11 @@ void Client::makeScreenshot()
raw_image->drop();
}
bool Client::shouldShowMinimap() const
{
return !m_minimap_disabled_by_server;
}
void Client::pushToEventQueue(ClientEvent *event)
{
m_client_event_queue.push(event);
}
void Client::showMinimap(const bool show)
{
m_game_ui->showMinimap(show);
}
// IGameDef interface
// Under envlock
IItemDefManager* Client::getItemDefManager()
@ -2133,3 +2123,11 @@ const std::string &Client::getFormspecPrepend() const
{
return m_env.getLocalPlayer()->formspec_prepend;
}
void Client::removeActiveObjectSounds(u16 id)
{
for (auto it : m_sounds_to_objects) {
if (it.second == id)
m_sound->stopSound(it.first);
}
}

@ -369,8 +369,6 @@ public:
Camera* getCamera () { return m_camera; }
scene::ISceneManager *getSceneManager();
bool shouldShowMinimap() const;
// IGameDef interface
IItemDefManager* getItemDefManager() override;
const NodeDefManager* getNodeDefManager() override;
@ -412,8 +410,6 @@ public:
void pushToEventQueue(ClientEvent *event);
void showMinimap(bool show = true);
// IP and port we're connected to
const Address getServerAddress();
@ -475,6 +471,9 @@ private:
bool canSendChatMessage() const;
// remove sounds attached to object
void removeActiveObjectSounds(u16 id);
float m_packetcounter_timer = 0.0f;
float m_connection_reinit_timer = 0.1f;
float m_avg_rtt_timer = 0.0f;
@ -498,7 +497,6 @@ private:
ELoginRegister m_allow_login_or_register = ELoginRegister::Any;
Camera *m_camera = nullptr;
Minimap *m_minimap = nullptr;
bool m_minimap_disabled_by_server = false;
// Server serialization version
u8 m_server_ser_ver;

@ -182,7 +182,7 @@ void ClientEnvironment::step(float dtime)
Stuff that has a maximum time increment
*/
u32 steps = ceil(dtime / dtime_max_increment);
u32 steps = std::ceil(dtime / dtime_max_increment);
f32 dtime_part = dtime / steps;
for (; steps > 0; --steps) {
/*

@ -114,12 +114,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
m_rendering_engine->setupTopLevelWindow();
/*
This changes the minimum allowed number of vertices in a VBO.
Default is 500.
*/
//driver->setMinHardwareBufferVertexCount(50);
// Create game callback for menus
g_gamecallback = new MainGameCallback();
@ -204,11 +198,18 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
while (m_rendering_engine->run() && !*kill &&
!g_gamecallback->shutdown_requested) {
// Set the window caption
#if IRRLICHT_VERSION_MT_REVISION >= 15
auto driver_name = m_rendering_engine->getVideoDriver()->getName();
#else
auto driver_name = wide_to_utf8(m_rendering_engine->getVideoDriver()->getName());
#endif
std::string caption = std::string(PROJECT_NAME_C) +
" " + g_version_hash +
" [" + gettext("Main Menu") + "]" +
" [" + driver_name + "]";
m_rendering_engine->get_raw_device()->
setWindowCaption((utf8_to_wide(PROJECT_NAME_C) +
L" " + utf8_to_wide(g_version_hash) +
L" [" + wstrgettext("Main Menu") + L"]" +
L" [" + m_rendering_engine->getVideoDriver()->getName() + L"]" ).c_str());
setWindowCaption(utf8_to_wide(caption).c_str());
try { // This is used for catching disconnects
@ -248,10 +249,10 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
m_rendering_engine->get_video_driver()->setTextureCreationFlag(
video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map"));
#ifdef HAVE_TOUCHSCREENGUI
receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver);
g_touchscreengui = receiver->m_touchscreengui;
#endif
if (g_settings->getBool("enable_touch")) {
receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver);
g_touchscreengui = receiver->m_touchscreengui;
}
the_game(
kill,
@ -282,11 +283,11 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
m_rendering_engine->get_scene_manager()->clear();
#ifdef HAVE_TOUCHSCREENGUI
delete g_touchscreengui;
g_touchscreengui = NULL;
receiver->m_touchscreengui = NULL;
#endif
if (g_touchscreengui) {
delete g_touchscreengui;
g_touchscreengui = NULL;
receiver->m_touchscreengui = NULL;
}
/* Save the settings when leaving the game.
* This makes sure that setting changes made in-game are persisted even

@ -1305,7 +1305,7 @@ void ClientMap::updateTransparentMeshBuffers()
ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG);
u32 sorted_blocks = 0;
u32 unsorted_blocks = 0;
f32 sorting_distance_sq = pow(m_cache_transparency_sorting_distance * BS, 2.0f);
f32 sorting_distance_sq = std::pow(m_cache_transparency_sorting_distance * BS, 2.0f);
// Update the order of transparent mesh buffers in each mesh

@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/serialize.h"
#include "util/sha1.h"
#include "util/string.h"
#include <sstream>
static std::string getMediaCacheDir()
{
@ -41,7 +42,16 @@ bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &file
std::string sha1_hex = hex_encode(raw_hash);
if (!media_cache.exists(sha1_hex))
return media_cache.update(sha1_hex, filedata);
return true;
return false;
}
bool clientMediaUpdateCacheCopy(const std::string &raw_hash, const std::string &path)
{
FileCache media_cache(getMediaCacheDir());
std::string sha1_hex = hex_encode(raw_hash);
if (!media_cache.exists(sha1_hex))
return media_cache.updateCopyFile(sha1_hex, path);
return false;
}
/*
@ -189,10 +199,6 @@ void ClientMediaDownloader::initialStep(Client *client)
assert(m_uncached_received_count == 0);
// Create the media cache dir if we are likely to write to it
if (m_uncached_count != 0)
createCacheDirs();
// If we found all files in the cache, report this fact to the server.
// If the server reported no remote servers, immediately start
// conventional transfers. Note: if cURL support is not compiled in,
@ -511,18 +517,6 @@ IClientMediaDownloader::IClientMediaDownloader():
{
}
void IClientMediaDownloader::createCacheDirs()
{
if (!m_write_to_cache)
return;
std::string path = getMediaCacheDir();
if (!fs::CreateAllDirs(path)) {
errorstream << "Client: Could not create media cache directory: "
<< path << std::endl;
}
}
bool IClientMediaDownloader::tryLoadFromCache(const std::string &name,
const std::string &sha1, Client *client)
{
@ -547,11 +541,9 @@ bool IClientMediaDownloader::checkAndLoad(
// Compute actual checksum of data
std::string data_sha1;
{
SHA1 data_sha1_calculator;
data_sha1_calculator.addBytes(data.c_str(), data.size());
unsigned char *data_tmpdigest = data_sha1_calculator.getDigest();
data_sha1.assign((char*) data_tmpdigest, 20);
free(data_tmpdigest);
SHA1 ctx;
ctx.addBytes(data);
data_sha1 = ctx.getDigest();
}
// Check that received file matches announced checksum
@ -726,8 +718,6 @@ void SingleMediaDownloader::initialStep(Client *client)
if (isDone())
return;
createCacheDirs();
// If the server reported no remote servers, immediately fall back to
// conventional transfer.
if (!USE_CURL || m_remotes.empty()) {

@ -22,7 +22,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes.h"
#include "filecache.h"
#include "util/basic_macros.h"
#include <ostream>
#include <map>
#include <set>
#include <vector>
@ -35,10 +34,15 @@ struct HTTPFetchResult;
#define MTHASHSET_FILE_NAME "index.mth"
// Store file into media cache (unless it exists already)
// Validating the hash is responsibility of the caller
// Caller should check the hash.
// return true if something was updated
bool clientMediaUpdateCache(const std::string &raw_hash,
const std::string &filedata);
// Copy file on disk(!) into media cache (unless it exists already)
bool clientMediaUpdateCacheCopy(const std::string &raw_hash,
const std::string &path);
// more of a base class than an interface but this name was most convenient...
class IClientMediaDownloader
{
@ -81,8 +85,6 @@ protected:
virtual bool loadMedia(Client *client, const std::string &data,
const std::string &name) = 0;
void createCacheDirs();
bool tryLoadFromCache(const std::string &name, const std::string &sha1,
Client *client);

@ -1216,7 +1216,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
}
}
if (node && fabs(m_prop.automatic_rotate) > 0.001f) {
if (node && std::abs(m_prop.automatic_rotate) > 0.001f) {
// This is the child node's rotation. It is only used for automatic_rotate.
v3f local_rot = node->getRotation();
local_rot.Y = modulo360f(local_rot.Y - dtime * core::RADTODEG *

@ -77,7 +77,7 @@ MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector
scene::IMeshManipulator *mm):
data(input),
collector(output),
nodedef(data->m_client->ndef()),
nodedef(data->nodedef),
meshmanip(mm),
blockpos_nodes(data->m_blockpos * MAP_BLOCKSIZE),
enable_mesh_cache(g_settings->getBool("enable_mesh_cache") &&
@ -617,14 +617,14 @@ void MapblockMeshGenerator::calculateCornerLevels()
cur_liquid.corner_levels[k][i] = getCornerLevel(i, k);
}
f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
f32 MapblockMeshGenerator::getCornerLevel(int i, int k) const
{
float sum = 0;
int count = 0;
int air_count = 0;
for (int dk = 0; dk < 2; dk++)
for (int di = 0; di < 2; di++) {
LiquidData::NeighborData &neighbor_data = cur_liquid.neighbors[k + dk][i + di];
const LiquidData::NeighborData &neighbor_data = cur_liquid.neighbors[k + dk][i + di];
content_t content = neighbor_data.content;
// If top is liquid, draw starting from top of node

@ -138,7 +138,7 @@ private:
void prepareLiquidNodeDrawing();
void getLiquidNeighborhood();
void calculateCornerLevels();
f32 getCornerLevel(int i, int k);
f32 getCornerLevel(int i, int k) const;
void drawLiquidSides();
void drawLiquidTop();
void drawLiquidBottom();

@ -70,14 +70,13 @@ public:
}
void dereg(MtEvent::Type type, event_receive_func f, void *data) override
{
std::map<MtEvent::Type, Dest>::iterator i = m_dest.find(type);
auto i = m_dest.find(type);
if (i != m_dest.end()) {
std::list<FuncSpec> &funcs = i->second.funcs;
auto j = funcs.begin();
while (j != funcs.end()) {
for (auto j = funcs.begin(); j != funcs.end(); ) {
bool remove = (j->f == f && (!data || j->d == data));
if (remove)
funcs.erase(j++);
j = funcs.erase(j);
else
++j;
}

@ -28,6 +28,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <fstream>
#include <cstdlib>
void FileCache::createDir()
{
if (!fs::CreateAllDirs(m_dir)) {
errorstream << "Could not create cache directory: "
<< m_dir << std::endl;
}
}
bool FileCache::loadByPath(const std::string &path, std::ostream &os)
{
std::ifstream fis(path.c_str(), std::ios_base::binary);
@ -40,8 +48,8 @@ bool FileCache::loadByPath(const std::string &path, std::ostream &os)
bool bad = false;
for(;;){
char buf[1024];
fis.read(buf, 1024);
char buf[4096];
fis.read(buf, sizeof(buf));
std::streamsize len = fis.gcount();
os.write(buf, len);
if(fis.eof())
@ -59,8 +67,9 @@ bool FileCache::loadByPath(const std::string &path, std::ostream &os)
return !bad;
}
bool FileCache::updateByPath(const std::string &path, const std::string &data)
bool FileCache::updateByPath(const std::string &path, std::string_view data)
{
createDir();
std::ofstream file(path.c_str(), std::ios_base::binary |
std::ios_base::trunc);
@ -71,13 +80,13 @@ bool FileCache::updateByPath(const std::string &path, const std::string &data)
return false;
}
file.write(data.c_str(), data.length());
file << data;
file.close();
return !file.fail();
}
bool FileCache::update(const std::string &name, const std::string &data)
bool FileCache::update(const std::string &name, std::string_view data)
{
std::string path = m_dir + DIR_DELIM + name;
return updateByPath(path, data);
@ -95,3 +104,11 @@ bool FileCache::exists(const std::string &name)
std::ifstream fis(path.c_str(), std::ios_base::binary);
return fis.good();
}
bool FileCache::updateCopyFile(const std::string &name, const std::string &src_path)
{
std::string path = m_dir + DIR_DELIM + name;
createDir();
return fs::CopyFileContents(src_path, path);
}

@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iostream>
#include <string>
#include <string_view>
class FileCache
{
@ -31,13 +32,17 @@ public:
*/
FileCache(const std::string &dir) : m_dir(dir) {}
bool update(const std::string &name, const std::string &data);
bool update(const std::string &name, std::string_view data);
bool load(const std::string &name, std::ostream &os);
bool exists(const std::string &name);
// Copy another file on disk into the cache
bool updateCopyFile(const std::string &name, const std::string &src_path);
private:
std::string m_dir;
void createDir();
bool loadByPath(const std::string &path, std::ostream &os);
bool updateByPath(const std::string &path, const std::string &data);
bool updateByPath(const std::string &path, std::string_view data);
};

@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/mapblock_mesh.h"
#include "client/sound.h"
#include "clientmap.h"
#include "clientmedia.h" // For clientMediaUpdateCacheCopy
#include "clouds.h"
#include "config.h"
#include "content_cao.h"
@ -663,11 +664,7 @@ public:
}
};
#ifdef HAVE_TOUCHSCREENGUI
#define SIZE_TAG "size[11,5.5]"
#else
#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
#endif
#define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode)
/****************************************************************************
****************************************************************************/
@ -769,6 +766,7 @@ protected:
bool initSound();
bool createSingleplayerServer(const std::string &map_dir,
const SubgameSpec &gamespec, u16 port);
void copyServerClientCache();
// Client creation
bool createClient(const GameStartData &start_data);
@ -1019,13 +1017,11 @@ private:
// this happens in pause menu in singleplayer
bool m_is_paused = false;
#ifdef HAVE_TOUCHSCREENGUI
bool m_cache_hold_aux1;
bool m_touch_simulate_aux1 = false;
bool m_touch_use_crosshair;
inline bool isNoCrosshairAllowed() {
inline bool isTouchCrosshairDisabled() {
return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST;
}
#endif
#ifdef __ANDROID__
bool m_android_chat_open;
#endif
@ -1073,11 +1069,6 @@ Game::Game() :
&settingChangedCallback, this);
readSettings();
#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = false; // This is initialised properly later
#endif
}
@ -1180,9 +1171,7 @@ bool Game::startup(bool *kill,
m_first_loop_after_window_activation = true;
#ifdef HAVE_TOUCHSCREENGUI
m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair");
#endif
g_client_translations->clear();
@ -1217,10 +1206,8 @@ void Game::run()
set_light_table(g_settings->getFloat("display_gamma"));
#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = g_settings->getBool("fast_move")
m_touch_simulate_aux1 = g_settings->getBool("fast_move")
&& client->checkPrivilege("fast");
#endif
const irr::core::dimension2du initial_screen_size(
g_settings->getU16("screen_w"),
@ -1288,9 +1275,6 @@ void Game::run()
updateFrame(&graph, &stats, dtime, cam_view);
updateProfilerGraphs(&graph);
// Update if minimap has been disabled by the server
m_game_ui->m_flags.show_minimap &= client->shouldShowMinimap();
if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
showPauseMenu();
}
@ -1309,9 +1293,8 @@ void Game::shutdown()
// Clear text when exiting.
m_game_ui->clearText();
#ifdef HAVE_TOUCHSCREENGUI
g_touchscreengui->hide();
#endif
if (g_touchscreengui)
g_touchscreengui->hide();
showOverlayMessage(N_("Shutting down..."), 0, 0, false);
@ -1453,9 +1436,31 @@ bool Game::createSingleplayerServer(const std::string &map_dir,
false, nullptr, error_message);
server->start();
copyServerClientCache();
return true;
}
void Game::copyServerClientCache()
{
// It would be possible to let the client directly read the media files
// from where the server knows they are. But aside from being more complicated
// it would also *not* fill the media cache and cause slower joining of
// remote servers.
// (Imagine that you launch a game once locally and then connect to a server.)
assert(server);
auto map = server->getMediaList();
u32 n = 0;
for (auto &it : map) {
assert(it.first.size() == 20); // SHA1
if (clientMediaUpdateCacheCopy(it.first, it.second))
n++;
}
infostream << "Copied " << n << " files directly from server to client cache"
<< std::endl;
}
bool Game::createClient(const GameStartData &start_data)
{
showOverlayMessage(N_("Creating client..."), 0, 10);
@ -1499,11 +1504,10 @@ bool Game::createClient(const GameStartData &start_data)
if (client->modsLoaded())
client->getScript()->on_camera_ready(camera);
client->setCamera(camera);
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) {
g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
g_touchscreengui->setUseCrosshair(!isTouchCrosshairDisabled());
}
#endif
/* Clouds
*/
@ -1530,18 +1534,20 @@ bool Game::createClient(const GameStartData &start_data)
/* Set window caption
*/
std::wstring str = utf8_to_wide(PROJECT_NAME_C);
str += L" ";
str += utf8_to_wide(g_version_hash);
str += L" [";
str += simple_singleplayer_mode ? wstrgettext("Singleplayer")
: wstrgettext("Multiplayer");
str += L"]";
str += L" [";
str += driver->getName();
str += L"]";
#if IRRLICHT_VERSION_MT_REVISION >= 15
auto driver_name = driver->getName();
#else
auto driver_name = wide_to_utf8(driver->getName());
#endif
std::string str = std::string(PROJECT_NAME_C) +
" " + g_version_hash + " [";
str += simple_singleplayer_mode ? gettext("Singleplayer")
: gettext("Multiplayer");
str += "] [";
str += driver_name;
str += "]";
device->setWindowCaption(str.c_str());
device->setWindowCaption(utf8_to_wide(str).c_str());
LocalPlayer *player = client->getEnv().getLocalPlayer();
player->hurt_tilt_timer = 0;
@ -1571,10 +1577,8 @@ bool Game::initGui()
gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
-1, chat_backend, client, &g_menumgr);
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->init(texture_src);
#endif
return true;
}
@ -2008,18 +2012,17 @@ void Game::processUserInput(f32 dtime)
} else {
input->clear();
}
#ifdef HAVE_TOUCHSCREENGUI
g_touchscreengui->hide();
#endif
if (g_touchscreengui)
g_touchscreengui->hide();
} else {
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) {
/* on touchscreengui step may generate own input events which ain't
* what we want in case we just did clear them */
g_touchscreengui->show();
g_touchscreengui->step(dtime);
}
#endif
m_game_focused = true;
}
@ -2211,13 +2214,11 @@ void Game::processItemSelection(u16 *new_playeritem)
}
}
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) {
std::optional<u16> selection = g_touchscreengui->getHotbarSelection();
if (selection)
*new_playeritem = *selection;
}
#endif
// Clamp selection again in case it wasn't changed but max_item was
*new_playeritem = MYMIN(*new_playeritem, max_item);
@ -2368,9 +2369,7 @@ void Game::toggleFast()
m_game_ui->showTranslatedStatusText("Fast mode disabled");
}
#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = fast_move && has_fast_privs;
#endif
m_touch_simulate_aux1 = fast_move && has_fast_privs;
}
@ -2455,26 +2454,14 @@ void Game::toggleMinimap(bool shift_pressed)
// -->
u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
m_game_ui->m_flags.show_minimap = false;
} else {
// If radar is disabled, try to find a non radar mode or fall back to 0
if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
while (mapper->getModeIndex() &&
mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
mapper->nextMode();
m_game_ui->m_flags.show_minimap = mapper->getModeDef().type != MINIMAP_TYPE_OFF;
// <--
// End of 'not so satifying code'
if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
(hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
else
m_game_ui->showTranslatedStatusText(
"Minimap currently disabled by game or mod");
}
if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) {
// If radar is disabled, try to find a non radar mode or fall back to 0
if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
while (mapper->getModeIndex() &&
mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
mapper->nextMode();
}
m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
}
void Game::toggleFog()
@ -2627,10 +2614,8 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
Since Minetest has its own code to synthesize mouse events from touch events,
this results in duplicated input. To avoid that, we don't enable relative
mouse mode if we're in touchscreen mode. */
#ifndef HAVE_TOUCHSCREENGUI
if (cur_control)
cur_control->setRelativeMode(!isMenuActive());
#endif
cur_control->setRelativeMode(!g_touchscreengui && !isMenuActive());
if ((device->isWindowActive() && device->isWindowFocused()
&& !isMenuActive()) || input->isRandom()) {
@ -2668,17 +2653,15 @@ f32 Game::getSensitivityScaleFactor() const
// Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
// 16:9 aspect ratio to minimize disruption of existing sensitivity
// settings.
return tan(fov_y / 2.0f) * 1.3763818698f;
return std::tan(fov_y / 2.0f) * 1.3763819f;
}
void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
{
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) {
cam->camera_yaw += g_touchscreengui->getYawChange();
cam->camera_pitch += g_touchscreengui->getPitchChange();
} else {
#endif
v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
v2s32 dist = input->getMousePos() - center;
@ -2692,9 +2675,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
if (dist.X != 0 || dist.Y != 0)
input->setMousePos(center.X, center.Y);
#ifdef HAVE_TOUCHSCREENGUI
}
#endif
if (m_cache_enable_joysticks) {
f32 sens_scale = getSensitivityScaleFactor();
@ -2735,20 +2716,18 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
client->activeObjectsReceived() && !player->isDead()) {
control.movement_speed = 1.0f;
// sideways movement only
float dx = sin(control.movement_direction);
control.movement_direction = atan2(dx, 1.0f);
float dx = std::sin(control.movement_direction);
control.movement_direction = std::atan2(dx, 1.0f);
}
#ifdef HAVE_TOUCHSCREENGUI
/* For touch, simulate holding down AUX1 (fast move) if the user has
* the fast_move setting toggled on. If there is an aux1 key defined for
* touch then its meaning is inverted (i.e. holding aux1 means walk and
* not fast)
*/
if (m_cache_hold_aux1) {
if (g_touchscreengui && m_touch_simulate_aux1) {
control.aux1 = control.aux1 ^ true;
}
#endif
client->setPlayerControl(control);
@ -2777,10 +2756,16 @@ inline void Game::step(f32 dtime)
g_settings->getFloat("fps_max_unfocused") :
g_settings->getFloat("fps_max");
fps_max = std::max(fps_max, 1.0f);
float steplen = 1.0f / fps_max;
/*
* Unless you have a barebones game, running the server at more than 60Hz
* is hardly realistic and you're at the point of diminishing returns.
* fps_max is also not necessarily anywhere near the FPS actually achieved
* (also due to vsync).
*/
fps_max = std::min(fps_max, 60.0f);
server->setStepSettings(Server::StepSettings{
steplen,
1.0f / fps_max,
m_is_paused
});
@ -3229,10 +3214,8 @@ void Game::updateCamera(f32 dtime)
camera->toggleCameraMode();
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
#endif
g_touchscreengui->setUseCrosshair(!isTouchCrosshairDisabled());
// Make the player visible depending on camera mode.
playercao->updateMeshCulling();
@ -3333,8 +3316,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
}
shootline.end = shootline.start + camera_direction * BS * d;
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui && isNoCrosshairAllowed()) {
if (g_touchscreengui && isTouchCrosshairDisabled()) {
shootline = g_touchscreengui->getShootline();
// Scale shootline to the acual distance the player can reach
shootline.end = shootline.start +
@ -3342,7 +3324,6 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
shootline.start += intToFloat(camera_offset, BS);
shootline.end += intToFloat(camera_offset, BS);
}
#endif
PointedThing pointed = updatePointedThing(shootline,
selected_def.liquids_pointable,
@ -3353,10 +3334,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
if (pointed != runData.pointed_old)
infostream << "Pointing at " << pointed.dump() << std::endl;
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed));
#endif
// Note that updating the selection mesh every frame is not particularly efficient,
// but the halo rendering code is already inefficient so there's no point in optimizing it here
@ -3734,11 +3713,11 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
break;
};
case NDT_SIGNLIKE: {
rotate90 = abs(pdir.X) < abs(pdir.Z);
rotate90 = std::abs(pdir.X) < std::abs(pdir.Z);
break;
}
default: {
rotate90 = abs(pdir.X) > abs(pdir.Z);
rotate90 = std::abs(pdir.X) > std::abs(pdir.Z);
break;
}
}
@ -4276,14 +4255,14 @@ void Game::updateShadows()
if (!shadow)
return;
float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
float in_timeofday = std::fmod(runData.time_of_day_smooth, 1.0f);
float timeoftheday = getWickedTimeOfDay(in_timeofday);
bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
timeoftheday = std::fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
const float offset_constant = 10000.0f;
v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
@ -4339,12 +4318,12 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
bool draw_crosshair = (
(player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
(this->camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
#ifdef HAVE_TOUCHSCREENGUI
if (this->isNoCrosshairAllowed())
if (g_touchscreengui && isTouchCrosshairDisabled())
draw_crosshair = false;
#endif
this->m_rendering_engine->draw_scene(sky_color, this->m_game_ui->m_flags.show_hud,
this->m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
draw_wield_tool, draw_crosshair);
/*
Profiler graph
@ -4490,21 +4469,23 @@ void Game::showDeathFormspec()
#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
void Game::showPauseMenu()
{
#ifdef HAVE_TOUCHSCREENGUI
static const std::string control_text = strgettext("Controls:\n"
"No menu open:\n"
"- slide finger: look around\n"
"- tap: place/punch/use (default)\n"
"- long tap: dig/use (default)\n"
"Menu/inventory open:\n"
"- double tap (outside):\n"
" --> close\n"
"- touch stack, touch slot:\n"
" --> move stack\n"
"- touch&drag, tap 2nd finger\n"
" --> place single item to slot\n"
);
#endif
std::string control_text;
if (g_touchscreengui) {
control_text = strgettext("Controls:\n"
"No menu open:\n"
"- slide finger: look around\n"
"- tap: place/punch/use (default)\n"
"- long tap: dig/use (default)\n"
"Menu/inventory open:\n"
"- double tap (outside):\n"
" --> close\n"
"- touch stack, touch slot:\n"
" --> move stack\n"
"- touch&drag, tap 2nd finger\n"
" --> place single item to slot\n"
);
}
float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
std::ostringstream os;
@ -4534,9 +4515,9 @@ void Game::showPauseMenu()
<< strgettext("Exit to Menu") << "]";
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
<< strgettext("Exit to OS") << "]";
#ifdef HAVE_TOUCHSCREENGUI
if (!control_text.empty()) {
os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]";
#endif
}
os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
<< "\n"
<< strgettext("Game info:") << "\n";

@ -219,11 +219,6 @@ void GameUI::initFlags()
m_flags.show_minimal_debug = g_settings->getBool("show_debug");
}
void GameUI::showMinimap(bool show)
{
m_flags.show_minimap = show;
}
void GameUI::showTranslatedStatusText(const char *str)
{
showStatusText(wstrgettext(str));

@ -57,7 +57,6 @@ public:
{
bool show_chat = true;
bool show_hud = true;
bool show_minimap = false;
bool show_minimal_debug = false;
bool show_basic_debug = false;
bool show_profiler_graph = false;
@ -71,8 +70,6 @@ public:
void initFlags();
const Flags &getFlags() const { return m_flags; }
void showMinimap(bool show);
inline void setInfoText(const std::wstring &str) { m_infotext = str; }
inline void clearInfoText() { m_infotext.clear(); }

@ -39,10 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "wieldmesh.h"
#include "client/renderingengine.h"
#include "client/minimap.h"
#ifdef HAVE_TOUCHSCREENGUI
#include "gui/touchscreengui.h"
#endif
#define OBJECT_CROSSHAIR_LINE_SIZE 8
#define CROSSHAIR_LINE_SIZE 10
@ -292,10 +289,8 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
drawItem(mainlist->getItem(i), item_rect, (i + 1) == selectitem);
#ifdef HAVE_TOUCHSCREENGUI
if (is_hotbar && g_touchscreengui)
g_touchscreengui->registerHotbarRect(i, item_rect);
#endif
}
}
@ -340,6 +335,14 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
std::vector<HudElement*> elems;
elems.reserve(player->maxHudId());
// Add builtin minimap if the server doesn't send it.
HudElement minimap;
if (client->getProtoVersion() < 44 && (player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
minimap = {HUD_ELEM_MINIMAP, v2f(1, 0), "", v2f(), "", 0 , 0, 0, v2f(-1, 1),
v2f(-10, 10), v3f(), v2s32(256, 256), 0, "", 0};
elems.push_back(&minimap);
}
for (size_t i = 0; i != player->maxHudId(); i++) {
HudElement *e = player->getHud(i);
if (!e)
@ -741,10 +744,8 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
void Hud::drawHotbar(u16 playeritem)
{
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->resetHotbarRects();
#endif
InventoryList *mainlist = inventory->getList("main");
if (mainlist == NULL) {
@ -1179,17 +1180,26 @@ void drawItemStack(
(1 - wear) * progressrect.LowerRightCorner.X;
// Compute progressbar color
// default scheme:
// wear = 0.0: green
// wear = 0.5: yellow
// wear = 1.0: red
video::SColor color(255, 255, 255, 255);
int wear_i = MYMIN(std::floor(wear * 600), 511);
wear_i = MYMIN(wear_i + 10, 511);
if (wear_i <= 255)
color.set(255, wear_i, 255, 0);
else
color.set(255, 255, 511 - wear_i, 0);
video::SColor color;
auto barParams = item.getWearBarParams(client->idef());
if (barParams.has_value()) {
f32 durabilityPercent = 1.0 - wear;
color = barParams->getWearBarColor(durabilityPercent);
} else {
color = video::SColor(255, 255, 255, 255);
int wear_i = MYMIN(std::floor(wear * 600), 511);
wear_i = MYMIN(wear_i + 10, 511);
if (wear_i <= 255)
color.set(255, wear_i, 255, 0);
else
color.set(255, 255, 511 - wear_i, 0);
}
core::rect<s32> progressrect2 = progressrect;
progressrect2.LowerRightCorner.X = progressmid;

@ -65,20 +65,27 @@ public:
}
};
/* Fill in RGB values for transparent pixels, to correct for odd colors
* appearing at borders when blending. This is because many PNG optimizers
* like to discard RGB values of transparent pixels, but when blending then
* with non-transparent neighbors, their RGB values will show up nonetheless.
*
* This function modifies the original image in-place.
*
* Parameter "threshold" is the alpha level below which pixels are considered
* transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
* 0 when alpha blending is used.
*/
void imageCleanTransparent(video::IImage *src, u32 threshold)
template <bool IS_A8R8G8B8>
static void imageCleanTransparentWithInlining(video::IImage *src, u32 threshold)
{
core::dimension2d<u32> dim = src->getDimension();
void *const src_data = src->getData();
const core::dimension2d<u32> dim = src->getDimension();
auto get_pixel = [=](u32 x, u32 y) -> video::SColor {
if constexpr (IS_A8R8G8B8) {
return reinterpret_cast<u32 *>(src_data)[y*dim.Width + x];
} else {
return src->getPixel(x, y);
}
};
auto set_pixel = [=](u32 x, u32 y, video::SColor color) {
if constexpr (IS_A8R8G8B8) {
u32 *dest = &reinterpret_cast<u32 *>(src_data)[y*dim.Width + x];
*dest = color.color;
} else {
src->setPixel(x, y, color);
}
};
Bitmap bitmap(dim.Width, dim.Height);
@ -86,7 +93,7 @@ void imageCleanTransparent(video::IImage *src, u32 threshold)
// Note: loop y around x for better cache locality.
for (u32 ctry = 0; ctry < dim.Height; ctry++)
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {
if (src->getPixel(ctrx, ctry).getAlpha() > threshold)
if (get_pixel(ctrx, ctry).getAlpha() > threshold)
bitmap.set(ctrx, ctry);
}
@ -125,7 +132,7 @@ void imageCleanTransparent(video::IImage *src, u32 threshold)
// Add RGB values weighted by alpha IF the pixel is opaque, otherwise
// use full weight since we want to propagate colors.
video::SColor d = src->getPixel(sx, sy);
video::SColor d = get_pixel(sx, sy);
u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha();
ss += a;
sr += a * d.getRed();
@ -135,11 +142,11 @@ void imageCleanTransparent(video::IImage *src, u32 threshold)
// Set pixel to average weighted by alpha
if (ss > 0) {
video::SColor c = src->getPixel(ctrx, ctry);
video::SColor c = get_pixel(ctrx, ctry);
c.setRed(sr / ss);
c.setGreen(sg / ss);
c.setBlue(sb / ss);
src->setPixel(ctrx, ctry, c);
set_pixel(ctrx, ctry, c);
newmap.set(ctrx, ctry);
}
}
@ -154,6 +161,25 @@ void imageCleanTransparent(video::IImage *src, u32 threshold)
}
}
/* Fill in RGB values for transparent pixels, to correct for odd colors
* appearing at borders when blending. This is because many PNG optimizers
* like to discard RGB values of transparent pixels, but when blending then
* with non-transparent neighbors, their RGB values will show up nonetheless.
*
* This function modifies the original image in-place.
*
* Parameter "threshold" is the alpha level below which pixels are considered
* transparent. Should be 127 when the texture is used with ALPHA_CHANNEL_REF,
* 0 when alpha blending is used.
*/
void imageCleanTransparent(video::IImage *src, u32 threshold)
{
if (src->getColorFormat() == video::ECF_A8R8G8B8)
imageCleanTransparentWithInlining<true>(src, threshold);
else
imageCleanTransparentWithInlining<false>(src, threshold);
}
/* Scale a region of an image into another image, using nearest-neighbor with
* anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
* to prevent non-integer scaling ratio artifacts. Note that this may cause

@ -102,11 +102,9 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
React to nothing here if a menu is active
*/
if (isMenuActive()) {
#ifdef HAVE_TOUCHSCREENGUI
if (m_touchscreengui) {
m_touchscreengui->setVisible(false);
}
#endif
return g_menumgr.preprocessEvent(event);
}
@ -130,12 +128,10 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
return true;
}
#ifdef HAVE_TOUCHSCREENGUI
} else if (m_touchscreengui && event.EventType == irr::EET_TOUCH_INPUT_EVENT) {
// In case of touchscreengui, we have to handle different events
m_touchscreengui->translateEvent(event);
return true;
#endif
} else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
// joystick may be nullptr if game is launched with '--random-input' parameter

@ -24,10 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <list>
#include "keycode.h"
#include "renderingengine.h"
#ifdef HAVE_TOUCHSCREENGUI
#include "gui/touchscreengui.h"
#endif
class InputHandler;
@ -203,16 +200,12 @@ public:
MyEventReceiver()
{
#ifdef HAVE_TOUCHSCREENGUI
m_touchscreengui = NULL;
#endif
}
JoystickController *joystick = nullptr;
#ifdef HAVE_TOUCHSCREENGUI
TouchScreenGUI *m_touchscreengui;
#endif
private:
s32 mouse_wheel = 0;
@ -332,11 +325,9 @@ public:
return 0.0f;
return 1.0f; // If there is a keyboard event, assume maximum speed
}
#ifdef HAVE_TOUCHSCREENGUI
return m_receiver->m_touchscreengui->getMovementSpeed();
#else
if (m_receiver->m_touchscreengui && m_receiver->m_touchscreengui->getMovementSpeed())
return m_receiver->m_touchscreengui->getMovementSpeed();
return joystick.getMovementSpeed();
#endif
}
virtual float getMovementDirection()
@ -355,12 +346,9 @@ public:
if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */
return atan2(x, z);
else
#ifdef HAVE_TOUCHSCREENGUI
else if (m_receiver->m_touchscreengui && m_receiver->m_touchscreengui->getMovementDirection())
return m_receiver->m_touchscreengui->getMovementDirection();
#else
return joystick.getMovementDirection();
#endif
return joystick.getMovementDirection();
}
virtual bool cancelPressed()

@ -318,12 +318,14 @@ float JoystickController::getAxisWithoutDead(JoystickAxis axis)
float JoystickController::getMovementDirection()
{
return atan2(getAxisWithoutDead(JA_SIDEWARD_MOVE), -getAxisWithoutDead(JA_FORWARD_MOVE));
return std::atan2(getAxisWithoutDead(JA_SIDEWARD_MOVE),
-getAxisWithoutDead(JA_FORWARD_MOVE));
}
float JoystickController::getMovementSpeed()
{
float speed = sqrt(pow(getAxisWithoutDead(JA_FORWARD_MOVE), 2) + pow(getAxisWithoutDead(JA_SIDEWARD_MOVE), 2));
float speed = std::sqrt(std::pow(getAxisWithoutDead(JA_FORWARD_MOVE), 2) +
std::pow(getAxisWithoutDead(JA_SIDEWARD_MOVE), 2));
if (speed > 1.0f)
speed = 1.0f;
return speed;

@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include "keycode.h"
#include "exceptions.h"
#include "settings.h"
#include "log.h"
#include "debug.h"
@ -26,13 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/string.h"
#include "util/basic_macros.h"
class UnknownKeycode : public BaseException
{
public:
UnknownKeycode(const char *s) :
BaseException(s) {};
};
struct table_key {
const char *Name;
irr::EKEY_CODE Key;

@ -19,11 +19,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include "exceptions.h"
#include "irrlichttypes.h"
#include "Keycodes.h"
#include <IEventReceiver.h>
#include <string>
class UnknownKeycode : public BaseException
{
public:
UnknownKeycode(const char *s) :
BaseException(s) {};
};
/* A key press, consisting of either an Irrlicht keycode
or an actual char */

@ -600,7 +600,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
}
}
speedH = v3f(sin(control.movement_direction), 0.0f, cos(control.movement_direction));
speedH = v3f(std::sin(control.movement_direction), 0.0f,
std::cos(control.movement_direction));
if (m_autojump) {
// release autojump after a given time

@ -38,10 +38,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
MeshMakeData
*/
MeshMakeData::MeshMakeData(Client *client, bool use_shaders):
m_mesh_grid(client->getMeshGrid()),
side_length(MAP_BLOCKSIZE * m_mesh_grid.cell_size),
m_client(client),
MeshMakeData::MeshMakeData(const NodeDefManager *ndef, u16 side_length, bool use_shaders):
side_length(side_length),
nodedef(ndef),
m_use_shaders(use_shaders)
{}
@ -147,7 +146,7 @@ u16 getFaceLight(MapNode n, MapNode n2, const NodeDefManager *ndef)
static u16 getSmoothLightCombined(const v3s16 &p,
const std::array<v3s16,8> &dirs, MeshMakeData *data)
{
const NodeDefManager *ndef = data->m_client->ndef();
const NodeDefManager *ndef = data->nodedef;
u16 ambient_occlusion = 0;
u16 light_count = 0;
@ -360,7 +359,7 @@ static const v3s16 vertex_dirs_table[] = {
*/
void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, TileSpec &tile)
{
const NodeDefManager *ndef = data->m_client->ndef();
const NodeDefManager *ndef = data->nodedef;
const ContentFeatures &f = ndef->get(mn);
tile = f.tiles[tileindex];
bool has_crack = p == data->m_crack_pos_relative;
@ -380,7 +379,7 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data,
*/
void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *data, TileSpec &tile)
{
const NodeDefManager *ndef = data->m_client->ndef();
const NodeDefManager *ndef = data->nodedef;
// Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
// (0,0,1), (0,0,-1) or (0,0,0)
@ -635,9 +634,9 @@ void PartialMeshBuffer::afterDraw() const
MapBlockMesh
*/
MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
m_tsrc(data->m_client->getTextureSource()),
m_shdrsrc(data->m_client->getShaderSource()),
MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offset):
m_tsrc(client->getTextureSource()),
m_shdrsrc(client->getShaderSource()),
m_bounding_sphere_center((data->side_length * 0.5f - 0.5f) * BS),
m_animation_force_timer(0), // force initial animation
m_last_crack(-1),
@ -648,26 +647,27 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
m_enable_shaders = data->m_use_shaders;
m_enable_vbo = g_settings->getBool("enable_vbo");
auto mesh_grid = client->getMeshGrid();
v3s16 bp = data->m_blockpos;
// Only generate minimap mapblocks at even coordinates.
if (data->m_mesh_grid.isMeshPos(bp) && data->m_client->getMinimap()) {
m_minimap_mapblocks.resize(data->m_mesh_grid.getCellVolume(), nullptr);
if (mesh_grid.isMeshPos(bp) && client->getMinimap()) {
m_minimap_mapblocks.resize(mesh_grid.getCellVolume(), nullptr);
v3s16 ofs;
// See also client.cpp for the code that reads the array of minimap blocks.
for (ofs.Z = 0; ofs.Z < data->m_mesh_grid.cell_size; ofs.Z++)
for (ofs.Y = 0; ofs.Y < data->m_mesh_grid.cell_size; ofs.Y++)
for (ofs.X = 0; ofs.X < data->m_mesh_grid.cell_size; ofs.X++) {
for (ofs.Z = 0; ofs.Z < mesh_grid.cell_size; ofs.Z++)
for (ofs.Y = 0; ofs.Y < mesh_grid.cell_size; ofs.Y++)
for (ofs.X = 0; ofs.X < mesh_grid.cell_size; ofs.X++) {
v3s16 p = (bp + ofs) * MAP_BLOCKSIZE;
if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) {
MinimapMapblock *block = new MinimapMapblock;
m_minimap_mapblocks[data->m_mesh_grid.getOffsetIndex(ofs)] = block;
m_minimap_mapblocks[mesh_grid.getOffsetIndex(ofs)] = block;
block->getMinimapNodes(&data->m_vmanip, p);
}
}
}
v3f offset = intToFloat((data->m_blockpos - data->m_mesh_grid.getMeshPos(data->m_blockpos)) * MAP_BLOCKSIZE, BS);
v3f offset = intToFloat((data->m_blockpos - mesh_grid.getMeshPos(data->m_blockpos)) * MAP_BLOCKSIZE, BS);
MeshCollector collector(m_bounding_sphere_center, offset);
/*
Add special graphics:
@ -679,7 +679,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
{
MapblockMeshGenerator(data, &collector,
data->m_client->getSceneManager()->getMeshManipulator()).generate();
client->getSceneManager()->getMeshManipulator()).generate();
}
/*
@ -1011,7 +1011,7 @@ u8 get_solid_sides(MeshMakeData *data)
std::unordered_map<v3s16, u8> results;
v3s16 ofs;
v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE;
const NodeDefManager *ndef = data->m_client->ndef();
const NodeDefManager *ndef = data->nodedef;
u8 result = 0x3F; // all sides solid;

@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <unordered_map>
class Client;
class NodeDefManager;
class IShaderSource;
/*
@ -43,13 +44,12 @@ struct MeshMakeData
v3s16 m_blockpos = v3s16(-1337,-1337,-1337);
v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337);
bool m_smooth_lighting = false;
MeshGrid m_mesh_grid;
u16 side_length;
Client *m_client;
const NodeDefManager *nodedef;
bool m_use_shaders;
MeshMakeData(Client *client, bool use_shaders);
MeshMakeData(const NodeDefManager *ndef, u16 side_length, bool use_shaders);
/*
Copy block data manually (to allow optimizations by the caller)
@ -179,7 +179,7 @@ class MapBlockMesh
{
public:
// Builds the mesh given
MapBlockMesh(MeshMakeData *data, v3s16 camera_offset);
MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offset);
~MapBlockMesh();
// Main animation function, parameters:

@ -189,16 +189,17 @@ void MeshUpdateQueue::done(v3s16 pos)
void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q)
{
MeshMakeData *data = new MeshMakeData(m_client, m_cache_enable_shaders);
auto mesh_grid = m_client->getMeshGrid();
MeshMakeData *data = new MeshMakeData(m_client->ndef(), MAP_BLOCKSIZE * mesh_grid.cell_size, m_cache_enable_shaders);
q->data = data;
data->fillBlockDataBegin(q->p);
v3s16 pos;
int i = 0;
for (pos.X = q->p.X - 1; pos.X <= q->p.X + data->m_mesh_grid.cell_size; pos.X++)
for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + data->m_mesh_grid.cell_size; pos.Z++)
for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + data->m_mesh_grid.cell_size; pos.Y++) {
for (pos.X = q->p.X - 1; pos.X <= q->p.X + mesh_grid.cell_size; pos.X++)
for (pos.Z = q->p.Z - 1; pos.Z <= q->p.Z + mesh_grid.cell_size; pos.Z++)
for (pos.Y = q->p.Y - 1; pos.Y <= q->p.Y + mesh_grid.cell_size; pos.Y++) {
MapBlock *block = q->map_blocks[i++];
data->fillBlockData(pos, block ? block->getData() : block_placeholder.data);
}
@ -211,8 +212,8 @@ void MeshUpdateQueue::fillDataFromMapBlocks(QueuedMeshUpdate *q)
MeshUpdateWorkerThread
*/
MeshUpdateWorkerThread::MeshUpdateWorkerThread(MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset) :
UpdateThread("Mesh"), m_queue_in(queue_in), m_manager(manager), m_camera_offset(camera_offset)
MeshUpdateWorkerThread::MeshUpdateWorkerThread(Client *client, MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset) :
UpdateThread("Mesh"), m_client(client), m_queue_in(queue_in), m_manager(manager), m_camera_offset(camera_offset)
{
m_generation_interval = g_settings->getU16("mesh_generation_interval");
m_generation_interval = rangelim(m_generation_interval, 0, 50);
@ -226,7 +227,7 @@ void MeshUpdateWorkerThread::doUpdate()
sleep_ms(m_generation_interval);
ScopeProfiler sp(g_profiler, "Client: Mesh making (sum)");
MapBlockMesh *mesh_new = new MapBlockMesh(q->data, *m_camera_offset);
MapBlockMesh *mesh_new = new MapBlockMesh(m_client, q->data, *m_camera_offset);
@ -262,7 +263,7 @@ MeshUpdateManager::MeshUpdateManager(Client *client):
infostream << "MeshUpdateManager: using " << number_of_threads << " threads" << std::endl;
for (int i = 0; i < number_of_threads; i++)
m_workers.push_back(std::make_unique<MeshUpdateWorkerThread>(&m_queue_in, this, &m_camera_offset));
m_workers.push_back(std::make_unique<MeshUpdateWorkerThread>(client, &m_queue_in, this, &m_camera_offset));
}
void MeshUpdateManager::updateBlock(Map *map, v3s16 p, bool ack_block_to_server,

@ -109,12 +109,13 @@ class MeshUpdateManager;
class MeshUpdateWorkerThread : public UpdateThread
{
public:
MeshUpdateWorkerThread(MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset);
MeshUpdateWorkerThread(Client *client, MeshUpdateQueue *queue_in, MeshUpdateManager *manager, v3s16 *camera_offset);
protected:
virtual void doUpdate();
private:
Client *m_client;
MeshUpdateQueue *m_queue_in;
MeshUpdateManager *m_manager;
v3s16 *m_camera_offset;

@ -206,25 +206,6 @@ Minimap::Minimap(Client *client)
data->minimap_shape_round = g_settings->getBool("minimap_shape_round");
// Get round minimap textures
data->minimap_mask_round = driver->createImage(
m_tsrc->getTexture("minimap_mask_round.png"),
core::position2d<s32>(0, 0),
core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
data->minimap_overlay_round = m_tsrc->getTexture("minimap_overlay_round.png");
// Get square minimap textures
data->minimap_mask_square = driver->createImage(
m_tsrc->getTexture("minimap_mask_square.png"),
core::position2d<s32>(0, 0),
core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
data->minimap_overlay_square = m_tsrc->getTexture("minimap_overlay_square.png");
// Create player marker texture
data->player_marker = m_tsrc->getTexture("player_marker.png");
// Create object marker texture
data->object_marker_red = m_tsrc->getTexture("object_marker_red.png");
setModeIndex(0);
// Create mesh buffer for minimap
@ -243,8 +224,10 @@ Minimap::~Minimap()
m_meshbuffer->drop();
data->minimap_mask_round->drop();
data->minimap_mask_square->drop();
if (data->minimap_mask_round)
data->minimap_mask_round->drop();
if (data->minimap_mask_square)
data->minimap_mask_square->drop();
driver->removeTexture(data->texture);
driver->removeTexture(data->heightmap_texture);
@ -461,6 +444,29 @@ void Minimap::blitMinimapPixelsToImageSurface(
}
}
video::IImage *Minimap::getMinimapMask()
{
if (data->minimap_shape_round) {
if (!data->minimap_mask_round) {
// Get round minimap textures
data->minimap_mask_round = driver->createImage(
m_tsrc->getTexture("minimap_mask_round.png"),
core::position2d<s32>(0, 0),
core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
}
return data->minimap_mask_round;
}
if (!data->minimap_mask_square) {
// Get square minimap textures
data->minimap_mask_square = driver->createImage(
m_tsrc->getTexture("minimap_mask_square.png"),
core::position2d<s32>(0, 0),
core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
}
return data->minimap_mask_square;
}
video::ITexture *Minimap::getMinimapTexture()
{
// update minimap textures when new scan is ready
@ -509,16 +515,13 @@ video::ITexture *Minimap::getMinimapTexture()
map_image->copyToScaling(minimap_image);
map_image->drop();
video::IImage *minimap_mask = data->minimap_shape_round ?
data->minimap_mask_round : data->minimap_mask_square;
video::IImage *minimap_mask = getMinimapMask();
if (minimap_mask) {
for (s16 y = 0; y < MINIMAP_MAX_SY; y++)
for (s16 x = 0; x < MINIMAP_MAX_SX; x++) {
const video::SColor &mask_col = minimap_mask->getPixel(x, y);
if (!mask_col.getAlpha())
minimap_image->setPixel(x, y, video::SColor(0,0,0,0));
}
for (s16 y = 0; y < MINIMAP_MAX_SY; y++)
for (s16 x = 0; x < MINIMAP_MAX_SX; x++) {
const video::SColor &mask_col = minimap_mask->getPixel(x, y);
if (!mask_col.getAlpha())
minimap_image->setPixel(x, y, video::SColor(0,0,0,0));
}
if (data->texture)
@ -571,25 +574,22 @@ scene::SMeshBuffer *Minimap::getMinimapMeshBuffer()
return buf;
}
void Minimap::drawMinimap()
void Minimap::drawMinimap(core::rect<s32> rect)
{
// Non hud managed minimap drawing (legacy minimap)
v2u32 screensize = RenderingEngine::getWindowSize();
const u32 size = 0.25 * screensize.Y;
drawMinimap(core::rect<s32>(
screensize.X - size - 10, 10,
screensize.X - 10, size + 10));
}
void Minimap::drawMinimap(core::rect<s32> rect) {
if (data->mode.type == MINIMAP_TYPE_OFF)
return;
// Get textures
video::ITexture *minimap_texture = getMinimapTexture();
if (!minimap_texture)
return;
if (data->mode.type == MINIMAP_TYPE_OFF)
return;
if (!data->textures_initialised) {
data->minimap_overlay_round = m_tsrc->getTexture("minimap_overlay_round.png");
data->minimap_overlay_square = m_tsrc->getTexture("minimap_overlay_square.png");
data->player_marker = m_tsrc->getTexture("player_marker.png");
data->object_marker_red = m_tsrc->getTexture("object_marker_red.png");
data->textures_initialised = true;
}
updateActiveMarkers();
@ -700,8 +700,7 @@ void Minimap::removeMarker(MinimapMarker **m)
void Minimap::updateActiveMarkers()
{
video::IImage *minimap_mask = data->minimap_shape_round ?
data->minimap_mask_round : data->minimap_mask_square;
video::IImage *minimap_mask = getMinimapMask();
m_active_markers.clear();
v3f cam_offset = intToFloat(client->getCamera()->getOffset(), BS);

@ -79,6 +79,7 @@ struct MinimapData {
video::IImage *minimap_mask_square = nullptr;
video::ITexture *texture = nullptr;
video::ITexture *heightmap_texture = nullptr;
bool textures_initialised = false; // True if the following textures are not nullptrs.
video::ITexture *minimap_overlay_round = nullptr;
video::ITexture *minimap_overlay_square = nullptr;
video::ITexture *player_marker = nullptr;
@ -140,6 +141,7 @@ public:
MinimapModeDef getModeDef() const { return data->mode; }
video::IImage *getMinimapMask();
video::ITexture *getMinimapTexture();
void blitMinimapPixelsToImageRadar(video::IImage *map_image);
@ -152,7 +154,6 @@ public:
void removeMarker(MinimapMarker **marker);
void updateActiveMarkers();
void drawMinimap();
void drawMinimap(core::rect<s32> rect);
video::IVideoDriver *driver;

@ -213,10 +213,10 @@ void Particle::step(float dtime)
if (m_p.animation.type != TAT_NONE) {
m_animation_time += dtime;
int frame_length_i, frame_count;
int frame_length_i = 0;
m_p.animation.determineParams(
m_material.getTexture(0)->getSize(),
&frame_count, &frame_length_i, NULL);
NULL, &frame_length_i, NULL);
float frame_length = frame_length_i / 1000.0;
while (m_animation_time > frame_length) {
m_animation_frame++;

@ -36,7 +36,7 @@ RenderingCore::~RenderingCore()
delete shadow_renderer;
}
void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_minimap,
void RenderingCore::draw(video::SColor _skycolor, bool _show_hud,
bool _draw_wield_tool, bool _draw_crosshair)
{
v2u32 screensize = device->getVideoDriver()->getScreenSize();
@ -46,7 +46,6 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min
context.draw_crosshair = _draw_crosshair;
context.draw_wield_tool = _draw_wield_tool;
context.show_hud = _show_hud;
context.show_minimap = _show_minimap;
pipeline->reset(context);
pipeline->run(context);
@ -55,4 +54,4 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min
v2u32 RenderingCore::getVirtualSize() const
{
return virtual_size;
}
}

@ -53,7 +53,7 @@ public:
RenderingCore &operator=(const RenderingCore &) = delete;
RenderingCore &operator=(RenderingCore &&) = delete;
void draw(video::SColor _skycolor, bool _show_hud, bool _show_minimap,
void draw(video::SColor _skycolor, bool _show_hud,
bool _draw_wield_tool, bool _draw_crosshair);
v2u32 getVirtualSize() const;

@ -46,7 +46,6 @@ struct PipelineContext
v2u32 target_size;
bool show_hud {true};
bool show_minimap {true};
bool draw_wield_tool {true};
bool draw_crosshair {true};
};

@ -64,9 +64,6 @@ void DrawHUD::run(PipelineContext &context)
context.hud->drawHotbar(context.client->getEnv().getLocalPlayer()->getWieldIndex());
context.hud->drawLuaElements(context.client->getCamera()->getOffset());
context.client->getCamera()->drawNametags();
auto mapper = context.client->getMinimap();
if (mapper && context.show_minimap)
mapper->drawMinimap();
}
context.device->getGUIEnvironment()->drawAll();
}
@ -106,7 +103,7 @@ void UpscaleStep::run(PipelineContext &context)
std::unique_ptr<RenderStep> create3DStage(Client *client, v2f scale)
{
RenderStep *step = new Draw3D();
if (g_settings->getBool("enable_shaders")) {
if (g_settings->getBool("enable_shaders") && g_settings->getBool("enable_post_processing")) {
RenderPipeline *pipeline = new RenderPipeline();
pipeline->addStep(pipeline->own(std::unique_ptr<RenderStep>(step)));
@ -131,7 +128,7 @@ RenderStep* addUpscaling(RenderPipeline *pipeline, RenderStep *previousStep, v2f
return previousStep;
// When shaders are enabled, post-processing pipeline takes care of rescaling
if (g_settings->getBool("enable_shaders"))
if (g_settings->getBool("enable_shaders") && g_settings->getBool("enable_post_processing"))
return previousStep;

@ -152,6 +152,9 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
driver = m_device->getVideoDriver();
infostream << "Using the " << RenderingEngine::getVideoDriverInfo(driver->getDriverType()).friendly_name << " video driver" << std::endl;
// This changes the minimum allowed number of vertices in a VBO. Default is 500.
driver->setMinHardwareBufferVertexCount(4);
s_singleton = this;
auto skin = createSkin(m_device->getGUIEnvironment(),
@ -319,9 +322,9 @@ void RenderingEngine::finalize()
}
void RenderingEngine::draw_scene(video::SColor skycolor, bool show_hud,
bool show_minimap, bool draw_wield_tool, bool draw_crosshair)
bool draw_wield_tool, bool draw_crosshair)
{
core->draw(skycolor, show_hud, show_minimap, draw_wield_tool, draw_crosshair);
core->draw(skycolor, show_hud, draw_wield_tool, draw_crosshair);
}
const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_TYPE type)

@ -118,7 +118,7 @@ public:
float dtime = 0, int percent = 0, bool sky = true);
void draw_scene(video::SColor skycolor, bool show_hud,
bool show_minimap, bool draw_wield_tool, bool draw_crosshair);
bool draw_wield_tool, bool draw_crosshair);
void initialize(Client *client, Hud *hud);
void finalize();

@ -804,7 +804,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
}
void dumpShaderProgram(std::ostream &output_stream,
const std::string &program_type, const std::string &program)
const std::string &program_type, std::string_view program)
{
output_stream << program_type << " shader program:" << std::endl <<
"----------------------------------" << std::endl;

@ -192,4 +192,4 @@ public:
IWritableShaderSource *createShaderSource();
void dumpShaderProgram(std::ostream &output_stream,
const std::string &program_type, const std::string &program);
const std::string &program_type, std::string_view program);

@ -171,8 +171,8 @@ f32 ShadowRenderer::getMaxShadowFar() const
void ShadowRenderer::setShadowIntensity(float shadow_intensity)
{
m_shadow_strength = pow(shadow_intensity, 1.0f / m_shadow_strength_gamma);
if (m_shadow_strength > 1E-2)
m_shadow_strength = std::pow(shadow_intensity, 1.0f / m_shadow_strength_gamma);
if (m_shadow_strength > 1e-2f)
enable();
else
disable();
@ -183,8 +183,12 @@ void ShadowRenderer::addNodeToShadowList(
{
m_shadow_node_array.emplace_back(node, shadowMode);
// node should never be ClientMap
#if IRRLICHT_VERSION_MT_REVISION >= 15
assert(!node->getName().has_value() || *node->getName() != "ClientMap");
#else
// TODO: Remove this as soon as we require 1.9.0mt15
assert(strcmp(node->getName(), "ClientMap") != 0);
#endif
node->forEachMaterial([this] (auto &mat) {
mat.setTexture(TEXTURE_LAYER_SHADOW, shadowMapTextureFinal);
});

@ -680,7 +680,7 @@ void Sky::draw_stars(video::IVideoDriver * driver, float wicked_time_of_day)
float tod = wicked_time_of_day < 0.5f ? wicked_time_of_day : (1.0f - wicked_time_of_day);
float day_opacity = clamp(m_star_params.day_opacity, 0.0f, 1.0f);
float starbrightness = (0.25f - fabs(tod)) * 20.0f;
float starbrightness = (0.25f - std::abs(tod)) * 20.0f;
float alpha = clamp(starbrightness, day_opacity, 1.0f);
m_star_color = m_star_params.starcolor;

@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "guiscalingfilter.h"
#include "renderingengine.h"
#include "util/base64.h"
#include "irrlicht_changes/printing.h"
/*
A cache from texture name to texture path
@ -403,7 +404,7 @@ private:
// Generate image based on a string like "stone.png" or "[crack:1:0".
// if baseimg is NULL, it is created. Otherwise stuff is made on it.
// source_image_names is important to determine when to flush the image from a cache (dynamic media)
bool generateImagePart(std::string part_of_name, video::IImage *& baseimg, std::set<std::string> &source_image_names);
bool generateImagePart(std::string_view part_of_name, video::IImage *& baseimg, std::set<std::string> &source_image_names);
/*! Generates an image from a full string like
* "stone.png^mineral_coal.png^[crack:1:0".
@ -411,7 +412,7 @@ private:
* The returned Image should be dropped.
* source_image_names is important to determine when to flush the image from a cache (dynamic media)
*/
video::IImage* generateImage(const std::string &name, std::set<std::string> &source_image_names);
video::IImage* generateImage(std::string_view name, std::set<std::string> &source_image_names);
// Thread-safe cache of what source images are known (true = known)
MutexedMap<std::string, bool> m_source_image_existence;
@ -538,14 +539,11 @@ u32 TextureSource::getTextureId(const std::string &name)
// Draw an image on top of another one, using the alpha channel of the
// source image
// overlay: only modify destination pixels that are fully opaque.
template<bool overlay = false>
static void blit_with_alpha(video::IImage *src, video::IImage *dst,
v2s32 src_pos, v2s32 dst_pos, v2u32 size);
// Like blit_with_alpha, but only modifies destination pixels that
// are fully opaque
static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
v2s32 src_pos, v2s32 dst_pos, v2u32 size);
// Apply a color to an image. Uses an int (0-255) to calculate the ratio.
// If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
// color alpha with the destination alpha.
@ -595,7 +593,7 @@ static void draw_crack(video::IImage *crack, video::IImage *dst,
// Brighten image
void brighten(video::IImage *image);
// Parse a transform name
u32 parseImageTransform(const std::string& s);
u32 parseImageTransform(std::string_view s);
// Apply transform to image dimension
core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
// Apply transform to image data
@ -965,7 +963,8 @@ static video::IImage *createInventoryCubeImage(
return result;
}
video::IImage* TextureSource::generateImage(const std::string &name, std::set<std::string> &source_image_names)
video::IImage* TextureSource::generateImage(std::string_view name,
std::set<std::string> &source_image_names)
{
// Get the base image
@ -1026,15 +1025,15 @@ video::IImage* TextureSource::generateImage(const std::string &name, std::set<st
according to it
*/
std::string last_part_of_name = name.substr(last_separator_pos + 1);
auto last_part_of_name = name.substr(last_separator_pos + 1);
/*
If this name is enclosed in parentheses, generate it
and blit it onto the base image
*/
if (last_part_of_name[0] == paren_open
&& last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
std::string name2 = last_part_of_name.substr(1,
&& last_part_of_name.back() == paren_close) {
auto name2 = last_part_of_name.substr(1,
last_part_of_name.size() - 2);
video::IImage *tmp = generateImage(name2, source_image_names);
if (!tmp) {
@ -1201,7 +1200,7 @@ void blitBaseImage(video::IImage* &src, video::IImage* &dst)
} \
} while(0)
bool TextureSource::generateImagePart(std::string part_of_name,
bool TextureSource::generateImagePart(std::string_view part_of_name,
video::IImage *& baseimg, std::set<std::string> &source_image_names)
{
const char escape = '\\'; // same as in generateImage()
@ -1218,8 +1217,9 @@ bool TextureSource::generateImagePart(std::string part_of_name,
// Stuff starting with [ are special commands
if (part_of_name.empty() || part_of_name[0] != '[') {
source_image_names.insert(part_of_name);
video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
std::string part_s(part_of_name);
source_image_names.insert(part_s);
video::IImage *image = m_sourcecache.getOrLoad(part_s);
if (!image) {
// Do not create the dummy texture
if (part_of_name.empty())
@ -1323,37 +1323,45 @@ bool TextureSource::generateImagePart(std::string part_of_name,
sf.next(":");
u32 w0 = stoi(sf.next("x"));
u32 h0 = stoi(sf.next(":"));
CHECK_DIM(w0, h0);
core::dimension2d<u32> dim(w0,h0);
if (baseimg == NULL) {
baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
if (!baseimg) {
CHECK_DIM(w0, h0);
baseimg = driver->createImage(video::ECF_A8R8G8B8, {w0, h0});
baseimg->fill(video::SColor(0,0,0,0));
}
while (!sf.at_end()) {
u32 x = stoi(sf.next(","));
u32 y = stoi(sf.next("="));
v2s32 pos_base;
pos_base.X = stoi(sf.next(","));
pos_base.Y = stoi(sf.next("="));
std::string filename = unescape_string(sf.next_esc(":", escape), escape);
if (x >= w0 || y >= h0)
COMPLAIN_INVALID("X or Y offset");
infostream<<"Adding \""<<filename
<<"\" to combined ("<<x<<","<<y<<")"
<<std::endl;
auto basedim = baseimg->getDimension();
if (pos_base.X > (s32)basedim.Width || pos_base.Y > (s32)basedim.Height) {
warningstream << "generateImagePart(): Skipping \""
<< filename << "\" as it's out-of-bounds " << pos_base
<< " for [combine" << std::endl;
continue;
}
infostream << "Adding \"" << filename<< "\" to combined "
<< pos_base << std::endl;
video::IImage *img = generateImage(filename, source_image_names);
if (img) {
core::dimension2d<u32> dim = img->getDimension();
core::position2d<s32> pos_base(x, y);
video::IImage *img2 =
driver->createImage(video::ECF_A8R8G8B8, dim);
img->copyTo(img2);
img->drop();
blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
img2->drop();
} else {
if (!img) {
errorstream << "generateImagePart(): Failed to load image \""
<< filename << "\" for [combine" << std::endl;
continue;
}
const auto dim = img->getDimension();
if (pos_base.X + dim.Width <= 0 || pos_base.Y + dim.Height <= 0) {
warningstream << "generateImagePart(): Skipping \""
<< filename << "\" as it's out-of-bounds " << pos_base
<< " for [combine" << std::endl;
img->drop();
continue;
}
blit_with_alpha(img, baseimg, v2s32(0,0), pos_base, dim);
img->drop();
}
}
/*
@ -1510,8 +1518,10 @@ bool TextureSource::generateImagePart(std::string part_of_name,
return false;
}
str_replace(part_of_name, '&', '^');
Strfnd sf(part_of_name);
std::string part_s(part_of_name);
str_replace(part_s, '&', '^');
Strfnd sf(part_s);
sf.next("{");
std::string imagename_top = sf.next("{");
std::string imagename_left = sf.next("{");
@ -1867,7 +1877,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
else if (str_starts_with(part_of_name, "[png:")) {
std::string png;
{
std::string blob = part_of_name.substr(5);
auto blob = part_of_name.substr(5);
if (!base64_is_valid(blob)) {
errorstream << "generateImagePart(): "
<< "malformed base64 in [png" << std::endl;
@ -2033,32 +2043,21 @@ static inline video::SColor blitPixel(const video::SColor src_c, const video::SC
This exists because IImage::copyToWithAlpha() doesn't seem to always
work.
*/
template<bool overlay>
static void blit_with_alpha(video::IImage *src, video::IImage *dst,
v2s32 src_pos, v2s32 dst_pos, v2u32 size)
{
for (u32 y0=0; y0<size.Y; y0++)
for (u32 x0=0; x0<size.X; x0++)
{
s32 src_x = src_pos.X + x0;
s32 src_y = src_pos.Y + y0;
s32 dst_x = dst_pos.X + x0;
s32 dst_y = dst_pos.Y + y0;
video::SColor src_c = src->getPixel(src_x, src_y);
video::SColor dst_c = dst->getPixel(dst_x, dst_y);
dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
dst->setPixel(dst_x, dst_y, dst_c);
}
}
auto src_dim = src->getDimension();
auto dst_dim = dst->getDimension();
/*
Draw an image on top of another one, using the alpha channel of the
source image; only modify fully opaque pixels in destinaion
*/
static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
v2s32 src_pos, v2s32 dst_pos, v2u32 size)
{
for (u32 y0=0; y0<size.Y; y0++)
for (u32 x0=0; x0<size.X; x0++)
// Limit y and x to the overlapping ranges
// s.t. the positions are all in bounds after offsetting.
for (u32 y0 = std::max(0, -dst_pos.Y);
y0 < std::min<s64>({size.Y, src_dim.Height, dst_dim.Height - (s64) dst_pos.Y});
++y0)
for (u32 x0 = std::max(0, -dst_pos.X);
x0 < std::min<s64>({size.X, src_dim.Width, dst_dim.Width - (s64) dst_pos.X});
++x0)
{
s32 src_x = src_pos.X + x0;
s32 src_y = src_pos.Y + y0;
@ -2066,45 +2065,13 @@ static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
s32 dst_y = dst_pos.Y + y0;
video::SColor src_c = src->getPixel(src_x, src_y);
video::SColor dst_c = dst->getPixel(dst_x, dst_y);
if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
{
if (!overlay || (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)) {
dst_c = blitPixel(src_c, dst_c, src_c.getAlpha());
dst->setPixel(dst_x, dst_y, dst_c);
}
}
}
// This function has been disabled because it is currently unused.
// Feel free to re-enable if you find it handy.
#if 0
/*
Draw an image on top of another one, using the specified ratio
modify all partially-opaque pixels in the destination.
*/
static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
{
for (u32 y0 = 0; y0 < size.Y; y0++)
for (u32 x0 = 0; x0 < size.X; x0++)
{
s32 src_x = src_pos.X + x0;
s32 src_y = src_pos.Y + y0;
s32 dst_x = dst_pos.X + x0;
s32 dst_y = dst_pos.Y + y0;
video::SColor src_c = src->getPixel(src_x, src_y);
video::SColor dst_c = dst->getPixel(dst_x, dst_y);
if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
{
if (ratio == -1)
dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
else
dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
dst->setPixel(dst_x, dst_y, dst_c);
}
}
}
#endif
/*
Apply color to destination, using a weighted interpolation blend
*/
@ -2443,7 +2410,7 @@ static void draw_crack(video::IImage *crack, video::IImage *dst,
if (!crack_scaled)
return;
auto blit = use_overlay ? blit_with_alpha_overlay : blit_with_alpha;
auto blit = use_overlay ? blit_with_alpha<true> : blit_with_alpha<false>;
for (s32 i = 0; i < frame_count; ++i) {
v2s32 dst_pos(0, frame_size.Height * i);
blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size);
@ -2470,7 +2437,7 @@ void brighten(video::IImage *image)
}
}
u32 parseImageTransform(const std::string& s)
u32 parseImageTransform(std::string_view s)
{
int total_transform = 0;

@ -315,7 +315,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename,
static scene::SMesh *createSpecialNodeMesh(Client *client, MapNode n,
std::vector<ItemPartColor> *colors, const ContentFeatures &f)
{
MeshMakeData mesh_make_data(client, false);
MeshMakeData mesh_make_data(client->ndef(), 1, false);
MeshCollector collector(v3f(0.0f * BS), v3f());
mesh_make_data.setSmoothLighting(false);
MapblockMeshGenerator gen(&mesh_make_data, &collector,

Some files were not shown because too many files have changed in this diff Show More