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,9 +15,8 @@ body:
label: Minetest version label: Minetest version
description: | description: |
Paste the Minetest version below. Paste the Minetest version below.
If you are on a devel version, please add a git commit hash. If you are on a dev version, please also indicate the git commit hash.
You can use `minetest --version` to find it. Refer to the "About" tab of the menu or run `minetest --version` on the command line.
You can also refer to the "About" tab of the menu.
placeholder: | placeholder: |
Example: Example:
Minetest 5.7.0-dev-ca13c51 (Linux) Minetest 5.7.0-dev-ca13c51 (Linux)
@ -33,13 +32,6 @@ body:
render: "true" render: "true"
validations: validations:
required: true 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 - type: input
attributes: attributes:
label: Irrlicht device label: Irrlicht device
@ -50,31 +42,32 @@ body:
- type: input - type: input
attributes: attributes:
label: Operating system and version 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" placeholder: "Example: Ubuntu 22.04"
validations: validations:
required: true required: true
- type: input - type: input
attributes: attributes:
label: CPU model label: CPU model
description: Usually found in system settings. description: Usually found in OS/system settings.
placeholder: "Example: Intel i5-2410M (4) @ 2.900GHz" placeholder: "Example: Intel Core i5-2410M"
validations: validations:
required: false required: false
- type: markdown - type: markdown
attributes: 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 - type: input
attributes: attributes:
label: GPU model label: GPU model
description: Usually found in system settings. description: Usually found in OS/system settings.
placeholder: "Example: NVIDA GeForce RTX 4090" placeholder: "Example: NVIDIA GeForce GTX 1660"
validations: validations:
required: false required: false
- type: input - type: input
attributes: attributes:
label: OpenGL version label: Active renderer
placeholder: "Example: 4.6" description: You can find this in the "About" tab in the main menu.
placeholder: "Example: OpenGL 4.6.0"
validations: validations:
required: false required: false
- type: textarea - type: textarea
@ -86,6 +79,6 @@ body:
- type: textarea - type: textarea
attributes: attributes:
label: Steps to reproduce 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: validations:
required: true required: true

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

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

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

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

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

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

