Merge pull request 'master' (#6) from Mirrorlandia_minetest/minetest:master into master

Reviewed-on: #6
This commit is contained in:
Bruno Rybársky 2024-01-09 17:53:50 +01:00
commit 5adbc138c7
187 changed files with 3561 additions and 2518 deletions

@ -1,33 +0,0 @@
BasedOnStyle: LLVM
IndentWidth: 4
UseTab: Always
TabWidth: 4
BreakBeforeBraces: Custom
Standard: c++17
BraceWrapping:
AfterClass: true
AfterControlStatement: Never
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
AfterUnion: true
BeforeCatch: false
BeforeElse: false
FixNamespaceComments: false
AllowShortIfStatementsOnASingleLine: false
IndentCaseLabels: false
AccessModifierOffset: -4
ColumnLimit: 90
AllowShortFunctionsOnASingleLine: InlineOnly
SortIncludes: Never
IncludeCategories:
- Regex: '^".*'
Priority: 2
- Regex: '^<.*'
Priority: 1
AlignAfterOpenBracket: DontAlign
ContinuationIndentWidth: 8
ConstructorInitializerIndentWidth: 8
BreakConstructorInitializers: AfterColon
AlwaysBreakTemplateDeclarations: Yes

@ -67,20 +67,6 @@ Contributions are welcome! Here's how you can help:
might need more work in the future. might need more work in the future.
5. It uses protocols and formats which include the required compatibility. 5. It uses protocols and formats which include the required compatibility.
### Important note about automated GitHub checks
When you submit a pull request, GitHub automatically runs checks on the Minetest
Engine combined with your changes. One of these checks is called 'cpp lint /
clang format', which checks code formatting. Because formatting for readability
requires human judgement this check often fails and often makes unsuitable
formatting requests which make code readability worse.
If this check fails, look at the details to check for any clear mistakes and
correct those. However, you should not apply everything ClangFormat requests.
Ignore requests that make code readability worse and any other clearly
unsuitable requests. Discuss in the pull request with a core developer about how
to progress.
## Issues ## Issues
If you experience an issue, we would like to know the details - especially when If you experience an issue, we would like to know the details - especially when

@ -8,6 +8,8 @@ on:
- 'lib/**.cpp' - 'lib/**.cpp'
- 'src/**.[ch]' - 'src/**.[ch]'
- 'src/**.cpp' - 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'android/**' - 'android/**'
- '.github/workflows/android.yml' - '.github/workflows/android.yml'
pull_request: pull_request:
@ -16,6 +18,8 @@ on:
- 'lib/**.cpp' - 'lib/**.cpp'
- 'src/**.[ch]' - 'src/**.[ch]'
- 'src/**.cpp' - 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'android/**' - 'android/**'
- '.github/workflows/android.yml' - '.github/workflows/android.yml'

@ -1,274 +0,0 @@
name: build
# build on c/cpp changes or workflow changes
on:
push:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'util/buildbot/**'
- 'util/ci/**'
- '.github/workflows/**.yml'
- 'Dockerfile'
- '.dockerignore'
pull_request:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'util/buildbot/**'
- 'util/ci/**'
- '.github/workflows/**.yml'
- 'Dockerfile'
- '.dockerignore'
env:
MINETEST_POSTGRESQL_CONNECT_STRING: 'host=localhost user=minetest password=minetest dbname=minetest'
jobs:
# Older gcc version (should be close to our minimum supported version)
gcc_7:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps g++-7
- name: Build
run: |
./util/ci/build.sh
env:
CC: gcc-7
CXX: g++-7
- name: Test
run: |
./bin/minetest --run-unittests
# Current gcc version
gcc_12:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps g++-12 libluajit-5.1-dev
- name: Build
run: |
./util/ci/build.sh
env:
CC: gcc-12
CXX: g++-12
- name: Test
run: |
./bin/minetest --run-unittests
# Older clang version (should be close to our minimum supported version)
clang_7:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-7 valgrind
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-7
CXX: clang++-7
- name: Unittest
run: |
./bin/minetest --run-unittests
- name: Valgrind
run: |
valgrind --leak-check=full --leak-check-heuristics=all --undef-value-errors=no --error-exitcode=9 ./bin/minetest --run-unittests
# Current clang version
clang_14:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-14 gdb
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-14
CXX: clang++-14
- name: Test
run: |
./bin/minetest --run-unittests
- name: Integration test + devtest
run: |
./util/test_multiplayer.sh
# Build with prometheus-cpp (server-only)
clang_9_prometheus:
name: "clang_9 (PROMETHEUS=1)"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-9
- name: Build prometheus-cpp
run: |
./util/ci/build_prometheus_cpp.sh
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-9
CXX: clang++-9
CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0"
- name: Test
run: |
./bin/minetestserver --run-unittests
docker:
name: "Docker image"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Build docker image
run: |
docker build . -t minetest:latest
docker run --rm minetest:latest /usr/local/bin/minetestserver --version
win32:
name: "MinGW cross-compiler (32-bit)"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install compiler
run: |
sudo apt-get update && sudo apt-get install -y gettext
wget http://minetest.kitsunemimi.pw/mingw-w64-i686_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
sudo tar -xaf mingw.tar.xz -C /usr
- name: Build
run: |
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh winbuild
env:
NO_PACKAGE: 1
win64:
name: "MinGW cross-compiler (64-bit)"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install compiler
run: |
sudo apt-get update && sudo apt-get install -y gettext
wget http://minetest.kitsunemimi.pw/mingw-w64-x86_64_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
sudo tar -xaf mingw.tar.xz -C /usr
- name: Build
run: |
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh winbuild
env:
NO_PACKAGE: 1
msvc:
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
runs-on: windows-2019
env:
VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50
# 2023.10.19
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry
strategy:
fail-fast: false
matrix:
config:
- {
arch: x86,
generator: "-G'Visual Studio 16 2019' -A Win32",
vcpkg_triplet: x86-windows
}
- {
arch: x64,
generator: "-G'Visual Studio 16 2019' -A x64",
vcpkg_triplet: x64-windows
}
type: [portable]
# type: [portable, installer]
# The installer type is working, but disabled, to save runner jobs.
# Enable it, when working on the installer.
steps:
- uses: actions/checkout@v3
- name: Checkout IrrlichtMt
run: |
$ref = @(Get-Content misc\irrlichtmt_tag.txt)
git clone https://github.com/minetest/irrlicht lib\irrlichtmt --depth 1 -b $ref[0]
- name: Restore from cache and run vcpkg
uses: lukka/run-vcpkg@v7
with:
vcpkgArguments: ${{env.vcpkg_packages}}
vcpkgDirectory: '${{ github.workspace }}\vcpkg'
appendedCacheKey: ${{ matrix.config.vcpkg_triplet }}
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
- name: Minetest CMake
run: |
cmake ${{matrix.config.generator}} `
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" `
-DCMAKE_BUILD_TYPE=Release `
-DENABLE_POSTGRESQL=OFF `
-DENABLE_LUAJIT=TRUE `
-DREQUIRE_LUAJIT=TRUE `
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
- name: Build Minetest
run: cmake --build . --config Release
- name: CPack
run: |
If ($env:TYPE -eq "installer")
{
cpack -G WIX -B "$env:GITHUB_WORKSPACE\Package"
}
ElseIf($env:TYPE -eq "portable")
{
cpack -G ZIP -B "$env:GITHUB_WORKSPACE\Package"
}
env:
TYPE: ${{matrix.type}}
- name: Package Clean
run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
- uses: actions/upload-artifact@v3
with:
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
path: .\Package\

@ -23,32 +23,18 @@ on:
- 'util/ci/**' - 'util/ci/**'
- '.github/workflows/**.yml' - '.github/workflows/**.yml'
env:
CLANG_TIDY: clang-tidy-15
jobs: jobs:
# clang_format:
# runs-on: ubuntu-20.04
# steps:
# - uses: actions/checkout@v3
# - name: Install clang-format
# run: |
# sudo apt-get update
# sudo apt-get install -y clang-format-9
#
# - name: Run clang-format
# run: |
# source ./util/ci/clang-format.sh
# check_format
# env:
# CLANG_FORMAT: clang-format-9
clang_tidy: clang_tidy:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install deps - name: Install deps
run: | run: |
source ./util/ci/common.sh source ./util/ci/common.sh
install_linux_deps clang-tidy-9 install_linux_deps $CLANG_TIDY
- name: Run clang-tidy - name: Run clang-tidy
run: | run: |

163
.github/workflows/linux.yml vendored Normal file

@ -0,0 +1,163 @@
name: linux
# build on c/cpp changes or workflow changes
on:
push:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'util/ci/**'
- 'misc/irrlichtmt_tag.txt'
- 'Dockerfile'
- '.dockerignore'
- '.github/workflows/linux.yml'
pull_request:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'util/ci/**'
- 'misc/irrlichtmt_tag.txt'
- 'Dockerfile'
- '.dockerignore'
- '.github/workflows/linux.yml'
env:
MINETEST_POSTGRESQL_CONNECT_STRING: 'host=localhost user=minetest password=minetest dbname=minetest'
jobs:
# Older gcc version (should be close to our minimum supported version)
gcc_7:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps g++-7
- name: Build
run: |
./util/ci/build.sh
env:
CC: gcc-7
CXX: g++-7
- name: Test
run: |
./bin/minetest --run-unittests
# Current gcc version
gcc_12:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps g++-12 libluajit-5.1-dev
- name: Build
run: |
./util/ci/build.sh
env:
CC: gcc-12
CXX: g++-12
- name: Test
run: |
./bin/minetest --run-unittests
# Older clang version (should be close to our minimum supported version)
clang_7:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-7 valgrind
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-7
CXX: clang++-7
- name: Unittest
run: |
./bin/minetest --run-unittests
- name: Valgrind
run: |
valgrind --leak-check=full --leak-check-heuristics=all --undef-value-errors=no --error-exitcode=9 ./bin/minetest --run-unittests
# Current clang version
clang_14:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-14 gdb
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-14
CXX: clang++-14
- name: Test
run: |
./bin/minetest --run-unittests
- name: Integration test + devtest
run: |
./util/test_multiplayer.sh
# Build with prometheus-cpp (server-only)
clang_9_prometheus:
name: "clang_9 (PROMETHEUS=1)"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install deps
run: |
source ./util/ci/common.sh
install_linux_deps clang-9
- name: Build prometheus-cpp
run: |
./util/ci/build_prometheus_cpp.sh
- name: Build
run: |
./util/ci/build.sh
env:
CC: clang-9
CXX: clang++-9
CMAKE_FLAGS: "-DENABLE_PROMETHEUS=1 -DBUILD_CLIENT=0"
- name: Test
run: |
./bin/minetestserver --run-unittests
docker:
name: "Docker image"
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Build docker image
run: |
docker build . -t minetest:latest
docker run --rm minetest:latest /usr/local/bin/minetestserver --version

147
.github/workflows/windows.yml vendored Normal file

@ -0,0 +1,147 @@
name: windows
# build on c/cpp changes or workflow changes
on:
push:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'util/buildbot/**'
- 'misc/irrlichtmt_tag.txt'
- 'misc/*.manifest'
- '.github/workflows/windows.yml'
pull_request:
paths:
- 'lib/**.[ch]'
- 'lib/**.cpp'
- 'src/**.[ch]'
- 'src/**.cpp'
- '**/CMakeLists.txt'
- 'cmake/Modules/**'
- 'util/buildbot/**'
- 'misc/irrlichtmt_tag.txt'
- 'misc/*.manifest'
- '.github/workflows/windows.yml'
jobs:
mingw32:
name: "MinGW cross-compiler (32-bit)"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install compiler
run: |
sudo apt-get update && sudo apt-get install -y gettext
sudo ./util/buildbot/download_toolchain.sh i686 /usr
- name: Build
run: |
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh B
- uses: actions/upload-artifact@v3
with:
name: mingw32
path: B/build/*.zip
if-no-files-found: error
mingw64:
name: "MinGW cross-compiler (64-bit)"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install compiler
run: |
sudo apt-get update && sudo apt-get install -y gettext
sudo ./util/buildbot/download_toolchain.sh x86_64 /usr
- name: Build
run: |
EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh B
- uses: actions/upload-artifact@v3
with:
name: mingw64
path: B/build/*.zip
if-no-files-found: error
msvc:
name: VS 2019 ${{ matrix.config.arch }}-${{ matrix.type }}
runs-on: windows-2019
env:
VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50
# 2023.10.19
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry
strategy:
fail-fast: false
matrix:
config:
- {
arch: x86,
generator: "-G'Visual Studio 16 2019' -A Win32",
vcpkg_triplet: x86-windows
}
- {
arch: x64,
generator: "-G'Visual Studio 16 2019' -A x64",
vcpkg_triplet: x64-windows
}
type: [portable]
# type: [portable, installer]
# The installer type is working, but disabled, to save runner jobs.
# Enable it, when working on the installer.
steps:
- uses: actions/checkout@v3
- name: Checkout IrrlichtMt
run: |
$ref = @(Get-Content misc\irrlichtmt_tag.txt)
git clone https://github.com/minetest/irrlicht lib\irrlichtmt --depth 1 -b $ref[0]
- name: Restore from cache and run vcpkg
uses: lukka/run-vcpkg@v7
with:
vcpkgArguments: ${{env.vcpkg_packages}}
vcpkgDirectory: '${{ github.workspace }}\vcpkg'
appendedCacheKey: ${{ matrix.config.vcpkg_triplet }}
vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
- name: Minetest CMake
run: |
cmake ${{matrix.config.generator}} `
-DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" `
-DCMAKE_BUILD_TYPE=Release `
-DENABLE_POSTGRESQL=OFF `
-DENABLE_LUAJIT=TRUE `
-DREQUIRE_LUAJIT=TRUE `
-DRUN_IN_PLACE=${{ contains(matrix.type, 'portable') }} .
- name: Build Minetest
run: cmake --build . --config Release
- name: CPack
run: |
If ($env:TYPE -eq "installer")
{
cpack -G WIX -B "$env:GITHUB_WORKSPACE\Package"
}
ElseIf($env:TYPE -eq "portable")
{
cpack -G ZIP -B "$env:GITHUB_WORKSPACE\Package"
}
env:
TYPE: ${{matrix.type}}
- name: Package Clean
run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
- uses: actions/upload-artifact@v3
with:
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}
path: .\Package\
if-no-files-found: error

@ -128,28 +128,7 @@ Docker
- [Developing minetestserver with Docker](doc/developing/docker.md) - [Developing minetestserver with Docker](doc/developing/docker.md)
We provide Minetest server Docker images using the GitLab mirror registry. We provide a Dockerfile that can be used to build the server.
Images are built on each commit and available using the following tag scheme:
* `registry.gitlab.com/minetest/minetest/server:latest` (latest build)
* `registry.gitlab.com/minetest/minetest/server:<branch/tag>` (current branch or current tag)
* `registry.gitlab.com/minetest/minetest/server:<commit-id>` (current commit id)
If you want to test it on a Docker server you can easily run:
sudo docker run registry.gitlab.com/minetest/minetest/server:<docker tag>
If you want to use it in a production environment you should use volumes bound to the Docker host
to persist data and modify the configuration:
sudo docker create -v /home/minetest/data/:/var/lib/minetest/ -v /home/minetest/conf/:/etc/minetest/ registry.gitlab.com/minetest/minetest/server:master
Data will be written to `/home/minetest/data` on the host, and configuration will be read from `/home/minetest/conf/minetest.conf`.
**Note:** If you don't understand the previous commands please read the official Docker documentation before use.
You can also host your Minetest server inside a Kubernetes cluster. See our example implementation in [`misc/kubernetes.yml`](misc/kubernetes.yml).
Version scheme Version scheme

@ -51,8 +51,13 @@ public class GameActivity extends NativeActivity {
System.loadLibrary("minetest"); System.loadLibrary("minetest");
} }
private int messageReturnCode = -1; enum DialogType { TEXT_INPUT, SELECTION_INPUT }
enum DialogState { DIALOG_SHOWN, DIALOG_INPUTTED, DIALOG_CANCELED }
private DialogType lastDialogType = DialogType.TEXT_INPUT;
private DialogState inputDialogState = DialogState.DIALOG_CANCELED;
private String messageReturnValue = ""; private String messageReturnValue = "";
private int selectionReturnValue = 0;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -85,11 +90,17 @@ public class GameActivity extends NativeActivity {
// Ignore the back press so Minetest can handle it // Ignore the back press so Minetest can handle it
} }
public void showDialog(String acceptButton, String hint, String current, int editType) { public void showTextInputDialog(String hint, String current, int editType) {
runOnUiThread(() -> showDialogUI(hint, current, editType)); runOnUiThread(() -> showTextInputDialogUI(hint, current, editType));
} }
private void showDialogUI(String hint, String current, int editType) { public void showSelectionInputDialog(String[] optionList, int selectedIdx) {
runOnUiThread(() -> showSelectionInputDialogUI(optionList, selectedIdx));
}
private void showTextInputDialogUI(String hint, String current, int editType) {
lastDialogType = DialogType.TEXT_INPUT;
inputDialogState = DialogState.DIALOG_SHOWN;
final AlertDialog.Builder builder = new AlertDialog.Builder(this); final AlertDialog.Builder builder = new AlertDialog.Builder(this);
LinearLayout container = new LinearLayout(this); LinearLayout container = new LinearLayout(this);
container.setOrientation(LinearLayout.VERTICAL); container.setOrientation(LinearLayout.VERTICAL);
@ -114,7 +125,7 @@ public class GameActivity extends NativeActivity {
// For multi-line, do not submit the text after pressing Enter key // For multi-line, do not submit the text after pressing Enter key
if (keyCode == KeyEvent.KEYCODE_ENTER && editType != 1) { if (keyCode == KeyEvent.KEYCODE_ENTER && editType != 1) {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
messageReturnCode = 0; inputDialogState = DialogState.DIALOG_INPUTTED;
messageReturnValue = editText.getText().toString(); messageReturnValue = editText.getText().toString();
alertDialog.dismiss(); alertDialog.dismiss();
return true; return true;
@ -128,29 +139,55 @@ public class GameActivity extends NativeActivity {
doneButton.setText(R.string.ime_dialog_done); doneButton.setText(R.string.ime_dialog_done);
doneButton.setOnClickListener((view -> { doneButton.setOnClickListener((view -> {
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
messageReturnCode = 0; inputDialogState = DialogState.DIALOG_INPUTTED;
messageReturnValue = editText.getText().toString(); messageReturnValue = editText.getText().toString();
alertDialog.dismiss(); alertDialog.dismiss();
})); }));
} }
alertDialog.setOnCancelListener(dialog -> { alertDialog.setOnCancelListener(dialog -> {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
inputDialogState = DialogState.DIALOG_CANCELED;
messageReturnValue = current; messageReturnValue = current;
messageReturnCode = -1;
}); });
alertDialog.show(); alertDialog.show();
editText.requestFocusTryShow(); editText.requestFocusTryShow();
} }
public int getDialogState() { public void showSelectionInputDialogUI(String[] optionList, int selectedIdx) {
return messageReturnCode; lastDialogType = DialogType.SELECTION_INPUT;
inputDialogState = DialogState.DIALOG_SHOWN;
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setSingleChoiceItems(optionList, selectedIdx, (dialog, selection) -> {
inputDialogState = DialogState.DIALOG_INPUTTED;
selectionReturnValue = selection;
dialog.dismiss();
});
builder.setOnCancelListener(dialog -> {
inputDialogState = DialogState.DIALOG_CANCELED;
selectionReturnValue = selectedIdx;
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
} }
public String getDialogValue() { public int getLastDialogType() {
messageReturnCode = -1; return lastDialogType.ordinal();
}
public int getInputDialogState() {
return inputDialogState.ordinal();
}
public String getDialogMessage() {
inputDialogState = DialogState.DIALOG_CANCELED;
return messageReturnValue; return messageReturnValue;
} }
public int getDialogSelection() {
inputDialogState = DialogState.DIALOG_CANCELED;
return selectionReturnValue;
}
public float getDensity() { public float getDensity() {
return getResources().getDisplayMetrics().density; return getResources().getDisplayMetrics().density;
} }

@ -87,19 +87,20 @@ core.builtin_auth_handler = {
core.settings:get("default_password"))) core.settings:get("default_password")))
end end
local prev_privs = auth_entry.privileges
auth_entry.privileges = privileges auth_entry.privileges = privileges
core_auth.save(auth_entry) core_auth.save(auth_entry)
-- Run grant callbacks -- Run grant callbacks
for priv, _ in pairs(privileges) do for priv, _ in pairs(privileges) do
if not auth_entry.privileges[priv] then if not prev_privs[priv] then
core.run_priv_callbacks(name, priv, nil, "grant") core.run_priv_callbacks(name, priv, nil, "grant")
end end
end end
-- Run revoke callbacks -- Run revoke callbacks
for priv, _ in pairs(auth_entry.privileges) do for priv, _ in pairs(prev_privs) do
if not privileges[priv] then if not privileges[priv] then
core.run_priv_callbacks(name, priv, nil, "revoke") core.run_priv_callbacks(name, priv, nil, "revoke")
end end

@ -29,6 +29,7 @@ core.features = {
compress_zstd = true, compress_zstd = true,
sound_params_start_time = true, sound_params_start_time = true,
physics_overrides_v2 = true, physics_overrides_v2 = true,
hud_def_type_field = true,
} }
function core.has_feature(arg) function core.has_feature(arg)

@ -64,6 +64,13 @@ function core.encode_png(width, height, data, compression)
error("Incorrect type for 'height', expected number, got " .. type(height)) error("Incorrect type for 'height', expected number, got " .. type(height))
end end
if width < 1 then
error("Incorrect value for 'width', must be at least 1")
end
if height < 1 then
error("Incorrect value for 'height', must be at least 1")
end
local expected_byte_count = width * height * 4 local expected_byte_count = width * height * 4
if type(data) ~= "table" and type(data) ~= "string" then if type(data) ~= "table" and type(data) ~= "string" then

@ -3,7 +3,7 @@ local enable_damage = core.settings:get_bool("enable_damage")
local bar_definitions = { local bar_definitions = {
hp = { hp = {
hud_elem_type = "statbar", type = "statbar",
position = {x = 0.5, y = 1}, position = {x = 0.5, y = 1},
text = "heart.png", text = "heart.png",
text2 = "heart_gone.png", text2 = "heart_gone.png",
@ -14,7 +14,7 @@ local bar_definitions = {
offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)}, offset = {x = (-10 * 24) - 25, y = -(48 + 24 + 16)},
}, },
breath = { breath = {
hud_elem_type = "statbar", type = "statbar",
position = {x = 0.5, y = 1}, position = {x = 0.5, y = 1},
text = "bubble.png", text = "bubble.png",
text2 = "bubble_gone.png", text2 = "bubble_gone.png",
@ -139,7 +139,7 @@ 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" or
definition.hud_elem_type ~= "statbar" then (definition.type or definition.hud_elem_type) ~= "statbar" then
return false return false
end end

@ -28,6 +28,13 @@ end
local has_fetched = false local has_fetched = false
local latest_releases local latest_releases
do
local tmp = core.get_once("cdb_latest_releases")
if tmp then
latest_releases = core.deserialize(tmp, true)
has_fetched = latest_releases ~= nil
end
end
local function fetch_latest_releases() local function fetch_latest_releases()
@ -89,8 +96,9 @@ local function fetch()
has_fetched = false has_fetched = false
return return
end end
latest_releases = lowercase_keys(releases) latest_releases = lowercase_keys(releases)
core.set_once("cdb_latest_releases", core.serialize(latest_releases))
if update_detector.get_count() > 0 then if update_detector.get_count() > 0 then
local maintab = ui.find_by_name("maintab") local maintab = ui.find_by_name("maintab")
if not maintab.hidden then if not maintab.hidden then

@ -47,13 +47,13 @@ end
local change_keys = { local change_keys = {
query_text = "Change Keys", query_text = "Controls",
requires = { requires = {
keyboard_mouse = true, keyboard_mouse = true,
}, },
get_formspec = function(self, avail_w) get_formspec = function(self, avail_w)
local btn_w = math.min(avail_w, 3) local btn_w = math.min(avail_w, 3)
return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Change Keys")), 0.8 return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Controls")), 0.8
end, end,
on_submit = function(self, fields) on_submit = function(self, fields)
if fields.btn_change_keys then if fields.btn_change_keys then

@ -46,11 +46,18 @@ local register_functions = {
register_on_mapblocks_changed = 0, register_on_mapblocks_changed = 0,
} }
local function regex_escape(s)
return s:gsub("(%W)", "%%%1")
end
--- ---
-- Create an unique instrument name. -- Create an unique instrument name.
-- Generate a missing label with a running index number. -- Generate a missing label with a running index number.
-- --
local counts = {} local counts = {}
local worldmods_path = regex_escape(core.get_worldpath())
local user_path = regex_escape(core.get_user_path())
local builtin_path = regex_escape(core.get_builtin_path())
local function generate_name(def) local function generate_name(def)
local class, label, func_name = def.class, def.label, def.func_name local class, label, func_name = def.class, def.label, def.func_name
if label then if label then
@ -65,7 +72,16 @@ local function generate_name(def)
local index_id = def.mod .. (class or func_name) local index_id = def.mod .. (class or func_name)
local index = counts[index_id] or 1 local index = counts[index_id] or 1
counts[index_id] = index + 1 counts[index_id] = index + 1
return format("%s[%d] %s", class or func_name, index, class and func_name or ""):trim() local info = debug.getinfo(def.func)
local modpath = regex_escape(core.get_modpath(def.mod) or "")
local source = info.source
if modpath ~= "" then
source = source:gsub(modpath, def.mod)
end
source = source:gsub(worldmods_path, "")
source = source:gsub(builtin_path, "builtin" .. DIR_DELIM)
source = source:gsub(user_path, "")
return format("%s[%d] %s#%s", class or func_name, index, source, info.linedefined)
end end
--- ---

@ -77,7 +77,7 @@ local Formatter = {
end end
} }
local widths = { 55, 9, 9, 9, 5, 5, 5 } local widths = { 80, 9, 9, 9, 5, 5, 5 }
local txt_row_format = sprintf(" %%-%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds", unpack(widths)) local txt_row_format = sprintf(" %%-%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds | %%%ds", unpack(widths))
local HR = {} local HR = {}

@ -110,7 +110,7 @@ force_csm (Force client-side mods) bool false
# Smooths rotation of camera, also called look or mouse smoothing. 0 to disable. # Smooths rotation of camera, also called look or mouse smoothing. 0 to disable.
camera_smoothing (Camera smoothing) float 0.0 0.0 0.99 camera_smoothing (Camera smoothing) float 0.0 0.0 0.99
# Smooths rotation of camera when in cinematic mode, 0 to disable. Enter cinematic mode by using the key set in Change Keys. # Smooths rotation of camera when in cinematic mode, 0 to disable. Enter cinematic mode by using the key set in Controls.
# #
# Requires: keyboard_mouse # Requires: keyboard_mouse
cinematic_camera_smoothing (Camera smoothing in cinematic mode) float 0.7 0.0 0.99 cinematic_camera_smoothing (Camera smoothing in cinematic mode) float 0.7 0.0 0.99
@ -601,6 +601,17 @@ enable_auto_exposure (Enable Automatic Exposure) bool false
# Requires: shaders, enable_auto_exposure # Requires: shaders, 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.
# Dithering significantly increases the size of losslessly-compressed
# screenshots and it works incorrectly if the display or operating system
# performs additional dithering or if the color channels are not quantized
# to 8 bits.
# With OpenGL ES, dithering only works if the shader supports high
# floating-point precision and it may have a higher performance impact.
#
# Requires: shaders
debanding (Enable Debanding) bool true
[**Bloom] [**Bloom]
# Set to true to enable bloom effect. # Set to true to enable bloom effect.
@ -637,6 +648,10 @@ bloom_strength_factor (Bloom Strength Factor) float 1.0 0.1 10.0
# Requires: shaders, enable_bloom # Requires: shaders, 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").
#
# Requires: shaders, enable_bloom
enable_volumetric_lighting (Volumetric lighting) bool false
[*Audio] [*Audio]
@ -820,6 +835,14 @@ bind_address (Bind address) string
# to new servers, but they may not support all new features that you are expecting. # to new servers, but they may not support all new features that you are expecting.
strict_protocol_version_checking (Strict protocol checking) bool false strict_protocol_version_checking (Strict protocol checking) bool false
# Define the oldest clients allowed to connect.
# Older clients are compatible in the sense that they will not crash when connecting
# to new servers, but they may not support all new features that you are expecting.
# This allows for more fine-grained control than strict_protocol_version_checking.
# Minetest still enforces its own internal minimum, and enabling
# strict_protocol_version_checking will effectively override this.
protocol_version_min (Protocol version minimum) int 1 1 65535
# Specifies URL from which client fetches media instead of using UDP. # Specifies URL from which client fetches media instead of using UDP.
# $filename should be accessible from $remote_media$filename via cURL # $filename should be accessible from $remote_media$filename via cURL
# (obviously, remote_media should end with a slash). # (obviously, remote_media should end with a slash).
@ -1868,6 +1891,11 @@ texture_min_size (Base texture size) int 64 1 32768
# Systems with a low-end GPU (or no GPU) would benefit from smaller values. # Systems with a low-end GPU (or no GPU) would benefit from smaller values.
client_mesh_chunk (Client Mesh Chunksize) int 1 1 16 client_mesh_chunk (Client Mesh Chunksize) int 1 1 16
[**Sound]
# Comma-separated list of AL and ALC extensions that should not be used.
# Useful for testing. See al_extensions.[h,cpp] for details.
sound_extensions_blacklist (Sound Extensions Blacklist) string
[**Font] [**Font]
font_bold (Font bold by default) bool false font_bold (Font bold by default) bool false
@ -2082,8 +2110,8 @@ liquid_update (Liquid update tick) float 1.0 0.001
# as well as sometimes on land). # 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).
block_send_optimize_distance (Block send optimize distance) int 4 2 32767 block_send_optimize_distance (Block send optimize distance) int 4 2 2047
# If enabled, the server will perform map block occlusion culling based on # If enabled, the server will perform map block occlusion culling based on
# on the eye position of the player. This can reduce the number of blocks # on the eye position of the player. This can reduce the number of blocks
@ -2091,6 +2119,13 @@ block_send_optimize_distance (Block send optimize distance) int 4 2 32767
# invisible blocks, so that the utility of noclip mode is reduced. # invisible blocks, so that the utility of noclip mode is reduced.
server_side_occlusion_culling (Server-side occlusion culling) bool true server_side_occlusion_culling (Server-side occlusion culling) bool true
# At this distance the server will perform a simpler and cheaper occlusion check.
# Smaller values potentially improve performance, at the expense of temporarily visible
# rendering glitches (missing blocks).
# This is especially useful for very large viewing range (upwards of 500).
# Stated in MapBlocks (16 nodes).
block_cull_optimize_distance (Block cull optimize distance) int 25 2 2047
[**Mapgen] [**Mapgen]
# Size of mapchunks generated by mapgen, stated in mapblocks (16 nodes). # Size of mapchunks generated by mapgen, stated in mapblocks (16 nodes).

@ -1,6 +1,13 @@
#define rendered texture0 #define rendered texture0
#define bloom texture1 #define bloom texture1
#ifdef GL_ES
// Dithering requires sufficient floating-point precision
#ifndef GL_FRAGMENT_PRECISION_HIGH
#undef ENABLE_DITHERING
#endif
#endif
struct ExposureParams { struct ExposureParams {
float compensationFactor; float compensationFactor;
}; };
@ -61,14 +68,17 @@ vec3 uncharted2Tonemap(vec3 x)
vec4 applyToneMapping(vec4 color) vec4 applyToneMapping(vec4 color)
{ {
const float exposureBias = 2.0; color = vec4(pow(color.rgb, vec3(2.2)), color.a);
const float gamma = 1.6;
const float exposureBias = 5.5;
color.rgb = uncharted2Tonemap(exposureBias * color.rgb); color.rgb = uncharted2Tonemap(exposureBias * color.rgb);
// Precalculated white_scale from // Precalculated white_scale from
//vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W)); //vec3 whiteScale = 1.0 / uncharted2Tonemap(vec3(W));
vec3 whiteScale = vec3(1.036015346); vec3 whiteScale = vec3(1.036015346);
color.rgb *= whiteScale; color.rgb *= whiteScale;
return color; return vec4(pow(color.rgb, vec3(1.0 / gamma)), color.a);
} }
#endif
vec3 applySaturation(vec3 color, float factor) vec3 applySaturation(vec3 color, float factor)
{ {
@ -77,6 +87,19 @@ vec3 applySaturation(vec3 color, float factor)
float brightness = dot(color, vec3(0.2125, 0.7154, 0.0721)); float brightness = dot(color, vec3(0.2125, 0.7154, 0.0721));
return mix(vec3(brightness), color, factor); return mix(vec3(brightness), color, factor);
} }
#ifdef ENABLE_DITHERING
// From http://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf
// and https://www.shadertoy.com/view/MslGR8 (5th one starting from the bottom)
// NOTE: `frag_coord` is in pixels (i.e. not normalized UV).
vec3 screen_space_dither(highp vec2 frag_coord) {
// Iestyn's RGB dither (7 asm instructions) from Portal 2 X360, slightly modified for VR.
highp vec3 dither = vec3(dot(vec2(171.0, 231.0), frag_coord));
dither.rgb = fract(dither.rgb / vec3(103.0, 71.0, 97.0));
// Subtract 0.5 to avoid slightly brightening the whole viewport.
return (dither.rgb - 0.5) / 255.0;
}
#endif #endif
void main(void) void main(void)
@ -105,25 +128,31 @@ void main(void)
#endif #endif
} }
#ifdef ENABLE_BLOOM #ifdef ENABLE_BLOOM
color = applyBloom(color, uv); color = applyBloom(color, uv);
#endif #endif
color.rgb = clamp(color.rgb, vec3(0.), vec3(1.));
// return to sRGB colorspace (approximate)
color.rgb = pow(color.rgb, vec3(1.0 / 2.2));
#ifdef ENABLE_BLOOM_DEBUG #ifdef ENABLE_BLOOM_DEBUG
if (uv.x > 0.5 || uv.y > 0.5) if (uv.x > 0.5 || uv.y > 0.5)
#endif #endif
{ {
#if ENABLE_TONE_MAPPING #if ENABLE_TONE_MAPPING
color = applyToneMapping(color); color = applyToneMapping(color);
color.rgb = applySaturation(color.rgb, saturation);
#endif #endif
color.rgb = applySaturation(color.rgb, saturation);
} }
color.rgb = clamp(color.rgb, vec3(0.), vec3(1.)); #ifdef ENABLE_DITHERING
// Apply dithering just before quantisation
// return to sRGB colorspace (approximate) color.rgb += screen_space_dither(gl_FragCoord.xy);
color.rgb = pow(color.rgb, vec3(1.0 / 2.2)); #endif
gl_FragColor = vec4(color.rgb, 1.0); // force full alpha to avoid holes in the image. gl_FragColor = vec4(color.rgb, 1.0); // force full alpha to avoid holes in the image.
} }

@ -0,0 +1,115 @@
#define rendered texture0
#define depthmap texture1
uniform sampler2D rendered;
uniform sampler2D depthmap;
uniform vec3 sunPositionScreen;
uniform float sunBrightness;
uniform vec3 moonPositionScreen;
uniform float moonBrightness;
uniform lowp float volumetricLightStrength;
uniform vec3 dayLight;
#ifdef ENABLE_DYNAMIC_SHADOWS
uniform vec3 v_LightDirection;
#else
const vec3 v_LightDirection = vec3(0.0, -1.0, 0.0);
#endif
#ifdef GL_ES
varying mediump vec2 varTexCoord;
#else
centroid varying vec2 varTexCoord;
#endif
const float far = 1000.;
float mapDepth(float depth)
{
return min(1., 1. / (1.00001 - depth) / far);
}
float noise(vec3 uvd) {
return fract(dot(sin(uvd * vec3(13041.19699, 27723.29171, 61029.77801)), vec3(73137.11101, 37312.92319, 10108.89991)));
}
float sampleVolumetricLight(vec2 uv, vec3 lightVec, float rawDepth)
{
lightVec = 0.5 * lightVec / lightVec.z + 0.5;
const float samples = 30.;
float result = texture2D(depthmap, uv).r < 1. ? 0.0 : 1.0;
float bias = noise(vec3(uv, rawDepth));
vec2 samplepos;
for (float i = 1.; i < samples; i++) {
samplepos = mix(uv, lightVec.xy, (i + bias) / samples);
if (min(samplepos.x, samplepos.y) > 0. && max(samplepos.x, samplepos.y) < 1.)
result += texture2D(depthmap, samplepos).r < 1. ? 0.0 : 1.0;
}
return result / samples;
}
vec3 getDirectLightScatteringAtGround(vec3 v_LightDirection)
{
// Based on talk at 2002 Game Developers Conference by Naty Hoffman and Arcot J. Preetham
const float beta_r0 = 1e-5; // Rayleigh scattering beta
// These factors are calculated based on expected value of scattering factor of 1e-5
// for Nitrogen at 532nm (green), 2e25 molecules/m3 in atmosphere
const vec3 beta_r0_l = vec3(3.3362176e-01, 8.75378289198826e-01, 1.95342379700656) * beta_r0; // wavelength-dependent scattering
const float atmosphere_height = 15000.; // height of the atmosphere in meters
// sun/moon light at the ground level, after going through the atmosphere
return exp(-beta_r0_l * atmosphere_height / (1e-5 - dot(v_LightDirection, vec3(0., 1., 0.))));
}
vec3 applyVolumetricLight(vec3 color, vec2 uv, float rawDepth)
{
vec3 lookDirection = normalize(vec3(uv.x * 2. - 1., uv.y * 2. - 1., rawDepth));
const float boost = 4.0;
float brightness = 0.;
vec3 sourcePosition = vec3(-1., -1., -1);
if (sunPositionScreen.z > 0. && sunBrightness > 0.) {
brightness = sunBrightness;
sourcePosition = sunPositionScreen;
}
else if (moonPositionScreen.z > 0. && moonBrightness > 0.) {
brightness = moonBrightness * 0.05;
sourcePosition = moonPositionScreen;
}
float cameraDirectionFactor = pow(clamp(dot(sourcePosition, vec3(0., 0., 1.)), 0.0, 0.7), 2.5);
float viewAngleFactor = pow(max(0., dot(sourcePosition, lookDirection)), 8.);
float lightFactor = brightness * sampleVolumetricLight(uv, sourcePosition, rawDepth) *
(0.05 * cameraDirectionFactor + 0.95 * viewAngleFactor);
color = mix(color, boost * getDirectLightScatteringAtGround(v_LightDirection) * dayLight, lightFactor);
// a factor of 5 tested well
color *= volumetricLightStrength * 5.0;
// if (sunPositionScreen.z < 0.)
// color.rg += 1. - clamp(abs((2. * uv.xy - 1.) - sunPositionScreen.xy / sunPositionScreen.z) * 1000., 0., 1.);
// if (moonPositionScreen.z < 0.)
// color.rg += 1. - clamp(abs((2. * uv.xy - 1.) - moonPositionScreen.xy / moonPositionScreen.z) * 1000., 0., 1.);
return color;
}
void main(void)
{
vec2 uv = varTexCoord.st;
vec3 color = texture2D(rendered, uv).rgb;
// translate to linear colorspace (approximate)
color = pow(color, vec3(2.2));
if (volumetricLightStrength > 0.0) {
float rawDepth = texture2D(depthmap, uv).r;
color = applyVolumetricLight(color, uv, rawDepth);
}
gl_FragColor = vec4(color, 1.0); // force full alpha to avoid holes in the image.
}

@ -0,0 +1,12 @@
#ifdef GL_ES
varying mediump vec2 varTexCoord;
#else
centroid varying vec2 varTexCoord;
#endif
void main(void)
{
varTexCoord.st = inTexCoord0.st;
gl_Position = inVertexPosition;
}

@ -33,7 +33,7 @@ end)
core.after(1, function() core.after(1, function()
print("armor: " .. dump(core.localplayer:get_armor_groups())) print("armor: " .. dump(core.localplayer:get_armor_groups()))
id = core.localplayer:hud_add({ id = core.localplayer:hud_add({
hud_elem_type = "text", type = "text",
name = "example", name = "example",
number = 0xff0000, number = 0xff0000,
position = {x=0, y=1}, position = {x=0, y=1},

@ -1332,8 +1332,10 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or
```lua ```lua
{ {
hud_elem_type = "image", -- see HUD element types, default "text" type = "image", -- see HUD element types, default "text"
-- ^ type of HUD element, can be either of "image", "text", "statbar", or "inventory" -- ^ type of HUD element, can be either of "image", "text", "statbar", or "inventory"
hud_elem_type = "image",
-- ^ Deprecated, same as `type`. In case both are specified `type` will be used.
position = {x=0.5, y=0.5}, position = {x=0.5, y=0.5},
-- ^ Left corner position of element, default `{x=0,y=0}`. -- ^ Left corner position of element, default `{x=0,y=0}`.
name = "<name>", -- default "" name = "<name>", -- default ""

@ -30,7 +30,7 @@ For openSUSE users:
For Arch users: For Arch users:
sudo pacman -S base-devel libcurl-gnutls cmake libxi libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd gettext sudo pacman -S --needed base-devel libcurl-gnutls cmake libxi libpng sqlite libogg libvorbis openal freetype2 jsoncpp gmp luajit leveldb ncurses zstd gettext
For Alpine users: For Alpine users:

@ -33,13 +33,15 @@ mkdir build
cd build cd build
cmake .. \ cmake .. \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 \
-DCMAKE_FIND_FRAMEWORK=LAST \ -DCMAKE_FIND_FRAMEWORK=LAST \
-DCMAKE_INSTALL_PREFIX=../build/macos/ \ -DCMAKE_INSTALL_PREFIX=../build/macos/ \
-DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE
make -j$(sysctl -n hw.logicalcpu) make -j$(sysctl -n hw.logicalcpu)
make install make install
# M1 Macs w/ MacOS >= BigSur
codesign --force --deep -s - macos/minetest.app
``` ```
## Run ## Run

@ -5278,6 +5278,8 @@ Utilities
-- liquid_fluidity, liquid_fluidity_smooth, liquid_sink, -- liquid_fluidity, liquid_fluidity_smooth, liquid_sink,
-- acceleration_default, acceleration_air (5.8.0) -- acceleration_default, acceleration_air (5.8.0)
physics_overrides_v2 = true, physics_overrides_v2 = true,
-- In HUD definitions the field `type` is used and `hud_elem_type` is deprecated (5.9.0)
hud_def_type_field = true,
} }
``` ```
@ -5350,6 +5352,12 @@ Utilities
-- HUD Scaling multiplier -- HUD Scaling multiplier
-- Equal to the setting `hud_scaling` multiplied by `dpi / 96` -- Equal to the setting `hud_scaling` multiplied by `dpi / 96`
real_hud_scaling = 1, real_hud_scaling = 1,
-- Whether the touchscreen controls are enabled.
-- Usually (but not always) `true` on Android.
-- Requires at least Minetest 5.9.0 on the client. For older clients, it
-- is always set to `false`.
touch_controls = false,
} }
``` ```
@ -5412,9 +5420,8 @@ Utilities
* `compression`: Optional zlib compression level, number in range 0 to 9. * `compression`: Optional zlib compression level, number in range 0 to 9.
The data is one-dimensional, starting in the upper left corner of the image The data is one-dimensional, starting in the upper left corner of the image
and laid out in scanlines going from left to right, then top to bottom. and laid out in scanlines going from left to right, then top to bottom.
Please note that it's not safe to use string.char to generate raw data, You can use `colorspec_to_bytes` to generate raw RGBA values.
use `colorspec_to_bytes` to generate raw RGBA values in a predictable way. Palettes are not supported at the moment.
The resulting PNG image is always 32-bit. 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 non-unreserved URI characters by a
percent sign followed by two hex digits. See percent sign followed by two hex digits. See
@ -7458,15 +7465,20 @@ child will follow movement and rotation of that bone.
* Sets the position of the object. * Sets the position of the object.
* No-op if object is attached. * No-op if object is attached.
* `pos` is a vector `{x=num, y=num, z=num}` * `pos` is a vector `{x=num, y=num, z=num}`
* `add_pos(pos)`:
* Changes position by adding to the current position.
* No-op if object is attached.
* `pos` is a vector `{x=num, y=num, z=num}`.
* In comparison to using `set_pos`, `add_pos` will avoid synchronization problems.
* `get_velocity()`: returns the velocity, a vector. * `get_velocity()`: returns the velocity, a vector.
* `add_velocity(vel)` * `add_velocity(vel)`
* Changes velocity by adding to the current velocity. * Changes velocity by adding to the current velocity.
* `vel` is a vector, e.g. `{x=0.0, y=2.3, z=1.0}` * `vel` is a vector, e.g. `{x=0.0, y=2.3, z=1.0}`
* In comparison to using get_velocity, adding the velocity and then using * In comparison to using `get_velocity`, adding the velocity and then using
set_velocity, add_velocity is supposed to avoid synchronization problems. `set_velocity`, `add_velocity` is supposed to avoid synchronization problems.
Additionally, players also do not support set_velocity. Additionally, players also do not support `set_velocity`.
* If object is a player: * If object is a player:
* Does not apply during free_move. * Does not apply during `free_move`.
* Note that since the player speed is normalized at each move step, * Note that since the player speed is normalized at each move step,
increasing e.g. Y velocity beyond what would usually be achieved increasing e.g. Y velocity beyond what would usually be achieved
(see: physics overrides) will cause existing X/Z velocity to be reduced. (see: physics overrides) will cause existing X/Z velocity to be reduced.
@ -7550,17 +7562,32 @@ child will follow movement and rotation of that bone.
object. object.
* `set_detach()`: Detaches object. No-op if object was not attached. * `set_detach()`: Detaches object. No-op if object was not attached.
* `set_bone_position([bone, position, rotation])` * `set_bone_position([bone, position, rotation])`
* `bone`: string. Default is `""`, the root bone * Shorthand for `set_bone_override(bone, {position = position, rotation = rotation:apply(math.rad)})` using absolute values.
* `position`: `{x=num, y=num, z=num}`, relative, `default {x=0, y=0, z=0}` * **Note:** Rotation is in degrees, not radians.
* `rotation`: `{x=num, y=num, z=num}`, default `{x=0, y=0, z=0}` * **Deprecated:** Use `set_bone_override` instead.
* `get_bone_position(bone)`: * `get_bone_position(bone)`: returns the previously set position and rotation of the bone
* returns bone parameters previously set by `set_bone_position` * Shorthand for `get_bone_override(bone).position.vec, get_bone_override(bone).rotation.vec:apply(math.deg)`.
* returns `position, rotation` of the specified bone (as vectors) * **Note:** Returned rotation is in degrees, not radians.
* note: position is relative to the object * **Deprecated:** Use `get_bone_override` instead.
* `set_properties(object property table)`: * `set_bone_override(bone, override)`
* set a number of object properties in the given table * `bone`: string
* only properties listed in the table will be changed * `override`: `{ position = property, rotation = property, scale = property }` or `nil`
* see the 'Object properties' section for details * `property`: `{ vec = vector, interpolation = 0, absolute = false}` or `nil`;
* `vec` is in the same coordinate system as the model, and in degrees for rotation
* `property = nil` is equivalent to no override on that property
* `absolute`: If set to `false`, the override will be relative to the animated property:
* Transposition in the case of `position`;
* Composition in the case of `rotation`;
* Multiplication in the case of `scale`
* `interpolation`: Old and new values are interpolated over this timeframe (in seconds)
* `override = nil` (including omission) is shorthand for `override = {}` which clears the override
* **Note:** Unlike `set_bone_position`, the rotation is in radians, not degrees.
* Compatibility note: Clients prior to 5.9.0 only support absolute position and rotation.
All values are treated as absolute and are set immediately (no interpolation).
* `get_bone_override(bone)`: returns `override` in the above format
* **Note:** Unlike `get_bone_position`, the returned rotation is in radians, not degrees.
* `get_bone_overrides()`: returns all bone overrides as table `{[bonename] = override, ...}`
* `set_properties(object property table)`
* `get_properties()`: returns a table of all object properties * `get_properties()`: returns a table of all object properties
* `is_player()`: returns true for players, false otherwise * `is_player()`: returns true for players, false otherwise
* `get_nametag_attributes()` * `get_nametag_attributes()`
@ -7770,7 +7797,7 @@ child will follow movement and rotation of that bone.
* `hud_change(id, stat, value)`: change a value of a previously added HUD * `hud_change(id, stat, value)`: change a value of a previously added HUD
element. element.
* `stat` supports the same keys as in the hud definition table except for * `stat` supports the same keys as in the hud definition table except for
`"hud_elem_type"`. `"type"` (or the deprecated `"hud_elem_type"`).
* `hud_get(id)`: gets the HUD element definition structure of the specified ID * `hud_get(id)`: gets the HUD element definition structure of the specified ID
* `hud_set_flags(flags)`: sets specified HUD flags of player. * `hud_set_flags(flags)`: sets specified HUD flags of player.
* `flags`: A table with the following fields set to boolean values * `flags`: A table with the following fields set to boolean values
@ -8001,9 +8028,8 @@ child will follow movement and rotation of that bone.
* Passing no arguments resets lighting to its default values. * Passing no arguments resets lighting to its default values.
* `light_definition` is a table with the following optional fields: * `light_definition` is a table with the following optional fields:
* `saturation` sets the saturation (vividness; default: `1.0`). * `saturation` sets the saturation (vividness; default: `1.0`).
values > 1 increase the saturation * values > 1 increase the saturation
values in [0,1) decrease the saturation * values in [0,1] decrease the saturation
* This value has no effect on clients who have the "Tone Mapping" shader disabled.
* `shadows` is a table that controls ambient shadows * `shadows` is a table that controls ambient shadows
* `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness) * `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness)
* This value has no effect on clients who have the "Dynamic Shadows" shader disabled. * This value has no effect on clients who have the "Dynamic Shadows" shader disabled.
@ -8015,6 +8041,9 @@ child will follow movement and rotation of that bone.
* `speed_dark_bright` set the speed of adapting to bright light (default: `1000.0`) * `speed_dark_bright` set the speed of adapting to bright light (default: `1000.0`)
* `speed_bright_dark` set the speed of adapting to dark scene (default: `1000.0`) * `speed_bright_dark` set the speed of adapting to dark scene (default: `1000.0`)
* `center_weight_power` set the power factor for center-weighted luminance measurement (default: `1.0`) * `center_weight_power` set the power factor for center-weighted luminance measurement (default: `1.0`)
* `volumetric_light`: is a table that controls volumetric light (a.k.a. "godrays")
* `strength`: sets the strength of the volumetric light effect from 0 (off, default) to 1 (strongest)
* This value has no effect on clients who have the "Volumetric Lighting" or "Bloom" shaders disabled.
* `get_lighting()`: returns the current state of lighting for the player. * `get_lighting()`: returns the current state of lighting for the player.
* Result is a table with the same fields as `light_definition` in `set_lighting`. * Result is a table with the same fields as `light_definition` in `set_lighting`.
@ -10027,9 +10056,14 @@ Used by `ObjectRef:hud_add`. Returned by `ObjectRef:hud_get`.
```lua ```lua
{ {
hud_elem_type = "image", type = "image",
-- Type of element, can be "image", "text", "statbar", "inventory", -- Type of element, can be "image", "text", "statbar", "inventory",
-- "waypoint", "image_waypoint", "compass" or "minimap" -- "waypoint", "image_waypoint", "compass" or "minimap"
-- If undefined "text" will be used.
hud_elem_type = "image",
-- Deprecated, same as `type`.
-- In case both are specified `type` will be used.
position = {x=0.5, y=0.5}, position = {x=0.5, y=0.5},
-- Top left corner position of element -- Top left corner position of element

@ -38,7 +38,9 @@ Functions
--------- ---------
* `core.start()` * `core.start()`
* start game session
* `core.close()` * `core.close()`
* exit engine
* `core.get_min_supp_proto()` * `core.get_min_supp_proto()`
* returns the minimum supported network protocol version * returns the minimum supported network protocol version
* `core.get_max_supp_proto()` * `core.get_max_supp_proto()`
@ -53,6 +55,10 @@ Functions
* Android only. Shares file using the share popup * Android only. Shares file using the share popup
* `core.get_version()` (possible in async calls) * `core.get_version()` (possible in async calls)
* returns current core version * returns current core version
* `core.set_once(key, value)`:
* save a string value that persists even if menu is closed
* `core.get_once(key)`:
* get a string value saved by above function, or `nil`
@ -249,6 +255,10 @@ GUI
-- HUD Scaling multiplier -- HUD Scaling multiplier
-- Equal to the setting `hud_scaling` multiplied by `dpi / 96` -- Equal to the setting `hud_scaling` multiplied by `dpi / 96`
real_hud_scaling = 1, real_hud_scaling = 1,
-- Whether the touchscreen controls are enabled.
-- Usually (but not always) `true` on Android.
touch_controls = false,
} }
``` ```

@ -133,11 +133,6 @@ are placeholders intended to be overwritten by the game.
### Android textures ### Android textures
* `down_arrow.png`
* `left_arrow.png`
* `right_arrow.png`
* `up_arrow.png`
* `drop_btn.png` * `drop_btn.png`
* `fast_btn.png` * `fast_btn.png`
* `fly_btn.png` * `fly_btn.png`

@ -66,7 +66,7 @@ minetest.register_globalstep(function()
local ent = pointed_thing.ref:get_luaentity() local ent = pointed_thing.ref:get_luaentity()
if ent and ent.name == "testentities:selectionbox" then if ent and ent.name == "testentities:selectionbox" then
hud_ids[pname] = hud_id or player:hud_add({ hud_ids[pname] = hud_id or player:hud_add({
hud_elem_type = "text", -- See HUD element types type = "text", -- See HUD element types
position = {x=0.5, y=0.5}, position = {x=0.5, y=0.5},
text = "X", text = "X",
number = 0xFF0000, number = 0xFF0000,

@ -7,6 +7,8 @@ local function show_fullscreen_fs(name)
print(dump(window)) print(dump(window))
local size = { x = window.max_formspec_size.x * 1.1, y = window.max_formspec_size.y * 1.1 } local size = { x = window.max_formspec_size.x * 1.1, y = window.max_formspec_size.y * 1.1 }
local touch_text = window.touch_controls and "Touch controls enabled" or
"Touch controls disabled"
local fs = { local fs = {
"formspec_version[4]", "formspec_version[4]",
("size[%f,%f]"):format(size.x, size.y), ("size[%f,%f]"):format(size.x, size.y),
@ -16,7 +18,8 @@ local function show_fullscreen_fs(name)
("button[%f,%f;1,1;%s;%s]"):format(size.x - 1, size.y - 1, "br", "BR"), ("button[%f,%f;1,1;%s;%s]"):format(size.x - 1, size.y - 1, "br", "BR"),
("button[%f,%f;1,1;%s;%s]"):format(0, size.y - 1, "bl", "BL"), ("button[%f,%f;1,1;%s;%s]"):format(0, size.y - 1, "bl", "BL"),
("label[%f,%f;%s]"):format(size.x / 2, size.y / 2, "Fullscreen") ("label[%f,%f;%s]"):format(size.x / 2, size.y / 2, "Fullscreen"),
("label[%f,%f;%s]"):format(size.x / 2, size.y / 2 + 1, touch_text),
} }
minetest.show_formspec(name, "testfullscreenfs:fs", table.concat(fs)) minetest.show_formspec(name, "testfullscreenfs:fs", table.concat(fs))

@ -12,7 +12,7 @@ local font_states = {
local font_default_def = { local font_default_def = {
hud_elem_type = "text", type = "text",
position = {x = 0.5, y = 0.5}, position = {x = 0.5, y = 0.5},
scale = {x = 2, y = 2}, scale = {x = 2, y = 2},
alignment = { x = 0, y = 0 }, alignment = { x = 0, y = 0 },
@ -102,14 +102,14 @@ minetest.register_chatcommand("hudwaypoints", {
return false return false
end end
local regular = player:hud_add { local regular = player:hud_add {
hud_elem_type = "waypoint", type = "waypoint",
name = "regular waypoint", name = "regular waypoint",
text = "m", text = "m",
number = 0xFFFFFF, number = 0xFFFFFF,
world_pos = vector.add(player:get_pos(), {x = 0, y = 1.5, z = 0}) world_pos = vector.add(player:get_pos(), {x = 0, y = 1.5, z = 0})
} }
local reduced_precision = player:hud_add { local reduced_precision = player:hud_add {
hud_elem_type = "waypoint", type = "waypoint",
name = "imprecise waypoint", name = "imprecise waypoint",
text = "m (0.1 steps, precision = 10)", text = "m (0.1 steps, precision = 10)",
precision = 10, precision = 10,
@ -117,7 +117,7 @@ minetest.register_chatcommand("hudwaypoints", {
world_pos = vector.add(player:get_pos(), {x = 0, y = 1, z = 0}) world_pos = vector.add(player:get_pos(), {x = 0, y = 1, z = 0})
} }
local hidden_distance = player:hud_add { local hidden_distance = player:hud_add {
hud_elem_type = "waypoint", type = "waypoint",
name = "waypoint with hidden distance", name = "waypoint with hidden distance",
text = "this text is hidden as well (precision = 0)", text = "this text is hidden as well (precision = 0)",
precision = 0, precision = 0,
@ -149,7 +149,7 @@ minetest.register_chatcommand("hudwaypoints", {
minetest.after(0.5, change, player) minetest.after(0.5, change, player)
end end
local image_waypoint = player:hud_add { local image_waypoint = player:hud_add {
hud_elem_type = "image_waypoint", type = "image_waypoint",
text = "testhud_waypoint.png", text = "testhud_waypoint.png",
world_pos = player:get_pos(), world_pos = player:get_pos(),
scale = {x = 3, y = 3}, scale = {x = 3, y = 3},

@ -105,6 +105,19 @@ local function gen_checkers(w, h, tile)
return r return r
end end
-- The engine should perform color reduction of the generated PNG in certain
-- cases, so we have this helper to check the result
local function encode_and_check(w, h, ctype, data)
local ret = core.encode_png(w, h, data)
assert(ret:sub(1, 8) == "\137PNG\r\n\026\n", "missing png signature")
assert(ret:sub(9, 16) == "\000\000\000\rIHDR", "didn't find ihdr chunk")
local ctype_actual = ret:byte(26) -- Color Type (1 byte)
ctype = ({rgba=6, rgb=2, gray=0})[ctype]
assert(ctype_actual == ctype, "png should have color type " .. ctype ..
" but actually has " .. ctype_actual)
return ret
end
local fractal = mandelbrot(512, 512, 128) local fractal = mandelbrot(512, 512, 128)
local frac_emb = mandelbrot(64, 64, 64) local frac_emb = mandelbrot(64, 64, 64)
local checker = gen_checkers(512, 512, 32) local checker = gen_checkers(512, 512, 32)
@ -129,17 +142,21 @@ for i=1, #fractal do
b = floor(abs(1 - fractal[i]) * 255), b = floor(abs(1 - fractal[i]) * 255),
a = 255, a = 255,
} }
data_ck[i] = checker[i] > 0 and "#F80" or "#000" data_ck[i] = checker[i] > 0 and "#888" or "#000"
end end
fractal = nil
frac_emb = nil
checker = nil
local textures_path = minetest.get_modpath( minetest.get_current_modname() ) .. "/textures/" local textures_path = minetest.get_modpath( minetest.get_current_modname() ) .. "/textures/"
minetest.safe_file_write( minetest.safe_file_write(
textures_path .. "testnodes_generated_mb.png", textures_path .. "testnodes_generated_mb.png",
minetest.encode_png(512,512,data_mb) encode_and_check(512, 512, "rgb", data_mb)
) )
minetest.safe_file_write( minetest.safe_file_write(
textures_path .. "testnodes_generated_ck.png", textures_path .. "testnodes_generated_ck.png",
minetest.encode_png(512,512,data_ck) encode_and_check(512, 512, "gray", data_ck)
) )
minetest.register_node("testnodes:generated_png_mb", { minetest.register_node("testnodes:generated_png_mb", {
@ -155,7 +172,8 @@ minetest.register_node("testnodes:generated_png_ck", {
groups = { dig_immediate = 2 }, groups = { dig_immediate = 2 },
}) })
local png_emb = "[png:" .. minetest.encode_base64(minetest.encode_png(64,64,data_emb)) local png_emb = "[png:" .. minetest.encode_base64(
encode_and_check(64, 64, "rgba", data_emb))
minetest.register_node("testnodes:generated_png_emb", { minetest.register_node("testnodes:generated_png_emb", {
description = S("Generated In-Band Mandelbrot PNG Test Node"), description = S("Generated In-Band Mandelbrot PNG Test Node"),
@ -182,6 +200,10 @@ minetest.register_node("testnodes:generated_png_dst_emb", {
groups = { dig_immediate = 2 }, groups = { dig_immediate = 2 },
}) })
data_emb = nil
data_mb = nil
data_ck = nil
--[[ --[[
The following nodes can be used to demonstrate the TGA format support. The following nodes can be used to demonstrate the TGA format support.

@ -68,3 +68,19 @@ local function run_player_meta_tests(player)
assert(meta:equals(meta2)) assert(meta:equals(meta2))
end end
unittests.register("test_player_meta", run_player_meta_tests, {player=true}) unittests.register("test_player_meta", run_player_meta_tests, {player=true})
--
-- Player add pos
--
local function run_player_add_pos_tests(player)
local pos = player:get_pos()
player:add_pos(vector.new(0, 1000, 0))
local newpos = player:get_pos()
player:add_pos(vector.new(0, -1000, 0))
local backpos = player:get_pos()
local newdist = vector.distance(pos, newpos)
assert(math.abs(newdist - 1000) <= 1)
assert(vector.distance(pos, backpos) <= 1)
end
unittests.register("test_player_add_pos", run_player_add_pos_tests, {player=true})

@ -49,7 +49,7 @@
# type: float min: 0 max: 0.99 # type: float min: 0 max: 0.99
# camera_smoothing = 0.0 # camera_smoothing = 0.0
# Smooths rotation of camera when in cinematic mode, 0 to disable. Enter cinematic mode by using the key set in Change Keys. # Smooths rotation of camera when in cinematic mode, 0 to disable. Enter cinematic mode by using the key set in Controls.
# type: float min: 0 max: 0.99 # type: float min: 0 max: 0.99
# cinematic_camera_smoothing = 0.7 # cinematic_camera_smoothing = 0.7
@ -787,6 +787,15 @@
# type: bool # type: bool
# strict_protocol_version_checking = false # strict_protocol_version_checking = false
# Define the oldest clients allowed to connect.
# Older clients are compatible in the sense that they will not crash when connecting
# to new servers, but they may not support all new features that you are expecting.
# This allows more fine-grained control than strict_protocol_version_checking.
# Minetest may still enforce its own internal minimum, and enabling
# strict_protocol_version_checking will effectively override this.
# type: int min: 1 max: 65535
# protocol_version_min = 1
# Specifies URL from which client fetches media instead of using UDP. # Specifies URL from which client fetches media instead of using UDP.
# $filename should be accessible from $remote_media$filename via cURL # $filename should be accessible from $remote_media$filename via cURL
# (obviously, remote_media should end with a slash). # (obviously, remote_media should end with a slash).
@ -3654,4 +3663,3 @@
# See https://github.com/minetest/irrlicht/blob/master/include/Keycodes.h # See https://github.com/minetest/irrlicht/blob/master/include/Keycodes.h
# type: key # type: key
# keymap_decrease_viewing_range_min = - # keymap_decrease_viewing_range_min = -

@ -1,53 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: minetest
name: minetest
namespace: default
spec:
selector:
matchLabels:
app: minetest
template:
metadata:
labels:
app: minetest
spec:
containers:
- image: registry.gitlab.com/minetest/minetest/server:master
name: minetest
ports:
- containerPort: 30000
protocol: UDP
volumeMounts:
- mountPath: /var/lib/minetest
name: minetest-data
- mountPath: /etc/minetest
name: config
restartPolicy: Always
volumes:
- name: minetest-data
persistentVolumeClaim:
claimName: minetest-data
- configMap:
defaultMode: 420
name: minetest
name: config
---
apiVersion: v1
kind: Service
metadata:
labels:
app: minetest
name: minetest
namespace: default
spec:
ports:
- name: minetest
port: 30000
protocol: UDP
selector:
app: minetest
type: NodePort

@ -12,6 +12,7 @@
<windowsSettings> <windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage> <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
<heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType>
</windowsSettings> </windowsSettings>
</application> </application>
</assembly> </assembly>

@ -314,6 +314,18 @@ else()
endif() endif()
endif() endif()
# On clang and gcc, some functionalities of std::atomic require -latomic.
# See <https://en.cppreference.com/w/cpp/atomic/atomic#Notes>.
# Note that find_library does not reliably find it so we have to resort to this.
# Also, passing -latomic is not always the same as adding atomic to the library list.
include(CheckCSourceCompiles)
set(CMAKE_REQUIRED_LIBRARIES "-latomic")
check_c_source_compiles("int main(){}" HAVE_LINK_ATOMIC)
set(CMAKE_REQUIRED_LIBRARIES "")
if(HAVE_LINK_ATOMIC)
set(PLATFORM_LIBS ${PLATFORM_LIBS} "-latomic")
endif()
check_include_files(endian.h HAVE_ENDIAN_H) check_include_files(endian.h HAVE_ENDIAN_H)
configure_file( configure_file(
@ -761,7 +773,9 @@ else()
endif() endif()
if(MINGW) if(MINGW)
set(OTHER_FLAGS "${OTHER_FLAGS} -mthreads -fexceptions") if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(OTHER_FLAGS "${OTHER_FLAGS} -mthreads")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WIN32_WINNT=0x0601 -DWIN32_LEAN_AND_MEAN") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WIN32_WINNT=0x0601 -DWIN32_LEAN_AND_MEAN")
endif() endif()
@ -801,7 +815,7 @@ else()
endif() endif()
if(MINGW) if(MINGW)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mwindows") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "-mwindows")
endif() endif()
endif() endif()

@ -21,7 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irr_aabb3d.h" #include "irr_aabb3d.h"
#include "irr_v3d.h" #include "irr_v3d.h"
#include <quaternion.h>
#include <string> #include <string>
#include <unordered_map>
enum ActiveObjectType { enum ActiveObjectType {
@ -72,6 +74,78 @@ enum ActiveObjectCommand {
AO_CMD_SET_ANIMATION_SPEED AO_CMD_SET_ANIMATION_SPEED
}; };
struct BoneOverride
{
struct PositionProperty
{
v3f previous;
v3f vector;
bool absolute = false;
f32 interp_timer = 0;
} position;
v3f getPosition(v3f anim_pos) const {
f32 progress = dtime_passed / position.interp_timer;
if (progress > 1.0f || position.interp_timer == 0.0f)
progress = 1.0f;
return position.vector.getInterpolated(position.previous, progress)
+ (position.absolute ? v3f() : anim_pos);
}
struct RotationProperty
{
core::quaternion previous;
core::quaternion next;
bool absolute = false;
f32 interp_timer = 0;
} rotation;
v3f getRotationEulerDeg(v3f anim_rot_euler) const {
core::quaternion rot;
f32 progress = dtime_passed / rotation.interp_timer;
if (progress > 1.0f || rotation.interp_timer == 0.0f)
progress = 1.0f;
rot.slerp(rotation.previous, rotation.next, progress);
if (!rotation.absolute) {
core::quaternion anim_rot(anim_rot_euler * core::DEGTORAD);
rot = rot * anim_rot; // first rotate by anim. bone rot., then rot.
}
v3f rot_euler;
rot.toEuler(rot_euler);
return rot_euler * core::RADTODEG;
}
struct ScaleProperty
{
v3f previous;
v3f vector{1, 1, 1};
bool absolute = false;
f32 interp_timer = 0;
} scale;
v3f getScale(v3f anim_scale) const {
f32 progress = dtime_passed / scale.interp_timer;
if (progress > 1.0f || scale.interp_timer == 0.0f)
progress = 1.0f;
return scale.vector.getInterpolated(scale.previous, progress)
* (scale.absolute ? v3f(1) : anim_scale);
}
f32 dtime_passed = 0;
bool isIdentity() const
{
return !position.absolute && position.vector == v3f()
&& !rotation.absolute && rotation.next == core::quaternion()
&& !scale.absolute && scale.vector == v3f(1);
}
};
typedef std::unordered_map<std::string, BoneOverride> BoneOverrideMap;
/* /*
Parent class for ServerActiveObject and ClientActiveObject Parent class for ServerActiveObject and ClientActiveObject
*/ */

@ -2,6 +2,7 @@ set (BENCHMARK_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/benchmark.cpp ${CMAKE_CURRENT_SOURCE_DIR}/benchmark.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_lighting.cpp ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_lighting.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_serialize.cpp ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_serialize.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapblock.cpp
PARENT_SCOPE) PARENT_SCOPE)
set (BENCHMARK_CLIENT_SRCS set (BENCHMARK_CLIENT_SRCS

@ -23,10 +23,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define CATCH_CONFIG_RUNNER #define CATCH_CONFIG_RUNNER
#include "benchmark_setup.h" #include "benchmark_setup.h"
int run_benchmarks() bool run_benchmarks(const char *arg)
{ {
int argc = 1; const char *const argv[] = {
const char *argv[] = { "MinetestBenchmark", NULL }; "MinetestBenchmark", arg, nullptr
};
const int argc = arg ? 2 : 1;
int errCount = Catch::Session().run(argc, argv); int errCount = Catch::Session().run(argc, argv);
return errCount ? 1 : 0; return errCount == 0;
} }

@ -22,5 +22,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "config.h" #include "config.h"
#if BUILD_BENCHMARKS #if BUILD_BENCHMARKS
extern int run_benchmarks(); extern bool run_benchmarks(const char *arg = nullptr);
#endif #endif

@ -0,0 +1,187 @@
/*
Minetest
Copyright (C) 2023 Minetest Authors
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "benchmark_setup.h"
#include "mapblock.h"
#include <vector>
typedef std::vector<MapBlock*> MBContainer;
static void allocateSome(MBContainer &vec, u32 n)
{
vec.reserve(vec.size() + n);
for (u32 i = 0; i < n; i++) {
auto *mb = new MapBlock({i & 0xff, 0, i >> 8}, nullptr);
vec.push_back(mb);
}
}
static void freeSome(MBContainer &vec, u32 n)
{
// deallocate from end since that has no cost moving data inside the vector
u32 start_i = 0;
if (vec.size() > n)
start_i = vec.size() - n;
for (u32 i = start_i; i < vec.size(); i++)
delete vec[i];
vec.resize(start_i);
}
static inline void freeAll(MBContainer &vec) { freeSome(vec, vec.size()); }
// usage patterns inspired by ClientMap::updateDrawList()
static void workOnMetadata(const MBContainer &vec)
{
for (MapBlock *block : vec) {
#ifndef SERVER
bool foo = !!block->mesh;
#else
bool foo = true;
#endif
if (block->refGet() > 2)
block->refDrop();
v3s16 pos = block->getPos() * MAP_BLOCKSIZE;
if (foo)
pos += v3s16(MAP_BLOCKSIZE / 2);
if (pos.getDistanceFrom(v3s16(0)) > 30000)
continue;
block->resetUsageTimer();
block->refGrab();
}
}
// usage patterns inspired by LBMManager::applyLBMs()
static u32 workOnNodes(const MBContainer &vec)
{
u32 foo = 0;
for (MapBlock *block : vec) {
block->resetUsageTimer();
if (block->isOrphan())
continue;
v3s16 pos_of_block = block->getPosRelative();
v3s16 pos;
MapNode n;
for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++) {
for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++) {
for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
n = block->getNodeNoCheck(pos);
if (n.getContent() == CONTENT_AIR) {
auto p = pos + pos_of_block;
foo ^= p.X + p.Y + p.Z;
}
}
}
}
}
return foo;
}
// usage patterns inspired by ABMHandler::apply()
// touches both metadata and node data at the same time
static u32 workOnBoth(const MBContainer &vec)
{
int foo = 0;
for (MapBlock *block : vec) {
block->contents.clear();
bool want_contents_cached = block->contents.empty() && !block->do_not_cache_contents;
v3s16 p0;
for(p0.X=0; p0.X<MAP_BLOCKSIZE; p0.X++)
for(p0.Y=0; p0.Y<MAP_BLOCKSIZE; p0.Y++)
for(p0.Z=0; p0.Z<MAP_BLOCKSIZE; p0.Z++)
{
MapNode n = block->getNodeNoCheck(p0);
content_t c = n.getContent();
if (want_contents_cached && !CONTAINS(block->contents, c)) {
if (block->contents.size() >= 10) {
want_contents_cached = false;
block->do_not_cache_contents = true;
block->contents.clear();
block->contents.shrink_to_fit();
} else {
block->contents.push_back(c);
}
}
}
foo += block->contents.size();
}
return foo;
}
#define BENCH1(_count) \
BENCHMARK_ADVANCED("allocate_" #_count)(Catch::Benchmark::Chronometer meter) { \
MBContainer vec; \
const u32 pcount = _count / meter.runs(); \
meter.measure([&] { \
allocateSome(vec, pcount); \
return vec.size(); \
}); \
freeAll(vec); \
}; \
BENCHMARK_ADVANCED("testCase1_" #_count)(Catch::Benchmark::Chronometer meter) { \
MBContainer vec; \
allocateSome(vec, _count); \
meter.measure([&] { \
workOnMetadata(vec); \
}); \
freeAll(vec); \
}; \
BENCHMARK_ADVANCED("testCase2_" #_count)(Catch::Benchmark::Chronometer meter) { \
MBContainer vec; \
allocateSome(vec, _count); \
meter.measure([&] { \
return workOnNodes(vec); \
}); \
freeAll(vec); \
}; \
BENCHMARK_ADVANCED("testCase3_" #_count)(Catch::Benchmark::Chronometer meter) { \
MBContainer vec; \
allocateSome(vec, _count); \
meter.measure([&] { \
return workOnBoth(vec); \
}); \
freeAll(vec); \
}; \
BENCHMARK_ADVANCED("free_" #_count)(Catch::Benchmark::Chronometer meter) { \
MBContainer vec; \
allocateSome(vec, _count); \
/* catch2 does multiple runs so we have to be careful to not dealloc too many */ \
const u32 pcount = _count / meter.runs(); \
meter.measure([&] { \
freeSome(vec, pcount); \
return vec.size(); \
}); \
freeAll(vec); \
};
TEST_CASE("benchmark_mapblock") {
BENCH1(900)
BENCH1(2200)
BENCH1(7500) // <- default client_mapblock_limit
}

@ -2,6 +2,7 @@ set(sound_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/sound.cpp)
if(USE_SOUND) if(USE_SOUND)
set(sound_SRCS ${sound_SRCS} set(sound_SRCS ${sound_SRCS}
${CMAKE_CURRENT_SOURCE_DIR}/sound/al_extensions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sound/al_helpers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sound/al_helpers.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sound/ogg_file.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sound/ogg_file.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sound/playing_sound.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sound/playing_sound.cpp

@ -50,7 +50,6 @@ void ActiveObjectMgr::step(
} }
} }
// clang-format off
bool ActiveObjectMgr::registerObject(std::unique_ptr<ClientActiveObject> obj) bool ActiveObjectMgr::registerObject(std::unique_ptr<ClientActiveObject> obj)
{ {
assert(obj); // Pre-condition assert(obj); // Pre-condition
@ -93,7 +92,6 @@ void ActiveObjectMgr::removeObject(u16 id)
obj->removeFromScene(true); obj->removeFromScene(true);
} }
// clang-format on
void ActiveObjectMgr::getActiveObjects(const v3f &origin, f32 max_d, void ActiveObjectMgr::getActiveObjects(const v3f &origin, f32 max_d,
std::vector<DistanceSortedActiveObject> &dest) std::vector<DistanceSortedActiveObject> &dest)
{ {

@ -97,7 +97,6 @@ void PacketCounter::print(std::ostream &o) const
Client::Client( Client::Client(
const char *playername, const char *playername,
const std::string &password, const std::string &password,
const std::string &address_name,
MapDrawControl &control, MapDrawControl &control,
IWritableTextureSource *tsrc, IWritableTextureSource *tsrc,
IWritableShaderSource *shsrc, IWritableShaderSource *shsrc,
@ -106,7 +105,6 @@ Client::Client(
ISoundManager *sound, ISoundManager *sound,
MtEventManager *event, MtEventManager *event,
RenderingEngine *rendering_engine, RenderingEngine *rendering_engine,
bool ipv6,
GameUI *game_ui, GameUI *game_ui,
ELoginRegister allow_login_or_register ELoginRegister allow_login_or_register
): ):
@ -123,8 +121,6 @@ Client::Client(
tsrc, this tsrc, this
), ),
m_particle_manager(std::make_unique<ParticleManager>(&m_env)), m_particle_manager(std::make_unique<ParticleManager>(&m_env)),
m_con(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this)),
m_address_name(address_name),
m_allow_login_or_register(allow_login_or_register), m_allow_login_or_register(allow_login_or_register),
m_server_ser_ver(SER_FMT_VER_INVALID), m_server_ser_ver(SER_FMT_VER_INVALID),
m_last_chat_message_sent(time(NULL)), m_last_chat_message_sent(time(NULL)),
@ -338,7 +334,8 @@ bool Client::isShutdown()
Client::~Client() Client::~Client()
{ {
m_shutdown = true; m_shutdown = true;
m_con->Disconnect(); if (m_con)
m_con->Disconnect();
deleteAuthData(); deleteAuthData();
@ -381,13 +378,32 @@ Client::~Client()
m_sounds_client_to_server.clear(); m_sounds_client_to_server.clear();
} }
void Client::connect(Address address, bool is_local_server) void Client::connect(const Address &address, const std::string &address_name,
bool is_local_server)
{ {
initLocalMapSaving(address, m_address_name, is_local_server); if (m_con) {
// can't do this if the connection has entered auth phase
sanity_check(m_state == LC_Created && m_proto_ver == 0);
infostream << "Client connection will be recreated" << std::endl;
m_access_denied = false;
m_access_denied_reconnect = false;
m_access_denied_reason.clear();
}
m_address_name = address_name;
m_con.reset(new con::Connection(PROTOCOL_ID, 512, CONNECTION_TIMEOUT,
address.isIPv6(), this));
infostream << "Connecting to server at ";
address.print(infostream);
infostream << std::endl;
// Since we use TryReceive() a timeout here would be ineffective anyway // Since we use TryReceive() a timeout here would be ineffective anyway
m_con->SetTimeoutMs(0); m_con->SetTimeoutMs(0);
m_con->Connect(address); m_con->Connect(address);
initLocalMapSaving(address, m_address_name, is_local_server);
} }
void Client::step(float dtime) void Client::step(float dtime)
@ -908,6 +924,10 @@ void Client::initLocalMapSaving(const Address &address,
if (!g_settings->getBool("enable_local_map_saving") || is_local_server) { if (!g_settings->getBool("enable_local_map_saving") || is_local_server) {
return; return;
} }
if (m_localdb) {
infostream << "Local map saving already running" << std::endl;
return;
}
std::string world_path; std::string world_path;
#define set_world_path(hostname) \ #define set_world_path(hostname) \
@ -935,6 +955,8 @@ void Client::ReceiveAll()
NetworkPacket pkt; NetworkPacket pkt;
u64 start_ms = porting::getTimeMs(); u64 start_ms = porting::getTimeMs();
const u64 budget = 10; const u64 budget = 10;
FATAL_ERROR_IF(!m_con, "Networking not initialized");
for(;;) { for(;;) {
// Limit time even if there would be huge amounts of data to // Limit time even if there would be huge amounts of data to
// process // process
@ -1439,6 +1461,7 @@ void Client::sendUpdateClientInfo(const ClientDynamicInfo& info)
pkt << info.real_gui_scaling; pkt << info.real_gui_scaling;
pkt << info.real_hud_scaling; pkt << info.real_hud_scaling;
pkt << (f32)info.max_fs_size.X << (f32)info.max_fs_size.Y; pkt << (f32)info.max_fs_size.X << (f32)info.max_fs_size.Y;
pkt << info.touch_controls;
Send(&pkt); Send(&pkt);
} }
@ -1766,7 +1789,7 @@ ClientEvent *Client::getClientEvent()
const Address Client::getServerAddress() const Address Client::getServerAddress()
{ {
return m_con->GetPeerAddress(PEER_ID_SERVER); return m_con ? m_con->GetPeerAddress(PEER_ID_SERVER) : Address();
} }
float Client::mediaReceiveProgress() float Client::mediaReceiveProgress()
@ -1872,11 +1895,13 @@ void Client::afterContentReceived()
float Client::getRTT() float Client::getRTT()
{ {
assert(m_con);
return m_con->getPeerStat(PEER_ID_SERVER,con::AVG_RTT); return m_con->getPeerStat(PEER_ID_SERVER,con::AVG_RTT);
} }
float Client::getCurRate() float Client::getCurRate()
{ {
assert(m_con);
return (m_con->getLocalStat(con::CUR_INC_RATE) + return (m_con->getLocalStat(con::CUR_INC_RATE) +
m_con->getLocalStat(con::CUR_DL_RATE)); m_con->getLocalStat(con::CUR_DL_RATE));
} }

@ -122,7 +122,6 @@ public:
Client( Client(
const char *playername, const char *playername,
const std::string &password, const std::string &password,
const std::string &address_name,
MapDrawControl &control, MapDrawControl &control,
IWritableTextureSource *tsrc, IWritableTextureSource *tsrc,
IWritableShaderSource *shsrc, IWritableShaderSource *shsrc,
@ -131,7 +130,6 @@ public:
ISoundManager *sound, ISoundManager *sound,
MtEventManager *event, MtEventManager *event,
RenderingEngine *rendering_engine, RenderingEngine *rendering_engine,
bool ipv6,
GameUI *game_ui, GameUI *game_ui,
ELoginRegister allow_login_or_register ELoginRegister allow_login_or_register
); );
@ -155,11 +153,8 @@ public:
bool isShutdown(); bool isShutdown();
/* void connect(const Address &address, const std::string &address_name,
The name of the local player should already be set when bool is_local_server);
calling this, as it is sent in the initialization.
*/
void connect(Address address, bool is_local_server);
/* /*
Stuff that references the environment is valid only as Stuff that references the environment is valid only as
@ -196,6 +191,7 @@ public:
void handleCommand_HP(NetworkPacket* pkt); void handleCommand_HP(NetworkPacket* pkt);
void handleCommand_Breath(NetworkPacket* pkt); void handleCommand_Breath(NetworkPacket* pkt);
void handleCommand_MovePlayer(NetworkPacket* pkt); void handleCommand_MovePlayer(NetworkPacket* pkt);
void handleCommand_MovePlayerRel(NetworkPacket* pkt);
void handleCommand_DeathScreen(NetworkPacket* pkt); void handleCommand_DeathScreen(NetworkPacket* pkt);
void handleCommand_AnnounceMedia(NetworkPacket* pkt); void handleCommand_AnnounceMedia(NetworkPacket* pkt);
void handleCommand_Media(NetworkPacket* pkt); void handleCommand_Media(NetworkPacket* pkt);
@ -350,7 +346,7 @@ public:
bool activeObjectsReceived() const bool activeObjectsReceived() const
{ return m_activeobjects_received; } { return m_activeobjects_received; }
u16 getProtoVersion() u16 getProtoVersion() const
{ return m_proto_ver; } { return m_proto_ver; }
bool m_simple_singleplayer_mode; bool m_simple_singleplayer_mode;
@ -362,6 +358,10 @@ public:
float getRTT(); float getRTT();
float getCurRate(); float getCurRate();
// has the server ever replied to us, used for connection retry/fallback
bool hasServerReplied() const {
return getProtoVersion() != 0; // (set in TOCLIENT_HELLO)
}
Minimap* getMinimap() { return m_minimap; } Minimap* getMinimap() { return m_minimap; }
void setCamera(Camera* camera) { m_camera = camera; } void setCamera(Camera* camera) { m_camera = camera; }
@ -414,8 +414,10 @@ public:
void showMinimap(bool show = true); void showMinimap(bool show = true);
// IP and port we're connected to
const Address getServerAddress(); const Address getServerAddress();
// Hostname of the connected server (but can also be a numerical IP)
const std::string &getAddressName() const const std::string &getAddressName() const
{ {
return m_address_name; return m_address_name;

@ -34,33 +34,43 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <queue> #include <queue>
// struct MeshBufListList namespace {
void MeshBufListList::clear() // A helper struct
{ struct MeshBufListMaps
for (auto &list : lists) {
list.clear(); struct MaterialHash
} {
size_t operator()(const video::SMaterial &m) const noexcept
{
// Only hash first texture. Simple and fast.
return std::hash<video::ITexture *>{}(m.TextureLayers[0].Texture);
}
};
void MeshBufListList::add(scene::IMeshBuffer *buf, v3s16 position, u8 layer) using MeshBufListMap = std::unordered_map<
{ video::SMaterial,
// Append to the correct layer std::vector<std::pair<v3s16, scene::IMeshBuffer *>>,
std::vector<MeshBufList> &list = lists[layer]; MaterialHash>;
const video::SMaterial &m = buf->getMaterial();
for (MeshBufList &l : list) {
// comparing a full material is quite expensive so we don't do it if
// not even first texture is equal
if (l.m.TextureLayers[0].Texture != m.TextureLayers[0].Texture)
continue;
if (l.m == m) { std::array<MeshBufListMap, MAX_TILE_LAYERS> maps;
l.bufs.emplace_back(position, buf);
return; void clear()
{
for (auto &map : maps)
map.clear();
} }
}
MeshBufList l; void add(scene::IMeshBuffer *buf, v3s16 position, u8 layer)
l.m = m; {
l.bufs.emplace_back(position, buf); assert(layer < MAX_TILE_LAYERS);
list.emplace_back(l);
// Append to the correct layer
auto &map = maps[layer];
const video::SMaterial &m = buf->getMaterial();
auto &bufs = map[m]; // default constructs if non-existent
bufs.emplace_back(position, buf);
}
};
} }
static void on_settings_changed(const std::string &name, void *data) static void on_settings_changed(const std::string &name, void *data)
@ -737,7 +747,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
Draw the selected MapBlocks Draw the selected MapBlocks
*/ */
MeshBufListList grouped_buffers; MeshBufListMaps grouped_buffers;
std::vector<DrawDescriptor> draw_order; std::vector<DrawDescriptor> draw_order;
video::SMaterial previous_material; video::SMaterial previous_material;
@ -793,7 +803,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
} }
else { else {
// otherwise, group buffers across meshes // otherwise, group buffers across meshes
// using MeshBufListList // using MeshBufListMaps
for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) { for (int layer = 0; layer < MAX_TILE_LAYERS; layer++) {
scene::IMesh *mesh = block_mesh->getMesh(layer); scene::IMesh *mesh = block_mesh->getMesh(layer);
assert(mesh); assert(mesh);
@ -819,11 +829,11 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
} }
// Capture draw order for all solid meshes // Capture draw order for all solid meshes
for (auto &lists : grouped_buffers.lists) { for (auto &map : grouped_buffers.maps) {
for (MeshBufList &list : lists) { for (auto &list : map) {
// iterate in reverse to draw closest blocks first // iterate in reverse to draw closest blocks first
for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it) { for (auto it = list.second.rbegin(); it != list.second.rend(); ++it) {
draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin()); draw_order.emplace_back(it->first, it->second, it != list.second.rbegin());
} }
} }
} }
@ -1103,7 +1113,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
u32 drawcall_count = 0; u32 drawcall_count = 0;
u32 vertex_count = 0; u32 vertex_count = 0;
MeshBufListList grouped_buffers; MeshBufListMaps grouped_buffers;
std::vector<DrawDescriptor> draw_order; std::vector<DrawDescriptor> draw_order;
@ -1144,7 +1154,7 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
} }
else { else {
// otherwise, group buffers across meshes // otherwise, group buffers across meshes
// using MeshBufListList // using MeshBufListMaps
MapBlockMesh *mapBlockMesh = block->mesh; MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh); assert(mapBlockMesh);
@ -1167,18 +1177,18 @@ void ClientMap::renderMapShadows(video::IVideoDriver *driver,
} }
u32 buffer_count = 0; u32 buffer_count = 0;
for (auto &lists : grouped_buffers.lists) for (auto &map : grouped_buffers.maps)
for (MeshBufList &list : lists) for (auto &list : map)
buffer_count += list.bufs.size(); buffer_count += list.second.size();
draw_order.reserve(draw_order.size() + buffer_count); draw_order.reserve(draw_order.size() + buffer_count);
// Capture draw order for all solid meshes // Capture draw order for all solid meshes
for (auto &lists : grouped_buffers.lists) { for (auto &map : grouped_buffers.maps) {
for (MeshBufList &list : lists) { for (auto &list : map) {
// iterate in reverse to draw closest blocks first // iterate in reverse to draw closest blocks first
for (auto it = list.bufs.rbegin(); it != list.bufs.rend(); ++it) for (auto it = list.second.rbegin(); it != list.second.rend(); ++it)
draw_order.emplace_back(it->first, it->second, it != list.bufs.rbegin()); draw_order.emplace_back(it->first, it->second, it != list.second.rbegin());
} }
} }

@ -37,25 +37,6 @@ struct MapDrawControl
bool show_wireframe = false; bool show_wireframe = false;
}; };
struct MeshBufList
{
video::SMaterial m;
std::vector<std::pair<v3s16,scene::IMeshBuffer*>> bufs;
};
struct MeshBufListList
{
/*!
* Stores the mesh buffers of the world.
* The array index is the material's layer.
* The vector part groups vertices by material.
*/
std::vector<MeshBufList> lists[MAX_TILE_LAYERS];
void clear();
void add(scene::IMeshBuffer *buf, v3s16 position, u8 layer);
};
class Client; class Client;
class ITextureSource; class ITextureSource;
class PartialMeshBuffer; class PartialMeshBuffer;

@ -46,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cmath> #include <cmath>
#include "client/shader.h" #include "client/shader.h"
#include "client/minimap.h" #include "client/minimap.h"
#include <quaternion.h>
class Settings; class Settings;
struct ToolCapabilities; struct ToolCapabilities;
@ -828,7 +829,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
updateMarker(); updateMarker();
updateNodePos(); updateNodePos();
updateAnimation(); updateAnimation();
updateBonePosition(); updateBones(.0f);
updateAttachments(); updateAttachments();
setNodeLight(m_last_light); setNodeLight(m_last_light);
updateMeshCulling(); updateMeshCulling();
@ -1246,7 +1247,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
updatePositionRecursive(m_matrixnode); updatePositionRecursive(m_matrixnode);
m_animated_meshnode->updateAbsolutePosition(); m_animated_meshnode->updateAbsolutePosition();
m_animated_meshnode->animateJoints(); m_animated_meshnode->animateJoints();
updateBonePosition(); updateBones(dtime);
} }
} }
@ -1527,19 +1528,28 @@ void GenericCAO::updateAnimationSpeed()
m_animated_meshnode->setAnimationSpeed(m_animation_speed); m_animated_meshnode->setAnimationSpeed(m_animation_speed);
} }
void GenericCAO::updateBonePosition() void GenericCAO::updateBones(f32 dtime)
{ {
if (m_bone_position.empty() || !m_animated_meshnode) if (!m_animated_meshnode)
return; return;
if (m_bone_override.empty()) {
m_animated_meshnode->setJointMode(scene::EJUOR_NONE);
return;
}
m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render m_animated_meshnode->setJointMode(scene::EJUOR_CONTROL); // To write positions to the mesh on render
for (auto &it : m_bone_position) { for (auto &it : m_bone_override) {
std::string bone_name = it.first; std::string bone_name = it.first;
scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str()); scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
if (bone) { if (!bone)
bone->setPosition(it.second.X); continue;
bone->setRotation(it.second.Y);
} BoneOverride &props = it.second;
props.dtime_passed += dtime;
bone->setPosition(props.getPosition(bone->getPosition()));
bone->setRotation(props.getRotationEulerDeg(bone->getRotation()));
bone->setScale(props.getScale(bone->getScale()));
} }
// search through bones to find mistakenly rotated bones due to bug in Irrlicht // search through bones to find mistakenly rotated bones due to bug in Irrlicht
@ -1550,7 +1560,7 @@ void GenericCAO::updateBonePosition()
//If bone is manually positioned there is no need to perform the bug check //If bone is manually positioned there is no need to perform the bug check
bool skip = false; bool skip = false;
for (auto &it : m_bone_position) { for (auto &it : m_bone_override) {
if (it.first == bone->getName()) { if (it.first == bone->getName()) {
skip = true; skip = true;
break; break;
@ -1852,11 +1862,46 @@ void GenericCAO::processMessage(const std::string &data)
updateAnimationSpeed(); updateAnimationSpeed();
} else if (cmd == AO_CMD_SET_BONE_POSITION) { } else if (cmd == AO_CMD_SET_BONE_POSITION) {
std::string bone = deSerializeString16(is); std::string bone = deSerializeString16(is);
v3f position = readV3F32(is); auto it = m_bone_override.find(bone);
v3f rotation = readV3F32(is); BoneOverride props;
m_bone_position[bone] = core::vector2d<v3f>(position, rotation); if (it != m_bone_override.end()) {
props = it->second;
// updateBonePosition(); now called every step // Reset timer
props.dtime_passed = 0;
// Save previous values for interpolation
props.position.previous = props.position.vector;
props.rotation.previous = props.rotation.next;
props.scale.previous = props.scale.vector;
} else {
// Disable interpolation
props.position.interp_timer = 0.0f;
props.rotation.interp_timer = 0.0f;
props.scale.interp_timer = 0.0f;
}
// Read new values
props.position.vector = readV3F32(is);
props.rotation.next = core::quaternion(readV3F32(is) * core::DEGTORAD);
props.scale.vector = readV3F32(is); // reads past end of string on older cmds
if (is.eof()) {
// Backwards compatibility
props.scale.vector = v3f(1, 1, 1); // restore the scale which was not sent
props.position.absolute = true;
props.rotation.absolute = true;
} else {
props.position.interp_timer = readF32(is);
props.rotation.interp_timer = readF32(is);
props.scale.interp_timer = readF32(is);
u8 absoluteFlag = readU8(is);
props.position.absolute = (absoluteFlag & 1) > 0;
props.rotation.absolute = (absoluteFlag & 2) > 0;
props.scale.absolute = (absoluteFlag & 4) > 0;
}
if (props.isIdentity()) {
m_bone_override.erase(bone);
} else {
m_bone_override[bone] = props;
}
// updateBones(); now called every step
} else if (cmd == AO_CMD_ATTACH_TO) { } else if (cmd == AO_CMD_ATTACH_TO) {
u16 parent_id = readS16(is); u16 parent_id = readS16(is);
std::string bone = deSerializeString16(is); std::string bone = deSerializeString16(is);

@ -104,7 +104,7 @@ private:
float m_animation_blend = 0.0f; float m_animation_blend = 0.0f;
bool m_animation_loop = true; bool m_animation_loop = true;
// stores position and rotation for each bone name // stores position and rotation for each bone name
std::unordered_map<std::string, core::vector2d<v3f>> m_bone_position; BoneOverrideMap m_bone_override;
int m_attachment_parent_id = 0; int m_attachment_parent_id = 0;
std::unordered_set<int> m_attachment_child_ids; std::unordered_set<int> m_attachment_child_ids;
@ -267,7 +267,7 @@ public:
void updateAnimationSpeed(); void updateAnimationSpeed();
void updateBonePosition(); void updateBones(f32 dtime);
void processMessage(const std::string &data) override; void processMessage(const std::string &data) override;

@ -404,6 +404,12 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
CachedPixelShaderSetting<float> m_bloom_radius_pixel; CachedPixelShaderSetting<float> m_bloom_radius_pixel;
float m_bloom_radius; float m_bloom_radius;
CachedPixelShaderSetting<float> m_saturation_pixel; CachedPixelShaderSetting<float> m_saturation_pixel;
bool m_volumetric_light_enabled;
CachedPixelShaderSetting<float, 3> m_sun_position_pixel;
CachedPixelShaderSetting<float> m_sun_brightness_pixel;
CachedPixelShaderSetting<float, 3> m_moon_position_pixel;
CachedPixelShaderSetting<float> m_moon_brightness_pixel;
CachedPixelShaderSetting<float> m_volumetric_light_strength_pixel;
public: public:
void onSettingsChange(const std::string &name) void onSettingsChange(const std::string &name)
@ -461,7 +467,12 @@ public:
m_bloom_intensity_pixel("bloomIntensity"), m_bloom_intensity_pixel("bloomIntensity"),
m_bloom_strength_pixel("bloomStrength"), m_bloom_strength_pixel("bloomStrength"),
m_bloom_radius_pixel("bloomRadius"), m_bloom_radius_pixel("bloomRadius"),
m_saturation_pixel("saturation") m_saturation_pixel("saturation"),
m_sun_position_pixel("sunPositionScreen"),
m_sun_brightness_pixel("sunBrightness"),
m_moon_position_pixel("moonPositionScreen"),
m_moon_brightness_pixel("moonBrightness"),
m_volumetric_light_strength_pixel("volumetricLightStrength")
{ {
g_settings->registerChangedCallback("enable_fog", settingsCallback, this); g_settings->registerChangedCallback("enable_fog", settingsCallback, this);
g_settings->registerChangedCallback("exposure_compensation", settingsCallback, this); g_settings->registerChangedCallback("exposure_compensation", settingsCallback, this);
@ -475,6 +486,7 @@ public:
m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f); m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f);
m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f); m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f);
m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f); m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f);
m_volumetric_light_enabled = g_settings->getBool("enable_volumetric_lighting") && m_bloom_enabled;
} }
~GameGlobalShaderConstantSetter() ~GameGlobalShaderConstantSetter()
@ -579,6 +591,54 @@ public:
} }
float saturation = m_client->getEnv().getLocalPlayer()->getLighting().saturation; float saturation = m_client->getEnv().getLocalPlayer()->getLighting().saturation;
m_saturation_pixel.set(&saturation, services); m_saturation_pixel.set(&saturation, services);
if (m_volumetric_light_enabled) {
// Map directional light to screen space
auto camera_node = m_client->getCamera()->getCameraNode();
core::matrix4 transform = camera_node->getProjectionMatrix();
transform *= camera_node->getViewMatrix();
if (m_sky->getSunVisible()) {
v3f sun_position = camera_node->getAbsolutePosition() +
10000. * m_sky->getSunDirection();
transform.transformVect(sun_position);
sun_position.normalize();
float sun_position_array[3] = { sun_position.X, sun_position.Y, sun_position.Z};
m_sun_position_pixel.set(sun_position_array, services);
float sun_brightness = rangelim(107.143f * m_sky->getSunDirection().dotProduct(v3f(0.f, 1.f, 0.f)), 0.f, 1.f);
m_sun_brightness_pixel.set(&sun_brightness, services);
} else {
float sun_position_array[3] = { 0.f, 0.f, -1.f };
m_sun_position_pixel.set(sun_position_array, services);
float sun_brightness = 0.f;
m_sun_brightness_pixel.set(&sun_brightness, services);
}
if (m_sky->getMoonVisible()) {
v3f moon_position = camera_node->getAbsolutePosition() +
10000. * m_sky->getMoonDirection();
transform.transformVect(moon_position);
moon_position.normalize();
float moon_position_array[3] = { moon_position.X, moon_position.Y, moon_position.Z};
m_moon_position_pixel.set(moon_position_array, services);
float moon_brightness = rangelim(107.143f * m_sky->getMoonDirection().dotProduct(v3f(0.f, 1.f, 0.f)), 0.f, 1.f);
m_moon_brightness_pixel.set(&moon_brightness, services);
}
else {
float moon_position_array[3] = { 0.f, 0.f, -1.f };
m_moon_position_pixel.set(moon_position_array, services);
float moon_brightness = 0.f;
m_moon_brightness_pixel.set(&moon_brightness, services);
}
float volumetric_light_strength = m_client->getEnv().getLocalPlayer()->getLighting().volumetric_light_strength;
m_volumetric_light_strength_pixel.set(&volumetric_light_strength, services);
}
} }
void onSetMaterial(const video::SMaterial &material) override void onSetMaterial(const video::SMaterial &material) override
@ -821,6 +881,7 @@ protected:
const CameraOrientation &cam); const CameraOrientation &cam);
void updateClouds(float dtime); void updateClouds(float dtime);
void updateShadows(); void updateShadows();
void drawScene(ProfilerGraph *graph, RunStats *stats);
// Misc // Misc
void showOverlayMessage(const char *msg, float dtime, int percent, void showOverlayMessage(const char *msg, float dtime, int percent,
@ -1243,8 +1304,8 @@ void Game::run()
updatePauseState(); updatePauseState();
if (m_is_paused) if (m_is_paused)
dtime = 0.0f; dtime = 0.0f;
else
step(dtime); step(dtime);
processClientEvents(&cam_view_target); processClientEvents(&cam_view_target);
updateDebugState(); updateDebugState();
@ -1272,6 +1333,9 @@ void Game::shutdown()
if (formspec) if (formspec)
formspec->quitMenu(); formspec->quitMenu();
// Clear text when exiting.
m_game_ui->clearText();
#ifdef HAVE_TOUCHSCREENGUI #ifdef HAVE_TOUCHSCREENGUI
g_touchscreengui->hide(); g_touchscreengui->hide();
#endif #endif
@ -1428,12 +1492,6 @@ bool Game::createClient(const GameStartData &start_data)
return false; return false;
bool could_connect, connect_aborted; bool could_connect, connect_aborted;
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) {
g_touchscreengui->init(texture_src);
g_touchscreengui->hide();
}
#endif
if (!connectToServer(start_data, &could_connect, &connect_aborted)) if (!connectToServer(start_data, &could_connect, &connect_aborted))
return false; return false;
@ -1542,10 +1600,8 @@ bool Game::initGui()
-1, chat_backend, client, &g_menumgr); -1, chat_backend, client, &g_menumgr);
#ifdef HAVE_TOUCHSCREENGUI #ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) if (g_touchscreengui)
g_touchscreengui->show(); g_touchscreengui->init(texture_src);
#endif #endif
return true; return true;
@ -1557,15 +1613,18 @@ bool Game::connectToServer(const GameStartData &start_data,
*connect_ok = false; // Let's not be overly optimistic *connect_ok = false; // Let's not be overly optimistic
*connection_aborted = false; *connection_aborted = false;
bool local_server_mode = false; bool local_server_mode = false;
const auto &address_name = start_data.address;
showOverlayMessage(N_("Resolving address..."), 0, 15); showOverlayMessage(N_("Resolving address..."), 0, 15);
Address connect_address(0, 0, 0, 0, start_data.socket_port); Address connect_address(0, 0, 0, 0, start_data.socket_port);
Address fallback_address;
try { try {
connect_address.Resolve(start_data.address.c_str()); connect_address.Resolve(address_name.c_str(), &fallback_address);
if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY if (connect_address.isAny()) {
// replace with localhost IP
if (connect_address.isIPv6()) { if (connect_address.isIPv6()) {
IPv6AddressBytes addr_bytes; IPv6AddressBytes addr_bytes;
addr_bytes.bytes[15] = 1; addr_bytes.bytes[15] = 1;
@ -1582,45 +1641,58 @@ bool Game::connectToServer(const GameStartData &start_data,
return false; return false;
} }
if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) { // this shouldn't normally happen since Address::Resolve() checks for enable_ipv6
if (g_settings->getBool("enable_ipv6")) {
// empty
} else if (connect_address.isIPv6()) {
*error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str()); *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str());
errorstream << *error_message << std::endl; errorstream << *error_message << std::endl;
return false; return false;
} else if (fallback_address.isIPv6()) {
fallback_address = Address();
} }
fallback_address.setPort(connect_address.getPort());
if (fallback_address.isValid()) {
infostream << "Resolved two addresses for \"" << address_name
<< "\" isIPv6[0]=" << connect_address.isIPv6()
<< " isIPv6[1]=" << fallback_address.isIPv6() << std::endl;
} else {
infostream << "Resolved one address for \"" << address_name
<< "\" isIPv6=" << connect_address.isIPv6() << std::endl;
}
try { try {
client = new Client(start_data.name.c_str(), client = new Client(start_data.name.c_str(),
start_data.password, start_data.address, start_data.password,
*draw_control, texture_src, shader_src, *draw_control, texture_src, shader_src,
itemdef_manager, nodedef_manager, sound_manager.get(), eventmgr, itemdef_manager, nodedef_manager, sound_manager.get(), eventmgr,
m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(), m_rendering_engine, m_game_ui.get(),
start_data.allow_login_or_register); start_data.allow_login_or_register);
client->migrateModStorage();
} catch (const BaseException &e) { } catch (const BaseException &e) {
*error_message = fmtgettext("Error creating client: %s", e.what()); *error_message = fmtgettext("Error creating client: %s", e.what());
errorstream << *error_message << std::endl; errorstream << *error_message << std::endl;
return false; return false;
} }
client->migrateModStorage();
client->m_simple_singleplayer_mode = simple_singleplayer_mode; client->m_simple_singleplayer_mode = simple_singleplayer_mode;
infostream << "Connecting to server at ";
connect_address.print(infostream);
infostream << std::endl;
client->connect(connect_address,
simple_singleplayer_mode || local_server_mode);
/* /*
Wait for server to accept connection Wait for server to accept connection
*/ */
client->connect(connect_address, address_name,
simple_singleplayer_mode || local_server_mode);
try { try {
input->clear(); input->clear();
FpsControl fps_control; FpsControl fps_control;
f32 dtime; f32 dtime;
f32 wait_time = 0; // in seconds f32 wait_time = 0; // in seconds
bool did_fallback = false;
fps_control.reset(); fps_control.reset();
@ -1629,10 +1701,7 @@ bool Game::connectToServer(const GameStartData &start_data,
fps_control.limit(device, &dtime); fps_control.limit(device, &dtime);
// Update client and server // Update client and server
client->step(dtime); step(dtime);
if (server != NULL)
server->step(dtime);
// End condition // End condition
if (client->getState() == LC_Init) { if (client->getState() == LC_Init) {
@ -1658,8 +1727,15 @@ bool Game::connectToServer(const GameStartData &start_data,
} }
wait_time += dtime; wait_time += dtime;
// Only time out if we aren't waiting for the server we started if (local_server_mode) {
if (!start_data.address.empty() && wait_time > 10) { // never time out
} else if (wait_time > GAME_FALLBACK_TIMEOUT && !did_fallback) {
if (!client->hasServerReplied() && fallback_address.isValid()) {
client->connect(fallback_address, address_name,
simple_singleplayer_mode || local_server_mode);
}
did_fallback = true;
} else if (wait_time > GAME_CONNECTION_TIMEOUT) {
*error_message = gettext("Connection timed out."); *error_message = gettext("Connection timed out.");
errorstream << *error_message << std::endl; errorstream << *error_message << std::endl;
break; break;
@ -1669,8 +1745,7 @@ bool Game::connectToServer(const GameStartData &start_data,
showOverlayMessage(N_("Connecting to server..."), dtime, 20); showOverlayMessage(N_("Connecting to server..."), dtime, 20);
} }
} catch (con::PeerNotFoundException &e) { } catch (con::PeerNotFoundException &e) {
// TODO: Should something be done here? At least an info/error warningstream << "This should not happen. Please report a bug." << std::endl;
// message?
return false; return false;
} }
@ -1691,10 +1766,7 @@ bool Game::getServerContent(bool *aborted)
fps_control.limit(device, &dtime); fps_control.limit(device, &dtime);
// Update client and server // Update client and server
client->step(dtime); step(dtime);
if (server != NULL)
server->step(dtime);
// End condition // End condition
if (client->mediaReceived() && client->itemdefReceived() && if (client->mediaReceived() && client->itemdefReceived() &&
@ -2232,7 +2304,7 @@ void Game::openConsole(float scale, const wchar_t *line)
assert(scale > 0.0f && scale <= 1.0f); assert(scale > 0.0f && scale <= 1.0f);
#ifdef __ANDROID__ #ifdef __ANDROID__
porting::showInputDialog(gettext("ok"), "", "", 2); porting::showTextInputDialog("", "", 2);
m_android_chat_open = true; m_android_chat_open = true;
#else #else
if (gui_chat_console->isOpenInhibited()) if (gui_chat_console->isOpenInhibited())
@ -2248,15 +2320,19 @@ void Game::openConsole(float scale, const wchar_t *line)
#ifdef __ANDROID__ #ifdef __ANDROID__
void Game::handleAndroidChatInput() void Game::handleAndroidChatInput()
{ {
if (m_android_chat_open && porting::getInputDialogState() == 0) { // It has to be a text input
std::string text = porting::getInputDialogValue(); if (m_android_chat_open && porting::getLastInputDialogType() == porting::TEXT_INPUT) {
client->typeChatMessage(utf8_to_wide(text)); porting::AndroidDialogState dialogState = porting::getInputDialogState();
m_android_chat_open = false; if (dialogState == porting::DIALOG_INPUTTED) {
std::string text = porting::getInputDialogMessage();
client->typeChatMessage(utf8_to_wide(text));
}
if (dialogState != porting::DIALOG_SHOWN)
m_android_chat_open = false;
} }
} }
#endif #endif
void Game::toggleFreeMove() void Game::toggleFreeMove()
{ {
bool free_move = !g_settings->getBool("free_move"); bool free_move = !g_settings->getBool("free_move");
@ -2626,7 +2702,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
#ifdef HAVE_TOUCHSCREENGUI #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->getPitch(); cam->camera_pitch += g_touchscreengui->getPitchChange();
} else { } else {
#endif #endif
v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2); v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
@ -2722,10 +2798,23 @@ void Game::updatePauseState()
inline void Game::step(f32 dtime) inline void Game::step(f32 dtime)
{ {
if (server) if (server) {
server->step(dtime); float fps_max = (!device->isWindowFocused() || g_menumgr.pausesGame()) ?
g_settings->getFloat("fps_max_unfocused") :
g_settings->getFloat("fps_max");
fps_max = std::max(fps_max, 1.0f);
float steplen = 1.0f / fps_max;
client->step(dtime); server->setStepSettings(Server::StepSettings{
steplen,
m_is_paused
});
server->step();
}
if (!m_is_paused)
client->step(dtime);
} }
static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) { static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
@ -3045,7 +3134,6 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
else else
sky->setFogStart(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f)); sky->setFogStart(rangelim(g_settings->getFloat("fog_start"), 0.0f, 0.99f));
delete event->set_sky; delete event->set_sky;
} }
@ -4019,31 +4107,6 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
*/ */
client->getParticleManager()->step(dtime); client->getParticleManager()->step(dtime);
/*
Fog
*/
if (m_cache_enable_fog) {
driver->setFog(
sky->getBgColor(),
video::EFT_FOG_LINEAR,
runData.fog_range * sky->getFogStart(),
runData.fog_range * 1.0,
0.01,
false, // pixel fog
true // range fog
);
} else {
driver->setFog(
sky->getBgColor(),
video::EFT_FOG_LINEAR,
100000 * BS,
110000 * BS,
0.01f,
false, // pixel fog
false // range fog
);
}
/* /*
Damage camera tilt Damage camera tilt
*/ */
@ -4143,52 +4206,18 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
/* /*
==================== Drawing begins ==================== ==================== Drawing begins ====================
*/ */
const video::SColor skycolor = sky->getSkyColor(); if (RenderingEngine::shouldRender())
drawScene(graph, stats);
TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
driver->beginScene(true, true, skycolor);
bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
(player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
(camera->getCameraMode() == CAMERA_MODE_FIRST));
bool draw_crosshair = (
(player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
(camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
#ifdef HAVE_TOUCHSCREENGUI
if (isNoCrosshairAllowed())
draw_crosshair = false;
#endif
m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud,
m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
/*
Profiler graph
*/
v2u32 screensize = driver->getScreenSize();
if (m_game_ui->m_flags.show_profiler_graph)
graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
/*
Damage flash
*/
if (runData.damage_flash > 0.0f) {
video::SColor color(runData.damage_flash, 180, 0, 0);
driver->draw2DRectangle(color,
core::rect<s32>(0, 0, screensize.X, screensize.Y),
NULL);
runData.damage_flash -= 384.0f * dtime;
}
/* /*
==================== End scene ==================== ==================== End scene ====================
*/ */
driver->endScene(); // Damage flash is drawn in drawScene, but the timing update is done here to
// keep dtime out of the drawing code.
if (runData.damage_flash > 0.0f) {
runData.damage_flash -= 384.0f * dtime;
}
stats->drawtime = tt_draw.stop(true);
g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true)); g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true));
} }
@ -4256,6 +4285,81 @@ void Game::updateShadows()
shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed); shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed);
} }
void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
{
const video::SColor bg_color = this->sky->getBgColor();
const video::SColor sky_color = this->sky->getSkyColor();
/*
Fog
*/
if (this->m_cache_enable_fog) {
this->driver->setFog(
bg_color,
video::EFT_FOG_LINEAR,
this->runData.fog_range * this->sky->getFogStart(),
this->runData.fog_range * 1.0f,
0.01f,
false, // pixel fog
true // range fog
);
} else {
this->driver->setFog(
bg_color,
video::EFT_FOG_LINEAR,
100000 * BS,
110000 * BS,
0.01f,
false, // pixel fog
false // range fog
);
}
/*
Drawing
*/
TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO);
this->driver->beginScene(true, true, sky_color);
const LocalPlayer *player = this->client->getEnv().getLocalPlayer();
bool draw_wield_tool = (this->m_game_ui->m_flags.show_hud &&
(player->hud_flags & HUD_FLAG_WIELDITEM_VISIBLE) &&
(this->camera->getCameraMode() == CAMERA_MODE_FIRST));
bool draw_crosshair = (
(player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
(this->camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
#ifdef HAVE_TOUCHSCREENGUI
if (this->isNoCrosshairAllowed())
draw_crosshair = false;
#endif
this->m_rendering_engine->draw_scene(sky_color, this->m_game_ui->m_flags.show_hud,
this->m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair);
/*
Profiler graph
*/
v2u32 screensize = this->driver->getScreenSize();
if (this->m_game_ui->m_flags.show_profiler_graph)
graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
/*
Damage flash
*/
if (this->runData.damage_flash > 0.0f) {
video::SColor color(this->runData.damage_flash, 180, 0, 0);
this->driver->draw2DRectangle(color,
core::rect<s32>(0, 0, screensize.X, screensize.Y),
NULL);
}
this->driver->endScene();
stats->drawtime = tt_draw.stop(true);
g_profiler->graphAdd("Draw scene [us]", stats->drawtime);
}
/**************************************************************************** /****************************************************************************
Misc Misc
****************************************************************************/ ****************************************************************************/
@ -4389,41 +4493,6 @@ void Game::showPauseMenu()
"- touch&drag, tap 2nd finger\n" "- touch&drag, tap 2nd finger\n"
" --> place single item to slot\n" " --> place single item to slot\n"
); );
#else
static const std::string control_text_template = strgettext("Controls:\n"
"- %s: move forwards\n"
"- %s: move backwards\n"
"- %s: move left\n"
"- %s: move right\n"
"- %s: jump/climb up\n"
"- %s: dig/punch/use\n"
"- %s: place/use\n"
"- %s: sneak/climb down\n"
"- %s: drop item\n"
"- %s: inventory\n"
"- Mouse: turn/look\n"
"- Mouse wheel: select item\n"
"- %s: chat\n"
);
char control_text_buf[600];
porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(),
GET_KEY_NAME(keymap_forward),
GET_KEY_NAME(keymap_backward),
GET_KEY_NAME(keymap_left),
GET_KEY_NAME(keymap_right),
GET_KEY_NAME(keymap_jump),
GET_KEY_NAME(keymap_dig),
GET_KEY_NAME(keymap_place),
GET_KEY_NAME(keymap_sneak),
GET_KEY_NAME(keymap_drop),
GET_KEY_NAME(keymap_inventory),
GET_KEY_NAME(keymap_chat)
);
std::string control_text = std::string(control_text_buf);
str_formspec_escape(control_text);
#endif #endif
float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
@ -4448,30 +4517,29 @@ void Game::showPauseMenu()
} }
#endif #endif
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
<< strgettext("Change Keys") << "]"; << strgettext("Controls") << "]";
#endif #endif
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
<< 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") << "]";
<< "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" #ifdef HAVE_TOUCHSCREENGUI
<< "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]";
#endif
os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
<< "\n" << "\n"
<< strgettext("Game info:") << "\n"; << strgettext("Game info:") << "\n";
const std::string &address = client->getAddressName(); const std::string &address = client->getAddressName();
static const std::string mode = strgettext("- Mode: "); os << strgettext("- Mode: ");
if (!simple_singleplayer_mode) { if (!simple_singleplayer_mode) {
Address serverAddress = client->getServerAddress(); if (address.empty())
if (!address.empty()) { os << strgettext("Hosting server");
os << mode << strgettext("Remote server") << "\n" else
<< strgettext("- Address: ") << address; os << strgettext("Remote server");
} else {
os << mode << strgettext("Hosting server");
}
os << "\n" << strgettext("- Port: ") << serverAddress.getPort() << "\n";
} else { } else {
os << mode << strgettext("Singleplayer") << "\n"; os << strgettext("Singleplayer");
} }
os << "\n";
if (simple_singleplayer_mode || address.empty()) { if (simple_singleplayer_mode || address.empty()) {
static const std::string on = strgettext("On"); static const std::string on = strgettext("On");
static const std::string off = strgettext("Off"); static const std::string off = strgettext("Off");

@ -43,6 +43,8 @@ struct CameraOrientation {
f32 camera_pitch; // "up/down" f32 camera_pitch; // "up/down"
}; };
#define GAME_FALLBACK_TIMEOUT 1.8f
#define GAME_CONNECTION_TIMEOUT 10.0f
void the_game(bool *kill, void the_game(bool *kill,
InputHandler *input, InputHandler *input,

@ -334,3 +334,36 @@ void GameUI::deleteFormspec()
m_formname.clear(); m_formname.clear();
} }
void GameUI::clearText()
{
if (m_guitext_chat) {
m_guitext_chat->remove();
m_guitext_chat = nullptr;
}
if (m_guitext) {
m_guitext->remove();
m_guitext = nullptr;
}
if (m_guitext2) {
m_guitext2->remove();
m_guitext2 = nullptr;
}
if (m_guitext_info) {
m_guitext_info->remove();
m_guitext_info = nullptr;
}
if (m_guitext_status) {
m_guitext_status->remove();
m_guitext_status = nullptr;
}
if (m_guitext_profiler) {
m_guitext_profiler->remove();
m_guitext_profiler = nullptr;
}
}

@ -106,6 +106,7 @@ public:
const std::string &getFormspecName() { return m_formname; } const std::string &getFormspecName() { return m_formname; }
GUIFormSpecMenu *&getFormspecGUI() { return m_formspec; } GUIFormSpecMenu *&getFormspecGUI() { return m_formspec; }
void deleteFormspec(); void deleteFormspec();
void clearText();
private: private:
Flags m_flags; Flags m_flags;

@ -129,6 +129,11 @@ public:
m_position = position; m_position = position;
m_sneak_node_exists = false; m_sneak_node_exists = false;
} }
inline void addPosition(const v3f &added_pos)
{
m_position += added_pos;
m_sneak_node_exists = false;
}
v3f getPosition() const { return m_position; } v3f getPosition() const { return m_position; }