@ -32,17 +32,17 @@ jobs:
name: "MinGW cross-compiler (32-bit)" name: "MinGW cross-compiler (32-bit)"
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install compiler - name: Install compiler
run: | run: |
sudo apt-get update && sudo apt-get install -y gettext 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 - name: Build
run: | run: |
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh B EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh B
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: mingw32 name: mingw32
path: B/build/*.zip path: B/build/*.zip
@ -52,17 +52,17 @@ jobs:
name: "MinGW cross-compiler (64-bit)" name: "MinGW cross-compiler (64-bit)"
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install compiler - name: Install compiler
run: | run: |
sudo apt-get update && sudo apt-get install -y gettext 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 - name: Build
run: | run: |
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh B EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh B
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: mingw64 name: mingw64
path: B/build/*.zip path: B/build/*.zip
@ -74,7 +74,7 @@ jobs:
env: env:
VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50 VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50
# 2023.10.19 # 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -95,7 +95,7 @@ jobs:
# Enable it, when working on the installer. # Enable it, when working on the installer.
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Checkout IrrlichtMt - name: Checkout IrrlichtMt
run: | run: |
@ -145,7 +145,7 @@ jobs:
env: env:
TYPE: ${{matrix.type}} TYPE: ${{matrix.type}}
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }} name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
path: .\Package\ path: .\Package\

3
.gitignore vendored

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

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

@ -1,11 +1,4 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.12)
# 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()
# This can be read from ${PROJECT_NAME} after project() is called # This can be read from ${PROJECT_NAME} after project() is called
project(minetest) project(minetest)
@ -44,6 +37,25 @@ set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests")
set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks") set(BUILD_BENCHMARKS FALSE CACHE BOOL "Build benchmarks")
set(BUILD_DOCUMENTATION TRUE CACHE BOOL "Build documentation") 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) set(DEFAULT_RUN_IN_PLACE FALSE)
if(WIN32) if(WIN32)
set(DEFAULT_RUN_IN_PLACE TRUE) set(DEFAULT_RUN_IN_PLACE TRUE)
@ -140,7 +152,7 @@ elseif(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt)
endif() endif()
message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}") 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}) string(REPLACE "mt" "." TARGET_VER ${TARGET_VER_S})
if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER}) if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER})
message(FATAL_ERROR "At least IrrlichtMt ${TARGET_VER_S} is required to build") 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()
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_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}")
message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_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 FROM $DOCKER_IMAGE AS dev
ENV IRRLICHT_VERSION master ENV IRRLICHT_VERSION master
ENV SPATIALINDEX_VERSION 1.9.3 ENV SPATIALINDEX_VERSION master
ENV LUAJIT_VERSION v2.1 ENV LUAJIT_VERSION v2.1
RUN apk add --no-cache git build-base cmake curl-dev zlib-dev zstd-dev \ 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 gmp-dev jsoncpp-dev ninja ca-certificates
WORKDIR /usr/src/ 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 && \ cd prometheus-cpp && \
cmake -B build \ cmake -B build \
-DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_INSTALL_PREFIX=/usr/local \
@ -29,9 +29,9 @@ RUN git clone --recursive https://github.com/jupp0r/prometheus-cpp/ && \
cd /usr/src/ && \ cd /usr/src/ && \
git clone --recursive https://luajit.org/git/luajit.git -b ${LUAJIT_VERSION} && \ git clone --recursive https://luajit.org/git/luajit.git -b ${LUAJIT_VERSION} && \
cd luajit && \ cd luajit && \
make && make install && \ make amalg && make install && \
cd /usr/src/ && \ 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 cp -r irrlicht/include /usr/include/irrlichtmt
FROM dev as builder FROM dev as builder
@ -56,13 +56,12 @@ RUN cmake -B build \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DBUILD_SERVER=TRUE \ -DBUILD_SERVER=TRUE \
-DENABLE_PROMETHEUS=TRUE \ -DENABLE_PROMETHEUS=TRUE \
-DBUILD_UNITTESTS=FALSE \ -DBUILD_UNITTESTS=FALSE -DBUILD_BENCHMARKS=FALSE \
-DBUILD_CLIENT=FALSE \ -DBUILD_CLIENT=FALSE \
-GNinja && \ -GNinja && \
cmake --build build && \ cmake --build build && \
cmake --install build cmake --install build
ARG DOCKER_IMAGE=alpine:3.16
FROM $DOCKER_IMAGE AS runtime FROM $DOCKER_IMAGE AS runtime
RUN apk add --no-cache curl gmp libstdc++ libgcc libpq jsoncpp zstd-libs \ 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 .. "after.lua")
dofile(commonpath .. "mod_storage.lua") dofile(commonpath .. "mod_storage.lua")
dofile(commonpath .. "chatcommands.lua") dofile(commonpath .. "chatcommands.lua")
dofile(commonpath .. "information_formspecs.lua")
dofile(clientpath .. "chatcommands.lua") dofile(clientpath .. "chatcommands.lua")
dofile(clientpath .. "death_formspec.lua") dofile(clientpath .. "death_formspec.lua")
dofile(clientpath .. "misc.lua") dofile(clientpath .. "misc.lua")

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

@ -61,15 +61,20 @@ local function build_chatcommands_formspec(name, sel, copy)
for i, data in ipairs(mod_cmds) do for i, data in ipairs(mod_cmds) do
rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. "," rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. ","
for j, cmds in ipairs(data[2]) do 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( rows[#rows + 1] = ("%s,1,%s,%s"):format(
has_priv and COLOR_GREEN or COLOR_GRAY, has_priv and COLOR_GREEN or COLOR_GRAY,
cmds[1], F(cmds[2].params)) cmds[1], F(cmds[2].params))
if sel == #rows then if sel == #rows then
description = cmds[2].description description = cmds[2].description
if copy then if copy then
core.chat_send_player(name, S("Command: @1 @2", local msg = S("Command: @1 @2",
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params)) 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 end
end end
@ -111,26 +116,46 @@ end
-- DETAILED CHAT COMMAND INFORMATION -- 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) local event = core.explode_table_event(fields.list)
if formname ~= "__builtin:help_cmds" or fields.quit then if event.type ~= "INV" then
return core.show_formspec("__builtin:help_cmds",
end 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) local event = core.explode_table_event(fields.list)
if event.type ~= "INV" then if event.type ~= "INV" then
local name = player:get_player_name() local name = player:get_player_name()
core.show_formspec(name, "__builtin:help_cmds", core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name, event.row, event.type == "DCL")) build_chatcommands_formspec(name, event.row, event.type == "DCL"))
end end
end) end)
end
function core.show_general_help_formspec(name) function core.show_general_help_formspec(name)
core.show_formspec(name, "__builtin:help_cmds", if INIT == "client" then
build_chatcommands_formspec(name)) core.show_formspec("__builtin:help_cmds",
build_chatcommands_formspec(name))
else
core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name))
end
end end
function core.show_privs_help_formspec(name) if INIT ~= "client" then
core.show_formspec(name, "__builtin:help_privs", function core.show_privs_help_formspec(name)
build_privs_formspec(name)) core.show_formspec(name, "__builtin:help_privs",
build_privs_formspec(name))
end
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 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) local function buttonbar_formspec(self)
if self.hidden then 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 -- The number of buttons per page is always calculated as if the scroll
-- buttons were visible. -- 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)) local btns_per_page = math.floor((avail_space - BASE_SPACING) / (btn_size + BASE_SPACING))
self.num_pages = math.ceil(#self.buttons / btns_per_page) 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 local btn_start_x = self.pos.x + btn_spacing
if show_scroll_btns then 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 end
for i = first_btn, first_btn + btns_per_page - 1 do 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, y = self.pos.y + BASE_SPACING,
} }
local btn_next_pos = { 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, y = self.pos.y + BASE_SPACING,
} }
@ -88,11 +90,11 @@ local function buttonbar_formspec(self)
self.btn_prev_name, self.btn_next_name)) self.btn_prev_name, self.btn_next_name))
table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;<]", 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)) self.btn_prev_name))
table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;>]", 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)) self.btn_next_name))
end end

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

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

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

@ -24,6 +24,13 @@ local bar_definitions = {
size = {x = 24, y = 24}, size = {x = 24, y = 24},
offset = {x = 25, y= -(48 + 24 + 16)}, 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 = {} local hud_ids = {}
@ -92,6 +99,16 @@ local function update_builtin_statbars(player)
end, name, hud.id_breathbar) end, name, hud.id_breathbar)
hud.id_breathbar = nil hud.id_breathbar = nil
end 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 end
local function cleanup_builtin_statbars(player) local function cleanup_builtin_statbars(player)
@ -138,8 +155,7 @@ local function player_event_handler(player,eventname)
end end
function core.hud_replace_builtin(hud_name, definition) function core.hud_replace_builtin(hud_name, definition)
if type(definition) ~= "table" or if type(definition) ~= "table" then
(definition.type or definition.hud_elem_type) ~= "statbar" then
return false return false
end end
@ -175,6 +191,20 @@ function core.hud_replace_builtin(hud_name, definition)
return true return true
end 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 return false
end end

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

@ -154,7 +154,9 @@ local function start_install(package, reason)
if conf_path then if conf_path then
local conf = Settings(conf_path) 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 if not name_is_title then
conf:set("name", package.name) conf:set("name", package.name)
end end
@ -642,8 +644,21 @@ local function fetch_pkgs()
end end
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 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 if not response.succeeded then
return return
end end
@ -898,7 +913,7 @@ local function get_info_formspec(text)
return table.concat({ return table.concat({
"formspec_version[6]", "formspec_version[6]",
"size[15.75,9.5]", "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, "]", "label[4,4.35;", text, "]",
"container[0,", H - 0.8 - 0.375, "]", "container[0,", H - 0.8 - 0.375, "]",
@ -928,7 +943,7 @@ function store.get_formspec(dlgdata)
local formspec = { local formspec = {
"formspec_version[6]", "formspec_version[6]",
"size[15.75,9.5]", "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]", "style[status,downloading,queued;border=false]",
@ -1175,8 +1190,8 @@ end
function store.handle_events(event) function store.handle_events(event)
if event == "DialogShow" then if event == "DialogShow" then
-- On mobile, don't show the "MINETEST" header behind the dialog. -- On touchscreen, don't show the "MINETEST" header behind the dialog.
mm_game_theme.set_engine(TOUCHSCREEN_GUI) mm_game_theme.set_engine(core.settings:get_bool("enable_touch"))
-- If the store is already loaded, auto-install packages here. -- If the store is already loaded, auto-install packages here.
do_auto_install() do_auto_install()

@ -150,6 +150,8 @@ function pkgmgr.get_mods(path, virtual_path, listing, modpack)
toadd.virtual_path = mod_virtual_path toadd.virtual_path = mod_virtual_path
toadd.type = "mod" toadd.type = "mod"
pkgmgr.update_translations({ toadd })
-- Check modpack.txt -- Check modpack.txt
-- Note: modpack.conf is already checked above -- Note: modpack.conf is already checked above
local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt") 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) load_texture_packs(txtpath_system, retval)
end end
pkgmgr.update_translations(retval)
table.sort(retval, function(a, b) table.sort(retval, function(a, b)
return a.title:lower() < b.title:lower() return a.title:lower() < b.title:lower()
end) end)
@ -775,6 +779,29 @@ function pkgmgr.update_gamelist()
table.sort(pkgmgr.games, function(a, b) table.sort(pkgmgr.games, function(a, b)
return a.title:lower() < b.title:lower() return a.title:lower() < b.title:lower()
end) 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 end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

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

@ -316,8 +316,8 @@ local function check_requirements(name, requires)
local special = { local special = {
android = PLATFORM == "Android", android = PLATFORM == "Android",
desktop = PLATFORM ~= "Android", desktop = PLATFORM ~= "Android",
touchscreen_gui = TOUCHSCREEN_GUI, touchscreen_gui = core.settings:get_bool("enable_touch"),
keyboard_mouse = not TOUCHSCREEN_GUI, keyboard_mouse = not core.settings:get_bool("enable_touch"),
shaders_support = shaders_support, shaders_support = shaders_support,
shaders = core.settings:get_bool("enable_shaders") and shaders_support, shaders = core.settings:get_bool("enable_shaders") and shaders_support,
opengl = video_driver == "opengl", opengl = video_driver == "opengl",
@ -449,13 +449,14 @@ local function get_formspec(dialogdata)
local extra_h = 1 -- not included in tabsize.height local extra_h = 1 -- not included in tabsize.height
local tabsize = { local tabsize = {
width = TOUCHSCREEN_GUI and 16.5 or 15.5, width = core.settings:get_bool("enable_touch") and 16.5 or 15.5,
height = TOUCHSCREEN_GUI and (10 - extra_h) or 12, 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 search_width = left_pane_width + scrollbar_w - (0.75 * 2)
local back_w = 3 local back_w = 3
@ -468,7 +469,7 @@ local function get_formspec(dialogdata)
local fs = { local fs = {
"formspec_version[6]", "formspec_version[6]",
"size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]", "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]", "bgcolor[#0000]",
-- HACK: this is needed to allow resubmitting the same formspec -- HACK: this is needed to allow resubmitting the same formspec
@ -516,9 +517,9 @@ local function get_formspec(dialogdata)
y = y + 0.82 y = y + 0.82
end end
fs[#fs + 1] = ("box[0,%f;%f,0.8;%s]"):format( 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]") 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 y = y + 0.82
end end
@ -641,11 +642,22 @@ local function buttonhandler(this, fields)
local value = core.is_yes(fields.show_advanced) local value = core.is_yes(fields.show_advanced)
core.settings:set_bool("show_advanced", value) core.settings:set_bool("show_advanced", value)
write_settings_early() 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) local suggested_page_id = update_filtered_pages(dialogdata.query)
dialogdata.components = nil
if not filtered_page_by_id[dialogdata.page_id] then if not filtered_page_by_id[dialogdata.page_id] then
dialogdata.components = nil
dialogdata.leftscroll = 0 dialogdata.leftscroll = 0
dialogdata.rightscroll = 0 dialogdata.rightscroll = 0

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

@ -94,7 +94,7 @@ function singleplayer_refresh_gamebar()
local btnbar = buttonbar_create( local btnbar = buttonbar_create(
"game_button_bar", "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}, {x = 15.5, y = 1.25},
"#000000", "#000000",
game_buttonbar_button_handler) game_buttonbar_button_handler)

@ -168,6 +168,11 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false
[*Touchscreen] [*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. # The length in pixels it takes for touchscreen interaction to start.
# #
# Requires: touchscreen_gui # Requires: touchscreen_gui
@ -422,7 +427,8 @@ anisotropic_filter (Anisotropic filtering) bool false
# #
# * None - No antialiasing (default) # * 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) # A.K.A multi-sample antialiasing (MSAA)
# Smoothens out block edges but does not affect the insides of textures. # Smoothens out block edges but does not affect the insides of textures.
# A restart is required to change this option. # 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] [**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. # Enables Hable's 'Uncharted 2' filmic tone mapping.
# Simulates the tone curve of photographic film and how this approximates the # Simulates the tone curve of photographic film and how this approximates the
# appearance of high dynamic range images. Mid-range contrast is slightly # appearance of high dynamic range images. Mid-range contrast is slightly
# enhanced, highlights and shadows are gradually compressed. # enhanced, highlights and shadows are gradually compressed.
# #
# Requires: shaders # Requires: shaders, enable_post_processing
tone_mapping (Filmic tone mapping) bool false tone_mapping (Filmic tone mapping) bool false
# Enable automatic exposure correction # Enable automatic exposure correction
@ -590,14 +601,14 @@ tone_mapping (Filmic tone mapping) bool false
# automatically adjust to the brightness of the scene, # automatically adjust to the brightness of the scene,
# simulating the behavior of human eye. # simulating the behavior of human eye.
# #
# Requires: shaders # Requires: shaders, enable_post_processing
enable_auto_exposure (Enable Automatic Exposure) bool false enable_auto_exposure (Enable Automatic Exposure) bool false
# Set the exposure compensation in EV units. # Set the exposure compensation in EV units.
# Value of 0.0 (default) means no exposure compensation. # Value of 0.0 (default) means no exposure compensation.
# Range: from -1 to 1.0 # 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 exposure_compensation (Exposure compensation) float 0.0 -1.0 1.0
# Apply dithering to reduce color banding artifacts. # 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 # With OpenGL ES, dithering only works if the shader supports high
# floating-point precision and it may have a higher performance impact. # floating-point precision and it may have a higher performance impact.
# #
# Requires: shaders # Requires: shaders, enable_post_processing
debanding (Enable Debanding) bool true debanding (Enable Debanding) bool true
[**Bloom] [**Bloom]
@ -616,7 +627,7 @@ debanding (Enable Debanding) bool true
# Set to true to enable bloom effect. # Set to true to enable bloom effect.
# Bright colors will bleed over the neighboring objects. # Bright colors will bleed over the neighboring objects.
# #
# Requires: shaders # Requires: shaders, enable_post_processing
enable_bloom (Enable Bloom) bool false enable_bloom (Enable Bloom) bool false
# Set to true to render debugging breakdown of the bloom effect. # 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 # top-left - processed base image, top-right - final image
# bottom-left - raw base image, bottom-right - bloom texture. # 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 enable_bloom_debug (Enable Bloom Debug) bool false
# Defines how much bloom is applied to the rendered image # Defines how much bloom is applied to the rendered image
# Smaller values make bloom more subtle # Smaller values make bloom more subtle
# Range: from 0.01 to 1.0, default: 0.05 # 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 bloom_intensity (Bloom Intensity) float 0.05 0.01 1.0
# Defines the magnitude of bloom overexposure. # Defines the magnitude of bloom overexposure.
# Range: from 0.1 to 10.0, default: 1.0 # 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 bloom_strength_factor (Bloom Strength Factor) float 1.0 0.1 10.0
# Logical value that controls how far the bloom effect spreads # Logical value that controls how far the bloom effect spreads
# from the bright objects. # from the bright objects.
# Range: from 0.1 to 8, default: 1 # 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 bloom_radius (Bloom Radius) float 1 0.1 8
# Set to true to enable volumetric lighting effect (a.k.a. "Godrays"). # 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 enable_volumetric_lighting (Volumetric lighting) bool false
[*Audio] [*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. # The 'snowbiomes' flag enables the new 5 biome system.
# When the 'snowbiomes' flag is enabled jungles are automatically enabled and # When the 'snowbiomes' flag is enabled jungles are automatically enabled and
# the 'jungles' flag is ignored. # 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. # Deserts occur when np_biome exceeds this value.
# When the 'snowbiomes' flag is enabled, this is ignored. # 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] [**Server/Env Performance]
# Length of a server tick and the interval at which objects are generally updated over # Length of a server tick (the interval at which everything is generally updated),
# network, stated in seconds. # stated in seconds.
dedicated_server_step (Dedicated server step) float 0.09 0.0 # 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. # Whether players are shown to clients without any range limit.
# Deprecated, use the setting player_transfer_distance instead. # 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 # At this distance the server will aggressively optimize which blocks are sent to
# clients. # clients.
# Small values potentially improve performance a lot, at the expense of visible # 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, # rendering glitches (some blocks might not be rendered correctly in caves).
# as well as sometimes on land).
# Setting this to a value greater than max_block_send_distance disables this # Setting this to a value greater than max_block_send_distance disables this
# optimization. # optimization.
# Stated in MapBlocks (16 nodes). # 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_REDIS=ON - Build with libhiredis; Enables use of Redis map backend
ENABLE_SPATIAL=ON - Build with LibSpatial; Speeds up AreaStores ENABLE_SPATIAL=ON - Build with LibSpatial; Speeds up AreaStores
ENABLE_SOUND=ON - Build with OpenAL, libogg & libvorbis; in-game sounds 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_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_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) 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 INSTALL_DEVTEST=FALSE - Whether the Development Test game should be installed alongside Minetest
USE_GPROF=FALSE - Enable profiling using GProf USE_GPROF=FALSE - Enable profiling using GProf
VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar) 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: Library specific options:

@ -8,7 +8,7 @@
Install dependencies with homebrew: 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 ## 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: After you successfully built vcpkg you can easily install the required libraries:
```powershell ```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`: - **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: * `game.conf`, with the following keys:
* `title`: Required, a human-readable title to address the game, e.g. `title = Minetest Game`. * `title`: Required, a human-readable title to address the game, e.g. `title = Minetest Game`.
* `name`: (Deprecated) same as title. * `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>` * `allowed_mapgens = <comma-separated mapgens>`
e.g. `allowed_mapgens = v5,v6,flat` e.g. `allowed_mapgens = v5,v6,flat`
Mapgens not in this list are removed from the list of mapgens for the 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`. `enable_damage`, `creative_mode`, `enable_server`.
* `map_persistent`: Specifies whether newly created worlds should use * `map_persistent`: Specifies whether newly created worlds should use
a persistent map backend. Defaults to `true` (= "sqlite3") a persistent map backend. Defaults to `true` (= "sqlite3")
* `author`: The author of the game. It only appears when downloaded from * `author`: The author's ContentDB username.
ContentDB.
* `release`: Ignore this: Should only ever be set by ContentDB, as it is * `release`: Ignore this: Should only ever be set by ContentDB, as it is
an internal ID used to track versions. 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`: * `minetest.conf`:
Used to set default settings when running this game. Used to set default settings when running this game.
* `settingtypes.txt`: * `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 * `name`: The modpack name. Allows Minetest to determine the modpack name even
if the folder is wrongly named. 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 * `description`: Description of mod to be shown in the Mods tab of the main
menu. menu. See [Translating content meta](#translating-content-meta).
* `author`: The author of the modpack. It only appears when downloaded from * `author`: The author's ContentDB username.
ContentDB.
* `release`: Ignore this: Should only ever be set by ContentDB, as it is an * `release`: Ignore this: Should only ever be set by ContentDB, as it is an
internal ID used to track versions. 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. 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 * `name`: The mod name. Allows Minetest to determine the mod name even if the
folder is wrongly named. 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 * `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 * `depends`: A comma separated list of dependencies. These are mods that must be
loaded before this mod. loaded before this mod.
* `optional_depends`: A comma separated list of optional dependencies. * `optional_depends`: A comma separated list of optional dependencies.
Like a dependency, but no error if the mod doesn't exist. Like a dependency, but no error if the mod doesn't exist.
* `author`: The author of the mod. It only appears when downloaded from * `author`: The author's ContentDB username.
ContentDB.
* `release`: Ignore this: Should only ever be set by ContentDB, as it is an * `release`: Ignore this: Should only ever be set by ContentDB, as it is an
internal ID used to track versions. 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` ### `screenshot.png`
@ -506,8 +510,8 @@ Example:
* `<w>`: width * `<w>`: width
* `<h>`: height * `<h>`: height
* `<x>`: x position * `<x>`: x position, negative numbers allowed
* `<y>`: y position * `<y>`: y position, negative numbers allowed
* `<file>`: texture to combine * `<file>`: texture to combine
Creates a texture of size `<w>` times `<h>` and blits the listed files to their 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 * `<y>`: y position
* `<color>`: a `ColorString`. * `<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`. 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 '^'. 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 the resolution of the texture, the base texture will determine the output
resolution. resolution.
@ -2167,6 +2171,8 @@ to games.
Negative damage values are discarded as no damage. Negative damage values are discarded as no damage.
* `falling_node`: if there is no walkable block under the node it will fall * `falling_node`: if there is no walkable block under the node it will fall
* `float`: the node will not fall through liquids (`liquidtype ~= "none"`) * `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. * `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 * 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 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. 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. 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 Perlin noise
============ ============
@ -4677,6 +4723,7 @@ differences:
into it; it's not necessary to call `VoxelManip:read_from_map()`. 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` 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. 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 * The `on_generated()` callbacks of some mods may place individual nodes in the
generated area using non-VoxelManip map modification methods. Because 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 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 unnecessary recalculations, these can be retrieved using the
`minetest.get_mapgen_object()` function. If the requested Mapgen object is `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. callback, `nil` is returned.
The following Mapgen objects are currently available: The following Mapgen objects are currently available:
@ -4908,12 +4955,14 @@ generated chunk by the current mapgen.
### `gennotify` ### `gennotify`
Returns a table mapping requested generation notification types to arrays of Returns a table. You need to announce your interest in a specific
positions at which the corresponding generated structures are located within field by calling `minetest.set_gen_notify()` *before* map generation happens.
the current chunk. To enable the capture of positions of interest to be recorded
call `minetest.set_gen_notify()` first.
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 * `dungeon`: bottom center position of dungeon rooms
* `temple`: as above but for desert temples (mgv6 only) * `temple`: as above but for desert temples (mgv6 only)
@ -4921,7 +4970,12 @@ Possible fields of the returned table are:
* `cave_end` * `cave_end`
* `large_cave_begin` * `large_cave_begin`
* `large_cave_end` * `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 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()`. numeric unique decoration ID as returned by `minetest.get_decoration_id()`.
@ -5302,6 +5356,10 @@ Utilities
item_specific_pointabilities = true, item_specific_pointabilities = true,
-- Nodes `pointable` property can be `"blocking"` (5.9.0) -- Nodes `pointable` property can be `"blocking"` (5.9.0)
blocking_pointability_type = true, 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 * `minetest.sha1(data, [raw])`: returns the sha1 hash of data
* `data`: string of data to hash * `data`: string of data to hash
* `raw`: return raw bytes instead of hex digits, default: false * `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 * `minetest.colorspec_to_colorstring(colorspec)`: Converts a ColorSpec to a
ColorString. If the ColorSpec is invalid, returns `nil`. ColorString. If the ColorSpec is invalid, returns `nil`.
* `colorspec`: The ColorSpec to convert * `colorspec`: The ColorSpec to convert
@ -5445,7 +5506,7 @@ Utilities
You can use `colorspec_to_bytes` to generate raw RGBA values. You can use `colorspec_to_bytes` to generate raw RGBA values.
Palettes are not supported at the moment. Palettes are not supported at the moment.
You may use this to procedurally generate textures during server init. 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 percent sign followed by two hex digits. See
[RFC 3986, section 2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-2.3). [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))` * `minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing))`
* Called when a node is punched * Called when a node is punched
* `minetest.register_on_generated(function(minp, maxp, blockseed))` * `minetest.register_on_generated(function(minp, maxp, blockseed))`
* Called after generating a piece of world. Modifying nodes inside the area * Called after generating a piece of world between `minp` and `maxp`.
is a bit faster than usual. * **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))` * `minetest.register_on_newplayer(function(ObjectRef))`
* Called when a new player enters the world for the first time * 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))` * `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 * `minetest.add_entity(pos, name, [staticdata])`: Spawn Lua-defined entity at
position. position.
* Returns `ObjectRef`, or `nil` if failed * 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 * `minetest.add_item(pos, item)`: Spawn item
* Returns `ObjectRef`, or `nil` if failed * 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_player_by_name(name)`: Get an `ObjectRef` to a player
* `minetest.get_objects_inside_radius(pos, radius)`: returns a list of * `minetest.get_objects_inside_radius(pos, radius)`: returns a list of
ObjectRefs. ObjectRefs.
@ -5995,20 +6061,18 @@ Environment access
* `minetest.get_voxel_manip([pos1, pos2])` * `minetest.get_voxel_manip([pos1, pos2])`
* Return voxel manipulator object. * Return voxel manipulator object.
* Loads the manipulator from the map if positions are passed. * 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. * Set the types of on-generate notifications that should be collected.
* `flags` is a flag field with the available flags: * `flags`: flag field, see [`gennotify`] for available generation notification types.
* dungeon * The following parameters are optional:
* temple * `deco_ids` is a list of IDs of decorations which notification
* cave_begin
* cave_end
* large_cave_begin
* large_cave_end
* decoration
* The second parameter is a list of IDs of decorations which notification
is requested for. 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()` * `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)` * `minetest.get_decoration_id(decoration_name)`
* Returns the decoration ID number for the provided decoration name string, * Returns the decoration ID number for the provided decoration name string,
or `nil` on failure. or `nil` on failure.
@ -6180,6 +6244,17 @@ Environment access
* increase level of leveled node by level, default `level` equals `1` * increase level of leveled node by level, default `level` equals `1`
* if `totallevel > maxlevel`, returns rest (`total-max`) * if `totallevel > maxlevel`, returns rest (`total-max`)
* `level` must be between -127 and 127 * `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` * `minetest.fix_light(pos1, pos2)`: returns `true`/`false`
* resets the light in a cuboid-shaped part of * resets the light in a cuboid-shaped part of
the map and removes lighting bugs. the map and removes lighting bugs.
@ -6544,7 +6619,6 @@ Class instances that can be transferred between environments:
Functions: Functions:
* Standalone helpers such as logging, filesystem, encoding, * Standalone helpers such as logging, filesystem, encoding,
hashing or compression APIs hashing or compression APIs
* `minetest.request_insecure_environment` (same restrictions apply)
Variables: Variables:
* `minetest.settings` * `minetest.settings`
@ -6553,6 +6627,85 @@ Variables:
* with all functions and userdata values replaced by `true`, calling any * with all functions and userdata values replaced by `true`, calling any
callbacks here is obviously not possible 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 Server
------ ------
@ -6583,11 +6736,15 @@ Server
* Returns boolean indicating success (false if player nonexistent) * Returns boolean indicating success (false if player nonexistent)
* `minetest.dynamic_add_media(options, callback)` * `minetest.dynamic_add_media(options, callback)`
* `options`: table containing the following parameters * `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 * `to_player`: name of the player the media should be sent to instead of
all players (optional) all players (optional)
* `ephemeral`: boolean that marks the media as ephemeral, * `ephemeral`: boolean that marks the media as ephemeral,
it will not be cached on the client (optional, default false) 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 * `callback`: function with arguments `name`, which is a player name
* Pushes the specified media file to client(s). (details below) * Pushes the specified media file to client(s). (details below)
The file must be a supported image, sound or model format. 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 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 use of `to_player` to send the same, already existent file to multiple
chosen players. 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, * Clients will attempt to fetch files added this way via remote media,
this can make transfer of bigger files painless (if set up). Nevertheless this can make transfer of bigger files painless (if set up). Nevertheless
it is advised not to use dynamic media for big media files. it is advised not to use dynamic media for big media files.
@ -6786,7 +6946,7 @@ Misc.
(regardless of online status) (regardless of online status)
* `minetest.hud_replace_builtin(name, hud_definition)` * `minetest.hud_replace_builtin(name, hud_definition)`
* Replaces definition of a builtin hud element * Replaces definition of a builtin hud element
* `name`: `"breath"` or `"health"` * `name`: `"breath"`, `"health"` or `"minimap"`
* `hud_definition`: definition to replace builtin definition * `hud_definition`: definition to replace builtin definition
* `minetest.parse_relative_number(arg, relative_to)`: returns number or nil * `minetest.parse_relative_number(arg, relative_to)`: returns number or nil
* Helper function for chat commands. * Helper function for chat commands.
@ -7054,10 +7214,6 @@ Global tables
* Map of registered decoration definitions, indexed by the `name` field. * Map of registered decoration definitions, indexed by the `name` field.
* If `name` is nil, the key is the object handle returned by * If `name` is nil, the key is the object handle returned by
`minetest.register_decoration`. `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` * `minetest.registered_chatcommands`
* Map of registered chat command definitions, indexed by name * Map of registered chat command definitions, indexed by name
* `minetest.registered_privileges` * `minetest.registered_privileges`
@ -7273,6 +7429,8 @@ an itemstring, a table or `nil`.
the item breaks after `max_uses` times the item breaks after `max_uses` times
* Valid `max_uses` range is [0,65536] * Valid `max_uses` range is [0,65536]
* Does nothing if item is not a tool or if `max_uses` is 0 * 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` * `add_item(item)`: returns leftover `ItemStack`
* Put some item or stack onto this stack * Put some item or stack onto this stack
* `item_fits(item)`: returns `true` if item or stack can be fully added to * `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 * Overrides the item's tool capabilities
* A nil value will clear the override data and restore the original * A nil value will clear the override data and restore the original
behavior. 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` `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 = 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. -- 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. -- 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, pointable = true,
-- Can be `true` if it is pointable, `false` if it can be pointed through, -- Can be `true` if it is pointable, `false` if it can be pointed through,
-- or `"blocking"` if it is pointable but not selectable. -- 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. -- Can be overridden by the `pointabilities` of the held item.
visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem" / "item", visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem" / "item",
@ -8814,6 +8979,19 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- fallback behavior. -- 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, node_placement_prediction = nil,
-- If nil and item is node, prediction is made automatically. -- If nil and item is node, prediction is made automatically.
-- If nil and item is not a node, no prediction is made. -- If nil and item is not a node, no prediction is made.
@ -9015,6 +9193,7 @@ Used by `minetest.register_node`.
pointable = true, pointable = true,
-- Can be `true` if it is pointable, `false` if it can be pointed through, -- Can be `true` if it is pointable, `false` if it can be pointed through,
-- or `"blocking"` if it is pointable but not selectable. -- 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. -- 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. -- 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 Crafting recipes
---------------- ----------------

@ -323,6 +323,7 @@ Package - content which is downloadable from the content db, may or may not be i
description = "description", description = "description",
author = "author", author = "author",
path = "path/to/content", path = "path/to/content",
textdomain = "textdomain", -- textdomain to translate title / description with
depends = {"mod", "names"}, -- mods only depends = {"mod", "names"}, -- mods only
optional_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 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 Logging
------- -------

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

@ -25,8 +25,14 @@ texture pack. The name must not be “base”.
### `texture_pack.conf` ### `texture_pack.conf`
A key-value config file with the following keys: 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 * `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` ### `description.txt`
**Deprecated**, you should use texture_pack.conf instead. **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' Nodes support all targets, but other items only support 'inventory'
and 'wield'. 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 ### Using the special targets

@ -420,36 +420,141 @@ minetest.register_tool("basetools:dagger_steel", {
} }
}) })
-- Test tool uses and punch_attack_uses -- Test tool uses, punch_attack_uses, and wear bar coloring
local uses = { 1, 2, 3, 5, 10, 50, 100, 1000, 10000, 65535 } local tool_params = {
for i=1, #uses do {uses = 1},
local u = uses[i] {uses = 2},
local ustring {uses = 3},
if i == 1 then {
ustring = u.."-Use" uses = 5,
else wear_color = "#5865f2",
ustring = u.."-Uses" wear_description = "Solid color: #5865f2",
end },
local color = string.format("#FF00%02X", math.floor(((i-1)/#uses) * 255)) {
minetest.register_tool("basetools:pick_uses_"..string.format("%05d", u), { 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".. 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", inventory_image = "basetools_usespick.png^[colorize:"..color..":127",
tool_capabilities = { tool_capabilities = {
max_drop_level=0, max_drop_level=0,
groupcaps={ 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".. description = ustring.." Sword".."\n"..
"Damage: fleshy=1", "Damage: fleshy=1",
inventory_image = "basetools_usessword.png^[colorize:"..color..":127", inventory_image = "basetools_usessword.png^[colorize:"..color..":127",
tool_capabilities = { tool_capabilities = {
damage_groups = {fleshy=1}, damage_groups = {fleshy=1},
punch_attack_uses = u, punch_attack_uses = uses,
}, },
}) })
end 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 end
minetest.register_node("testnodes:rliquid_"..d, { minetest.register_node("testnodes:rliquid_"..d, {
description = "Test Liquid Source, Range "..d.. description = "Test Liquid Source, Range "..d..
tt_normal, tt_normal .. "\n" .. "(falling & floating node)",
drawtype = "liquid", drawtype = "liquid",
tiles = {"testnodes_liquidsource_r"..d..".png"}, tiles = {"testnodes_liquidsource_r"..d..".png"},
special_tiles = { special_tiles = {
@ -25,6 +25,8 @@ for d=0, 8 do
liquid_alternative_flowing = "testnodes:rliquid_flowing_"..d, liquid_alternative_flowing = "testnodes:rliquid_flowing_"..d,
liquid_alternative_source = "testnodes:rliquid_"..d, liquid_alternative_source = "testnodes:rliquid_"..d,
liquid_range = 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, { minetest.register_node("testnodes:rliquid_flowing_"..d, {

@ -149,15 +149,28 @@ fractal = nil
frac_emb = nil frac_emb = nil
checker = nil checker = nil
local textures_path = minetest.get_modpath( minetest.get_current_modname() ) .. "/textures/" do
minetest.safe_file_write( -- we used to write the textures to our mod folder. in order to avoid
textures_path .. "testnodes_generated_mb.png", -- 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) encode_and_check(512, 512, "rgb", data_mb)
) )
minetest.safe_file_write( local png_ck = encode_and_check(512, 512, "gray", data_ck)
textures_path .. "testnodes_generated_ck.png", core.dynamic_add_media({
encode_and_check(512, 512, "gray", data_ck) 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", { minetest.register_node("testnodes:generated_png_mb", {
description = S("Generated Mandelbrot PNG Test Node"), description = S("Generated Mandelbrot PNG Test Node"),
@ -200,6 +213,8 @@ minetest.register_node("testnodes:generated_png_dst_emb", {
groups = { dig_immediate = 2 }, groups = { dig_immediate = 2 },
}) })
png_ck = nil
png_emb = nil
data_emb = nil data_emb = nil
data_mb = nil data_mb = nil
data_ck = nil data_ck = nil

@ -6,6 +6,7 @@ testtools = {}
dofile(minetest.get_modpath("testtools") .. "/light.lua") dofile(minetest.get_modpath("testtools") .. "/light.lua")
dofile(minetest.get_modpath("testtools") .. "/privatizer.lua") dofile(minetest.get_modpath("testtools") .. "/privatizer.lua")
dofile(minetest.get_modpath("testtools") .. "/particles.lua") dofile(minetest.get_modpath("testtools") .. "/particles.lua")
dofile(minetest.get_modpath("testtools") .. "/node_box_visualizer.lua")
local pointabilities_nodes = { local pointabilities_nodes = {
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)) assert(not meta:equals(compare_meta))
end 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_a = core.get_mod_storage()
local storage_b = core.get_mod_storage() local storage_b = core.get_mod_storage()
local function test_mod_storage() local function test_mod_storage()
@ -86,7 +109,9 @@ end
unittests.register("test_mod_storage", test_mod_storage) unittests.register("test_mod_storage", test_mod_storage)
local function test_item_metadata() 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 end
unittests.register("test_item_metadata", test_item_metadata) 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() 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 -- We have comprehensive unit tests in C++, this is just to make sure the API code isn't messing up
local gen1 = PseudoRandom(13) local gen1 = PseudoRandom(13)
@ -108,6 +111,13 @@ unittests.register("test_punch_node", function(_, pos)
-- currently failing: assert(on_punch_called) -- currently failing: assert(on_punch_called)
end, {map=true}) 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() local function test_compress()
-- This text should be compressible, to make sure the results are... normal -- 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." 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 end
unittests.register("test_compress", test_compress) 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 function test_game_info()
local info = minetest.get_game_info() local info = minetest.get_game_info()
local game_conf = Settings(info.path .. "/game.conf") local game_conf = Settings(info.path .. "/game.conf")
@ -204,3 +220,30 @@ local function test_on_mapblocks_changed(cb, player, pos)
end end
end end
unittests.register("test_on_mapblocks_changed", test_on_mapblocks_changed, {map=true, async=true}) 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, 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", { core.register_chatcommand("set_saturation", {
params = "<saturation>", params = "<saturation>",
description = "Set the saturation for current player.", 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()
endif() endif()
option(ENABLE_TOUCH "Enable Touchscreen support" FALSE) option(ENABLE_TOUCH "Enable touchscreen by default" FALSE)
if(ENABLE_TOUCH) if(ENABLE_TOUCH)
add_definitions(-DHAVE_TOUCHSCREENGUI) message(STATUS "Touchscreen support enabled by default.")
add_definitions(-DENABLE_TOUCH)
endif() endif()
if(BUILD_CLIENT) if(BUILD_CLIENT)

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

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

@ -138,8 +138,8 @@ void Camera::notifyFovChange()
// Returns the fractional part of x // Returns the fractional part of x
inline f32 my_modf(f32 x) inline f32 my_modf(f32 x)
{ {
double dummy; float dummy;
return modf(x, &dummy); return std::modf(x, &dummy);
} }
void Camera::step(f32 dtime) 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 bobdir = (m_view_bobbing_anim < 0.5) ? 1.0 : -1.0;
f32 bobknob = 1.2; 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( v3f bobvec = v3f(
0.3 * bobdir * sin(bobfrac * M_PI), 0.3 * bobdir * std::sin(bobfrac * M_PI),
-0.28 * bobtmp * bobtmp, -0.28 * bobtmp * bobtmp,
0.); 0.);
@ -531,11 +531,9 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio)
addArmInertia(yaw); addArmInertia(yaw);
// Position the wielded item // 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_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); 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) if(m_digging_anim < 0.05 || m_digging_anim > 0.5)
{ {
f32 frac = 1.0; 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); frac = 2.0 * (m_digging_anim - 0.5);
// This value starts from 1 and settles to 0 // This value starts from 1 and settles to 0
f32 ratiothing = std::pow((1.0f - tool_reload_ratio), 0.5f); 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; f32 ratiothing2 = (easeCurve(ratiothing*0.5))*2.0;
wield_position.Y -= frac * 25.0 * pow(ratiothing2, 1.7f); wield_position.Y -= frac * 25.0f * std::pow(ratiothing2, 1.7f);
//wield_position.Z += frac * 5.0 * ratiothing2; wield_position.X -= frac * 35.0f * std::pow(ratiothing2, 1.1f);
wield_position.X -= frac * 35.0 * pow(ratiothing2, 1.1f); wield_rotation.Y += frac * 70.0f * std::pow(ratiothing2, 1.4f);
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);
} }
if (m_digging_button != -1) if (m_digging_button != -1)
{ {
f32 digfrac = m_digging_anim; f32 digfrac = m_digging_anim;
wield_position.X -= 50 * sin(pow(digfrac, 0.8f) * M_PI); wield_position.X -= 50 * std::sin(std::pow(digfrac, 0.8f) * M_PI);
wield_position.Y += 24 * sin(digfrac * 1.8 * M_PI); wield_position.Y += 24 * std::sin(digfrac * 1.8 * M_PI);
wield_position.Z += 25 * 0.5; wield_position.Z += 25 * 0.5;
// Euler angles are PURE EVIL, so why not use quaternions? // Euler angles are PURE EVIL, so why not use quaternions?
core::quaternion quat_begin(wield_rotation * core::DEGTORAD); core::quaternion quat_begin(wield_rotation * core::DEGTORAD);
core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD); core::quaternion quat_end(v3f(80, 30, 100) * core::DEGTORAD);
core::quaternion quat_slerp; 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); quat_slerp.toEuler(wield_rotation);
wield_rotation *= core::RADTODEG; wield_rotation *= core::RADTODEG;
} else { } else {
f32 bobfrac = my_modf(m_view_bobbing_anim); f32 bobfrac = my_modf(m_view_bobbing_anim);
wield_position.X -= sin(bobfrac*M_PI*2.0) * 3.0; wield_position.X -= std::sin(bobfrac*M_PI*2.0) * 3.0;
wield_position.Y += sin(my_modf(bobfrac*2.0)*M_PI) * 3.0; wield_position.Y += std::sin(my_modf(bobfrac*2.0)*M_PI) * 3.0;
} }
m_wieldnode->setPosition(wield_position); m_wieldnode->setPosition(wield_position);
m_wieldnode->setRotation(wield_rotation); 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, // view bobbing is enabled and free_move is off,
// start (or continue) the view bobbing animation. // start (or continue) the view bobbing animation.
const v3f &speed = player->getSpeed(); const v3f &speed = player->getSpeed();
const bool movement_XZ = hypot(speed.X, speed.Z) > BS; const bool movement_XZ = std::hypot(speed.X, speed.Z) > BS;
const bool movement_Y = fabs(speed.Y) > BS; const bool movement_Y = std::abs(speed.Y) > BS;
const bool walking = movement_XZ && player->touching_ground; const bool walking = movement_XZ && player->touching_ground;
const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid; 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) void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress)
{ {
TextureUpdateArgs* targs = (TextureUpdateArgs*) args; 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 // update the loading menu -- if necessary
bool do_draw = false; bool do_draw = false;
@ -1971,21 +1971,11 @@ void Client::makeScreenshot()
raw_image->drop(); raw_image->drop();
} }
bool Client::shouldShowMinimap() const
{
return !m_minimap_disabled_by_server;
}
void Client::pushToEventQueue(ClientEvent *event) void Client::pushToEventQueue(ClientEvent *event)
{ {
m_client_event_queue.push(event); m_client_event_queue.push(event);
} }
void Client::showMinimap(const bool show)
{
m_game_ui->showMinimap(show);
}
// IGameDef interface // IGameDef interface
// Under envlock // Under envlock
IItemDefManager* Client::getItemDefManager() IItemDefManager* Client::getItemDefManager()
@ -2133,3 +2123,11 @@ const std::string &Client::getFormspecPrepend() const
{ {
return m_env.getLocalPlayer()->formspec_prepend; 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; } Camera* getCamera () { return m_camera; }
scene::ISceneManager *getSceneManager(); scene::ISceneManager *getSceneManager();
bool shouldShowMinimap() const;
// IGameDef interface // IGameDef interface
IItemDefManager* getItemDefManager() override; IItemDefManager* getItemDefManager() override;
const NodeDefManager* getNodeDefManager() override; const NodeDefManager* getNodeDefManager() override;
@ -412,8 +410,6 @@ public:
void pushToEventQueue(ClientEvent *event); void pushToEventQueue(ClientEvent *event);
void showMinimap(bool show = true);
// IP and port we're connected to // IP and port we're connected to
const Address getServerAddress(); const Address getServerAddress();
@ -475,6 +471,9 @@ private:
bool canSendChatMessage() const; bool canSendChatMessage() const;
// remove sounds attached to object
void removeActiveObjectSounds(u16 id);
float m_packetcounter_timer = 0.0f; float m_packetcounter_timer = 0.0f;
float m_connection_reinit_timer = 0.1f; float m_connection_reinit_timer = 0.1f;
float m_avg_rtt_timer = 0.0f; float m_avg_rtt_timer = 0.0f;
@ -498,7 +497,6 @@ private:
ELoginRegister m_allow_login_or_register = ELoginRegister::Any; ELoginRegister m_allow_login_or_register = ELoginRegister::Any;
Camera *m_camera = nullptr; Camera *m_camera = nullptr;
Minimap *m_minimap = nullptr; Minimap *m_minimap = nullptr;
bool m_minimap_disabled_by_server = false;
// Server serialization version // Server serialization version
u8 m_server_ser_ver; u8 m_server_ser_ver;

@ -182,7 +182,7 @@ void ClientEnvironment::step(float dtime)
Stuff that has a maximum time increment 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; f32 dtime_part = dtime / steps;
for (; steps > 0; --steps) { for (; steps > 0; --steps) {
/* /*

@ -114,12 +114,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
m_rendering_engine->setupTopLevelWindow(); 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 // Create game callback for menus
g_gamecallback = new MainGameCallback(); g_gamecallback = new MainGameCallback();
@ -204,11 +198,18 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
while (m_rendering_engine->run() && !*kill && while (m_rendering_engine->run() && !*kill &&
!g_gamecallback->shutdown_requested) { !g_gamecallback->shutdown_requested) {
// Set the window caption // 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()-> m_rendering_engine->get_raw_device()->
setWindowCaption((utf8_to_wide(PROJECT_NAME_C) + setWindowCaption(utf8_to_wide(caption).c_str());
L" " + utf8_to_wide(g_version_hash) +
L" [" + wstrgettext("Main Menu") + L"]" +
L" [" + m_rendering_engine->getVideoDriver()->getName() + L"]" ).c_str());
try { // This is used for catching disconnects 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( m_rendering_engine->get_video_driver()->setTextureCreationFlag(
video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map")); video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map"));
#ifdef HAVE_TOUCHSCREENGUI if (g_settings->getBool("enable_touch")) {
receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver); receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver);
g_touchscreengui = receiver->m_touchscreengui; g_touchscreengui = receiver->m_touchscreengui;
#endif }
the_game( the_game(
kill, kill,
@ -282,11 +283,11 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
m_rendering_engine->get_scene_manager()->clear(); m_rendering_engine->get_scene_manager()->clear();
#ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) {
delete g_touchscreengui; delete g_touchscreengui;
g_touchscreengui = NULL; g_touchscreengui = NULL;
receiver->m_touchscreengui = NULL; receiver->m_touchscreengui = NULL;
#endif }
/* Save the settings when leaving the game. /* Save the settings when leaving the game.
* This makes sure that setting changes made in-game are persisted even * 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); ScopeProfiler sp(g_profiler, "CM::updateTransparentMeshBuffers", SPT_AVG);
u32 sorted_blocks = 0; u32 sorted_blocks = 0;
u32 unsorted_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 // 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/serialize.h"
#include "util/sha1.h" #include "util/sha1.h"
#include "util/string.h" #include "util/string.h"
#include <sstream>
static std::string getMediaCacheDir() 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); std::string sha1_hex = hex_encode(raw_hash);
if (!media_cache.exists(sha1_hex)) if (!media_cache.exists(sha1_hex))
return media_cache.update(sha1_hex, filedata); 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); 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 we found all files in the cache, report this fact to the server.
// If the server reported no remote servers, immediately start // If the server reported no remote servers, immediately start
// conventional transfers. Note: if cURL support is not compiled in, // 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, bool IClientMediaDownloader::tryLoadFromCache(const std::string &name,
const std::string &sha1, Client *client) const std::string &sha1, Client *client)
{ {
@ -547,11 +541,9 @@ bool IClientMediaDownloader::checkAndLoad(
// Compute actual checksum of data // Compute actual checksum of data
std::string data_sha1; std::string data_sha1;
{ {
SHA1 data_sha1_calculator; SHA1 ctx;
data_sha1_calculator.addBytes(data.c_str(), data.size()); ctx.addBytes(data);
unsigned char *data_tmpdigest = data_sha1_calculator.getDigest(); data_sha1 = ctx.getDigest();
data_sha1.assign((char*) data_tmpdigest, 20);
free(data_tmpdigest);
} }
// Check that received file matches announced checksum // Check that received file matches announced checksum
@ -726,8 +718,6 @@ void SingleMediaDownloader::initialStep(Client *client)
if (isDone()) if (isDone())
return; return;
createCacheDirs();
// If the server reported no remote servers, immediately fall back to // If the server reported no remote servers, immediately fall back to
// conventional transfer. // conventional transfer.
if (!USE_CURL || m_remotes.empty()) { 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 "irrlichttypes.h"
#include "filecache.h" #include "filecache.h"
#include "util/basic_macros.h" #include "util/basic_macros.h"
#include <ostream>
#include <map> #include <map>
#include <set> #include <set>
#include <vector> #include <vector>
@ -35,10 +34,15 @@ struct HTTPFetchResult;
#define MTHASHSET_FILE_NAME "index.mth" #define MTHASHSET_FILE_NAME "index.mth"
// Store file into media cache (unless it exists already) // 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, bool clientMediaUpdateCache(const std::string &raw_hash,
const std::string &filedata); 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... // more of a base class than an interface but this name was most convenient...
class IClientMediaDownloader class IClientMediaDownloader
{ {
@ -81,8 +85,6 @@ protected:
virtual bool loadMedia(Client *client, const std::string &data, virtual bool loadMedia(Client *client, const std::string &data,
const std::string &name) = 0; const std::string &name) = 0;
void createCacheDirs();
bool tryLoadFromCache(const std::string &name, const std::string &sha1, bool tryLoadFromCache(const std::string &name, const std::string &sha1,
Client *client); 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. // This is the child node's rotation. It is only used for automatic_rotate.
v3f local_rot = node->getRotation(); v3f local_rot = node->getRotation();
local_rot.Y = modulo360f(local_rot.Y - dtime * core::RADTODEG * local_rot.Y = modulo360f(local_rot.Y - dtime * core::RADTODEG *

@ -77,7 +77,7 @@ MapblockMeshGenerator::MapblockMeshGenerator(MeshMakeData *input, MeshCollector
scene::IMeshManipulator *mm): scene::IMeshManipulator *mm):
data(input), data(input),
collector(output), collector(output),
nodedef(data->m_client->ndef()), nodedef(data->nodedef),
meshmanip(mm), meshmanip(mm),
blockpos_nodes(data->m_blockpos * MAP_BLOCKSIZE), blockpos_nodes(data->m_blockpos * MAP_BLOCKSIZE),
enable_mesh_cache(g_settings->getBool("enable_mesh_cache") && 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); 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; float sum = 0;
int count = 0; int count = 0;
int air_count = 0; int air_count = 0;
for (int dk = 0; dk < 2; dk++) for (int dk = 0; dk < 2; dk++)
for (int di = 0; di < 2; di++) { 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; content_t content = neighbor_data.content;
// If top is liquid, draw starting from top of node // If top is liquid, draw starting from top of node

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

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

@ -28,6 +28,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <fstream> #include <fstream>
#include <cstdlib> #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) bool FileCache::loadByPath(const std::string &path, std::ostream &os)
{ {
std::ifstream fis(path.c_str(), std::ios_base::binary); 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; bool bad = false;
for(;;){ for(;;){
char buf[1024]; char buf[4096];
fis.read(buf, 1024); fis.read(buf, sizeof(buf));
std::streamsize len = fis.gcount(); std::streamsize len = fis.gcount();
os.write(buf, len); os.write(buf, len);
if(fis.eof()) if(fis.eof())
@ -59,8 +67,9 @@ bool FileCache::loadByPath(const std::string &path, std::ostream &os)
return !bad; 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::ofstream file(path.c_str(), std::ios_base::binary |
std::ios_base::trunc); std::ios_base::trunc);
@ -71,13 +80,13 @@ bool FileCache::updateByPath(const std::string &path, const std::string &data)
return false; return false;
} }
file.write(data.c_str(), data.length()); file << data;
file.close(); file.close();
return !file.fail(); 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; std::string path = m_dir + DIR_DELIM + name;
return updateByPath(path, data); 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); std::ifstream fis(path.c_str(), std::ios_base::binary);
return fis.good(); 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 <iostream>
#include <string> #include <string>
#include <string_view>
class FileCache class FileCache
{ {
@ -31,13 +32,17 @@ public:
*/ */
FileCache(const std::string &dir) : m_dir(dir) {} 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 load(const std::string &name, std::ostream &os);
bool exists(const std::string &name); 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: private:
std::string m_dir; std::string m_dir;
void createDir();
bool loadByPath(const std::string &path, std::ostream &os); 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/mapblock_mesh.h"
#include "client/sound.h" #include "client/sound.h"
#include "clientmap.h" #include "clientmap.h"
#include "clientmedia.h" // For clientMediaUpdateCacheCopy
#include "clouds.h" #include "clouds.h"
#include "config.h" #include "config.h"
#include "content_cao.h" #include "content_cao.h"
@ -663,11 +664,7 @@ public:
} }
}; };
#ifdef HAVE_TOUCHSCREENGUI #define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode)
#define SIZE_TAG "size[11,5.5]"
#else
#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
#endif
/**************************************************************************** /****************************************************************************
****************************************************************************/ ****************************************************************************/
@ -769,6 +766,7 @@ protected:
bool initSound(); bool initSound();
bool createSingleplayerServer(const std::string &map_dir, bool createSingleplayerServer(const std::string &map_dir,
const SubgameSpec &gamespec, u16 port); const SubgameSpec &gamespec, u16 port);
void copyServerClientCache();
// Client creation // Client creation
bool createClient(const GameStartData &start_data); bool createClient(const GameStartData &start_data);
@ -1019,13 +1017,11 @@ private:
// this happens in pause menu in singleplayer // this happens in pause menu in singleplayer
bool m_is_paused = false; bool m_is_paused = false;
#ifdef HAVE_TOUCHSCREENGUI bool m_touch_simulate_aux1 = false;
bool m_cache_hold_aux1;
bool m_touch_use_crosshair; bool m_touch_use_crosshair;
inline bool isNoCrosshairAllowed() { inline bool isTouchCrosshairDisabled() {
return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST; return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST;
} }
#endif
#ifdef __ANDROID__ #ifdef __ANDROID__
bool m_android_chat_open; bool m_android_chat_open;
#endif #endif
@ -1073,11 +1069,6 @@ Game::Game() :
&settingChangedCallback, this); &settingChangedCallback, this);
readSettings(); 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; m_first_loop_after_window_activation = true;
#ifdef HAVE_TOUCHSCREENGUI
m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair"); m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair");
#endif
g_client_translations->clear(); g_client_translations->clear();
@ -1217,10 +1206,8 @@ void Game::run()
set_light_table(g_settings->getFloat("display_gamma")); set_light_table(g_settings->getFloat("display_gamma"));
#ifdef HAVE_TOUCHSCREENGUI m_touch_simulate_aux1 = g_settings->getBool("fast_move")
m_cache_hold_aux1 = g_settings->getBool("fast_move")
&& client->checkPrivilege("fast"); && client->checkPrivilege("fast");
#endif
const irr::core::dimension2du initial_screen_size( const irr::core::dimension2du initial_screen_size(
g_settings->getU16("screen_w"), g_settings->getU16("screen_w"),
@ -1288,9 +1275,6 @@ void Game::run()
updateFrame(&graph, &stats, dtime, cam_view); updateFrame(&graph, &stats, dtime, cam_view);
updateProfilerGraphs(&graph); 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()) { if (m_does_lost_focus_pause_game && !device->isWindowFocused() && !isMenuActive()) {
showPauseMenu(); showPauseMenu();
} }
@ -1309,9 +1293,8 @@ void Game::shutdown()
// Clear text when exiting. // Clear text when exiting.
m_game_ui->clearText(); m_game_ui->clearText();
#ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui)
g_touchscreengui->hide(); g_touchscreengui->hide();
#endif
showOverlayMessage(N_("Shutting down..."), 0, 0, false); showOverlayMessage(N_("Shutting down..."), 0, 0, false);
@ -1453,9 +1436,31 @@ bool Game::createSingleplayerServer(const std::string &map_dir,
false, nullptr, error_message); false, nullptr, error_message);
server->start(); server->start();
copyServerClientCache();
return true; 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) bool Game::createClient(const GameStartData &start_data)
{ {
showOverlayMessage(N_("Creating client..."), 0, 10); showOverlayMessage(N_("Creating client..."), 0, 10);
@ -1499,11 +1504,10 @@ bool Game::createClient(const GameStartData &start_data)
if (client->modsLoaded()) if (client->modsLoaded())
client->getScript()->on_camera_ready(camera); client->getScript()->on_camera_ready(camera);
client->setCamera(camera); client->setCamera(camera);
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) { if (g_touchscreengui) {
g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed()); g_touchscreengui->setUseCrosshair(!isTouchCrosshairDisabled());
} }
#endif
/* Clouds /* Clouds
*/ */
@ -1530,18 +1534,20 @@ bool Game::createClient(const GameStartData &start_data)
/* Set window caption /* Set window caption
*/ */
std::wstring str = utf8_to_wide(PROJECT_NAME_C); #if IRRLICHT_VERSION_MT_REVISION >= 15
str += L" "; auto driver_name = driver->getName();
str += utf8_to_wide(g_version_hash); #else
str += L" ["; auto driver_name = wide_to_utf8(driver->getName());
str += simple_singleplayer_mode ? wstrgettext("Singleplayer") #endif
: wstrgettext("Multiplayer"); std::string str = std::string(PROJECT_NAME_C) +
str += L"]"; " " + g_version_hash + " [";
str += L" ["; str += simple_singleplayer_mode ? gettext("Singleplayer")
str += driver->getName(); : gettext("Multiplayer");
str += L"]"; str += "] [";
str += driver_name;
str += "]";
device->setWindowCaption(str.c_str()); device->setWindowCaption(utf8_to_wide(str).c_str());
LocalPlayer *player = client->getEnv().getLocalPlayer(); LocalPlayer *player = client->getEnv().getLocalPlayer();
player->hurt_tilt_timer = 0; player->hurt_tilt_timer = 0;
@ -1571,10 +1577,8 @@ bool Game::initGui()
gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
-1, chat_backend, client, &g_menumgr); -1, chat_backend, client, &g_menumgr);
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) if (g_touchscreengui)
g_touchscreengui->init(texture_src); g_touchscreengui->init(texture_src);
#endif
return true; return true;
} }
@ -2008,18 +2012,17 @@ void Game::processUserInput(f32 dtime)
} else { } else {
input->clear(); input->clear();
} }
#ifdef HAVE_TOUCHSCREENGUI
g_touchscreengui->hide(); if (g_touchscreengui)
#endif g_touchscreengui->hide();
} else { } else {
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) { if (g_touchscreengui) {
/* on touchscreengui step may generate own input events which ain't /* on touchscreengui step may generate own input events which ain't
* what we want in case we just did clear them */ * what we want in case we just did clear them */
g_touchscreengui->show(); g_touchscreengui->show();
g_touchscreengui->step(dtime); g_touchscreengui->step(dtime);
} }
#endif
m_game_focused = true; m_game_focused = true;
} }
@ -2211,13 +2214,11 @@ void Game::processItemSelection(u16 *new_playeritem)
} }
} }
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) { if (g_touchscreengui) {
std::optional<u16> selection = g_touchscreengui->getHotbarSelection(); std::optional<u16> selection = g_touchscreengui->getHotbarSelection();
if (selection) if (selection)
*new_playeritem = *selection; *new_playeritem = *selection;
} }
#endif
// Clamp selection again in case it wasn't changed but max_item was // Clamp selection again in case it wasn't changed but max_item was
*new_playeritem = MYMIN(*new_playeritem, max_item); *new_playeritem = MYMIN(*new_playeritem, max_item);
@ -2368,9 +2369,7 @@ void Game::toggleFast()
m_game_ui->showTranslatedStatusText("Fast mode disabled"); m_game_ui->showTranslatedStatusText("Fast mode disabled");
} }
#ifdef HAVE_TOUCHSCREENGUI m_touch_simulate_aux1 = fast_move && has_fast_privs;
m_cache_hold_aux1 = fast_move && has_fast_privs;
#endif
} }
@ -2455,26 +2454,14 @@ void Game::toggleMinimap(bool shift_pressed)
// --> // -->
u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags; u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) { if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) {
m_game_ui->m_flags.show_minimap = false; // If radar is disabled, try to find a non radar mode or fall back to 0
} else { if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
while (mapper->getModeIndex() &&
// If radar is disabled, try to find a non radar mode or fall back to 0 mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE)) mapper->nextMode();
while (mapper->getModeIndex() && }
mapper->getModeDef().type == MINIMAP_TYPE_RADAR) m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
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");
}
} }
void Game::toggleFog() 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, 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 this results in duplicated input. To avoid that, we don't enable relative
mouse mode if we're in touchscreen mode. */ mouse mode if we're in touchscreen mode. */
#ifndef HAVE_TOUCHSCREENGUI
if (cur_control) if (cur_control)
cur_control->setRelativeMode(!isMenuActive()); cur_control->setRelativeMode(!g_touchscreengui && !isMenuActive());
#endif
if ((device->isWindowActive() && device->isWindowFocused() if ((device->isWindowActive() && device->isWindowFocused()
&& !isMenuActive()) || input->isRandom()) { && !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 // 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 // 16:9 aspect ratio to minimize disruption of existing sensitivity
// settings. // 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) void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
{ {
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) { if (g_touchscreengui) {
cam->camera_yaw += g_touchscreengui->getYawChange(); cam->camera_yaw += g_touchscreengui->getYawChange();
cam->camera_pitch += g_touchscreengui->getPitchChange(); cam->camera_pitch += g_touchscreengui->getPitchChange();
} else { } else {
#endif
v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2); v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
v2s32 dist = input->getMousePos() - center; v2s32 dist = input->getMousePos() - center;
@ -2692,9 +2675,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
if (dist.X != 0 || dist.Y != 0) if (dist.X != 0 || dist.Y != 0)
input->setMousePos(center.X, center.Y); input->setMousePos(center.X, center.Y);
#ifdef HAVE_TOUCHSCREENGUI
} }
#endif
if (m_cache_enable_joysticks) { if (m_cache_enable_joysticks) {
f32 sens_scale = getSensitivityScaleFactor(); f32 sens_scale = getSensitivityScaleFactor();
@ -2735,20 +2716,18 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
client->activeObjectsReceived() && !player->isDead()) { client->activeObjectsReceived() && !player->isDead()) {
control.movement_speed = 1.0f; control.movement_speed = 1.0f;
// sideways movement only // sideways movement only
float dx = sin(control.movement_direction); float dx = std::sin(control.movement_direction);
control.movement_direction = atan2(dx, 1.0f); control.movement_direction = std::atan2(dx, 1.0f);
} }
#ifdef HAVE_TOUCHSCREENGUI
/* For touch, simulate holding down AUX1 (fast move) if the user has /* 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 * 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 * touch then its meaning is inverted (i.e. holding aux1 means walk and
* not fast) * not fast)
*/ */
if (m_cache_hold_aux1) { if (g_touchscreengui && m_touch_simulate_aux1) {
control.aux1 = control.aux1 ^ true; control.aux1 = control.aux1 ^ true;
} }
#endif
client->setPlayerControl(control); client->setPlayerControl(control);
@ -2777,10 +2756,16 @@ inline void Game::step(f32 dtime)
g_settings->getFloat("fps_max_unfocused") : g_settings->getFloat("fps_max_unfocused") :
g_settings->getFloat("fps_max"); g_settings->getFloat("fps_max");
fps_max = std::max(fps_max, 1.0f); 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{ server->setStepSettings(Server::StepSettings{
steplen, 1.0f / fps_max,
m_is_paused m_is_paused
}); });
@ -3229,10 +3214,8 @@ void Game::updateCamera(f32 dtime)
camera->toggleCameraMode(); camera->toggleCameraMode();
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) if (g_touchscreengui)
g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed()); g_touchscreengui->setUseCrosshair(!isTouchCrosshairDisabled());
#endif
// Make the player visible depending on camera mode. // Make the player visible depending on camera mode.
playercao->updateMeshCulling(); playercao->updateMeshCulling();
@ -3333,8 +3316,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
} }
shootline.end = shootline.start + camera_direction * BS * d; shootline.end = shootline.start + camera_direction * BS * d;
#ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui && isTouchCrosshairDisabled()) {
if (g_touchscreengui && isNoCrosshairAllowed()) {
shootline = g_touchscreengui->getShootline(); shootline = g_touchscreengui->getShootline();
// Scale shootline to the acual distance the player can reach // Scale shootline to the acual distance the player can reach
shootline.end = shootline.start + shootline.end = shootline.start +
@ -3342,7 +3324,6 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
shootline.start += intToFloat(camera_offset, BS); shootline.start += intToFloat(camera_offset, BS);
shootline.end += intToFloat(camera_offset, BS); shootline.end += intToFloat(camera_offset, BS);
} }
#endif
PointedThing pointed = updatePointedThing(shootline, PointedThing pointed = updatePointedThing(shootline,
selected_def.liquids_pointable, selected_def.liquids_pointable,
@ -3353,10 +3334,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
if (pointed != runData.pointed_old) if (pointed != runData.pointed_old)
infostream << "Pointing at " << pointed.dump() << std::endl; infostream << "Pointing at " << pointed.dump() << std::endl;
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) if (g_touchscreengui)
g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed)); g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed));
#endif
// Note that updating the selection mesh every frame is not particularly efficient, // 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 // 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; break;
}; };
case NDT_SIGNLIKE: { case NDT_SIGNLIKE: {
rotate90 = abs(pdir.X) < abs(pdir.Z); rotate90 = std::abs(pdir.X) < std::abs(pdir.Z);
break; break;
} }
default: { default: {
rotate90 = abs(pdir.X) > abs(pdir.Z); rotate90 = std::abs(pdir.X) > std::abs(pdir.Z);
break; break;
} }
} }
@ -4276,14 +4255,14 @@ void Game::updateShadows()
if (!shadow) if (!shadow)
return; 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); float timeoftheday = getWickedTimeOfDay(in_timeofday);
bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75; bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible(); bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f); 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; const float offset_constant = 10000.0f;
v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection(); v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
@ -4339,12 +4318,12 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
bool draw_crosshair = ( bool draw_crosshair = (
(player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
(this->camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT)); (this->camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
#ifdef HAVE_TOUCHSCREENGUI
if (this->isNoCrosshairAllowed()) if (g_touchscreengui && isTouchCrosshairDisabled())
draw_crosshair = false; draw_crosshair = false;
#endif
this->m_rendering_engine->draw_scene(sky_color, this->m_game_ui->m_flags.show_hud, 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 Profiler graph
@ -4490,21 +4469,23 @@ void Game::showDeathFormspec()
#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name()) #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
void Game::showPauseMenu() void Game::showPauseMenu()
{ {
#ifdef HAVE_TOUCHSCREENGUI std::string control_text;
static const std::string control_text = strgettext("Controls:\n"
"No menu open:\n" if (g_touchscreengui) {
"- slide finger: look around\n" control_text = strgettext("Controls:\n"
"- tap: place/punch/use (default)\n" "No menu open:\n"
"- long tap: dig/use (default)\n" "- slide finger: look around\n"
"Menu/inventory open:\n" "- tap: place/punch/use (default)\n"
"- double tap (outside):\n" "- long tap: dig/use (default)\n"
" --> close\n" "Menu/inventory open:\n"
"- touch stack, touch slot:\n" "- double tap (outside):\n"
" --> move stack\n" " --> close\n"
"- touch&drag, tap 2nd finger\n" "- touch stack, touch slot:\n"
" --> place single item to slot\n" " --> move stack\n"
); "- touch&drag, tap 2nd finger\n"
#endif " --> place single item to slot\n"
);
}
float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
std::ostringstream os; std::ostringstream os;
@ -4534,9 +4515,9 @@ void Game::showPauseMenu()
<< strgettext("Exit to Menu") << "]"; << strgettext("Exit to Menu") << "]";
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
<< strgettext("Exit to OS") << "]"; << strgettext("Exit to OS") << "]";
#ifdef HAVE_TOUCHSCREENGUI if (!control_text.empty()) {
os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"; 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" os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
<< "\n" << "\n"
<< strgettext("Game info:") << "\n"; << strgettext("Game info:") << "\n";

@ -219,11 +219,6 @@ void GameUI::initFlags()
m_flags.show_minimal_debug = g_settings->getBool("show_debug"); 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) void GameUI::showTranslatedStatusText(const char *str)
{ {
showStatusText(wstrgettext(str)); showStatusText(wstrgettext(str));

@ -57,7 +57,6 @@ public:
{ {
bool show_chat = true; bool show_chat = true;
bool show_hud = true; bool show_hud = true;
bool show_minimap = false;
bool show_minimal_debug = false; bool show_minimal_debug = false;
bool show_basic_debug = false; bool show_basic_debug = false;
bool show_profiler_graph = false; bool show_profiler_graph = false;
@ -71,8 +70,6 @@ public:
void initFlags(); void initFlags();
const Flags &getFlags() const { return m_flags; } const Flags &getFlags() const { return m_flags; }
void showMinimap(bool show);
inline void setInfoText(const std::wstring &str) { m_infotext = str; } inline void setInfoText(const std::wstring &str) { m_infotext = str; }
inline void clearInfoText() { m_infotext.clear(); } 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 "wieldmesh.h"
#include "client/renderingengine.h" #include "client/renderingengine.h"
#include "client/minimap.h" #include "client/minimap.h"
#ifdef HAVE_TOUCHSCREENGUI
#include "gui/touchscreengui.h" #include "gui/touchscreengui.h"
#endif
#define OBJECT_CROSSHAIR_LINE_SIZE 8 #define OBJECT_CROSSHAIR_LINE_SIZE 8
#define CROSSHAIR_LINE_SIZE 10 #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); drawItem(mainlist->getItem(i), item_rect, (i + 1) == selectitem);
#ifdef HAVE_TOUCHSCREENGUI
if (is_hotbar && g_touchscreengui) if (is_hotbar && g_touchscreengui)
g_touchscreengui->registerHotbarRect(i, item_rect); g_touchscreengui->registerHotbarRect(i, item_rect);
#endif
} }
} }
@ -340,6 +335,14 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
std::vector<HudElement*> elems; std::vector<HudElement*> elems;
elems.reserve(player->maxHudId()); 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++) { for (size_t i = 0; i != player->maxHudId(); i++) {
HudElement *e = player->getHud(i); HudElement *e = player->getHud(i);
if (!e) if (!e)
@ -741,10 +744,8 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
void Hud::drawHotbar(u16 playeritem) void Hud::drawHotbar(u16 playeritem)
{ {
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) if (g_touchscreengui)
g_touchscreengui->resetHotbarRects(); g_touchscreengui->resetHotbarRects();
#endif
InventoryList *mainlist = inventory->getList("main"); InventoryList *mainlist = inventory->getList("main");
if (mainlist == NULL) { if (mainlist == NULL) {
@ -1179,17 +1180,26 @@ void drawItemStack(
(1 - wear) * progressrect.LowerRightCorner.X; (1 - wear) * progressrect.LowerRightCorner.X;
// Compute progressbar color // Compute progressbar color
// default scheme:
// wear = 0.0: green // wear = 0.0: green
// wear = 0.5: yellow // wear = 0.5: yellow
// wear = 1.0: red // 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) video::SColor color;
color.set(255, wear_i, 255, 0); auto barParams = item.getWearBarParams(client->idef());
else if (barParams.has_value()) {
color.set(255, 255, 511 - wear_i, 0); 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; core::rect<s32> progressrect2 = progressrect;
progressrect2.LowerRightCorner.X = progressmid; progressrect2.LowerRightCorner.X = progressmid;

@ -65,20 +65,27 @@ public:
} }
}; };
/* Fill in RGB values for transparent pixels, to correct for odd colors template <bool IS_A8R8G8B8>
* appearing at borders when blending. This is because many PNG optimizers static void imageCleanTransparentWithInlining(video::IImage *src, u32 threshold)
* 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)
{ {
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); 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. // Note: loop y around x for better cache locality.
for (u32 ctry = 0; ctry < dim.Height; ctry++) for (u32 ctry = 0; ctry < dim.Height; ctry++)
for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) { 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); 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 // Add RGB values weighted by alpha IF the pixel is opaque, otherwise
// use full weight since we want to propagate colors. // 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(); u32 a = d.getAlpha() <= threshold ? 255 : d.getAlpha();
ss += a; ss += a;
sr += a * d.getRed(); sr += a * d.getRed();
@ -135,11 +142,11 @@ void imageCleanTransparent(video::IImage *src, u32 threshold)
// Set pixel to average weighted by alpha // Set pixel to average weighted by alpha
if (ss > 0) { if (ss > 0) {
video::SColor c = src->getPixel(ctrx, ctry); video::SColor c = get_pixel(ctrx, ctry);
c.setRed(sr / ss); c.setRed(sr / ss);
c.setGreen(sg / ss); c.setGreen(sg / ss);
c.setBlue(sb / ss); c.setBlue(sb / ss);
src->setPixel(ctrx, ctry, c); set_pixel(ctrx, ctry, c);
newmap.set(ctrx, ctry); 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 /* 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 * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
* to prevent non-integer scaling ratio artifacts. Note that this may cause * 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 React to nothing here if a menu is active
*/ */
if (isMenuActive()) { if (isMenuActive()) {
#ifdef HAVE_TOUCHSCREENGUI
if (m_touchscreengui) { if (m_touchscreengui) {
m_touchscreengui->setVisible(false); m_touchscreengui->setVisible(false);
} }
#endif
return g_menumgr.preprocessEvent(event); return g_menumgr.preprocessEvent(event);
} }
@ -130,12 +128,10 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
return true; return true;
} }
#ifdef HAVE_TOUCHSCREENGUI
} else if (m_touchscreengui && event.EventType == irr::EET_TOUCH_INPUT_EVENT) { } else if (m_touchscreengui && event.EventType == irr::EET_TOUCH_INPUT_EVENT) {
// In case of touchscreengui, we have to handle different events // In case of touchscreengui, we have to handle different events
m_touchscreengui->translateEvent(event); m_touchscreengui->translateEvent(event);
return true; return true;
#endif
} else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { } else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
// joystick may be nullptr if game is launched with '--random-input' parameter // 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 <list>
#include "keycode.h" #include "keycode.h"
#include "renderingengine.h" #include "renderingengine.h"
#ifdef HAVE_TOUCHSCREENGUI
#include "gui/touchscreengui.h" #include "gui/touchscreengui.h"
#endif
class InputHandler; class InputHandler;
@ -203,16 +200,12 @@ public:
MyEventReceiver() MyEventReceiver()
{ {
#ifdef HAVE_TOUCHSCREENGUI
m_touchscreengui = NULL; m_touchscreengui = NULL;
#endif
} }
JoystickController *joystick = nullptr; JoystickController *joystick = nullptr;
#ifdef HAVE_TOUCHSCREENGUI
TouchScreenGUI *m_touchscreengui; TouchScreenGUI *m_touchscreengui;
#endif
private: private:
s32 mouse_wheel = 0; s32 mouse_wheel = 0;
@ -332,11 +325,9 @@ public:
return 0.0f; return 0.0f;
return 1.0f; // If there is a keyboard event, assume maximum speed return 1.0f; // If there is a keyboard event, assume maximum speed
} }
#ifdef HAVE_TOUCHSCREENGUI if (m_receiver->m_touchscreengui && m_receiver->m_touchscreengui->getMovementSpeed())
return m_receiver->m_touchscreengui->getMovementSpeed(); return m_receiver->m_touchscreengui->getMovementSpeed();
#else
return joystick.getMovementSpeed(); return joystick.getMovementSpeed();
#endif
} }
virtual float getMovementDirection() virtual float getMovementDirection()
@ -355,12 +346,9 @@ public:
if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */ if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */
return atan2(x, z); return atan2(x, z);
else else if (m_receiver->m_touchscreengui && m_receiver->m_touchscreengui->getMovementDirection())
#ifdef HAVE_TOUCHSCREENGUI
return m_receiver->m_touchscreengui->getMovementDirection(); return m_receiver->m_touchscreengui->getMovementDirection();
#else return joystick.getMovementDirection();
return joystick.getMovementDirection();
#endif
} }
virtual bool cancelPressed() virtual bool cancelPressed()