@ -47,7 +47,6 @@ struct MeshCollector
// offset: offset added to vertices // offset: offset added to vertices
MeshCollector(const v3f center_pos, v3f offset = v3f()) : m_center_pos(center_pos), offset(offset) {} MeshCollector(const v3f center_pos, v3f offset = v3f()) : m_center_pos(center_pos), offset(offset) {}
// clang-format off
void append(const TileSpec &material, void append(const TileSpec &material,
const video::S3DVertex *vertices, u32 numVertices, const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices); const u16 *indices, u32 numIndices);
@ -55,10 +54,8 @@ struct MeshCollector
const video::S3DVertex *vertices, u32 numVertices, const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices, const u16 *indices, u32 numIndices,
v3f pos, video::SColor c, u8 light_source); v3f pos, video::SColor c, u8 light_source);
// clang-format on
private: private:
// clang-format off
void append(const TileLayer &material, void append(const TileLayer &material,
const video::S3DVertex *vertices, u32 numVertices, const video::S3DVertex *vertices, u32 numVertices,
const u16 *indices, u32 numIndices, const u16 *indices, u32 numIndices,
@ -68,7 +65,6 @@ private:
const u16 *indices, u32 numIndices, const u16 *indices, u32 numIndices,
v3f pos, video::SColor c, u8 light_source, v3f pos, video::SColor c, u8 light_source,
u8 layernum, bool use_scale = false); u8 layernum, bool use_scale = false);
// clang-format on
PreMeshBuffer &findBuffer(const TileLayer &layer, u8 layernum, u32 numVertices); PreMeshBuffer &findBuffer(const TileLayer &layer, u8 layernum, u32 numVertices);
}; };

@ -120,8 +120,9 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
static const u8 TEXTURE_EXPOSURE_1 = 3; static const u8 TEXTURE_EXPOSURE_1 = 3;
static const u8 TEXTURE_EXPOSURE_2 = 4; static const u8 TEXTURE_EXPOSURE_2 = 4;
static const u8 TEXTURE_FXAA = 5; static const u8 TEXTURE_FXAA = 5;
static const u8 TEXTURE_BLOOM_DOWN = 10; static const u8 TEXTURE_VOLUME = 6;
static const u8 TEXTURE_BLOOM_UP = 20; static const u8 TEXTURE_SCALE_DOWN = 10;
static const u8 TEXTURE_SCALE_UP = 20;
// Super-sampling is simply rendering into a larger texture. // Super-sampling is simply rendering into a larger texture.
// Downscaling is done by the final step when rendering to the screen. // Downscaling is done by the final step when rendering to the screen.
@ -130,6 +131,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
const bool enable_auto_exposure = g_settings->getBool("enable_auto_exposure"); const bool enable_auto_exposure = g_settings->getBool("enable_auto_exposure");
const bool enable_ssaa = antialiasing == "ssaa"; const bool enable_ssaa = antialiasing == "ssaa";
const bool enable_fxaa = antialiasing == "fxaa"; const bool enable_fxaa = antialiasing == "fxaa";
const bool enable_volumetric_light = g_settings->getBool("enable_volumetric_lighting") && enable_bloom;
if (enable_ssaa) { if (enable_ssaa) {
u16 ssaa_scale = MYMAX(2, g_settings->getU16("fsaa")); u16 ssaa_scale = MYMAX(2, g_settings->getU16("fsaa"));
@ -160,9 +162,9 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
v2f downscale = scale * 0.5; v2f downscale = scale * 0.5;
for (u8 i = 0; i < MIPMAP_LEVELS; i++) { for (u8 i = 0; i < MIPMAP_LEVELS; i++) {
buffer->setTexture(TEXTURE_BLOOM_DOWN + i, downscale, std::string("downsample") + std::to_string(i), color_format); buffer->setTexture(TEXTURE_SCALE_DOWN + i, downscale, std::string("downsample") + std::to_string(i), color_format);
if (enable_bloom) if (enable_bloom)
buffer->setTexture(TEXTURE_BLOOM_UP + i, downscale, std::string("upsample") + std::to_string(i), color_format); buffer->setTexture(TEXTURE_SCALE_UP + i, downscale, std::string("upsample") + std::to_string(i), color_format);
downscale *= 0.5; downscale *= 0.5;
} }
@ -171,20 +173,30 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
// get bright spots // get bright spots
u32 shader_id = client->getShaderSource()->getShader("extract_bloom", TILE_MATERIAL_PLAIN, NDT_MESH); u32 shader_id = client->getShaderSource()->getShader("extract_bloom", TILE_MATERIAL_PLAIN, NDT_MESH);
RenderStep *extract_bloom = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR, TEXTURE_EXPOSURE_1 }); RenderStep *extract_bloom = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source, TEXTURE_EXPOSURE_1 });
extract_bloom->setRenderSource(buffer); extract_bloom->setRenderSource(buffer);
extract_bloom->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_BLOOM)); extract_bloom->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_BLOOM));
source = TEXTURE_BLOOM; source = TEXTURE_BLOOM;
} }
if (enable_volumetric_light) {
buffer->setTexture(TEXTURE_VOLUME, scale, "volume", color_format);
shader_id = client->getShaderSource()->getShader("volumetric_light", TILE_MATERIAL_PLAIN, NDT_MESH);
auto volume = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source, TEXTURE_DEPTH });
volume->setRenderSource(buffer);
volume->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_VOLUME));
source = TEXTURE_VOLUME;
}
// downsample // downsample
shader_id = client->getShaderSource()->getShader("bloom_downsample", TILE_MATERIAL_PLAIN, NDT_MESH); shader_id = client->getShaderSource()->getShader("bloom_downsample", TILE_MATERIAL_PLAIN, NDT_MESH);
for (u8 i = 0; i < MIPMAP_LEVELS; i++) { for (u8 i = 0; i < MIPMAP_LEVELS; i++) {
auto step = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source }); auto step = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source });
step->setRenderSource(buffer); step->setRenderSource(buffer);
step->setBilinearFilter(0, true); step->setBilinearFilter(0, true);
step->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_BLOOM_DOWN + i)); step->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_SCALE_DOWN + i));
source = TEXTURE_BLOOM_DOWN + i; source = TEXTURE_SCALE_DOWN + i;
} }
} }
@ -193,19 +205,19 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
// upsample // upsample
shader_id = client->getShaderSource()->getShader("bloom_upsample", TILE_MATERIAL_PLAIN, NDT_MESH); shader_id = client->getShaderSource()->getShader("bloom_upsample", TILE_MATERIAL_PLAIN, NDT_MESH);
for (u8 i = MIPMAP_LEVELS - 1; i > 0; i--) { for (u8 i = MIPMAP_LEVELS - 1; i > 0; i--) {
auto step = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { u8(TEXTURE_BLOOM_DOWN + i - 1), source }); auto step = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { u8(TEXTURE_SCALE_DOWN + i - 1), source });
step->setRenderSource(buffer); step->setRenderSource(buffer);
step->setBilinearFilter(0, true); step->setBilinearFilter(0, true);
step->setBilinearFilter(1, true); step->setBilinearFilter(1, true);
step->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, u8(TEXTURE_BLOOM_UP + i - 1))); step->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, u8(TEXTURE_SCALE_UP + i - 1)));
source = TEXTURE_BLOOM_UP + i - 1; source = TEXTURE_SCALE_UP + i - 1;
} }
} }
// Dynamic Exposure pt2 // Dynamic Exposure pt2
if (enable_auto_exposure) { if (enable_auto_exposure) {
shader_id = client->getShaderSource()->getShader("update_exposure", TILE_MATERIAL_PLAIN, NDT_MESH); shader_id = client->getShaderSource()->getShader("update_exposure", TILE_MATERIAL_PLAIN, NDT_MESH);
auto update_exposure = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_EXPOSURE_1, u8(TEXTURE_BLOOM_DOWN + MIPMAP_LEVELS - 1) }); auto update_exposure = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_EXPOSURE_1, u8(TEXTURE_SCALE_DOWN + MIPMAP_LEVELS - 1) });
update_exposure->setBilinearFilter(1, true); update_exposure->setBilinearFilter(1, true);
update_exposure->setRenderSource(buffer); update_exposure->setRenderSource(buffer);
update_exposure->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_EXPOSURE_2)); update_exposure->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_EXPOSURE_2));
@ -228,7 +240,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
// final merge // final merge
shader_id = client->getShaderSource()->getShader("second_stage", TILE_MATERIAL_PLAIN, NDT_MESH); shader_id = client->getShaderSource()->getShader("second_stage", TILE_MATERIAL_PLAIN, NDT_MESH);
PostProcessingStep *effect = pipeline->createOwned<PostProcessingStep>(shader_id, std::vector<u8> { final_stage_source, TEXTURE_BLOOM_UP, TEXTURE_EXPOSURE_2 }); PostProcessingStep *effect = pipeline->createOwned<PostProcessingStep>(shader_id, std::vector<u8> { final_stage_source, TEXTURE_SCALE_UP, TEXTURE_EXPOSURE_2 });
pipeline->addStep(effect); pipeline->addStep(effect);
if (enable_ssaa) if (enable_ssaa)
effect->setBilinearFilter(0, true); effect->setBilinearFilter(0, true);