@ -318,12 +318,14 @@ float JoystickController::getAxisWithoutDead(JoystickAxis axis)
float JoystickController::getMovementDirection() 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 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) if (speed > 1.0f)
speed = 1.0f; speed = 1.0f;
return speed; return speed;

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

@ -19,11 +19,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once #pragma once
#include "exceptions.h"
#include "irrlichttypes.h" #include "irrlichttypes.h"
#include "Keycodes.h" #include "Keycodes.h"
#include <IEventReceiver.h> #include <IEventReceiver.h>
#include <string> #include <string>
class UnknownKeycode : public BaseException
{
public:
UnknownKeycode(const char *s) :
BaseException(s) {};
};
/* A key press, consisting of either an Irrlicht keycode /* A key press, consisting of either an Irrlicht keycode
or an actual char */ 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) { if (m_autojump) {
// release autojump after a given time // 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::MeshMakeData(Client *client, bool use_shaders): MeshMakeData::MeshMakeData(const NodeDefManager *ndef, u16 side_length, bool use_shaders):
m_mesh_grid(client->getMeshGrid()), side_length(side_length),
side_length(MAP_BLOCKSIZE * m_mesh_grid.cell_size), nodedef(ndef),
m_client(client),
m_use_shaders(use_shaders) m_use_shaders(use_shaders)
{} {}
@ -147,7 +146,7 @@ u16 getFaceLight(MapNode n, MapNode n2, const NodeDefManager *ndef)
static u16 getSmoothLightCombined(const v3s16 &p, static u16 getSmoothLightCombined(const v3s16 &p,
const std::array<v3s16,8> &dirs, MeshMakeData *data) 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 ambient_occlusion = 0;
u16 light_count = 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) 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); const ContentFeatures &f = ndef->get(mn);
tile = f.tiles[tileindex]; tile = f.tiles[tileindex];
bool has_crack = p == data->m_crack_pos_relative; 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) 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), // 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) // (0,0,1), (0,0,-1) or (0,0,0)
@ -635,9 +634,9 @@ void PartialMeshBuffer::afterDraw() const
MapBlockMesh MapBlockMesh
*/ */
MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): MapBlockMesh::MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offset):
m_tsrc(data->m_client->getTextureSource()), m_tsrc(client->getTextureSource()),
m_shdrsrc(data->m_client->getShaderSource()), m_shdrsrc(client->getShaderSource()),
m_bounding_sphere_center((data->side_length * 0.5f - 0.5f) * BS), m_bounding_sphere_center((data->side_length * 0.5f - 0.5f) * BS),
m_animation_force_timer(0), // force initial animation m_animation_force_timer(0), // force initial animation
m_last_crack(-1), m_last_crack(-1),
@ -648,26 +647,27 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
m_enable_shaders = data->m_use_shaders; m_enable_shaders = data->m_use_shaders;
m_enable_vbo = g_settings->getBool("enable_vbo"); m_enable_vbo = g_settings->getBool("enable_vbo");
auto mesh_grid = client->getMeshGrid();
v3s16 bp = data->m_blockpos; v3s16 bp = data->m_blockpos;
// Only generate minimap mapblocks at even coordinates. // Only generate minimap mapblocks at even coordinates.
if (data->m_mesh_grid.isMeshPos(bp) && data->m_client->getMinimap()) { if (mesh_grid.isMeshPos(bp) && client->getMinimap()) {
m_minimap_mapblocks.resize(data->m_mesh_grid.getCellVolume(), nullptr); m_minimap_mapblocks.resize(mesh_grid.getCellVolume(), nullptr);
v3s16 ofs; v3s16 ofs;
// See also client.cpp for the code that reads the array of minimap blocks. // 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.Z = 0; ofs.Z < mesh_grid.cell_size; ofs.Z++)
for (ofs.Y = 0; ofs.Y < data->m_mesh_grid.cell_size; ofs.Y++) for (ofs.Y = 0; ofs.Y < mesh_grid.cell_size; ofs.Y++)
for (ofs.X = 0; ofs.X < data->m_mesh_grid.cell_size; ofs.X++) { for (ofs.X = 0; ofs.X < mesh_grid.cell_size; ofs.X++) {
v3s16 p = (bp + ofs) * MAP_BLOCKSIZE; v3s16 p = (bp + ofs) * MAP_BLOCKSIZE;
if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) { if (data->m_vmanip.getNodeNoEx(p).getContent() != CONTENT_IGNORE) {
MinimapMapblock *block = new MinimapMapblock; 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); 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); MeshCollector collector(m_bounding_sphere_center, offset);
/* /*
Add special graphics: Add special graphics:
@ -679,7 +679,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
{ {
MapblockMeshGenerator(data, &collector, 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; std::unordered_map<v3s16, u8> results;
v3s16 ofs; v3s16 ofs;
v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; 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; 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> #include <unordered_map>
class Client; class Client;
class NodeDefManager;
class IShaderSource; class IShaderSource;
/* /*
@ -43,13 +44,12 @@ struct MeshMakeData
v3s16 m_blockpos = v3s16(-1337,-1337,-1337); v3s16 m_blockpos = v3s16(-1337,-1337,-1337);
v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337); v3s16 m_crack_pos_relative = v3s16(-1337,-1337,-1337);
bool m_smooth_lighting = false; bool m_smooth_lighting = false;
MeshGrid m_mesh_grid;
u16 side_length; u16 side_length;
Client *m_client; const NodeDefManager *nodedef;
bool m_use_shaders; 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) Copy block data manually (to allow optimizations by the caller)
@ -179,7 +179,7 @@ class MapBlockMesh
{ {
public: public:
// Builds the mesh given // Builds the mesh given
MapBlockMesh(MeshMakeData *data, v3s16 camera_offset); MapBlockMesh(Client *client, MeshMakeData *data, v3s16 camera_offset);
~MapBlockMesh(); ~MapBlockMesh();
// Main animation function, parameters: // Main animation function, parameters:

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

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

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

@ -79,6 +79,7 @@ struct MinimapData {
video::IImage *minimap_mask_square = nullptr; video::IImage *minimap_mask_square = nullptr;
video::ITexture *texture = nullptr; video::ITexture *texture = nullptr;
video::ITexture *heightmap_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_round = nullptr;
video::ITexture *minimap_overlay_square = nullptr; video::ITexture *minimap_overlay_square = nullptr;
video::ITexture *player_marker = nullptr; video::ITexture *player_marker = nullptr;
@ -140,6 +141,7 @@ public:
MinimapModeDef getModeDef() const { return data->mode; } MinimapModeDef getModeDef() const { return data->mode; }
video::IImage *getMinimapMask();
video::ITexture *getMinimapTexture(); video::ITexture *getMinimapTexture();
void blitMinimapPixelsToImageRadar(video::IImage *map_image); void blitMinimapPixelsToImageRadar(video::IImage *map_image);
@ -152,7 +154,6 @@ public:
void removeMarker(MinimapMarker **marker); void removeMarker(MinimapMarker **marker);
void updateActiveMarkers(); void updateActiveMarkers();
void drawMinimap();
void drawMinimap(core::rect<s32> rect); void drawMinimap(core::rect<s32> rect);
video::IVideoDriver *driver; video::IVideoDriver *driver;

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

@ -36,7 +36,7 @@ RenderingCore::~RenderingCore()
delete shadow_renderer; 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) bool _draw_wield_tool, bool _draw_crosshair)
{ {
v2u32 screensize = device->getVideoDriver()->getScreenSize(); 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_crosshair = _draw_crosshair;
context.draw_wield_tool = _draw_wield_tool; context.draw_wield_tool = _draw_wield_tool;
context.show_hud = _show_hud; context.show_hud = _show_hud;
context.show_minimap = _show_minimap;
pipeline->reset(context); pipeline->reset(context);
pipeline->run(context); pipeline->run(context);

@ -53,7 +53,7 @@ public:
RenderingCore &operator=(const RenderingCore &) = delete; RenderingCore &operator=(const RenderingCore &) = delete;
RenderingCore &operator=(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); bool _draw_wield_tool, bool _draw_crosshair);
v2u32 getVirtualSize() const; v2u32 getVirtualSize() const;

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

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

@ -152,6 +152,9 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
driver = m_device->getVideoDriver(); driver = m_device->getVideoDriver();
infostream << "Using the " << RenderingEngine::getVideoDriverInfo(driver->getDriverType()).friendly_name << " video driver" << std::endl; 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; s_singleton = this;
auto skin = createSkin(m_device->getGUIEnvironment(), auto skin = createSkin(m_device->getGUIEnvironment(),
@ -319,9 +322,9 @@ void RenderingEngine::finalize()
} }
void RenderingEngine::draw_scene(video::SColor skycolor, bool show_hud, 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) const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_TYPE type)

@ -118,7 +118,7 @@ public:
float dtime = 0, int percent = 0, bool sky = true); float dtime = 0, int percent = 0, bool sky = true);
void draw_scene(video::SColor skycolor, bool show_hud, 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 initialize(Client *client, Hud *hud);
void finalize(); void finalize();

@ -804,7 +804,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
} }
void dumpShaderProgram(std::ostream &output_stream, 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 << output_stream << program_type << " shader program:" << std::endl <<
"----------------------------------" << std::endl; "----------------------------------" << std::endl;

@ -192,4 +192,4 @@ public:
IWritableShaderSource *createShaderSource(); IWritableShaderSource *createShaderSource();
void dumpShaderProgram(std::ostream &output_stream, 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) void ShadowRenderer::setShadowIntensity(float shadow_intensity)
{ {
m_shadow_strength = pow(shadow_intensity, 1.0f / m_shadow_strength_gamma); m_shadow_strength = std::pow(shadow_intensity, 1.0f / m_shadow_strength_gamma);
if (m_shadow_strength > 1E-2) if (m_shadow_strength > 1e-2f)
enable(); enable();
else else
disable(); disable();
@ -183,8 +183,12 @@ void ShadowRenderer::addNodeToShadowList(
{ {
m_shadow_node_array.emplace_back(node, shadowMode); m_shadow_node_array.emplace_back(node, shadowMode);
// node should never be ClientMap // 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); assert(strcmp(node->getName(), "ClientMap") != 0);
#endif
node->forEachMaterial([this] (auto &mat) { node->forEachMaterial([this] (auto &mat) {
mat.setTexture(TEXTURE_LAYER_SHADOW, shadowMapTextureFinal); 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 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 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); float alpha = clamp(starbrightness, day_opacity, 1.0f);
m_star_color = m_star_params.starcolor; 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 "guiscalingfilter.h"
#include "renderingengine.h" #include "renderingengine.h"
#include "util/base64.h" #include "util/base64.h"
#include "irrlicht_changes/printing.h"
/* /*
A cache from texture name to texture path 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". // 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. // 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) // 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 /*! Generates an image from a full string like
* "stone.png^mineral_coal.png^[crack:1:0". * "stone.png^mineral_coal.png^[crack:1:0".
@ -411,7 +412,7 @@ private:
* The returned Image should be dropped. * The returned Image should be dropped.
* source_image_names is important to determine when to flush the image from a cache (dynamic media) * 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) // Thread-safe cache of what source images are known (true = known)
MutexedMap<std::string, bool> m_source_image_existence; 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 // Draw an image on top of another one, using the alpha channel of the
// source image // 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, static void blit_with_alpha(video::IImage *src, video::IImage *dst,
v2s32 src_pos, v2s32 dst_pos, v2u32 size); 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. // 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 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
// color alpha with the destination alpha. // color alpha with the destination alpha.
@ -595,7 +593,7 @@ static void draw_crack(video::IImage *crack, video::IImage *dst,
// Brighten image // Brighten image
void brighten(video::IImage *image); void brighten(video::IImage *image);
// Parse a transform name // Parse a transform name
u32 parseImageTransform(const std::string& s); u32 parseImageTransform(std::string_view s);
// Apply transform to image dimension // Apply transform to image dimension
core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim); core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
// Apply transform to image data // Apply transform to image data
@ -965,7 +963,8 @@ static video::IImage *createInventoryCubeImage(
return result; 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 // Get the base image
@ -1026,15 +1025,15 @@ video::IImage* TextureSource::generateImage(const std::string &name, std::set<st
according to it 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 If this name is enclosed in parentheses, generate it
and blit it onto the base image and blit it onto the base image
*/ */
if (last_part_of_name[0] == paren_open if (last_part_of_name[0] == paren_open
&& last_part_of_name[last_part_of_name.size() - 1] == paren_close) { && last_part_of_name.back() == paren_close) {
std::string name2 = last_part_of_name.substr(1, auto name2 = last_part_of_name.substr(1,
last_part_of_name.size() - 2); last_part_of_name.size() - 2);
video::IImage *tmp = generateImage(name2, source_image_names); video::IImage *tmp = generateImage(name2, source_image_names);
if (!tmp) { if (!tmp) {
@ -1201,7 +1200,7 @@ void blitBaseImage(video::IImage* &src, video::IImage* &dst)
} \ } \
} while(0) } 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) video::IImage *& baseimg, std::set<std::string> &source_image_names)
{ {
const char escape = '\\'; // same as in generateImage() 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 // Stuff starting with [ are special commands
if (part_of_name.empty() || part_of_name[0] != '[') { if (part_of_name.empty() || part_of_name[0] != '[') {
source_image_names.insert(part_of_name); std::string part_s(part_of_name);
video::IImage *image = m_sourcecache.getOrLoad(part_of_name); source_image_names.insert(part_s);
video::IImage *image = m_sourcecache.getOrLoad(part_s);
if (!image) { if (!image) {
// Do not create the dummy texture // Do not create the dummy texture
if (part_of_name.empty()) if (part_of_name.empty())
@ -1323,37 +1323,45 @@ bool TextureSource::generateImagePart(std::string part_of_name,
sf.next(":"); sf.next(":");
u32 w0 = stoi(sf.next("x")); u32 w0 = stoi(sf.next("x"));
u32 h0 = stoi(sf.next(":")); u32 h0 = stoi(sf.next(":"));
CHECK_DIM(w0, h0); if (!baseimg) {
core::dimension2d<u32> dim(w0,h0); CHECK_DIM(w0, h0);
if (baseimg == NULL) { baseimg = driver->createImage(video::ECF_A8R8G8B8, {w0, h0});
baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
baseimg->fill(video::SColor(0,0,0,0)); baseimg->fill(video::SColor(0,0,0,0));
} }
while (!sf.at_end()) { while (!sf.at_end()) {
u32 x = stoi(sf.next(",")); v2s32 pos_base;
u32 y = stoi(sf.next("=")); pos_base.X = stoi(sf.next(","));
pos_base.Y = stoi(sf.next("="));
std::string filename = unescape_string(sf.next_esc(":", escape), escape); std::string filename = unescape_string(sf.next_esc(":", escape), escape);
if (x >= w0 || y >= h0) auto basedim = baseimg->getDimension();
COMPLAIN_INVALID("X or Y offset"); if (pos_base.X > (s32)basedim.Width || pos_base.Y > (s32)basedim.Height) {
infostream<<"Adding \""<<filename warningstream << "generateImagePart(): Skipping \""
<<"\" to combined ("<<x<<","<<y<<")" << filename << "\" as it's out-of-bounds " << pos_base
<<std::endl; << " for [combine" << std::endl;
continue;
}
infostream << "Adding \"" << filename<< "\" to combined "
<< pos_base << std::endl;
video::IImage *img = generateImage(filename, source_image_names); video::IImage *img = generateImage(filename, source_image_names);
if (img) { 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 {
errorstream << "generateImagePart(): Failed to load image \"" errorstream << "generateImagePart(): Failed to load image \""
<< filename << "\" for [combine" << std::endl; << 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; return false;
} }
str_replace(part_of_name, '&', '^'); std::string part_s(part_of_name);
Strfnd sf(part_of_name); str_replace(part_s, '&', '^');
Strfnd sf(part_s);
sf.next("{"); sf.next("{");
std::string imagename_top = sf.next("{"); std::string imagename_top = sf.next("{");
std::string imagename_left = 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:")) { else if (str_starts_with(part_of_name, "[png:")) {
std::string png; std::string png;
{ {
std::string blob = part_of_name.substr(5); auto blob = part_of_name.substr(5);
if (!base64_is_valid(blob)) { if (!base64_is_valid(blob)) {
errorstream << "generateImagePart(): " errorstream << "generateImagePart(): "
<< "malformed base64 in [png" << std::endl; << "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 This exists because IImage::copyToWithAlpha() doesn't seem to always
work. work.
*/ */
template<bool overlay>
static void blit_with_alpha(video::IImage *src, video::IImage *dst, static void blit_with_alpha(video::IImage *src, video::IImage *dst,
v2s32 src_pos, v2s32 dst_pos, v2u32 size) v2s32 src_pos, v2s32 dst_pos, v2u32 size)
{ {
for (u32 y0=0; y0<size.Y; y0++) auto src_dim = src->getDimension();
for (u32 x0=0; x0<size.X; x0++) auto dst_dim = dst->getDimension();
{
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);
}
}
/* // Limit y and x to the overlapping ranges
Draw an image on top of another one, using the alpha channel of the // s.t. the positions are all in bounds after offsetting.
source image; only modify fully opaque pixels in destinaion 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});
static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst, ++y0)
v2s32 src_pos, v2s32 dst_pos, v2u32 size) 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});
for (u32 y0=0; y0<size.Y; y0++) ++x0)
for (u32 x0=0; x0<size.X; x0++)
{ {
s32 src_x = src_pos.X + x0; s32 src_x = src_pos.X + x0;
s32 src_y = src_pos.Y + y0; 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; s32 dst_y = dst_pos.Y + y0;
video::SColor src_c = src->getPixel(src_x, src_y); video::SColor src_c = src->getPixel(src_x, src_y);
video::SColor dst_c = dst->getPixel(dst_x, dst_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_c = blitPixel(src_c, dst_c, src_c.getAlpha());
dst->setPixel(dst_x, dst_y, dst_c); 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 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) if (!crack_scaled)
return; 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) { for (s32 i = 0; i < frame_count; ++i) {
v2s32 dst_pos(0, frame_size.Height * i); v2s32 dst_pos(0, frame_size.Height * i);
blit(crack_scaled, dst, v2s32(0,0), dst_pos, frame_size); 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; int total_transform = 0;

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

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