@ -249,8 +249,10 @@ void RenderingEngine::draw_load_screen(const std::wstring &text,
#ifndef __ANDROID__ #ifndef __ANDROID__
const core::dimension2d<u32> &img_size = const core::dimension2d<u32> &img_size =
progress_img_bg->getSize(); progress_img_bg->getSize();
u32 imgW = rangelim(img_size.Width, 200, 600) * getDisplayDensity(); float density = g_settings->getFloat("gui_scaling", 0.5f, 20.0f) *
u32 imgH = rangelim(img_size.Height, 24, 72) * getDisplayDensity(); getDisplayDensity();
u32 imgW = rangelim(img_size.Width, 200, 600) * density;
u32 imgH = rangelim(img_size.Height, 24, 72) * density;
#else #else
const core::dimension2d<u32> img_size(256, 48); const core::dimension2d<u32> img_size(256, 48);
float imgRatio = (float)img_size.Height / img_size.Width; float imgRatio = (float)img_size.Height / img_size.Width;

@ -138,6 +138,17 @@ public:
const irr::core::dimension2d<u32> initial_screen_size, const irr::core::dimension2d<u32> initial_screen_size,
const bool initial_window_maximized); const bool initial_window_maximized);
static bool shouldRender()
{
// On Android, pause rendering while the app is in background (generally not visible).
// Don't do this on desktop because windows can be partially visible.
#ifdef __ANDROID__
return get_raw_device()->isWindowActive();
#else
return true;
#endif
};
private: private:
v2u32 _getWindowSize() const; v2u32 _getWindowSize() const;

@ -767,6 +767,13 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
shaders_header << "#define SSAA_SCALE " << ssaa_scale << ".\n"; shaders_header << "#define SSAA_SCALE " << ssaa_scale << ".\n";
} }
if (g_settings->getBool("debanding"))
shaders_header << "#define ENABLE_DITHERING 1\n";
if (g_settings->getBool("enable_volumetric_lighting")) {
shaders_header << "#define VOLUMETRIC_LIGHT 1\n";
}
shaders_header << "#line 0\n"; // reset the line counter for meaningful diagnostics shaders_header << "#line 0\n"; // reset the line counter for meaningful diagnostics
std::string common_header = shaders_header.str(); std::string common_header = shaders_header.str();

@ -120,6 +120,9 @@ public:
void setFogStart(float fog_start) { m_sky_params.fog_start = fog_start; } void setFogStart(float fog_start) { m_sky_params.fog_start = fog_start; }
float getFogStart() const { return m_sky_params.fog_start; } float getFogStart() const { return m_sky_params.fog_start; }
void setVolumetricLightStrength(float volumetric_light_strength) { m_sky_params.volumetric_light_strength = volumetric_light_strength; }
float getVolumetricLightStrength() const { return m_sky_params.volumetric_light_strength; }
private: private:
aabb3f m_box; aabb3f m_box;
video::SMaterial m_materials[SKY_MATERIAL_COUNT]; video::SMaterial m_materials[SKY_MATERIAL_COUNT];

@ -0,0 +1,58 @@
/*
Minetest
Copyright (C) 2023 DS
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "al_extensions.h"
#include "settings.h"
#include "util/string.h"
#include <unordered_set>
namespace sound {
ALExtensions::ALExtensions(const ALCdevice *deviceHandle [[maybe_unused]])
{
auto blacklist_vec = str_split(g_settings->get("sound_extensions_blacklist"), ',');
for (auto &s : blacklist_vec) {
s = trim(s);
}
std::unordered_set<std::string> blacklist;
blacklist.insert(blacklist_vec.begin(), blacklist_vec.end());
{
constexpr const char *ext_name = "AL_SOFT_direct_channels_remix";
bool blacklisted = blacklist.find(ext_name) != blacklist.end();
if (blacklisted)
infostream << "ALExtensions: Blacklisted: " << ext_name << std::endl;
#ifndef AL_SOFT_direct_channels_remix
infostream << "ALExtensions: Not compiled with: " << ext_name << std::endl;
#else
bool found = alIsExtensionPresent(ext_name);
if (found)
infostream << "ALExtensions: Found: " << ext_name << std::endl;
else
infostream << "ALExtensions: Not found: " << ext_name << std::endl;
if (found && !blacklisted) {
have_ext_AL_SOFT_direct_channels_remix = true;
}
#endif
}
}
}

@ -0,0 +1,38 @@
/*
Minetest
Copyright (C) 2023 DS
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "al_helpers.h"
namespace sound {
/**
* Struct for AL and ALC extensions
*/
struct ALExtensions
{
explicit ALExtensions(const ALCdevice *deviceHandle [[maybe_unused]]);
#ifdef AL_SOFT_direct_channels_remix
bool have_ext_AL_SOFT_direct_channels_remix = false;
#endif
};
}

@ -18,7 +18,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -18,7 +18,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -18,7 +18,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -18,7 +18,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -18,12 +18,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "playing_sound.h" #include "playing_sound.h"
#include "al_extensions.h"
#include "debug.h" #include "debug.h"
#include <cassert> #include <cassert>
#include <cmath> #include <cmath>
@ -32,7 +33,8 @@ namespace sound {
PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> data, PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> data,
bool loop, f32 volume, f32 pitch, f32 start_time, bool loop, f32 volume, f32 pitch, f32 start_time,
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt) const std::optional<std::pair<v3f, v3f>> &pos_vel_opt,
const ALExtensions &exts [[maybe_unused]])
: m_source_id(source_id), m_data(std::move(data)), m_looping(loop), : m_source_id(source_id), m_data(std::move(data)), m_looping(loop),
m_is_positional(pos_vel_opt.has_value()) m_is_positional(pos_vel_opt.has_value())
{ {
@ -113,6 +115,15 @@ PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> dat
alSource3f(m_source_id, AL_POSITION, 0.0f, 0.0f, 0.0f); alSource3f(m_source_id, AL_POSITION, 0.0f, 0.0f, 0.0f);
alSource3f(m_source_id, AL_VELOCITY, 0.0f, 0.0f, 0.0f); alSource3f(m_source_id, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
warn_if_al_error("PlayingSound::PlayingSound at making position-less"); warn_if_al_error("PlayingSound::PlayingSound at making position-less");
#ifdef AL_SOFT_direct_channels_remix
// Play directly on stereo output channels if possible. Improves sound quality.
if (exts.have_ext_AL_SOFT_direct_channels_remix
&& m_data->m_decode_info.is_stereo) {
alSourcei(m_source_id, AL_DIRECT_CHANNELS_SOFT, AL_REMIX_UNMATCHED_SOFT);
warn_if_al_error("PlayingSound::PlayingSound at setting AL_DIRECT_CHANNELS_SOFT");
}
#endif
} }
setGain(volume); setGain(volume);
setPitch(pitch); setPitch(pitch);

@ -18,13 +18,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#pragma once #pragma once
#include "sound_data.h" #include "sound_data.h"
namespace sound { struct ALExtensions; }
namespace sound { namespace sound {
@ -51,7 +52,8 @@ class PlayingSound final
public: public:
PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> data, bool loop, PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> data, bool loop,
f32 volume, f32 pitch, f32 start_time, f32 volume, f32 pitch, f32 start_time,
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt); const std::optional<std::pair<v3f, v3f>> &pos_vel_opt,
const ALExtensions &exts [[maybe_unused]]);
~PlayingSound() noexcept ~PlayingSound() noexcept
{ {

@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -18,13 +18,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "sound_data.h" #include "sound_data.h"
#include "sound_constants.h" #include "sound_constants.h"
#include <algorithm>
namespace sound { namespace sound {

@ -18,7 +18,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -18,7 +18,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
@ -181,7 +181,7 @@ std::shared_ptr<PlayingSound> OpenALSoundManager::createPlayingSound(
} }
auto sound = std::make_shared<PlayingSound>(source_id, std::move(lsnd), loop, auto sound = std::make_shared<PlayingSound>(source_id, std::move(lsnd), loop,
volume, pitch, start_time, pos_vel_opt); volume, pitch, start_time, pos_vel_opt, m_exts);
sound->play(); sound->play();
@ -271,7 +271,8 @@ OpenALSoundManager::OpenALSoundManager(SoundManagerSingleton *smg,
Thread("OpenALSoundManager"), Thread("OpenALSoundManager"),
m_fallback_path_provider(std::move(fallback_path_provider)), m_fallback_path_provider(std::move(fallback_path_provider)),
m_device(smg->m_device.get()), m_device(smg->m_device.get()),
m_context(smg->m_context.get()) m_context(smg->m_context.get()),
m_exts(m_device)
{ {
SANITY_CHECK(!!m_fallback_path_provider); SANITY_CHECK(!!m_fallback_path_provider);

@ -18,13 +18,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#pragma once #pragma once
#include "playing_sound.h" #include "playing_sound.h"
#include "al_extensions.h"
#include "sound_constants.h" #include "sound_constants.h"
#include "sound_manager_messages.h" #include "sound_manager_messages.h"
#include "../sound.h" #include "../sound.h"
@ -51,8 +52,10 @@ class OpenALSoundManager final : public Thread
private: private:
std::unique_ptr<SoundFallbackPathProvider> m_fallback_path_provider; std::unique_ptr<SoundFallbackPathProvider> m_fallback_path_provider;
ALCdevice *m_device; ALCdevice *const m_device;
ALCcontext *m_context; ALCcontext *const m_context;
const ALExtensions m_exts;
// time in seconds until which removeDeadSounds will be called again // time in seconds until which removeDeadSounds will be called again
f32 m_time_until_dead_removal = REMOVE_DEAD_SOUNDS_INTERVAL; f32 m_time_until_dead_removal = REMOVE_DEAD_SOUNDS_INTERVAL;

@ -13,7 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -17,7 +17,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -18,7 +18,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -18,7 +18,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along You should have received a copy of the GNU Lesser General Public License along
with this program; ifnot, write to the Free Software Foundation, Inc., with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */

@ -1598,6 +1598,13 @@ bool TextureSource::generateImagePart(std::string part_of_name,
u32 frame_count = stoi(sf.next(":")); u32 frame_count = stoi(sf.next(":"));
u32 frame_index = stoi(sf.next(":")); u32 frame_index = stoi(sf.next(":"));
if (frame_count == 0){
errorstream << "generateImagePart(): invalid frame_count "
<< "for part_of_name=\"" << part_of_name
<< "\", using frame_count = 1 instead." << std::endl;
frame_count = 1;
}
if (baseimg == NULL){ if (baseimg == NULL){
errorstream<<"generateImagePart(): baseimg != NULL " errorstream<<"generateImagePart(): baseimg != NULL "
<<"for part_of_name=\""<<part_of_name <<"for part_of_name=\""<<part_of_name
@ -1899,6 +1906,13 @@ bool TextureSource::generateImagePart(std::string part_of_name,
u32 x0 = stoi(sf.next(",")); u32 x0 = stoi(sf.next(","));
u32 y0 = stoi(sf.next(":")); u32 y0 = stoi(sf.next(":"));
if (w0 == 0 || h0 == 0) {
errorstream << "generateImagePart(): invalid width or height "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
core::dimension2d<u32> img_dim = baseimg->getDimension(); core::dimension2d<u32> img_dim = baseimg->getDimension();
core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0)); core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));

@ -33,11 +33,13 @@ public:
f32 real_gui_scaling; f32 real_gui_scaling;
f32 real_hud_scaling; f32 real_hud_scaling;
v2f32 max_fs_size; v2f32 max_fs_size;
bool touch_controls;
bool equal(const ClientDynamicInfo &other) const { bool equal(const ClientDynamicInfo &other) const {
return render_target_size == other.render_target_size && return render_target_size == other.render_target_size &&
abs(real_gui_scaling - other.real_gui_scaling) < 0.001f && abs(real_gui_scaling - other.real_gui_scaling) < 0.001f &&
abs(real_hud_scaling - other.real_hud_scaling) < 0.001f; abs(real_hud_scaling - other.real_hud_scaling) < 0.001f &&
touch_controls == other.touch_controls;
} }
#ifndef SERVER #ifndef SERVER
@ -48,10 +50,16 @@ public:
f32 hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f); f32 hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f);
f32 real_gui_scaling = gui_scaling * density; f32 real_gui_scaling = gui_scaling * density;
f32 real_hud_scaling = hud_scaling * density; f32 real_hud_scaling = hud_scaling * density;
#ifdef HAVE_TOUCHSCREENGUI
bool touch_controls = true;
#else
bool touch_controls = false;
#endif
return { return {
screen_size, real_gui_scaling, real_hud_scaling, screen_size, real_gui_scaling, real_hud_scaling,
ClientDynamicInfo::calculateMaxFSSize(screen_size, gui_scaling) ClientDynamicInfo::calculateMaxFSSize(screen_size, gui_scaling),
touch_controls
}; };
} }
#endif #endif

@ -59,6 +59,7 @@ RemoteClient::RemoteClient() :
g_settings->getFloat("full_block_send_enable_min_time_from_building")), g_settings->getFloat("full_block_send_enable_min_time_from_building")),
m_max_send_distance(g_settings->getS16("max_block_send_distance")), m_max_send_distance(g_settings->getS16("max_block_send_distance")),
m_block_optimize_distance(g_settings->getS16("block_send_optimize_distance")), m_block_optimize_distance(g_settings->getS16("block_send_optimize_distance")),
m_block_cull_optimize_distance(g_settings->getS16("block_cull_optimize_distance")),
m_max_gen_distance(g_settings->getS16("max_block_generate_distance")), m_max_gen_distance(g_settings->getS16("max_block_generate_distance")),
m_occ_cull(g_settings->getBool("server_side_occlusion_culling")) m_occ_cull(g_settings->getBool("server_side_occlusion_culling"))
{ {
@ -225,7 +226,10 @@ void RemoteClient::GetNextBlocks (
wanted_range); wanted_range);
const s16 d_opt = std::min(adjustDist(m_block_optimize_distance, prop_zoom_fov), const s16 d_opt = std::min(adjustDist(m_block_optimize_distance, prop_zoom_fov),
wanted_range); wanted_range);
const s16 d_blocks_in_sight = full_d_max * BS * MAP_BLOCKSIZE; const s16 d_cull_opt = std::min(adjustDist(m_block_cull_optimize_distance, prop_zoom_fov),
wanted_range);
// f32 to prevent overflow, it is also what isBlockInSight(...) expects
const f32 d_blocks_in_sight = full_d_max * BS * MAP_BLOCKSIZE;
s16 d_max_gen = std::min(adjustDist(m_max_gen_distance, prop_zoom_fov), s16 d_max_gen = std::min(adjustDist(m_max_gen_distance, prop_zoom_fov),
wanted_range); wanted_range);
@ -258,10 +262,9 @@ void RemoteClient::GetNextBlocks (
Get the border/face dot coordinates of a "d-radiused" Get the border/face dot coordinates of a "d-radiused"
box box
*/ */
std::vector<v3s16> list = FacePositionCache::getFacePositions(d); const auto &list = FacePositionCache::getFacePositions(d);
std::vector<v3s16>::iterator li; for (auto li = list.begin(); li != list.end(); ++li) {
for (li = list.begin(); li != list.end(); ++li) {
v3s16 p = *li + center; v3s16 p = *li + center;
/* /*
@ -347,18 +350,21 @@ void RemoteClient::GetNextBlocks (
if (!block->getIsUnderground() && !block->getDayNightDiff()) if (!block->getIsUnderground() && !block->getDayNightDiff())
continue; continue;
} }
}
/* /*
Check occlusion cache first. Check occlusion cache first.
*/ */
if (m_blocks_occ.find(p) != m_blocks_occ.end()) if (m_blocks_occ.find(p) != m_blocks_occ.end())
continue; continue;
if (m_occ_cull && !block_not_found && /*
env->getMap().isBlockOccluded(block, cam_pos_nodes)) { Note that we do this even before the block is loaded as this does not depend on its contents.
m_blocks_occ.insert(p); */
continue; if (m_occ_cull &&
} env->getMap().isBlockOccluded(p * MAP_BLOCKSIZE, cam_pos_nodes, d >= d_cull_opt)) {
m_blocks_occ.insert(p);
continue;
} }
/* /*

@ -397,6 +397,7 @@ private:
const float m_min_time_from_building; const float m_min_time_from_building;
const s16 m_max_send_distance; const s16 m_max_send_distance;
const s16 m_block_optimize_distance; const s16 m_block_optimize_distance;
const s16 m_block_cull_optimize_distance;
const s16 m_max_gen_distance; const s16 m_max_gen_distance;
const bool m_occ_cull; const bool m_occ_cull;

@ -108,10 +108,8 @@ bool parseModContents(ModSpec &spec)
if (info.exists("depends")) { if (info.exists("depends")) {
mod_conf_has_depends = true; mod_conf_has_depends = true;
std::string dep = info.get("depends"); std::string dep = info.get("depends");
// clang-format off
dep.erase(std::remove_if(dep.begin(), dep.end(), dep.erase(std::remove_if(dep.begin(), dep.end(),
static_cast<int (*)(int)>(&std::isspace)), dep.end()); static_cast<int (*)(int)>(&std::isspace)), dep.end());
// clang-format on
for (const auto &dependency : str_split(dep, ',')) { for (const auto &dependency : str_split(dep, ',')) {
spec.depends.insert(dependency); spec.depends.insert(dependency);
} }
@ -120,10 +118,8 @@ bool parseModContents(ModSpec &spec)
if (info.exists("optional_depends")) { if (info.exists("optional_depends")) {
mod_conf_has_depends = true; mod_conf_has_depends = true;
std::string dep = info.get("optional_depends"); std::string dep = info.get("optional_depends");
// clang-format off
dep.erase(std::remove_if(dep.begin(), dep.end(), dep.erase(std::remove_if(dep.begin(), dep.end(),
static_cast<int (*)(int)>(&std::isspace)), dep.end()); static_cast<int (*)(int)>(&std::isspace)), dep.end());
// clang-format on
for (const auto &dependency : str_split(dep, ',')) { for (const auto &dependency : str_split(dep, ',')) {
spec.optdepends.insert(dependency); spec.optdepends.insert(dependency);
} }

@ -942,8 +942,8 @@ void ModStorageDatabaseSQLite3::listMods(std::vector<std::string> *res)
return 0; return 0;
}, (void *) res, &errmsg); }, (void *) res, &errmsg);
if (status != SQLITE_OK) { if (status != SQLITE_OK) {
DatabaseException e(std::string("Error trying to list mods with metadata: ") + errmsg); auto msg = std::string("Error trying to list mods with metadata: ") + errmsg;
sqlite3_free(errmsg); sqlite3_free(errmsg);
throw e; throw DatabaseException(msg);
} }
} }

@ -42,6 +42,7 @@ void set_default_settings()
settings->setDefault("sound_volume", "0.8"); settings->setDefault("sound_volume", "0.8");
settings->setDefault("sound_volume_unfocused", "0.3"); settings->setDefault("sound_volume_unfocused", "0.3");
settings->setDefault("mute_sound", "false"); settings->setDefault("mute_sound", "false");
settings->setDefault("sound_extensions_blacklist", "");
settings->setDefault("enable_mesh_cache", "false"); settings->setDefault("enable_mesh_cache", "false");
settings->setDefault("mesh_generation_interval", "0"); settings->setDefault("mesh_generation_interval", "0");
settings->setDefault("mesh_generation_threads", "0"); settings->setDefault("mesh_generation_threads", "0");
@ -268,12 +269,14 @@ void set_default_settings()
settings->setDefault("enable_waving_plants", "false"); settings->setDefault("enable_waving_plants", "false");
settings->setDefault("exposure_compensation", "0.0"); settings->setDefault("exposure_compensation", "0.0");
settings->setDefault("enable_auto_exposure", "false"); settings->setDefault("enable_auto_exposure", "false");
settings->setDefault("debanding", "true");
settings->setDefault("antialiasing", "none"); settings->setDefault("antialiasing", "none");
settings->setDefault("enable_bloom", "false"); settings->setDefault("enable_bloom", "false");
settings->setDefault("enable_bloom_debug", "false"); settings->setDefault("enable_bloom_debug", "false");
settings->setDefault("bloom_strength_factor", "1.0"); settings->setDefault("bloom_strength_factor", "1.0");
settings->setDefault("bloom_intensity", "0.05"); settings->setDefault("bloom_intensity", "0.05");
settings->setDefault("bloom_radius", "1"); settings->setDefault("bloom_radius", "1");
settings->setDefault("enable_volumetric_lighting", "false");
// Effects Shadows // Effects Shadows
settings->setDefault("enable_dynamic_shadows", "false"); settings->setDefault("enable_dynamic_shadows", "false");
@ -368,6 +371,7 @@ void set_default_settings()
settings->setDefault("max_packets_per_iteration", "1024"); settings->setDefault("max_packets_per_iteration", "1024");
settings->setDefault("port", "30000"); settings->setDefault("port", "30000");
settings->setDefault("strict_protocol_version_checking", "false"); settings->setDefault("strict_protocol_version_checking", "false");
settings->setDefault("protocol_version_min", "1");
settings->setDefault("player_transfer_distance", "0"); settings->setDefault("player_transfer_distance", "0");
settings->setDefault("max_simultaneous_block_sends_per_client", "40"); settings->setDefault("max_simultaneous_block_sends_per_client", "40");
settings->setDefault("time_send_interval", "5"); settings->setDefault("time_send_interval", "5");
@ -397,6 +401,7 @@ void set_default_settings()
// This causes frametime jitter on client side, or does it? // This causes frametime jitter on client side, or does it?
settings->setDefault("max_block_send_distance", "12"); settings->setDefault("max_block_send_distance", "12");
settings->setDefault("block_send_optimize_distance", "4"); settings->setDefault("block_send_optimize_distance", "4");
settings->setDefault("block_cull_optimize_distance", "25");
settings->setDefault("server_side_occlusion_culling", "true"); settings->setDefault("server_side_occlusion_culling", "true");
settings->setDefault("csm_restriction_flags", "62"); settings->setDefault("csm_restriction_flags", "62");
settings->setDefault("csm_restriction_noderange", "0"); settings->setDefault("csm_restriction_noderange", "0");
@ -502,6 +507,7 @@ void set_default_settings()
settings->setDefault("active_block_range", "2"); settings->setDefault("active_block_range", "2");
settings->setDefault("viewing_range", "50"); settings->setDefault("viewing_range", "50");
settings->setDefault("leaves_style", "simple"); settings->setDefault("leaves_style", "simple");
settings->setDefault("debanding", "false");
settings->setDefault("curl_verify_cert", "false"); settings->setDefault("curl_verify_cert", "false");
// Apply settings according to screen size // Apply settings according to screen size

@ -25,6 +25,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/string.h" #include "util/string.h"
#include "log.h" #include "log.h"
#ifdef _WIN32
#define setenv(n,v,o) _putenv_s(n,v)
#endif
#if USE_GETTEXT && defined(_MSC_VER) #if USE_GETTEXT && defined(_MSC_VER)
#include <windows.h> #include <windows.h>
#include <map> #include <map>
@ -37,7 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
static std::map<std::wstring, std::wstring> glb_supported_locales; static std::map<std::wstring, std::wstring> glb_supported_locales;
/******************************************************************************/ /******************************************************************************/
BOOL CALLBACK UpdateLocaleCallback(LPTSTR pStr) static BOOL CALLBACK UpdateLocaleCallback(LPTSTR pStr)
{ {
char* endptr = 0; char* endptr = 0;
int LOCALEID = strtol(pStr, &endptr,16); int LOCALEID = strtol(pStr, &endptr,16);
@ -78,7 +82,8 @@ BOOL CALLBACK UpdateLocaleCallback(LPTSTR pStr)
} }
/******************************************************************************/ /******************************************************************************/
const char* MSVC_LocaleLookup(const char* raw_shortname) { static const char* MSVC_LocaleLookup(const char* raw_shortname)
{
/* NULL is used to read locale only so we need to return it too */ /* NULL is used to read locale only so we need to return it too */
if (raw_shortname == NULL) return NULL; if (raw_shortname == NULL) return NULL;
@ -102,9 +107,9 @@ const char* MSVC_LocaleLookup(const char* raw_shortname) {
last_raw_value = shortname; last_raw_value = shortname;
if (glb_supported_locales.find(utf8_to_wide(shortname)) != glb_supported_locales.end()) { auto key = utf8_to_wide(shortname);
last_full_name = wide_to_utf8( if (glb_supported_locales.find(key) != glb_supported_locales.end()) {
glb_supported_locales[utf8_to_wide(shortname)]); last_full_name = wide_to_utf8(glb_supported_locales[key]);
return last_full_name.c_str(); return last_full_name.c_str();
} }
@ -114,6 +119,54 @@ const char* MSVC_LocaleLookup(const char* raw_shortname) {
return ""; return "";
} }
static void MSVC_LocaleWorkaround()
{
errorstream << "MSVC localization workaround active. "
"Restarting " PROJECT_NAME_C " in a new environment!" << std::endl;
std::string parameters;
for (int i = 1; i < argc; i++) {
if (i > 1)
parameters += ' ';
parameters += porting::QuoteArgv(argv[i]);
}
char *ptr_parameters = nullptr;
if (!parameters.empty())
ptr_parameters = &parameters[0];
// Allow calling without an extension
std::string app_name = argv[0];
if (app_name.compare(app_name.size() - 4, 4, ".exe") != 0)
app_name += ".exe";
STARTUPINFO startup_info = {};
PROCESS_INFORMATION process_info = {};
bool success = CreateProcess(app_name.c_str(), ptr_parameters,
NULL, NULL, false, DETACHED_PROCESS | CREATE_UNICODE_ENVIRONMENT,
NULL, NULL, &startup_info, &process_info);
if (success) {
exit(0);
// NOTREACHED
} else {
char buffer[1024];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), buffer,
sizeof(buffer) - 1, NULL);
errorstream << "*******************************************************" << std::endl;
errorstream << "CMD: " << app_name << std::endl;
errorstream << "Failed to restart with current locale: " << std::endl;
errorstream << buffer;
errorstream << "Expect language to be broken!" << std::endl;
errorstream << "*******************************************************" << std::endl;
}
}
}
#endif #endif
/******************************************************************************/ /******************************************************************************/
@ -123,72 +176,26 @@ void init_gettext(const char *path, const std::string &configured_language,
#if USE_GETTEXT #if USE_GETTEXT
// First, try to set user override environment // First, try to set user override environment
if (!configured_language.empty()) { if (!configured_language.empty()) {
#ifndef _WIN32 // Set LANGUAGE which overrides all others, see
// Add user specified locale to environment // <https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html>
#ifndef _MSC_VER
setenv("LANGUAGE", configured_language.c_str(), 1); setenv("LANGUAGE", configured_language.c_str(), 1);
#ifdef __ANDROID__
setenv("LANG", configured_language.c_str(), 1);
#endif
// Reload locale with changed environment // Reload locale with changed environment
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
#elif defined(_MSC_VER) #else
std::string current_language; std::string current_language;
const char *env_lang = getenv("LANGUAGE"); const char *env_lang = getenv("LANGUAGE");
if (env_lang) if (env_lang)
current_language = env_lang; current_language = env_lang;
_putenv(("LANGUAGE=" + configured_language).c_str()); setenv("LANGUAGE", configured_language.c_str(), 1);
SetEnvironmentVariableA("LANGUAGE", configured_language.c_str()); SetEnvironmentVariableA("LANGUAGE", configured_language.c_str());
#ifndef SERVER #ifndef SERVER
// Hack to force gettext to see the right environment // Hack to force gettext to see the right environment
if (current_language != configured_language) { if (current_language != configured_language)
errorstream << "MSVC localization workaround active. " MSVC_LocaleWorkaround();
"Restarting " PROJECT_NAME_C " in a new environment!" << std::endl;
std::string parameters;
for (int i = 1; i < argc; i++) {
if (i > 1)
parameters += ' ';
parameters += porting::QuoteArgv(argv[i]);
}
char *ptr_parameters = nullptr;
if (!parameters.empty())
ptr_parameters = &parameters[0];
// Allow calling without an extension
std::string app_name = argv[0];
if (app_name.compare(app_name.size() - 4, 4, ".exe") != 0)
app_name += ".exe";
STARTUPINFO startup_info = {};
PROCESS_INFORMATION process_info = {};
bool success = CreateProcess(app_name.c_str(), ptr_parameters,
NULL, NULL, false, DETACHED_PROCESS | CREATE_UNICODE_ENVIRONMENT,
NULL, NULL, &startup_info, &process_info);
if (success) {
exit(0);
// NOTREACHED
} else {
char buffer[1024];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), buffer,
sizeof(buffer) - 1, NULL);
errorstream << "*******************************************************" << std::endl;
errorstream << "CMD: " << app_name << std::endl;
errorstream << "Failed to restart with current locale: " << std::endl;
errorstream << buffer;
errorstream << "Expect language to be broken!" << std::endl;
errorstream << "*******************************************************" << std::endl;
}
}
#else #else
errorstream << "*******************************************************" << std::endl; errorstream << "*******************************************************" << std::endl;
errorstream << "Can't apply locale workaround for server!" << std::endl; errorstream << "Can't apply locale workaround for server!" << std::endl;
@ -197,15 +204,8 @@ void init_gettext(const char *path, const std::string &configured_language,
#endif #endif
setlocale(LC_ALL, configured_language.c_str()); setlocale(LC_ALL, configured_language.c_str());
#else // Mingw #endif // ifdef _MSC_VER
_putenv(("LANGUAGE=" + configured_language).c_str()); } else {
setlocale(LC_ALL, "");
#endif // ifndef _WIN32
}
else {
#ifdef __ANDROID__
setenv("LANG", porting::getLanguageAndroid().c_str(), 1);
#endif
/* set current system default locale */ /* set current system default locale */
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
} }
@ -228,18 +228,13 @@ void init_gettext(const char *path, const std::string &configured_language,
bindtextdomain(name.c_str(), path); bindtextdomain(name.c_str(), path);
textdomain(name.c_str()); textdomain(name.c_str());
#if defined(_WIN32) #ifdef _WIN32
// Set character encoding for Win32 // set character encoding
char *tdomain = textdomain( (char *) NULL ); char *tdomain = textdomain(nullptr);
if( tdomain == NULL ) assert(tdomain);
{ if (tdomain)
errorstream << "Warning: domainname parameter is the null pointer" << bind_textdomain_codeset(tdomain, "UTF-8");
", default domain is not set" << std::endl; #endif
tdomain = (char *) "messages";
}
/* char *codeset = */bind_textdomain_codeset( tdomain, "UTF-8" );
//errorstream << "Gettext debug: domainname = " << tdomain << "; codeset = "<< codeset << std::endl;
#endif // defined(_WIN32)
#else #else
/* set current system default locale */ /* set current system default locale */
@ -247,7 +242,7 @@ void init_gettext(const char *path, const std::string &configured_language,
#endif // if USE_GETTEXT #endif // if USE_GETTEXT
/* no matter what locale is used we need number format to be "C" */ /* no matter what locale is used we need number format to be "C" */
/* to ensure formspec parameters are evaluated correct! */ /* to ensure formspec parameters are evaluated correctly! */
setlocale(LC_NUMERIC, "C"); setlocale(LC_NUMERIC, "C");
infostream << "Message locale is now set to: " infostream << "Message locale is now set to: "

@ -455,7 +455,6 @@ bool GUIEditBox::processKey(const SEvent &event)
bool GUIEditBox::onKeyUp(const SEvent &event, s32 &mark_begin, s32 &mark_end) bool GUIEditBox::onKeyUp(const SEvent &event, s32 &mark_begin, s32 &mark_end)
{ {
// clang-format off
if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) { if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) {
s32 lineNo = getLineFromPos(m_cursor_pos); s32 lineNo = getLineFromPos(m_cursor_pos);
s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos :
@ -481,13 +480,11 @@ bool GUIEditBox::onKeyUp(const SEvent &event, s32 &mark_begin, s32 &mark_end)
return true; return true;
} }
// clang-format on
return false; return false;
} }
bool GUIEditBox::onKeyDown(const SEvent &event, s32 &mark_begin, s32 &mark_end) bool GUIEditBox::onKeyDown(const SEvent &event, s32 &mark_begin, s32 &mark_end)
{ {
// clang-format off
if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) { if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) {
s32 lineNo = getLineFromPos(m_cursor_pos); s32 lineNo = getLineFromPos(m_cursor_pos);
s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos :
@ -513,7 +510,6 @@ bool GUIEditBox::onKeyDown(const SEvent &event, s32 &mark_begin, s32 &mark_end)
return true; return true;
} }
// clang-format on
return false; return false;
} }

@ -265,35 +265,36 @@ void GUIEngine::run()
f32 dtime = 0.0f; f32 dtime = 0.0f;
while (m_rendering_engine->run() && (!m_startgame) && (!m_kill)) { while (m_rendering_engine->run() && (!m_startgame) && (!m_kill)) {
if (RenderingEngine::shouldRender()) {
// check if we need to update the "upper left corner"-text
if (text_height != g_fontengine->getTextHeight()) {
updateTopLeftTextSize();
text_height = g_fontengine->getTextHeight();
}
//check if we need to update the "upper left corner"-text driver->beginScene(true, true, RenderingEngine::MENU_SKY_COLOR);
if (text_height != g_fontengine->getTextHeight()) {
updateTopLeftTextSize(); if (m_clouds_enabled)
text_height = g_fontengine->getTextHeight(); {
cloudPreProcess();
drawOverlay(driver);
}
else
drawBackground(driver);
drawFooter(driver);
m_rendering_engine->get_gui_env()->drawAll();
// The header *must* be drawn after the menu because it uses
// GUIFormspecMenu::getAbsoluteRect().
// The header *can* be drawn after the menu because it never intersects
// the menu.
drawHeader(driver);
driver->endScene();
} }
driver->beginScene(true, true, RenderingEngine::MENU_SKY_COLOR);
if (m_clouds_enabled)
{
cloudPreProcess();
drawOverlay(driver);
}
else
drawBackground(driver);
drawFooter(driver);
m_rendering_engine->get_gui_env()->drawAll();
// The header *must* be drawn after the menu because it uses
// GUIFormspecMenu::getAbsoluteRect().
// The header *can* be drawn after the menu because it never intersects
// the menu.
drawHeader(driver);
driver->endScene();
IrrlichtDevice *device = m_rendering_engine->get_raw_device(); IrrlichtDevice *device = m_rendering_engine->get_raw_device();
u32 frametime_min = 1000 / (device->isWindowFocused() u32 frametime_min = 1000 / (device->isWindowFocused()

@ -3497,46 +3497,58 @@ void GUIFormSpecMenu::legacySortElements(std::list<IGUIElement *>::iterator from
} }
#ifdef __ANDROID__ #ifdef __ANDROID__
bool GUIFormSpecMenu::getAndroidUIInput() void GUIFormSpecMenu::getAndroidUIInput()
{ {
if (!hasAndroidUIInput()) porting::AndroidDialogState dialogState = getAndroidUIInputState();
return false; if (dialogState == porting::DIALOG_SHOWN) {
return;
} else if (dialogState == porting::DIALOG_CANCELED) {
m_jni_field_name.clear();
return;
}
// still waiting porting::AndroidDialogType dialog_type = porting::getLastInputDialogType();
if (porting::getInputDialogState() == -1)
return true;
std::string fieldname = m_jni_field_name; std::string fieldname = m_jni_field_name;
m_jni_field_name.clear(); m_jni_field_name.clear();
for (const FieldSpec &field : m_fields) { for (const FieldSpec &field : m_fields) {
if (field.fname != fieldname) if (field.fname != fieldname)
continue; continue; // Iterate until found
IGUIElement *element = getElementFromId(field.fid, true); IGUIElement *element = getElementFromId(field.fid, true);
if (!element || element->getType() != irr::gui::EGUIET_EDIT_BOX) if (!element)
return false; return;
gui::IGUIEditBox *editbox = (gui::IGUIEditBox *)element; auto element_type = element->getType();
std::string text = porting::getInputDialogValue(); if (dialog_type == porting::TEXT_INPUT && element_type == irr::gui::EGUIET_EDIT_BOX) {
editbox->setText(utf8_to_wide(text).c_str()); gui::IGUIEditBox *editbox = (gui::IGUIEditBox *)element;
std::string text = porting::getInputDialogMessage();
editbox->setText(utf8_to_wide(text).c_str());
bool enter_after_edit = false; bool enter_after_edit = false;
auto iter = field_enter_after_edit.find(fieldname); auto iter = field_enter_after_edit.find(fieldname);
if (iter != field_enter_after_edit.end()) { if (iter != field_enter_after_edit.end()) {
enter_after_edit = iter->second; enter_after_edit = iter->second;
} }
if (enter_after_edit && editbox->getParent()) { if (enter_after_edit && editbox->getParent()) {
SEvent enter; SEvent enter;
enter.EventType = EET_GUI_EVENT; enter.EventType = EET_GUI_EVENT;
enter.GUIEvent.Caller = editbox; enter.GUIEvent.Caller = editbox;
enter.GUIEvent.Element = nullptr; enter.GUIEvent.Element = nullptr;
enter.GUIEvent.EventType = gui::EGET_EDITBOX_ENTER; enter.GUIEvent.EventType = gui::EGET_EDITBOX_ENTER;
editbox->getParent()->OnEvent(enter); editbox->getParent()->OnEvent(enter);
}
} else if (dialog_type == porting::SELECTION_INPUT &&
element_type == irr::gui::EGUIET_COMBO_BOX) {
auto dropdown = (gui::IGUIComboBox *) element;
int selected = porting::getInputDialogSelection();
dropdown->setAndSendSelected(selected);
} }
return; // Early-return after found
} }
return false;
} }
#endif #endif
@ -3656,22 +3668,18 @@ void GUIFormSpecMenu::drawMenu()
NULL, m_client, IT_ROT_HOVERED); NULL, m_client, IT_ROT_HOVERED);
} }
// On touchscreens, m_pointer is set by GUIModalMenu::preprocessEvent instead.
#ifndef HAVE_TOUCHSCREENGUI
m_pointer = RenderingEngine::get_raw_device()->getCursorControl()->getPosition();
#endif
/* /*
Draw fields/buttons tooltips and update the mouse cursor Draw fields/buttons tooltips and update the mouse cursor
*/ */
gui::IGUIElement *hovered = gui::IGUIElement *hovered =
Environment->getRootGUIElement()->getElementFromPoint(m_pointer); Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
#ifndef HAVE_TOUCHSCREENGUI
gui::ICursorControl *cursor_control = RenderingEngine::get_raw_device()-> gui::ICursorControl *cursor_control = RenderingEngine::get_raw_device()->
getCursorControl(); getCursorControl();
gui::ECURSOR_ICON current_cursor_icon = cursor_control->getActiveIcon(); gui::ECURSOR_ICON current_cursor_icon = gui::ECI_NORMAL;
#endif if (cursor_control)
current_cursor_icon = cursor_control->getActiveIcon();
bool hovered_element_found = false; bool hovered_element_found = false;
if (hovered) { if (hovered) {
@ -3715,11 +3723,10 @@ void GUIFormSpecMenu::drawMenu()
m_tooltips[field.fname].bgcolor); m_tooltips[field.fname].bgcolor);
} }
#ifndef HAVE_TOUCHSCREENGUI if (cursor_control &&
if (field.ftype != f_HyperText && // Handled directly in guiHyperText field.ftype != f_HyperText && // Handled directly in guiHyperText
current_cursor_icon != field.fcursor_icon) current_cursor_icon != field.fcursor_icon)
cursor_control->setActiveIcon(field.fcursor_icon); cursor_control->setActiveIcon(field.fcursor_icon);
#endif
hovered_element_found = true; hovered_element_found = true;
@ -3730,10 +3737,8 @@ void GUIFormSpecMenu::drawMenu()
if (!hovered_element_found) { if (!hovered_element_found) {
// no element is hovered // no element is hovered
#ifndef HAVE_TOUCHSCREENGUI if (cursor_control && current_cursor_icon != ECI_NORMAL)
if (current_cursor_icon != ECI_NORMAL)
cursor_control->setActiveIcon(ECI_NORMAL); cursor_control->setActiveIcon(ECI_NORMAL);
#endif
} }
m_tooltip_element->draw(); m_tooltip_element->draw();
@ -3764,16 +3769,13 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text,
v2u32 screenSize = Environment->getVideoDriver()->getScreenSize(); v2u32 screenSize = Environment->getVideoDriver()->getScreenSize();
int tooltip_offset_x = m_btn_height; int tooltip_offset_x = m_btn_height;
int tooltip_offset_y = m_btn_height; int tooltip_offset_y = m_btn_height;
#ifdef HAVE_TOUCHSCREENGUI
tooltip_offset_x *= 3;
tooltip_offset_y = 0;
if (m_pointer.X > (s32)screenSize.X / 2)
tooltip_offset_x = -(tooltip_offset_x + tooltip_width);
// Hide tooltip after ETIE_LEFT_UP if (m_pointer_type == PointerType::Touch) {
if (m_pointer.X == 0) tooltip_offset_x *= 3;
return; tooltip_offset_y = 0;
#endif if (m_pointer.X > (s32)screenSize.X / 2)
tooltip_offset_x = -(tooltip_offset_x + tooltip_width);
}
// Calculate and set the tooltip position // Calculate and set the tooltip position
s32 tooltip_x = m_pointer.X + tooltip_offset_x; s32 tooltip_x = m_pointer.X + tooltip_offset_x;
@ -4070,6 +4072,11 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode)
bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
{ {
// This must be done first so that GUIModalMenu can set m_pointer_type
// correctly.
if (GUIModalMenu::preprocessEvent(event))
return true;
// The IGUITabControl renders visually using the skin's selected // The IGUITabControl renders visually using the skin's selected
// font, which we override for the duration of form drawing, // font, which we override for the duration of form drawing,
// but computes tab hotspots based on how it would have rendered // but computes tab hotspots based on how it would have rendered
@ -4147,7 +4154,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
return handled; return handled;
} }
return GUIModalMenu::preprocessEvent(event); return false;
} }
void GUIFormSpecMenu::tryClose() void GUIFormSpecMenu::tryClose()
@ -4326,14 +4333,12 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
} }
#ifdef HAVE_TOUCHSCREENGUI
// The second touch (see GUIModalMenu::preprocessEvent() function) // The second touch (see GUIModalMenu::preprocessEvent() function)
ButtonEventType touch = BET_OTHER; ButtonEventType touch = BET_OTHER;
if (event.EventType == EET_TOUCH_INPUT_EVENT) { if (event.EventType == EET_TOUCH_INPUT_EVENT) {
if (event.TouchInput.Event == ETIE_LEFT_UP) if (event.TouchInput.Event == ETIE_LEFT_UP)
touch = BET_RIGHT; touch = BET_RIGHT;
} }
#endif
// Set this number to a positive value to generate a move action // Set this number to a positive value to generate a move action
// from m_selected_item to s. // from m_selected_item to s.
@ -4678,7 +4683,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
break; break;
} }
#ifdef HAVE_TOUCHSCREENGUI
if (touch == BET_RIGHT && m_selected_item && !m_left_dragging) { if (touch == BET_RIGHT && m_selected_item && !m_left_dragging) {
if (!s.isValid()) { if (!s.isValid()) {
// Not a valid slot // Not a valid slot
@ -4698,7 +4702,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
} }
} }
#endif
// Update left-dragged slots // Update left-dragged slots
if (m_left_dragging && m_left_drag_stacks.size() > 1) { if (m_left_dragging && m_left_drag_stacks.size() > 1) {
@ -5067,10 +5070,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
} }
#ifdef HAVE_TOUCHSCREENGUI
if (m_second_touch) if (m_second_touch)
return true; // Stop propagating the event return true; // Stop propagating the event
#endif
return Parent ? Parent->OnEvent(event) : false; return Parent ? Parent->OnEvent(event) : false;
} }

@ -286,7 +286,7 @@ public:
core::rect<s32> getAbsoluteRect(); core::rect<s32> getAbsoluteRect();
#ifdef __ANDROID__ #ifdef __ANDROID__
bool getAndroidUIInput(); void getAndroidUIInput();
#endif #endif
protected: protected:

@ -1052,14 +1052,10 @@ void GUIHyperText::checkHover(s32 X, s32 Y)
} }
} }
#ifndef HAVE_TOUCHSCREENGUI ICursorControl *cursor_control = RenderingEngine::get_raw_device()->getCursorControl();
if (m_drawer.m_hovertag)
RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon( if (cursor_control)
gui::ECI_HAND); cursor_control->setActiveIcon(m_drawer.m_hovertag ? gui::ECI_HAND : gui::ECI_NORMAL);
else
RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
gui::ECI_NORMAL);
#endif
} }
bool GUIHyperText::OnEvent(const SEvent &event) bool GUIHyperText::OnEvent(const SEvent &event)
@ -1075,12 +1071,11 @@ bool GUIHyperText::OnEvent(const SEvent &event)
if (event.EventType == EET_GUI_EVENT && if (event.EventType == EET_GUI_EVENT &&
event.GUIEvent.EventType == EGET_ELEMENT_LEFT) { event.GUIEvent.EventType == EGET_ELEMENT_LEFT) {
m_drawer.m_hovertag = nullptr; m_drawer.m_hovertag = nullptr;
#ifndef HAVE_TOUCHSCREENGUI
gui::ICursorControl *cursor_control = ICursorControl *cursor_control = RenderingEngine::get_raw_device()->getCursorControl();
RenderingEngine::get_raw_device()->getCursorControl();
if (cursor_control->isVisible()) if (cursor_control && cursor_control->isVisible())
cursor_control->setActiveIcon(gui::ECI_NORMAL); cursor_control->setActiveIcon(gui::ECI_NORMAL);
#endif
} }
if (event.EventType == EET_MOUSE_INPUT_EVENT) { if (event.EventType == EET_MOUSE_INPUT_EVENT) {

@ -152,10 +152,10 @@ void GUIInventoryList::draw()
// Add hovering tooltip // Add hovering tooltip
bool show_tooltip = !item.empty() && hovering && !selected_item; bool show_tooltip = !item.empty() && hovering && !selected_item;
#ifdef HAVE_TOUCHSCREENGUI
// Make it possible to see item tooltips on touchscreens // Make it possible to see item tooltips on touchscreens
show_tooltip |= hovering && selected && m_fs_menu->getSelectedAmount() != 0; if (m_fs_menu->getPointerType() == PointerType::Touch) {
#endif show_tooltip |= hovering && selected && m_fs_menu->getSelectedAmount() != 0;
}
if (show_tooltip) { if (show_tooltip) {
std::string tooltip = orig_item.getDescription(client->idef()); std::string tooltip = orig_item.getDescription(client->idef());
if (m_fs_menu->doTooltipAppendItemname()) if (m_fs_menu->doTooltipAppendItemname())

@ -199,14 +199,12 @@ bool GUIPasswordChange::processInput()
bool GUIPasswordChange::OnEvent(const SEvent &event) bool GUIPasswordChange::OnEvent(const SEvent &event)
{ {
if (event.EventType == EET_KEY_INPUT_EVENT) { if (event.EventType == EET_KEY_INPUT_EVENT) {
// clang-format off
if ((event.KeyInput.Key == KEY_ESCAPE || if ((event.KeyInput.Key == KEY_ESCAPE ||
event.KeyInput.Key == KEY_CANCEL) && event.KeyInput.Key == KEY_CANCEL) &&
event.KeyInput.PressedDown) { event.KeyInput.PressedDown) {
quitMenu(); quitMenu();
return true; return true;
} }
// clang-format on
if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) { if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) {
acceptInput(); acceptInput();
if (processInput()) if (processInput())
@ -266,14 +264,19 @@ std::string GUIPasswordChange::getNameByID(s32 id)
} }
#ifdef __ANDROID__ #ifdef __ANDROID__
bool GUIPasswordChange::getAndroidUIInput() void GUIPasswordChange::getAndroidUIInput()
{ {
if (!hasAndroidUIInput()) porting::AndroidDialogState dialogState = getAndroidUIInputState();
return false; if (dialogState == porting::DIALOG_SHOWN) {
return;
} else if (dialogState == porting::DIALOG_CANCELED) {
m_jni_field_name.clear();
return;
}
// still waiting // It has to be a text input
if (porting::getInputDialogState() == -1) if (porting::getLastInputDialogType() != porting::TEXT_INPUT)
return true; return;
gui::IGUIElement *e = nullptr; gui::IGUIElement *e = nullptr;
if (m_jni_field_name == "old_password") if (m_jni_field_name == "old_password")
@ -285,10 +288,10 @@ bool GUIPasswordChange::getAndroidUIInput()
m_jni_field_name.clear(); m_jni_field_name.clear();
if (!e || e->getType() != irr::gui::EGUIET_EDIT_BOX) if (!e || e->getType() != irr::gui::EGUIET_EDIT_BOX)
return false; return;
std::string text = porting::getInputDialogValue(); std::string text = porting::getInputDialogMessage();
e->setText(utf8_to_wide(text).c_str()); e->setText(utf8_to_wide(text).c_str());
return false; return;
} }
#endif #endif

@ -45,7 +45,7 @@ public:
bool OnEvent(const SEvent &event); bool OnEvent(const SEvent &event);
#ifdef __ANDROID__ #ifdef __ANDROID__
bool getAndroidUIInput(); void getAndroidUIInput();
#endif #endif
protected: protected:

@ -155,7 +155,6 @@ bool GUIScrollBar::OnEvent(const SEvent &event)
if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
is_dragging = false; is_dragging = false;
// clang-format off
if (!dragged_by_slider) { if (!dragged_by_slider) {
if (is_inside) { if (is_inside) {
dragged_by_slider = slider_rect.isPointInside(p); dragged_by_slider = slider_rect.isPointInside(p);
@ -167,7 +166,6 @@ bool GUIScrollBar::OnEvent(const SEvent &event)
return is_inside; return is_inside;
} }
} }
// clang-format on
const s32 new_pos = getPosFromMousePos(p); const s32 new_pos = getPosFromMousePos(p);
const s32 old_pos = scroll_pos; const s32 old_pos = scroll_pos;

@ -60,7 +60,7 @@ GUITable::GUITable(gui::IGUIEnvironment *env,
m_rowheight = MYMAX(m_rowheight, 1); m_rowheight = MYMAX(m_rowheight, 1);
} }
const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE); const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE) * 1.5f;
m_scrollbar = new GUIScrollBar(Environment, this, -1, m_scrollbar = new GUIScrollBar(Environment, this, -1,
core::rect<s32>(RelativeRect.getWidth() - s, core::rect<s32>(RelativeRect.getWidth() - s,
0, 0,
@ -77,18 +77,6 @@ GUITable::GUITable(gui::IGUIEnvironment *env,
setTabStop(true); setTabStop(true);
setTabOrder(-1); setTabOrder(-1);
updateAbsolutePosition(); updateAbsolutePosition();
#ifdef HAVE_TOUCHSCREENGUI
float density = 1; // dp scaling is applied by the skin
#else
float density = RenderingEngine::getDisplayDensity();
#endif
core::rect<s32> relative_rect = m_scrollbar->getRelativePosition();
s32 width = (relative_rect.getWidth() / (2.0 / 3.0)) * density *
g_settings->getFloat("gui_scaling", 0.5f, 20.0f);
m_scrollbar->setRelativePosition(core::rect<s32>(
relative_rect.LowerRightCorner.X-width,relative_rect.UpperLeftCorner.Y,
relative_rect.LowerRightCorner.X,relative_rect.LowerRightCorner.Y
));
} }
GUITable::~GUITable() GUITable::~GUITable()

@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/ */
#include <cstdlib> #include <cstdlib>
#include <IEventReceiver.h>
#include "client/renderingengine.h" #include "client/renderingengine.h"
#include "modalMenu.h" #include "modalMenu.h"
#include "gettext.h" #include "gettext.h"
@ -29,7 +30,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "touchscreengui.h" #include "touchscreengui.h"
#endif #endif
// clang-format off
GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent,
s32 id, IMenuManager *menumgr, bool remap_dbl_click) : s32 id, IMenuManager *menumgr, bool remap_dbl_click) :
IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
@ -51,13 +51,9 @@ GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent,
setVisible(true); setVisible(true);
m_menumgr->createdMenu(this); m_menumgr->createdMenu(this);
m_doubleclickdetect[0].time = 0; m_last_touch.time = 0;
m_doubleclickdetect[1].time = 0; m_last_touch.pos = v2s32(0, 0);
m_doubleclickdetect[0].pos = v2s32(0, 0);
m_doubleclickdetect[1].pos = v2s32(0, 0);
} }
// clang-format on
GUIModalMenu::~GUIModalMenu() GUIModalMenu::~GUIModalMenu()
{ {
@ -105,13 +101,23 @@ void GUIModalMenu::quitMenu()
m_menumgr->deletingMenu(this); m_menumgr->deletingMenu(this);
this->remove(); this->remove();
#ifdef HAVE_TOUCHSCREENGUI #ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui && m_touchscreen_visible) if (g_touchscreengui)
g_touchscreengui->show(); g_touchscreengui->show();
#endif #endif
} }
// clang-format off static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent)
bool GUIModalMenu::DoubleClickDetection(const SEvent &event) {
while (tocheck) {
if (tocheck == parent) {
return true;
}
tocheck = tocheck->getParent();
}
return false;
}
bool GUIModalMenu::remapDoubleClick(const SEvent &event)
{ {
/* The following code is for capturing double-clicks of the mouse button /* The following code is for capturing double-clicks of the mouse button
* and translating the double-click into an EET_KEY_INPUT_EVENT event * and translating the double-click into an EET_KEY_INPUT_EVENT event
@ -126,58 +132,37 @@ bool GUIModalMenu::DoubleClickDetection(const SEvent &event)
if (!m_remap_dbl_click) if (!m_remap_dbl_click)
return false; return false;
if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { if (event.EventType != EET_MOUSE_INPUT_EVENT ||
m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos; event.MouseInput.Event != EMIE_LMOUSE_DOUBLE_CLICK)
m_doubleclickdetect[0].time = m_doubleclickdetect[1].time; return false;
m_doubleclickdetect[1].pos = m_pointer; // Only exit if the double-click happened outside the menu.
m_doubleclickdetect[1].time = porting::getTimeMs(); gui::IGUIElement *hovered =
} else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
u64 delta = porting::getDeltaMs( if (isChild(hovered, this))
m_doubleclickdetect[0].time, porting::getTimeMs()); return false;
if (delta > 400)
return false;
double squaredistance = m_doubleclickdetect[0].pos. // Translate double-click to escape.
getDistanceFromSQ(m_doubleclickdetect[1].pos); SEvent translated{};
translated.EventType = EET_KEY_INPUT_EVENT;
translated.KeyInput.Key = KEY_ESCAPE;
translated.KeyInput.Control = false;
translated.KeyInput.Shift = false;
translated.KeyInput.PressedDown = true;
translated.KeyInput.Char = 0;
OnEvent(translated);
if (squaredistance > (30 * 30)) { return true;
return false;
}
SEvent translated{};
// translate doubleclick to escape
translated.EventType = EET_KEY_INPUT_EVENT;
translated.KeyInput.Key = KEY_ESCAPE;
translated.KeyInput.Control = false;
translated.KeyInput.Shift = false;
translated.KeyInput.PressedDown = true;
translated.KeyInput.Char = 0;
OnEvent(translated);
return true;
}
return false;
}
// clang-format on
static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent)
{
while (tocheck) {
if (tocheck == parent) {
return true;
}
tocheck = tocheck->getParent();
}
return false;
} }
#ifdef HAVE_TOUCHSCREENGUI bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool second_try)
bool GUIModalMenu::simulateMouseEvent(
gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event)
{ {
IGUIElement *target;
if (!second_try)
target = Environment->getFocus();
else
target = m_touch_hovered.get();
SEvent mouse_event{}; // value-initialized, not unitialized SEvent mouse_event{}; // value-initialized, not unitialized
mouse_event.EventType = EET_MOUSE_INPUT_EVENT; mouse_event.EventType = EET_MOUSE_INPUT_EVENT;
mouse_event.MouseInput.X = m_pointer.X; mouse_event.MouseInput.X = m_pointer.X;
@ -195,48 +180,65 @@ bool GUIModalMenu::simulateMouseEvent(
mouse_event.MouseInput.Event = EMIE_LMOUSE_LEFT_UP; mouse_event.MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
mouse_event.MouseInput.ButtonStates = 0; mouse_event.MouseInput.ButtonStates = 0;
break; break;
case ETIE_COUNT:
// ETIE_COUNT is used for double-tap events.
mouse_event.MouseInput.Event = EMIE_LMOUSE_DOUBLE_CLICK;
mouse_event.MouseInput.ButtonStates = EMBSM_LEFT;
break;
default: default:
return false; return false;
} }
if (preprocessEvent(mouse_event))
return true; bool retval;
if (!target) m_simulated_mouse = true;
return false; do {
return target->OnEvent(mouse_event); if (preprocessEvent(mouse_event)) {
retval = true;
break;
}
if (!target) {
retval = false;
break;
}
retval = target->OnEvent(mouse_event);
} while (false);
m_simulated_mouse = false;
if (!retval && !second_try)
return simulateMouseEvent(touch_event, true);
return retval;
} }
void GUIModalMenu::enter(gui::IGUIElement *hovered) void GUIModalMenu::enter(gui::IGUIElement *hovered)
{ {
if (!hovered) if (!hovered)
return; return;
sanity_check(!m_hovered); sanity_check(!m_touch_hovered);
m_hovered.grab(hovered); m_touch_hovered.grab(hovered);
SEvent gui_event{}; SEvent gui_event{};
gui_event.EventType = EET_GUI_EVENT; gui_event.EventType = EET_GUI_EVENT;
gui_event.GUIEvent.Caller = m_hovered.get(); gui_event.GUIEvent.Caller = m_touch_hovered.get();
gui_event.GUIEvent.EventType = EGET_ELEMENT_HOVERED; gui_event.GUIEvent.EventType = gui::EGET_ELEMENT_HOVERED;
gui_event.GUIEvent.Element = gui_event.GUIEvent.Caller; gui_event.GUIEvent.Element = gui_event.GUIEvent.Caller;
m_hovered->OnEvent(gui_event); m_touch_hovered->OnEvent(gui_event);
} }
void GUIModalMenu::leave() void GUIModalMenu::leave()
{ {
if (!m_hovered) if (!m_touch_hovered)
return; return;
SEvent gui_event{}; SEvent gui_event{};
gui_event.EventType = EET_GUI_EVENT; gui_event.EventType = EET_GUI_EVENT;
gui_event.GUIEvent.Caller = m_hovered.get(); gui_event.GUIEvent.Caller = m_touch_hovered.get();
gui_event.GUIEvent.EventType = EGET_ELEMENT_LEFT; gui_event.GUIEvent.EventType = gui::EGET_ELEMENT_LEFT;
m_hovered->OnEvent(gui_event); m_touch_hovered->OnEvent(gui_event);
m_hovered.reset(); m_touch_hovered.reset();
} }
#endif
bool GUIModalMenu::preprocessEvent(const SEvent &event) bool GUIModalMenu::preprocessEvent(const SEvent &event)
{ {
#ifdef __ANDROID__ #ifdef __ANDROID__
// clang-format off
// display software keyboard when clicking edit boxes // display software keyboard when clicking edit boxes
if (event.EventType == EET_MOUSE_INPUT_EVENT && if (event.EventType == EET_MOUSE_INPUT_EVENT &&
event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
@ -266,36 +268,76 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
if (((gui::IGUIEditBox *)hovered)->isPasswordBox()) if (((gui::IGUIEditBox *)hovered)->isPasswordBox())
type = 3; type = 3;
porting::showInputDialog(gettext("OK"), "", porting::showTextInputDialog("",
wide_to_utf8(((gui::IGUIEditBox *)hovered)->getText()), type); wide_to_utf8(((gui::IGUIEditBox *) hovered)->getText()), type);
return retval; return retval;
} }
} }
if (event.EventType == EET_GUI_EVENT) {
if (event.GUIEvent.EventType == gui::EGET_LISTBOX_OPENED) {
gui::IGUIComboBox *dropdown = (gui::IGUIComboBox *) event.GUIEvent.Caller;
std::string field_name = getNameByID(dropdown->getID());
if (field_name.empty())
return false;
m_jni_field_name = field_name;
s32 selected_idx = dropdown->getSelected();
s32 option_size = dropdown->getItemCount();
std::string list_of_options[option_size];
for (s32 i = 0; i < option_size; i++) {
list_of_options[i] = wide_to_utf8(dropdown->getItem(i));
}
porting::showComboBoxDialog(list_of_options, option_size, selected_idx);
return true; // Prevent the Irrlicht dropdown from opening.
}
}
#endif #endif
#ifdef HAVE_TOUCHSCREENGUI // Convert touch events into mouse events.
if (event.EventType == EET_TOUCH_INPUT_EVENT) { if (event.EventType == EET_TOUCH_INPUT_EVENT) {
irr_ptr<GUIModalMenu> holder; irr_ptr<GUIModalMenu> holder;
holder.grab(this); // keep this alive until return (it might be dropped downstream [?]) holder.grab(this); // keep this alive until return (it might be dropped downstream [?])
if (event.TouchInput.ID == 0) { if (event.TouchInput.touchedCount == 1) {
if (event.TouchInput.Event == ETIE_PRESSED_DOWN || event.TouchInput.Event == ETIE_MOVED) m_pointer_type = PointerType::Touch;
m_pointer = v2s32(event.TouchInput.X, event.TouchInput.Y); m_pointer = v2s32(event.TouchInput.X, event.TouchInput.Y);
gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(core::position2d<s32>(m_pointer)); gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(core::position2d<s32>(m_pointer));
if (event.TouchInput.Event == ETIE_PRESSED_DOWN) if (event.TouchInput.Event == ETIE_PRESSED_DOWN)
Environment->setFocus(hovered); Environment->setFocus(hovered);
if (m_hovered != hovered) { if (m_touch_hovered != hovered) {
leave(); leave();
enter(hovered); enter(hovered);
} }
gui::IGUIElement *focused = Environment->getFocus(); bool ret = simulateMouseEvent(event.TouchInput.Event);
bool ret = simulateMouseEvent(focused, event.TouchInput.Event);
if (!ret && m_hovered != focused)
ret = simulateMouseEvent(m_hovered.get(), event.TouchInput.Event);
if (event.TouchInput.Event == ETIE_LEFT_UP) if (event.TouchInput.Event == ETIE_LEFT_UP)
leave(); leave();
// Detect double-taps and convert them into double-click events.
if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
u64 time_now = porting::getTimeMs();
u64 time_delta = porting::getDeltaMs(m_last_touch.time, time_now);
v2s32 pos_delta = m_pointer - m_last_touch.pos;
f32 distance_sq = (f32)pos_delta.X * pos_delta.X +
(f32)pos_delta.Y * pos_delta.Y;
if (time_delta < 400 && distance_sq < (30 * 30)) {
// ETIE_COUNT is used for double-tap events.
simulateMouseEvent(ETIE_COUNT);
}
m_last_touch.time = time_now;
m_last_touch.pos = m_pointer;
}
return ret; return ret;
} else if (event.TouchInput.ID == 1) { } else if (event.TouchInput.touchedCount == 2) {
if (event.TouchInput.Event != ETIE_LEFT_UP) if (event.TouchInput.Event != ETIE_LEFT_UP)
return true; // ignore return true; // ignore
auto focused = Environment->getFocus(); auto focused = Environment->getFocus();
@ -311,40 +353,29 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
return true; return true;
} }
} }
#endif
if (event.EventType == EET_MOUSE_INPUT_EVENT) { if (event.EventType == EET_MOUSE_INPUT_EVENT) {
s32 x = event.MouseInput.X; if (!m_simulated_mouse) {
s32 y = event.MouseInput.Y; // Only set the pointer type to mouse if this is a real mouse event.
gui::IGUIElement *hovered = m_pointer_type = PointerType::Mouse;
Environment->getRootGUIElement()->getElementFromPoint( m_pointer = v2s32(event.MouseInput.X, event.MouseInput.Y);
core::position2d<s32>(x, y)); m_touch_hovered.reset();
if (!isChild(hovered, this)) {
if (DoubleClickDetection(event)) {
return true;
}
} }
if (remapDoubleClick(event))
return true;
} }
return false; return false;
} }
#ifdef __ANDROID__ #ifdef __ANDROID__
bool GUIModalMenu::hasAndroidUIInput() porting::AndroidDialogState GUIModalMenu::getAndroidUIInputState()
{ {
// no dialog shown // No dialog is shown
if (m_jni_field_name.empty()) if (m_jni_field_name.empty())
return false; return porting::DIALOG_CANCELED;
// still waiting return porting::getInputDialogState();
if (porting::getInputDialogState() == -1)
return true;
// no value abort dialog processing
if (porting::getInputDialogState() != 0) {
m_jni_field_name.clear();
return false;
}
return true;
} }
#endif #endif

@ -22,6 +22,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes_extrabloated.h" #include "irrlichttypes_extrabloated.h"
#include "irr_ptr.h" #include "irr_ptr.h"
#include "util/string.h" #include "util/string.h"
#ifdef __ANDROID__
#include <porting_android.h>
#endif
enum class PointerType {
Mouse,
Touch,
};
class GUIModalMenu; class GUIModalMenu;
@ -54,42 +62,37 @@ public:
virtual bool OnEvent(const SEvent &event) { return false; }; virtual bool OnEvent(const SEvent &event) { return false; };
virtual bool pausesGame() { return false; } // Used for pause menu virtual bool pausesGame() { return false; } // Used for pause menu
#ifdef __ANDROID__ #ifdef __ANDROID__
virtual bool getAndroidUIInput() { return false; } virtual void getAndroidUIInput() {};
bool hasAndroidUIInput(); porting::AndroidDialogState getAndroidUIInputState();
#endif #endif
PointerType getPointerType() { return m_pointer_type; };
protected: protected:
virtual std::wstring getLabelByID(s32 id) = 0; virtual std::wstring getLabelByID(s32 id) = 0;
virtual std::string getNameByID(s32 id) = 0; virtual std::string getNameByID(s32 id) = 0;
/** // Stores the last known pointer type.
* check if event is part of a double click PointerType m_pointer_type = PointerType::Mouse;
* @param event event to evaluate // Stores the last known pointer position.
* @return true/false if a doubleclick was detected // If the last input event was a mouse event, it's the cursor position.
*/ // If the last input event was a touch event, it's the finger position.
bool DoubleClickDetection(const SEvent &event);
v2s32 m_pointer; v2s32 m_pointer;
v2s32 m_old_pointer; // Mouse position after previous mouse event v2s32 m_old_pointer; // Mouse position after previous mouse event
v2u32 m_screensize_old; v2u32 m_screensize_old;
float m_gui_scale; float m_gui_scale;
#ifdef __ANDROID__ #ifdef __ANDROID__
std::string m_jni_field_name; std::string m_jni_field_name;
#endif #endif
#ifdef HAVE_TOUCHSCREENGUI
// This is set to true if the menu is currently processing a second-touch event. // This is set to true if the menu is currently processing a second-touch event.
bool m_second_touch = false; bool m_second_touch = false;
bool m_touchscreen_visible = true; // This is set to true if the menu is currently processing a mouse event
#endif // that was synthesized by the menu itself from a touch event.
bool m_simulated_mouse = false;
private: private:
struct clickpos
{
v2s32 pos;
s64 time;
};
clickpos m_doubleclickdetect[2];
IMenuManager *m_menumgr; IMenuManager *m_menumgr;
/* If true, remap a double-click (or double-tap) action to ESC. This is so /* If true, remap a double-click (or double-tap) action to ESC. This is so
* that, for example, Android users can double-tap to close a formspec. * that, for example, Android users can double-tap to close a formspec.
@ -98,15 +101,23 @@ private:
* and the default value for the setting is true. * and the default value for the setting is true.
*/ */
bool m_remap_dbl_click; bool m_remap_dbl_click;
bool remapDoubleClick(const SEvent &event);
// This might be necessary to expose to the implementation if it // This might be necessary to expose to the implementation if it
// wants to launch other menus // wants to launch other menus
bool m_allow_focus_removal = false; bool m_allow_focus_removal = false;
#ifdef HAVE_TOUCHSCREENGUI // Stuff related to touchscreen input
irr_ptr<gui::IGUIElement> m_hovered;
bool simulateMouseEvent(gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event); irr_ptr<gui::IGUIElement> m_touch_hovered;
bool simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool second_try=false);
void enter(gui::IGUIElement *element); void enter(gui::IGUIElement *element);
void leave(); void leave();
#endif
// Used to detect double-taps and convert them into double-click events.
struct {
v2s32 pos;
s64 time;
} m_last_touch;
}; };

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