master #7

Merged
BRNSystems merged 95 commits from Mirrorlandia_minetest/minetest:master into master 2024-01-28 00:16:42 +01:00
224 changed files with 5564 additions and 3771 deletions

0
.editorconfig Executable file → Normal file

@ -73,7 +73,10 @@ jobs:
- name: Test - name: Test
run: | run: |
./bin/minetest --run-unittests mkdir nowrite
chmod a-w nowrite
cd nowrite
../bin/minetest --run-unittests
# Older clang version (should be close to our minimum supported version) # Older clang version (should be close to our minimum supported version)
clang_7: clang_7:

@ -74,7 +74,7 @@ jobs:
env: env:
VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50 VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50
# 2023.10.19 # 2023.10.19
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry sdl2
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -124,6 +124,13 @@ jobs:
- name: Build Minetest - name: Build Minetest
run: cmake --build . --config Release run: cmake --build . --config Release
- name: Unittests
# need this workaround for stdout to work
run: |
$proc = start .\bin\Release\minetest.exe --run-unittests -NoNewWindow -Wait -PassThru
exit $proc.ExitCode
continue-on-error: true # FIXME!!
- name: CPack - name: CPack
run: | run: |
If ($env:TYPE -eq "installer") If ($env:TYPE -eq "installer")
@ -134,12 +141,10 @@ jobs:
{ {
cpack -G ZIP -B "$env:GITHUB_WORKSPACE\Package" cpack -G ZIP -B "$env:GITHUB_WORKSPACE\Package"
} }
rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
env: env:
TYPE: ${{matrix.type}} TYPE: ${{matrix.type}}
- name: Package Clean
run: rm -r $env:GITHUB_WORKSPACE\Package\_CPack_Packages
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: msvc-${{ matrix.config.arch }}-${{ matrix.type }} name: msvc-${{ matrix.config.arch }}-${{ matrix.type }}

@ -3,128 +3,14 @@
# https://gitlab.com/minetest/minetest # https://gitlab.com/minetest/minetest
# Pipelines URL: https://gitlab.com/minetest/minetest/pipelines # Pipelines URL: https://gitlab.com/minetest/minetest/pipelines
stages:
- build
- package
- deploy
variables:
CONTAINER_IMAGE: registry.gitlab.com/$CI_PROJECT_PATH
.build_template:
stage: build
before_script:
- apt-get update
- DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential gettext git cmake libpng-dev libjpeg-dev libxi-dev libgl1-mesa-dev libsqlite3-dev libleveldb-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev
script:
- git clone https://github.com/minetest/irrlicht lib/irrlichtmt --depth 1 -b $(cat misc/irrlichtmt_tag.txt)
- mkdir build && cd build
- cmake -DCMAKE_INSTALL_PREFIX=../artifact/minetest/usr/ -DRUN_IN_PLACE=FALSE -DENABLE_GETTEXT=TRUE ..
- make -j $(($(nproc) + 1))
- make install
artifacts:
when: on_success
expire_in: 1h
paths:
- artifact/*
##
## Ubuntu (prerequisite for AppImage build)
##
build:ubuntu-20.04:
extends: .build_template
image: ubuntu:focal
##
## MinGW for Windows
##
.generic_win_template:
image: ubuntu:focal
before_script:
- apt-get update
- DEBIAN_FRONTEND=noninteractive apt-get install -y wget xz-utils unzip git cmake gettext
- wget -nv http://minetest.kitsunemimi.pw/mingw-w64-${WIN_ARCH}_11.2.0_ubuntu20.04.tar.xz -O mingw.tar.xz
- tar -xaf mingw.tar.xz -C /usr
.build_win_template:
extends: .generic_win_template
stage: build
artifacts:
expire_in: 90 day
paths:
- minetest-*-win*/*
build:win32:
extends: .build_win_template
script:
- EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin32.sh build
- unzip -q build/build/*.zip
variables:
WIN_ARCH: "i686"
build:win64:
extends: .build_win_template
script:
- EXISTING_MINETEST_DIR=$PWD ./util/buildbot/buildwin64.sh build
- unzip -q build/build/*.zip
variables:
WIN_ARCH: "x86_64"
##
## Docker
##
package:docker:
stage: package
image: docker:stable
services:
- docker:dind
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com
script:
- ./util/ci/docker.sh
##
## Gitlab Pages (Lua API documentation)
##
pages: pages:
stage: deploy stage: deploy
image: python:3.8 image: python:3.8
before_script:
- pip install -U -r doc/mkdocs/requirements.txt
script: script:
- cd doc/mkdocs && ./build.sh - ./misc/make_redirects.sh
artifacts: artifacts:
paths: paths:
- public - public
only: only:
- master - master
##
## AppImage
##
package:appimage-client:
stage: package
image: appimagecrafters/appimage-builder
needs:
- build:ubuntu-20.04
before_script:
- apt-get update
- apt-get install -y git
# Collect files
- mkdir AppDir
- cp -a artifact/minetest/usr/ AppDir/usr/
- cp -a clientmods AppDir/usr/share/minetest
# Remove PrefersNonDefaultGPU property due to validation errors
- sed -i '/PrefersNonDefaultGPU/d' AppDir/usr/share/applications/net.minetest.minetest.desktop
script:
- export VERSION=$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
- appimage-builder --skip-test --recipe misc/AppImageBuilder.yml
artifacts:
expire_in: 90 day
paths:
- ./*.AppImage

@ -140,7 +140,7 @@ elseif(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt)
endif() endif()
message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}") message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
set(TARGET_VER_S 1.9.0mt13) set(TARGET_VER_S 1.9.0mt14)
string(REPLACE "mt" "." TARGET_VER ${TARGET_VER_S}) string(REPLACE "mt" "." TARGET_VER ${TARGET_VER_S})
if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER}) if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER})
message(FATAL_ERROR "At least IrrlichtMt ${TARGET_VER_S} is required to build") message(FATAL_ERROR "At least IrrlichtMt ${TARGET_VER_S} is required to build")

@ -25,11 +25,11 @@ Table of Contents
Further documentation Further documentation
---------------------- ----------------------
- Website: https://minetest.net/ - Website: https://www.minetest.net/
- Wiki: https://wiki.minetest.net/ - Wiki: https://wiki.minetest.net/
- Developer wiki: https://dev.minetest.net/
- Forum: https://forum.minetest.net/ - Forum: https://forum.minetest.net/
- GitHub: https://github.com/minetest/minetest/ - GitHub: https://github.com/minetest/minetest/
- [Developer documentation](doc/developing/)
- [doc/](doc/) directory of source distribution - [doc/](doc/) directory of source distribution
Default controls Default controls

@ -85,6 +85,17 @@ public class GameActivity extends NativeActivity {
makeFullScreen(); makeFullScreen();
} }
private native void saveSettings();
@Override
protected void onStop() {
super.onStop();
// Avoid losing setting changes in case the app is onDestroy()ed later.
// Saving stuff in onStop() is recommended in the Android activity
// lifecycle documentation.
saveSettings();
}
@Override @Override
public void onBackPressed() { public void onBackPressed() {
// Ignore the back press so Minetest can handle it // Ignore the back press so Minetest can handle it

@ -20,23 +20,29 @@ with this program; if not, write to the Free Software Foundation, Inc.,
package net.minetest.minetest; package net.minetest.minetest;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import static net.minetest.minetest.UnzipService.*; import static net.minetest.minetest.UnzipService.*;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
public static final String NOTIFICATION_CHANNEL_ID = "Minetest channel";
private final static int versionCode = BuildConfig.VERSION_CODE; private final static int versionCode = BuildConfig.VERSION_CODE;
private static final String SETTINGS = "MinetestSettings"; private static final String SETTINGS = "MinetestSettings";
private static final String TAG_VERSION_CODE = "versionCode"; private static final String TAG_VERSION_CODE = "versionCode";
@ -81,12 +87,18 @@ public class MainActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
IntentFilter filter = new IntentFilter(ACTION_UPDATE); IntentFilter filter = new IntentFilter(ACTION_UPDATE);
registerReceiver(myReceiver, filter); registerReceiver(myReceiver, filter);
mProgressBar = findViewById(R.id.progressBar); mProgressBar = findViewById(R.id.progressBar);
mTextView = findViewById(R.id.textView); mTextView = findViewById(R.id.textView);
sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE); sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE);
checkAppVersion(); checkAppVersion();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
createNotificationChannel();
} }
private void checkAppVersion() { private void checkAppVersion() {
@ -114,6 +126,28 @@ public class MainActivity extends AppCompatActivity {
startActivity(intent); startActivity(intent);
} }
@RequiresApi(Build.VERSION_CODES.O)
private void createNotificationChannel() {
NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notifyManager == null)
return;
NotificationChannel notifyChannel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
getString(R.string.notification_channel_name),
NotificationManager.IMPORTANCE_LOW
);
notifyChannel.setDescription(getString(R.string.notification_channel_description));
// Configure the notification channel without sound set
notifyChannel.setSound(null, null);
notifyChannel.enableLights(false);
notifyChannel.enableVibration(false);
// It is fine to always create the notification channel because creating a channel
// with the same ID is the same as overriding it (only its name and description).
notifyManager.createNotificationChannel(notifyChannel);
}
@Override @Override
public void onBackPressed() { public void onBackPressed() {
// Prevent abrupt interruption when copy game files from assets // Prevent abrupt interruption when copy game files from assets

@ -22,7 +22,6 @@ package net.minetest.minetest;
import android.app.IntentService; import android.app.IntentService;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
@ -58,9 +57,11 @@ public class UnzipService extends IntentService {
private String failureMessage; private String failureMessage;
private static boolean isRunning = false; private static boolean isRunning = false;
public static synchronized boolean getIsRunning() { public static synchronized boolean getIsRunning() {
return isRunning; return isRunning;
} }
private static synchronized void setIsRunning(boolean v) { private static synchronized void setIsRunning(boolean v) {
isRunning = v; isRunning = v;
} }
@ -99,28 +100,13 @@ public class UnzipService extends IntentService {
} }
} }
@NonNull
private Notification.Builder createNotification() { private Notification.Builder createNotification() {
String name = "net.minetest.minetest";
String channelId = "Minetest channel";
String description = "notifications from Minetest";
Notification.Builder builder; Notification.Builder builder;
if (mNotifyManager == null) if (mNotifyManager == null)
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
int importance = NotificationManager.IMPORTANCE_LOW; builder = new Notification.Builder(this, MainActivity.NOTIFICATION_CHANNEL_ID);
NotificationChannel mChannel = null;
if (mNotifyManager != null)
mChannel = mNotifyManager.getNotificationChannel(channelId);
if (mChannel == null) {
mChannel = new NotificationChannel(channelId, name, importance);
mChannel.setDescription(description);
// Configure the notification channel, NO SOUND
mChannel.setSound(null, null);
mChannel.enableLights(false);
mChannel.enableVibration(false);
mNotifyManager.createNotificationChannel(mChannel);
}
builder = new Notification.Builder(this, channelId);
} else { } else {
builder = new Notification.Builder(this); builder = new Notification.Builder(this);
} }
@ -135,9 +121,9 @@ public class UnzipService extends IntentService {
PendingIntent intent = PendingIntent.getActivity(this, 0, PendingIntent intent = PendingIntent.getActivity(this, 0,
notificationIntent, pendingIntentFlag); notificationIntent, pendingIntentFlag);
builder.setContentTitle(getString(R.string.notification_title)) builder.setContentTitle(getString(R.string.unzip_notification_title))
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.mipmap.ic_launcher)
.setContentText(getString(R.string.notification_description)) .setContentText(getString(R.string.unzip_notification_description))
.setContentIntent(intent) .setContentIntent(intent)
.setOngoing(true) .setOngoing(true)
.setProgress(0, 0, true); .setProgress(0, 0, true);
@ -198,7 +184,7 @@ public class UnzipService extends IntentService {
} }
} }
private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) { private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) {
Intent intentUpdate = new Intent(ACTION_UPDATE); Intent intentUpdate = new Intent(ACTION_UPDATE);
intentUpdate.putExtra(ACTION_PROGRESS, progress); intentUpdate.putExtra(ACTION_PROGRESS, progress);
intentUpdate.putExtra(ACTION_PROGRESS_MESSAGE, message); intentUpdate.putExtra(ACTION_PROGRESS_MESSAGE, message);

@ -2,7 +2,9 @@
<resources> <resources>
<string name="label">Minetest</string> <string name="label">Minetest</string>
<string name="loading">Loading&#8230;</string> <string name="loading">Loading&#8230;</string>
<string name="notification_title">Loading Minetest</string> <string name="notification_channel_name">General notification</string>
<string name="notification_description">Less than 1 minute&#8230;</string> <string name="notification_channel_description">Notifications from Minetest</string>
<string name="unzip_notification_title">Loading Minetest</string>
<string name="unzip_notification_description">Less than 1 minute&#8230;</string>
<string name="ime_dialog_done">Done</string> <string name="ime_dialog_done">Done</string>
</resources> </resources>

@ -1,4 +1,116 @@
local jobs = {} -- This is an implementation of a job sheduling mechanism. It guarantees that
-- coexisting jobs will execute primarily in order of least expiry, and
-- secondarily in order of first registration.
-- These functions implement an intrusive singly linked list of one or more
-- elements where the first element has a pointer to the last. The next pointer
-- is stored with key list_next. The pointer to the last is with key list_end.
local function list_init(first)
first.list_end = first
end
local function list_append(first, append)
first.list_end.list_next = append
first.list_end = append
end
local function list_append_list(first, first_append)
first.list_end.list_next = first_append
first.list_end = first_append.list_end
end
-- The jobs are stored in a map from expiration times to linked lists of jobs
-- as above. The expiration times are also stored in an array representing a
-- binary min heap, which is a particular arrangement of binary tree. A parent
-- at index i has children at indices i*2 and i*2+1. Out-of-bounds indices
-- represent nonexistent children. A parent is never greater than its children.
-- This structure means that, if there is at least one job, the next expiration
-- time is the first item in the array.
-- Push element on a binary min-heap,
-- "bubbling up" the element by swapping with larger parents.
local function heap_push(heap, element)
local index = #heap + 1
while index > 1 do
local parent_index = math.floor(index / 2)
local parent = heap[parent_index]
if element < parent then
heap[index] = parent
index = parent_index
else
break
end
end
heap[index] = element
end
-- Pop smallest element from the heap,
-- "sinking down" the last leaf on the last layer of the heap
-- by swapping with the smaller child.
local function heap_pop(heap)
local removed_element = heap[1]
local length = #heap
local element = heap[length]
heap[length] = nil
length = length - 1
if length > 0 then
local index = 1
while true do
local old_index = index
local smaller_element = element
local left_index = index * 2
local right_index = index * 2 + 1
if left_index <= length then
local left_element = heap[left_index]
if left_element < smaller_element then
index = left_index
smaller_element = left_element
end
end
if right_index <= length then
if heap[right_index] < smaller_element then
index = right_index
end
end
if old_index ~= index then
heap[old_index] = heap[index]
else
break
end
end
heap[index] = element
end
return removed_element
end
local job_map = {}
local expiries = {}
-- Adds an individual job with the given expiry.
-- The worst-case complexity is O(log n), where n is the number of distinct
-- expiration times.
local function add_job(expiry, job)
local list = job_map[expiry]
if list then
list_append(list, job)
else
list_init(job)
job_map[expiry] = job
heap_push(expiries, expiry)
end
end
-- Removes the next expiring jobs and returns the linked list of them.
-- The worst-case complexity is O(log n), where n is the number of distinct
-- expiration times.
local function remove_first_jobs()
local removed_expiry = heap_pop(expiries)
local removed = job_map[removed_expiry]
job_map[removed_expiry] = nil
return removed
end
local time = 0.0 local time = 0.0
local time_next = math.huge local time_next = math.huge
@ -9,42 +121,54 @@ core.register_globalstep(function(dtime)
return return
end end
time_next = math.huge -- Remove the expired jobs.
local expired = remove_first_jobs()
-- Iterate backwards so that we miss any new timers added by -- Remove other expired jobs and append them to the list.
-- a timer callback. while true do
for i = #jobs, 1, -1 do time_next = expiries[1] or math.huge
local job = jobs[i] if time_next > time then
if time >= job.expire then break
core.set_last_run_mod(job.mod_origin)
job.func(unpack(job.arg))
local jobs_l = #jobs
jobs[i] = jobs[jobs_l]
jobs[jobs_l] = nil
elseif job.expire < time_next then
time_next = job.expire
end end
list_append_list(expired, remove_first_jobs())
end
-- Run the callbacks afterward to prevent infinite loops with core.after(0, ...).
local last_expired = expired.list_end
while true do
core.set_last_run_mod(expired.mod_origin)
expired.func(unpack(expired.args, 1, expired.args.n))
if expired == last_expired then
break
end
expired = expired.list_next
end end
end) end)
function core.after(after, func, ...) local job_metatable = {__index = {}}
assert(tonumber(after) and type(func) == "function",
"Invalid minetest.after invocation")
local expire = time + after
local new_job = {
func = func,
expire = expire,
arg = {...},
mod_origin = core.get_last_run_mod(),
}
jobs[#jobs + 1] = new_job local function dummy_func() end
time_next = math.min(time_next, expire) function job_metatable.__index:cancel()
self.func = dummy_func
return { self.args = {n = 0}
cancel = function() end
new_job.func = function() end
new_job.args = {} function core.after(after, func, ...)
end assert(tonumber(after) and not core.is_nan(after) and type(func) == "function",
} "Invalid minetest.after invocation")
local new_job = {
mod_origin = core.get_last_run_mod(),
func = func,
args = {
n = select("#", ...),
...
},
}
local expiry = time + after
add_job(expiry, new_job)
time_next = math.min(time_next, expiry)
return setmetatable(new_job, job_metatable)
end end

@ -144,6 +144,8 @@ local wallmounted_to_dir = {
vector.new(-1, 0, 0), vector.new(-1, 0, 0),
vector.new( 0, 0, 1), vector.new( 0, 0, 1),
vector.new( 0, 0, -1), vector.new( 0, 0, -1),
vector.new( 0, 1, 0),
vector.new( 0, -1, 0),
} }
function core.wallmounted_to_dir(wallmounted) function core.wallmounted_to_dir(wallmounted)
return wallmounted_to_dir[wallmounted % 8] return wallmounted_to_dir[wallmounted % 8]

@ -0,0 +1,113 @@
_G.core = {}
_G.vector = {metatable = {}}
dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua")
function core.get_last_run_mod() return "*test*" end
function core.set_last_run_mod() end
local do_step
function core.register_globalstep(func)
do_step = func
end
dofile("builtin/common/after.lua")
describe("after", function()
it("executes callbacks when expected", function()
local result = ""
core.after(0, function()
result = result .. "a"
end)
core.after(1, function()
result = result .. "b"
end)
core.after(1, function()
result = result .. "c"
end)
core.after(2, function()
result = result .. "d"
end)
local cancel = core.after(2, function()
result = result .. "e"
end)
do_step(0)
assert.same("a", result)
do_step(1)
assert.same("abc", result)
core.after(2, function()
result = result .. "f"
end)
core.after(1, function()
result = result .. "g"
end)
core.after(-1, function()
result = result .. "h"
end)
cancel:cancel()
do_step(1)
assert.same("abchdg", result)
do_step(1)
assert.same("abchdgf", result)
end)
it("defers jobs with delay 0", function()
local result = ""
core.after(0, function()
core.after(0, function()
result = result .. "b"
end)
result = result .. "a"
end)
do_step(1)
assert.same("a", result)
do_step(1)
assert.same("ab", result)
end)
it("passes arguments", function()
core.after(0, function(...)
assert.same(0, select("#", ...))
end)
core.after(0, function(...)
assert.same(4, select("#", ...))
assert.same(1, (select(1, ...)))
assert.same(nil, (select(2, ...)))
assert.same("a", (select(3, ...)))
assert.same(nil, (select(4, ...)))
end, 1, nil, "a", nil)
do_step(0)
end)
it("rejects invalid arguments", function()
assert.has.errors(function() core.after() end)
assert.has.errors(function() core.after(nil, nil) end)
assert.has.errors(function() core.after(0) end)
assert.has.errors(function() core.after(0, nil) end)
assert.has.errors(function() core.after(nil, function() end) end)
assert.has.errors(function() core.after(0 / 0, function() end) end)
end)
-- Make sure that the underlying heap is working correctly
it("can be abused as a heapsort", function()
local t = {}
for i = 1, 1000 do
t[i] = math.random(100)
end
local sorted = table.copy(t)
table.sort(sorted)
local i = 0
for _, v in ipairs(t) do
core.after(v, function()
i = i + 1
assert.equal(v, sorted[i])
end)
end
do_step(math.max(unpack(t)))
assert.equal(#t, i)
end)
end)

@ -92,16 +92,24 @@ core.builtin_auth_handler = {
core_auth.save(auth_entry) core_auth.save(auth_entry)
-- Run grant callbacks for priv, value in pairs(privileges) do
for priv, _ in pairs(privileges) do -- Warnings for improper API usage
if not prev_privs[priv] then if value == false then
core.log('deprecated', "`false` value given to `minetest.set_player_privs`, "..
"this is almost certainly a bug, "..
"granting a privilege rather than revoking it")
elseif value ~= true then
core.log('deprecated', "non-`true` value given to `minetest.set_player_privs`")
end
-- Run grant callbacks
if prev_privs[priv] == nil 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(prev_privs) do for priv, _ in pairs(prev_privs) do
if not privileges[priv] then if privileges[priv] == nil then
core.run_priv_callbacks(name, priv, nil, "revoke") core.run_priv_callbacks(name, priv, nil, "revoke")
end end
end end
@ -180,6 +188,20 @@ core.set_player_privs = auth_pass("set_privileges")
core.remove_player_auth = auth_pass("delete_auth") core.remove_player_auth = auth_pass("delete_auth")
core.auth_reload = auth_pass("reload") core.auth_reload = auth_pass("reload")
function core.change_player_privs(name, changes)
local privs = core.get_player_privs(name)
for priv, change in pairs(changes) do
if change == true then
privs[priv] = true
elseif change == false then
privs[priv] = nil
else
error("non-bool value given to `minetest.change_player_privs`")
end
end
core.set_player_privs(name, privs)
end
local record_login = auth_pass("record_login") local record_login = auth_pass("record_login")
core.register_on_joinplayer(function(player) core.register_on_joinplayer(function(player)
record_login(player:get_player_name()) record_login(player:get_player_name())

@ -150,7 +150,12 @@ core.register_entity(":__builtin:falling_node", {
-- Rotate entity -- Rotate entity
if def.drawtype == "torchlike" then if def.drawtype == "torchlike" then
self.object:set_yaw(math.pi*0.25) if (def.paramtype2 == "wallmounted" or def.paramtype2 == "colorwallmounted")
and node.param2 % 8 == 7 then
self.object:set_yaw(-math.pi*0.25)
else
self.object:set_yaw(math.pi*0.25)
end
elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh") elseif ((node.param2 ~= 0 or def.drawtype == "nodebox" or def.drawtype == "mesh")
and (def.wield_image == "" or def.wield_image == nil)) and (def.wield_image == "" or def.wield_image == nil))
or def.drawtype == "signlike" or def.drawtype == "signlike"
@ -190,6 +195,10 @@ core.register_entity(":__builtin:falling_node", {
pitch, yaw = 0, -math.pi/2 pitch, yaw = 0, -math.pi/2
elseif rot == 4 then elseif rot == 4 then
pitch, yaw = 0, math.pi pitch, yaw = 0, math.pi
elseif rot == 6 then
pitch, yaw = math.pi/2, 0
elseif rot == 7 then
pitch, yaw = -math.pi/2, math.pi
end end
else else
if rot == 1 then if rot == 1 then
@ -202,6 +211,10 @@ core.register_entity(":__builtin:falling_node", {
pitch, yaw = math.pi/2, math.pi pitch, yaw = math.pi/2, math.pi
elseif rot == 5 then elseif rot == 5 then
pitch, yaw = math.pi/2, 0 pitch, yaw = math.pi/2, 0
elseif rot == 6 then
pitch, yaw = math.pi, -math.pi/2
elseif rot == 7 then
pitch, yaw = 0, -math.pi/2
end end
end end
if def.drawtype == "signlike" then if def.drawtype == "signlike" then
@ -210,10 +223,20 @@ core.register_entity(":__builtin:falling_node", {
yaw = yaw + math.pi/2 yaw = yaw + math.pi/2
elseif rot == 1 then elseif rot == 1 then
yaw = yaw - math.pi/2 yaw = yaw - math.pi/2
elseif rot == 6 then
yaw = yaw - math.pi/2
pitch = pitch + math.pi
elseif rot == 7 then
yaw = yaw + math.pi/2
pitch = pitch + math.pi
end end
elseif def.drawtype == "mesh" or def.drawtype == "normal" or def.drawtype == "nodebox" then elseif def.drawtype == "mesh" or def.drawtype == "normal" or def.drawtype == "nodebox" then
if rot >= 0 and rot <= 1 then if rot == 0 or rot == 1 then
roll = roll + math.pi roll = roll + math.pi
elseif rot == 6 or rot == 7 then
if def.drawtype ~= "normal" then
roll = roll - math.pi/2
end
else else
yaw = yaw + math.pi yaw = yaw + math.pi
end end

@ -30,6 +30,11 @@ core.features = {
sound_params_start_time = true, sound_params_start_time = true,
physics_overrides_v2 = true, physics_overrides_v2 = true,
hud_def_type_field = true, hud_def_type_field = true,
random_state_restore = true,
after_order_expiry_registration = true,
wallmounted_rotate = true,
item_specific_pointabilities = true,
blocking_pointability_type = true,
} }
function core.has_feature(arg) function core.has_feature(arg)

@ -202,7 +202,40 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
elseif (def.paramtype2 == "wallmounted" or elseif (def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted") and not param2 then def.paramtype2 == "colorwallmounted") and not param2 then
local dir = vector.subtract(under, above) local dir = vector.subtract(under, above)
-- If you change this code, also change src/client/game.cpp
newnode.param2 = core.dir_to_wallmounted(dir) newnode.param2 = core.dir_to_wallmounted(dir)
if def.wallmounted_rotate_vertical and
(newnode.param2 == 0 or newnode.param2 == 1) then
local placer_pos = placer and placer:get_pos()
if placer_pos then
local pdir = {
x = above.x - placer_pos.x,
y = dir.y,
z = above.z - placer_pos.z
}
local rotate = false
if def.drawtype == "torchlike" then
if not ((pdir.x < 0 and pdir.z > 0) or
(pdir.x > 0 and pdir.z < 0)) then
rotate = true
end
if pdir.y > 0 then
rotate = not rotate
end
elseif def.drawtype == "signlike" then
if math.abs(pdir.x) < math.abs(pdir.z) then
rotate = true
end
else
if math.abs(pdir.x) > math.abs(pdir.z) then
rotate = true
end
end
if rotate then
newnode.param2 = newnode.param2 + 6
end
end
end
-- Calculate the direction for furnaces and chests and stuff -- Calculate the direction for furnaces and chests and stuff
elseif (def.paramtype2 == "facedir" or elseif (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir" or def.paramtype2 == "colorfacedir" or

@ -0,0 +1,246 @@
# textdomain: __builtin
Invalid parameters (see /help @1).=Nevalidaj parametroj (kontrolu /help @1)
Too many arguments, try using just /help <command>=Tro da parametroj, eble provu /help <ordono>
Available commands: @1=Nunaj ordonoj: @1
Use '/help <cmd>' to get more information, or '/help all' to list everything.=Uzu «/help <ordono>» por specifa informo, aŭ «/help all» por listigi ĉion.
Available commands:=Nunaj ordonoj:
Command not available: @1=Ordono neuzebla: @1
[all | privs | <cmd>] [-t]=[all | privs | <ordono>]
Get help for commands or list privileges (-t: output in chat)=Listigi helpon pri ordonoj («all» aŭ <ordono>) aŭ rajtoj («privs») (kun -t: presu en babilejo)
Available privileges:=Nunaj rajtoj:
Command=Ordono
Parameters=Parametroj
For more information, click on any entry in the list.=Por pliaj informoj, klaku ajnan listeron.
Double-click to copy the entry to the chat history.=Dufoje-klaku por kopii listeron al la babilejo.
Command: @1 @2=Ordono: @1 @2
Available commands: (see also: /help <cmd>)=Nunaj ordonoj: (vidu ankaŭ: /help <ordono>)
Close=Fermi
Privilege=Rajto
Description=Priskribo
Empty command.=Malplena ordono.
Invalid command: @1=Nevalida ordono: @1
Invalid command usage.=Nevalida ordonouzo.
(@1 s)= (@1 s)
Command execution took @1 s=Ruliĝo de ordono postulis @1 s
You don't have permission to run this command (missing privileges: @1).=Vi ne rajtas uzi tiun ĉi ordonon (mankataj rajtoj: @1)
Unable to get position of player @1.=Ne povis akiri pozicion de ludanto @1.
Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)=Nevalida loko-formo. Atendis: (x1,y1,z1) (x2,y2,z2)
<action>=<ago>
Show chat action (e.g., '/me orders a pizza' displays '<player name> orders a pizza')=Roli agon en la babilejo (ekz., «/me mendas picon» montras «<ludanto> mendas picon»)
Show the name of the server owner=Montri nomon de la serviladministranto
The administrator of this server is @1.=La adminstranto de tiu ĉi servilo estas @1.
There's no administrator named in the config file.=Estas neniu agordita administranto en la agordodosiero.
@1 does not have any privileges.=@1 havas neniun rajton.
Privileges of @1: @2=Rajtoj de @1: @2
[<name>]=[<nomo>]
Show privileges of yourself or another player=Montri rajtojn de vi aŭ alia ludanto
Player @1 does not exist.=Ludanto @1 ne ekzistas.
<privilege>=<rajto>
Return list of all online players with privilege=Listi ĉeretan ludanton kun specifa rajto
Invalid parameters (see /help haspriv).=Nevalidaj parametroj (vidu /help haspriv).
Unknown privilege!=Nekonata rajto!
No online player has the "@1" privilege.=Neniu ĉeretulo havas la rajton «@1».
Players online with the "@1" privilege: @2=Ĉeretuloj kun la rajto «@1»: @2
Your privileges are insufficient.=Viaj rajtoj ne sufiĉas.
Your privileges are insufficient. '@1' only allows you to grant: @2=Viaj rajtoj ne sufiĉas. «@1» sole lasas vin doni: @2
Unknown privilege: @1=Nekonata rajto: @1
@1 granted you privileges: @2=@1 donis al vi rajtojn: @2
<name> (<privilege> [, <privilege2> [<...>]] | all)=<nomo> (<rajto> [, <rajto2> [<...>]] | all)
Give privileges to player=Doni rajtojn al ludanto, aŭ aparte, aŭ ĉiun kune («all»)
Invalid parameters (see /help grant).=Nevalidaj parametroj (vidu /help grant).
<privilege> [, <privilege2> [<...>]] | all=<rajto> [, <rajto2> [<...>]] | all
Grant privileges to yourself=Doni rajtojn al vi mem, aŭ aparte, aŭ ĉiun kune («all»)
Invalid parameters (see /help grantme).=Nevalidaj parametroj (vidu /help grantme)
Your privileges are insufficient. '@1' only allows you to revoke: @2=Viaj rajtoj ne sufiĉas. «@1» sole lasas vin repreni: @2
Note: Cannot revoke in singleplayer: @1=Rimarko: Neeblas repreni rajton sole: @1
Note: Cannot revoke from admin: @1=Rimarko: Neeblas repreni rajnton de administranto: @1
No privileges were revoked.=Neniu rajto reprenita.
@1 revoked privileges from you: @2=@1 reprenis rajtojn de vi: @2
Remove privileges from player=Repreni rajtojn de ludanto, aŭ aparte, aŭ ĉiun kune («all»)
Invalid parameters (see /help revoke).=Nevalidaj parametroj (vidu /help revoke)
Revoke privileges from yourself=Repreni rajtojn de vi mem, aŭ aparte, aŭ ĉiun kune («all»)
Invalid parameters (see /help revokeme).=Nevalidaj parametroj (vidu /help revokeme).
<name> <password>=<nomo> <pasvorto>
Set player's password (sent unencrypted, thus insecure)=Agordi pasvorton de ludanto (sendute senĉifre, kaj tiel malsekure)
Name field required.=Nomo bezonata.
Your password was cleared by @1.=Via pasvorto forviŝiĝis de @1.
Password of player "@1" cleared.=Pasvorto de ludanto «@1» forviŝita.
Your password was set by @1.=Via pasvorto agordiĝis de @1.
Password of player "@1" set.=Pasvorto de ludanto «@1» agordita.
<name>=<nomo>
Set empty password for a player=Forviŝi pasvorton de ludanto
Reload authentication data=Reenlegi aŭtentigajn datumojn
Done.=Finite.
Failed.=Malsukcese..
Remove a player's data=Forviŝi datumojn de ludanto
Player "@1" removed.=Ludanto «@1» forigita.
No such player "@1" to remove.=Neniu ekzistanta ludanto «@1» forigebla.
Player "@1" is connected, cannot remove.=Ludanto «@1» ĉeretas, do ne forigeblas.
Unhandled remove_player return code @1.=Netraktata remove_player redonkodo @1.
Cannot teleport out of map bounds!=Neeblas teleporti ekstermonden!
Cannot get player with name @1.=Neeblas trovi ludanton nomitan «@1».
Cannot teleport, @1 is attached to an object!=Ne povas teleporti @1, ĝi estas ligita al objekto!
Teleporting @1 to @2.=Teleportante @1 al @2.
One does not teleport to oneself.=Oni kutimas ne teleportu al si mem.
Cannot get teleportee with name @1.=Ne trovis alteleportaton nomitan @1.
Cannot get target player with name @1.=Ne trovis celatan ludanton nomitan @1.
Teleporting @1 to @2 at @3.=Teleportante @1 al @2 ĉe @3.
<X>,<Y>,<Z> | <to_name> | <name> <X>,<Y>,<Z> | <name> <to_name>=<X>,<Y>,<Z> | <al_nomo> | <nomo> <X>,<Y>,<Z> | <nomo> <al_nomo>
Teleport to position or player=Teleportiĝi al pozicio aŭ ludanto
You don't have permission to teleport other players (missing privilege: @1).=Vi ne rajtas teleporti aliajn ludantojn (mankas rajto: @1).
([-n] <name> <value>) | <name>=([-n] <nomo> <valoro>) | <nomo>
Set or read server configuration setting=Agordi aŭ vidi servilan agordon
Failed. Cannot modify secure settings. Edit the settings file manually.=Malsukcese. Neeblas redakti sekurajn agordojn. Redaktu la agordodosieron permane.
Failed. Use '/set -n <name> <value>' to create a new setting.=Malsukcese. Uzu «/set -n <nomo> <valoro>» por krei novan agordon.
@1 @= @2=@1 @= @2
<not set>=<ne agordita>
Invalid parameters (see /help set).=Nevalidaj parametroj (vidu /help set).
Finished emerging @1 blocks in @2ms.=Finenlegis @1 monderojn dum @2ms.
emergeblocks update: @1/@2 blocks emerged (@3%)=emergeblocks ĝisdatigo: @1/@2 monderoj enlegitaj (@3%)
(here [<radius>]) | (<pos1> <pos2>)=(here [<radius>]) | (<pos1> <pos2>)
Load (or, if nonexistent, generate) map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Enlegi (aŭ, laŭnecese, krei) mondopecojn inter la pozicioj poz1 kaj pos2 (<poz1> kaj <poz2> devas esti inter krampoj)
Started emerge of area ranging from @1 to @2.=Ekenlegis mondpecojn inter @1 kaj @2.
Delete map blocks contained in area pos1 to pos2 (<pos1> and <pos2> must be in parentheses)=Forigi mondpecojn inter la pozicioj poz1 kaj poz2 (<poz1> kaj <poz2> devas esti inter krampoj)
Successfully cleared area ranging from @1 to @2.=Sukcese forigis mondpecojn inter @1 kaj @2.
Failed to clear one or more blocks in area.=Malsukcesis forigi unu aŭ pli da mondpecoj.
Resets lighting in the area between pos1 and pos2 (<pos1> and <pos2> must be in parentheses)=Reŝarĝas lumojn inter la pozicioj poz1 kaj poz2 (<poz1> kaj <poz2> devas esti inter krampoj)
Successfully reset light in the area ranging from @1 to @2.=Sukcesis reŝarĝi lumojn inter @1 kaj @2.
Failed to load one or more blocks in area.=Malsukcesis enlegante unu aŭ pli da monderoj.
List mods installed on the server=Listigi modifaĵojn instalitajn de la servilo.
No mods installed.=Neniu modifaĵ instalita.
Cannot give an empty item.=Neeblas doni neniun portaĵon.
Cannot give an unknown item.=Neeblas doni nekonatan portaĵon.
Giving 'ignore' is not allowed.=Doni «ignore» estas ne permesita.
@1 is not a known player.=@1 ne estas konata ludanto.
@1 partially added to inventory.=@1 parte enmetiĝis al portaĵujon.
@1 could not be added to inventory.=@1 ne enmetiĝis al portaĵujon.
@1 added to inventory.=@1 enmetiĝis al portaĵujon.
@1 partially added to inventory of @2.=@1 parte enmetiĝis al portaĵujon de @2.
@1 could not be added to inventory of @2.=@1 ne enmetiĝis al portaĵujon de @2.
@1 added to inventory of @2.=@1 enmetiĝis al portaĵujon de @2.
<name> <ItemString> [<count> [<wear>]]=<nomo> <PortaĵNomo> [<kvanto> <portu>]]
Give item to player=Doni portaĵon al ludanto
Name and ItemString required.=Nomo kaj PortaĵNomo postualtaj.
<ItemString> [<count> [<wear>]]=<PortaĵNomo> [<kvanto> [<portu>]]
Give item to yourself=Doni portaĵon al vi mem.
ItemString required.=PortaĵNomo postulata.
<EntityName> [<X>,<Y>,<Z>]=<EstaĵoNomo> [<X>, <Y>, <Z>]
Spawn entity at given (or your) position=Estigi estaĵon ĉe la donita (aŭ la via) pozicio
EntityName required.=EstaĵNomo postulata.
Unable to spawn entity, player is nil.=Ne povis estigi estaĵon, ĉar ludanto estas nil.
Cannot spawn an unknown entity.=Neeblas estigi nekonatan estaĵon.
Invalid parameters (@1).=Nevalidaj parametroj (@1).
@1 spawned.=@1 naskiĝis.
@1 failed to spawn.=@1 ne naskiĝis.
Destroy item in hand=Detrui portaĵon enmanan
Unable to pulverize, no player.=Ne povis detrui, neniu ludanto.
Unable to pulverize, no item in hand.=Ne povis detrui, ĉar nenio estas tenata.
An item was pulverized.=Portaĵo detruiĝis.
[<range>] [<seconds>] [<limit>]=[<intertempo>] [<sekundoj>] [<limo>]
Check who last touched a node or a node near it within the time specified by <seconds>. Default: range @= 0, seconds @= 86400 @= 24h, limit @= 5. Set <seconds> to inf for no time limit=Kontroli kiu lastafoje tuŝis monderon aŭ monderon proksiman al ĝi dum
Rollback functions are disabled.=Malfaraj funkcioj estas malŝaltitaj.
That limit is too high!=Tiu limo troaltas!
Checking @1 ...=Kontrolas @1…
Nobody has touched the specified location in @1 seconds.=Neniu tuŝis tiun lokon dum @1 sekundoj.
@1 @2 @3 -> @4 @5 seconds ago.=@1 @2 @3 -> @4 @5 sekundoj antaŭe.
Punch a node (range@=@1, seconds@=@2, limit@=@3).=Frapi monderon (intertempo@=@1, sekundoj@=@2, limo@=@3).
(<name> [<seconds>]) | (:<actor> [<seconds>])=(<nomo> [<sekundoj>]) | (:<aganto> [<sekundoj>])
Revert actions of a player. Default for <seconds> is 60. Set <seconds> to inf for no time limit=Malfari agojn de ludanto. Implicita valoro de <sekundoj> estas 60. Metu <sekundoj> kiel «inf» por neniu tempolimo
Invalid parameters. See /help rollback and /help rollback_check.=Nevalidaj parametroj. Vidu /help rollback kaj /help rollback_check.
Reverting actions of player '@1' since @2 seconds.=Malfaras agojn de ludanto «@1» ekde @2 sekundoj antaŭ nun.
Reverting actions of @1 since @2 seconds.=Malfaras agojn de @1 ekde @2 sekundoj antaŭ nun.
(log is too long to show)=(protokolo trolongas por montri)
Reverting actions succeeded.=Sukcesis malfari agojn.
Reverting actions FAILED.=MALsukcesis malfari agojn.
Show server status=Montri staton de servilon.
This command was disabled by a mod or game.=Tiun ordonon malŝaltis modifaĵo aŭ ludo.
[<0..23>:<0..59> | <0..24000>]=[<0..23>:<0..59> | <0..24000>]
Show or set time of day=Montri aŭ ŝanĝi la horon
Current time is @1:@2.=Nun estas @1:@2.
You don't have permission to run this command (missing privilege: @1).=Vi ne rajtas rulu tiun orodnon (mankata rajto: @1).
Invalid time (must be between 0 and 24000).=Nevalida tempo (estu inter 0 kaj 24000)
Time of day changed.=Horo ŝanĝita.
Invalid hour (must be between 0 and 23 inclusive).=Nevalida horo (estu inter 0 kaj 23, inkluzive).
Invalid minute (must be between 0 and 59 inclusive).=Nevalida minuto (estu inter 0 kaj 59, inkluzive).
Show day count since world creation=Montri pasitajn tagojn ekde mondokreo
Current day is @1.=Nuna tago estas @1.
[<delay_in_seconds> | -1] [-r] [<message>]=[<prokrasto_sekunde> | -1] [-r] [<mesaĝo>]
Shutdown server (-1 cancels a delayed shutdown, -r allows players to reconnect)=Malŝalti servilon (-1 nuligas planitan malŝalton, -r lasas ludantojn rekonektiĝi)
Server shutting down (operator request).=Servilo malŝaltiĝas (laŭ prizorganta peto)
Ban the IP of a player or show the ban list=Forbari la IP-adreson de ludanto, aŭ listigi forbaritojn
The ban list is empty.=La forbaritolisto malplenas.
Ban list: @1=Forbaritoj: @1
You cannot ban players in singleplayer!=Vi ne povas forbari ludantojn en unuludanta reĝimo!
Player is not online.=Ludanto ne ĉeretas.
Failed to ban player.=Malsukcesis forbari ludanton.
Banned @1.=Forbaris @1.
<name> | <IP_address>=<nomo> | <IP_adreso>
Remove IP ban belonging to a player/IP=Nuligi IP-forbaron de ludanto/IP
Failed to unban player/IP.=Malsukcesis nuligi forbaron de ludanto/IP-adreso
Unbanned @1.=Malforbaris @1.
<name> [<reason>]=<nomo> [<kialo>]
Kick a player=Elĵeti ludanton
Failed to kick player @1.=Malsukcesis elĵeti ludanton @1.
Kicked @1.=Elĵetis @1.
[full | quick]=[full | quick]
Clear all objects in world=Forigu ĉiujn lasitajn portaĵojn en la mondo
Invalid usage, see /help clearobjects.=Nevalida uzo, vidu /help clearobjects.
Clearing all objects. This may take a long time. You may experience a timeout. (by @1)=Forigante ĉiun lasitan portaĵon. Tio ĉi eble postulos longan tempon. Vi eble malkonektiĝos pro tempo-elĉerpo. (de @1)
Cleared all objects.=Forigis ĉiun lasitan portaĵon.
<name> <message>=<nomo> <mesaĝo>
Send a direct message to a player=Sendu rekte privatan mesaĝon al ludanto
Invalid usage, see /help msg.=Nevalida uzo, vidu /help msg.
The player @1 is not online.=La ludanto @1 ne ĉeretas.
DM from @1: @2=Privata mesaĝo de @1: @2
Message sent.=Mesaĝo sendita.
Get the last login time of a player or yourself=Vidi la lastan salutotempon de ludanto, aŭ vi mem
@1's last login time was @2.=Lasta salutotempo de @1 estas @2.
@1's last login time is unknown.=Lasta salutotempo de @1 estas nesciata.
Clear the inventory of yourself or another player=Malplenigi la portaĵujon de vi aŭ alia ludanto.
You don't have permission to clear another player's inventory (missing privilege: @1).=Vi ne rajtas malplenigi portaĵujon de alia ludanto (mankata rajto: @1).
@1 cleared your inventory.=@1 malplenigis vian portaĵujon.
Cleared @1's inventory.=Malplenigis portaĵujon de @1.
Player must be online to clear inventory!=Por malplenigi onian portaĵujon, tiu devas ĉereti!
Players can't be killed, damage has been disabled.=Nemortigeblas ludantoj, ĉar vundado estas malŝaltita.
Player @1 is not online.=Ludanto @1 ne ĉeretas.
You are already dead.=Vi jam estas mortinta.
@1 is already dead.=@1 estas mortinta.
@1 has been killed.=@1 estas murdita.
Kill player or yourself=Mortigi ludanton aŭ vin mem
@1 joined the game.=@1 aliĝis la ludon.
@1 left the game.=@1 foriris de la ludo.
@1 left the game (timed out).=@1 foriris de la ludo (tempo-elĉerpo)
(no description)=(neniu priskribo)
Can interact with things and modify the world=Povas interfaci kaj redakti la mondon
Can speak in chat=Povas paroli babileje
Can modify basic privileges (@1)=Povas redakti bazajn rajtojn (@1)
Can modify privileges=Povas redakti rajtojn
Can teleport self=Povas teleporti sin
Can teleport other players=Povas teleporti aliajn ludantojn
Can set the time of day using /time=Povas ŝanĝi la tempon per /time
Can do server maintenance stuff=Povas fari servilestrajn aferojn
Can bypass node protection in the world=Povas malatenti monderajn protektojn de la mondo
Can ban and unban players=Povas forbari kaj malforbari ludantojn
Can kick players=Povas elĵeti ludantojn
Can use /give and /giveme=Povas uzi /give kaj /giveme
Can use /setpassword and /clearpassword=Povas uzi /setpassword kaj /clearpassword
Can use fly mode=Povas ŝalti flugan reĝimon
Can use fast mode=Povas ŝalti rapidegan reĝimon
Can fly through solid nodes using noclip mode=Povas traflugi monderojn per trapasa reĝimo
Can use the rollback functionality=Povas uzi malfarajn funkciojn
Can enable wireframe=Povas ŝalti ĉirkaŭkadron
Unknown Item=Nekonata portaĵo
Air=Aero
Ignore=Malatenti
You can't place 'ignore' nodes!=Vi ne povas meti «malatentajn» monderojn!
print [<filter>] | dump [<filter>] | save [<format> [<filter>]] | reset=print [<filtrilo>] | dump [<filtrilo>] | save [<formo> [<filtrilo>]] | reset
Handle the profiler and profiling data=Trakti la analizilon kaj analizajn datumojn
Statistics written to action log.=Analizoj skribitaj al agoprotokolo.
Statistics were reset.=Analizoj forviŝitaj.
Usage: @1=Uzado: @1
Format can be one of txt, csv, lua, json, json_pretty (structures may be subject to change).=Formo povas estas txt, csv, lua, json, aŭ json_pretty (struktuoj eble iam ŝanĝiĝos).
Values below show absolute/relative times spend per server step by the instrumented function.=Valoroj subaj montras la malrelativan/relativan tempon pasigitan de la servilo je ĉiu paŝo de la funkcio.
A total of @1 sample(s) were taken.=Sume @1 ekzemplero(j) konserviĝis.
The output is limited to '@1'.=La eligo estas limigita al «@1».
Saving of profile failed: @1=Konservado de profilo malsukcesis: @1
Profile saved to @1=Profilo konservita al @1

@ -312,7 +312,7 @@ local function check_requirements(name, requires)
end end
local video_driver = core.get_active_driver() local video_driver = core.get_active_driver()
local shaders_support = video_driver == "opengl" or video_driver == "ogles2" local shaders_support = video_driver == "opengl" or video_driver == "opengl3" or video_driver == "ogles2"
local special = { local special = {
android = PLATFORM == "Android", android = PLATFORM == "Android",
desktop = PLATFORM ~= "Android", desktop = PLATFORM ~= "Android",
@ -608,6 +608,16 @@ local function get_formspec(dialogdata)
end end
-- On Android, closing the app via the "Recents screen" won't result in a clean
-- exit, discarding any setting changes made by the user.
-- To avoid that, we write the settings file in more cases on Android.
function write_settings_early()
if PLATFORM == "Android" then
core.settings:write()
end
end
local function buttonhandler(this, fields) local function buttonhandler(this, fields)
local dialogdata = this.data local dialogdata = this.data
dialogdata.leftscroll = core.explode_scrollbar_event(fields.leftscroll).value or dialogdata.leftscroll dialogdata.leftscroll = core.explode_scrollbar_event(fields.leftscroll).value or dialogdata.leftscroll
@ -622,12 +632,15 @@ local function buttonhandler(this, fields)
if fields.show_technical_names ~= nil then if fields.show_technical_names ~= nil then
local value = core.is_yes(fields.show_technical_names) local value = core.is_yes(fields.show_technical_names)
core.settings:set_bool("show_technical_names", value) core.settings:set_bool("show_technical_names", value)
write_settings_early()
return true return true
end end
if fields.show_advanced ~= nil then if fields.show_advanced ~= nil then
local value = core.is_yes(fields.show_advanced) local value = core.is_yes(fields.show_advanced)
core.settings:set_bool("show_advanced", value) core.settings:set_bool("show_advanced", value)
write_settings_early()
local suggested_page_id = update_filtered_pages(dialogdata.query) local suggested_page_id = update_filtered_pages(dialogdata.query)
@ -672,12 +685,15 @@ local function buttonhandler(this, fields)
for i, comp in ipairs(dialogdata.components) do for i, comp in ipairs(dialogdata.components) do
if comp.on_submit and comp:on_submit(fields, this) then if comp.on_submit and comp:on_submit(fields, this) then
write_settings_early()
-- Clear components so they regenerate -- Clear components so they regenerate
dialogdata.components = nil dialogdata.components = nil
return true return true
end end
if comp.setting and fields["reset_" .. i] then if comp.setting and fields["reset_" .. i] then
core.settings:remove(comp.setting.name) core.settings:remove(comp.setting.name)
write_settings_early()
-- Clear components so they regenerate -- Clear components so they regenerate
dialogdata.components = nil dialogdata.components = nil

@ -440,7 +440,6 @@ enable_raytraced_culling (Enable Raytraced Culling) bool true
# Shaders allow advanced visual effects and may increase performance on some video # Shaders allow advanced visual effects and may increase performance on some video
# cards. # cards.
# This only works with the OpenGL video backend.
# #
# Requires: shaders_support # Requires: shaders_support
enable_shaders (Shaders) bool true enable_shaders (Shaders) bool true
@ -649,7 +648,7 @@ mute_sound (Mute sound) bool false
[*User Interfaces] [*User Interfaces]
# Set the language. Leave empty to use the system language. # Set the language. By default, the system language is used.
# A restart is required after changing this. # A restart is required after changing this.
language (Language) enum ,be,bg,ca,cs,da,de,el,en,eo,es,et,eu,fi,fr,gd,gl,hu,id,it,ja,jbo,kk,ko,lt,lv,ms,nb,nl,nn,pl,pt,pt_BR,ro,ru,sk,sl,sr_Cyrl,sr_Latn,sv,sw,tr,uk,vi,zh_CN,zh_TW language (Language) enum ,be,bg,ca,cs,da,de,el,en,eo,es,et,eu,fi,fr,gd,gl,hu,id,it,ja,jbo,kk,ko,lt,lv,ms,nb,nl,nn,pl,pt,pt_BR,ro,ru,sk,sl,sr_Cyrl,sr_Latn,sv,sw,tr,uk,vi,zh_CN,zh_TW
@ -1798,8 +1797,8 @@ shader_path (Shader path) path
# The rendering back-end. # The rendering back-end.
# Note: A restart is required after changing this! # Note: A restart is required after changing this!
# OpenGL is the default for desktop, and OGLES2 for Android. # OpenGL is the default for desktop, and OGLES2 for Android.
# Shaders are supported by OpenGL and OGLES2 (experimental). # Shaders are supported by everything but OGLES1.
video_driver (Video driver) enum ,opengl,ogles1,ogles2 video_driver (Video driver) enum ,opengl,opengl3,ogles1,ogles2
# Distance in nodes at which transparency depth sorting is enabled # Distance in nodes at which transparency depth sorting is enabled
# Use this to limit the performance impact of transparency depth sorting # Use this to limit the performance impact of transparency depth sorting
@ -1827,11 +1826,6 @@ mesh_generation_interval (Mapblock mesh generation delay) int 0 0 50
# Value of 0 (default) will let Minetest autodetect the number of available threads. # Value of 0 (default) will let Minetest autodetect the number of available threads.
mesh_generation_threads (Mapblock mesh generation threads) int 0 0 8 mesh_generation_threads (Mapblock mesh generation threads) int 0 0 8
# Size of the MapBlock cache of the mesh generator. Increasing this will
# increase the cache hit %, reducing the data being copied from the main
# thread, thus reducing jitter.
meshgen_block_cache_size (Mapblock mesh generator's MapBlock cache size in MB) int 20 0 1000
# True = 256 # True = 256
# False = 128 # False = 128
# Usable to make minimap smoother on slower machines. # Usable to make minimap smoother on slower machines.

@ -14,7 +14,7 @@ centroid varying vec2 varTexCoord;
// smoothstep - squared // smoothstep - squared
float smstsq(float f) float smstsq(float f)
{ {
f = f * f * (3 - 2 * f); f = f * f * (3. - 2. * f);
return f; return f;
} }

@ -14,7 +14,7 @@ centroid varying vec2 varTexCoord;
// smoothstep - squared // smoothstep - squared
float smstsq(float f) float smstsq(float f)
{ {
f = f * f * (3 - 2 * f); f = f * f * (3. - 2. * f);
return f; return f;
} }

@ -12,13 +12,13 @@ void main (void)
//texture sampling rate //texture sampling rate
const float step = 1.0 / 256.0; const float step = 1.0 / 256.0;
float tl = texture2D(normalTexture, vec2(uv.x - step, uv.y + step)).r; float tl = texture2D(normalTexture, vec2(uv.x - step, uv.y + step)).r;
float t = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r; float t = texture2D(normalTexture, vec2(uv.x, uv.y + step)).r;
float tr = texture2D(normalTexture, vec2(uv.x + step, uv.y + step)).r; float tr = texture2D(normalTexture, vec2(uv.x + step, uv.y + step)).r;
float r = texture2D(normalTexture, vec2(uv.x + step, uv.y)).r; float r = texture2D(normalTexture, vec2(uv.x + step, uv.y )).r;
float br = texture2D(normalTexture, vec2(uv.x + step, uv.y - step)).r; float br = texture2D(normalTexture, vec2(uv.x + step, uv.y - step)).r;
float b = texture2D(normalTexture, vec2(uv.x, uv.y - step)).r; float b = texture2D(normalTexture, vec2(uv.x, uv.y - step)).r;
float bl = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r; float bl = texture2D(normalTexture, vec2(uv.x - step, uv.y - step)).r;
float l = texture2D(normalTexture, vec2(uv.x - step, uv.y)).r; float l = texture2D(normalTexture, vec2(uv.x - step, uv.y )).r;
float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl); float dX = (tr + 2.0 * r + br) - (tl + 2.0 * l + bl);
float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr); float dY = (bl + 2.0 * b + br) - (tl + 2.0 * t + tr);
vec4 bump = vec4 (normalize(vec3 (dX, dY, 0.1)),1.0); vec4 bump = vec4 (normalize(vec3 (dX, dY, 0.1)),1.0);

@ -1,7 +1,7 @@
uniform sampler2D baseTexture; uniform sampler2D baseTexture;
uniform vec3 dayLight; uniform vec3 dayLight;
uniform vec4 skyBgColor; uniform vec4 fogColor;
uniform float fogDistance; uniform float fogDistance;
uniform float fogShadingParameter; uniform float fogShadingParameter;
uniform vec3 eyePosition; uniform vec3 eyePosition;
@ -161,7 +161,7 @@ float getPenumbraRadius(sampler2D shadowsampler, vec2 smTexCoord, float realDist
float depth_to_blur = f_shadowfar / SOFTSHADOWRADIUS / xyPerspectiveBias0; float depth_to_blur = f_shadowfar / SOFTSHADOWRADIUS / xyPerspectiveBias0;
if (depth > 0.0 && f_normal_length > 0.0) if (depth > 0.0 && f_normal_length > 0.0)
// 5 is empirical factor that controls how fast shadow loses sharpness // 5 is empirical factor that controls how fast shadow loses sharpness
sharpness_factor = clamp(5 * depth * depth_to_blur, 0.0, 1.0); sharpness_factor = clamp(5.0 * depth * depth_to_blur, 0.0, 1.0);
depth = 0.0; depth = 0.0;
float world_to_texture = xyPerspectiveBias1 / perspective_factor / perspective_factor float world_to_texture = xyPerspectiveBias1 / perspective_factor / perspective_factor
@ -448,7 +448,7 @@ void main(void)
// Note: clarity = (1 - fogginess) // Note: clarity = (1 - fogginess)
float clarity = clamp(fogShadingParameter float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(skyBgColor, col, clarity); col = mix(fogColor, col, clarity);
col = vec4(col.rgb, base.a); col = vec4(col.rgb, base.a);
gl_FragData[0] = col; gl_FragData[0] = col;

@ -242,7 +242,7 @@ void main(void)
if (f_normal_length > 0.0) { if (f_normal_length > 0.0) {
nNormal = normalize(vNormal); nNormal = normalize(vNormal);
cosLight = max(1e-5, dot(nNormal, -v_LightDirection)); cosLight = max(1e-5, dot(nNormal, -v_LightDirection));
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5); float sinLight = pow(1.0 - pow(cosLight, 2.0), 0.5);
normalOffsetScale = 2.0 * pFactor * pFactor * sinLight * min(f_shadowfar, 500.0) / normalOffsetScale = 2.0 * pFactor * pFactor * sinLight * min(f_shadowfar, 500.0) /
xyPerspectiveBias1 / f_textureresolution; xyPerspectiveBias1 / f_textureresolution;
z_bias = 1.0 * sinLight / cosLight; z_bias = 1.0 * sinLight / cosLight;
@ -250,7 +250,7 @@ void main(void)
else { else {
nNormal = vec3(0.0); nNormal = vec3(0.0);
cosLight = clamp(dot(v_LightDirection, normalize(vec3(v_LightDirection.x, 0.0, v_LightDirection.z))), 1e-2, 1.0); cosLight = clamp(dot(v_LightDirection, normalize(vec3(v_LightDirection.x, 0.0, v_LightDirection.z))), 1e-2, 1.0);
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5); float sinLight = pow(1.0 - pow(cosLight, 2.0), 0.5);
normalOffsetScale = 0.0; normalOffsetScale = 0.0;
z_bias = 3.6e3 * sinLight / cosLight; z_bias = 3.6e3 * sinLight / cosLight;
} }

@ -1,7 +1,7 @@
uniform sampler2D baseTexture; uniform sampler2D baseTexture;
uniform vec3 dayLight; uniform vec3 dayLight;
uniform vec4 skyBgColor; uniform vec4 fogColor;
uniform float fogDistance; uniform float fogDistance;
uniform float fogShadingParameter; uniform float fogShadingParameter;
uniform vec3 eyePosition; uniform vec3 eyePosition;
@ -449,7 +449,7 @@ void main(void)
// Note: clarity = (1 - fogginess) // Note: clarity = (1 - fogginess)
float clarity = clamp(fogShadingParameter float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0); - fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
col = mix(skyBgColor, col, clarity); col = mix(fogColor, col, clarity);
col = vec4(col.rgb, base.a); col = vec4(col.rgb, base.a);
gl_FragData[0] = col; gl_FragData[0] = col;

@ -147,7 +147,7 @@ void main(void)
if (f_normal_length > 0.0) { if (f_normal_length > 0.0) {
nNormal = normalize(vNormal); nNormal = normalize(vNormal);
cosLight = max(1e-5, dot(nNormal, -v_LightDirection)); cosLight = max(1e-5, dot(nNormal, -v_LightDirection));
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5); float sinLight = pow(1.0 - pow(cosLight, 2.0), 0.5);
normalOffsetScale = 0.1 * pFactor * pFactor * sinLight * min(f_shadowfar, 500.0) / normalOffsetScale = 0.1 * pFactor * pFactor * sinLight * min(f_shadowfar, 500.0) /
xyPerspectiveBias1 / f_textureresolution; xyPerspectiveBias1 / f_textureresolution;
z_bias = 1e3 * sinLight / cosLight * (0.5 + f_textureresolution / 1024.0); z_bias = 1e3 * sinLight / cosLight * (0.5 + f_textureresolution / 1024.0);
@ -155,7 +155,7 @@ void main(void)
else { else {
nNormal = vec3(0.0); nNormal = vec3(0.0);
cosLight = clamp(dot(v_LightDirection, normalize(vec3(v_LightDirection.x, 0.0, v_LightDirection.z))), 1e-2, 1.0); cosLight = clamp(dot(v_LightDirection, normalize(vec3(v_LightDirection.x, 0.0, v_LightDirection.z))), 1e-2, 1.0);
float sinLight = pow(1 - pow(cosLight, 2.0), 0.5); float sinLight = pow(1.0 - pow(cosLight, 2.0), 0.5);
normalOffsetScale = 0.0; normalOffsetScale = 0.0;
z_bias = 3.6e3 * sinLight / cosLight; z_bias = 3.6e3 * sinLight / cosLight;
} }

@ -9,8 +9,8 @@ due to limited capabilities of common devices. What can be done is described bel
While you're playing the game normally (that is, no menu or inventory is While you're playing the game normally (that is, no menu or inventory is
shown), the following controls are available: shown), the following controls are available:
* Look around: touch screen and slide finger * Look around: touch screen and slide finger
* Tap: Place a node * Tap: Place a node, punch an object or use the selected item (default)
* Long tap: Dig node or use the held item * Long tap: Dig a node or use the selected item (default)
* Press back: Pause menu * Press back: Pause menu
* Touch buttons: Press button * Touch buttons: Press button
* Buttons: * Buttons:

@ -8,7 +8,7 @@ Introduction
** WARNING: The client API is currently unstable, and may break/change without warning. ** ** WARNING: The client API is currently unstable, and may break/change without warning. **
Content and functionality can be added to Minetest 0.4.15-dev+ by using Lua Content and functionality can be added to Minetest by using Lua
scripting in run-time loaded mods. scripting in run-time loaded mods.
A mod is a self-contained bunch of scripts, textures and other related A mod is a self-contained bunch of scripts, textures and other related
@ -62,7 +62,7 @@ Generic:
In a run-in-place version (e.g. the distributed windows version): In a run-in-place version (e.g. the distributed windows version):
* `minetest-0.4.x/clientmods/` (User-installed mods) * `minetest/clientmods/` (User-installed mods)
On an installed version on Linux: On an installed version on Linux:
@ -185,15 +185,9 @@ Examples of sound parameter tables:
pos = {x = 1, y = 2, z = 3}, pos = {x = 1, y = 2, z = 3},
gain = 1.0, -- default gain = 1.0, -- default
} }
-- Play connected to an object, looped
{
object = <an ObjectRef>,
gain = 1.0, -- default
loop = true,
}
``` ```
Looped sounds must either be connected to an object or played locationless. Looped sounds must be played locationless.
### SimpleSoundSpec ### SimpleSoundSpec
* e.g. `""` * e.g. `""`
@ -220,387 +214,20 @@ For helper functions see "Vector helpers".
Flag Specifier Format Flag Specifier Format
--------------------- ---------------------
Flags using the standardized flag specifier format can be specified in either of
two ways, by string or table.
The string format is a comma-delimited set of flag names; whitespace and Refer to `lua_api.md`.
unrecognized flag fields are ignored. Specifying a flag in the string sets the
flag, and specifying a flag prefixed by the string `"no"` explicitly
clears the flag from whatever the default may be.
In addition to the standard string flag format, the schematic flags field can
also be a table of flag names to boolean values representing whether or not the
flag is set. Additionally, if a field with the flag name prefixed with `"no"`
is present, mapped to a boolean of any value, the specified flag is unset.
E.g. A flag field of value
```lua
{place_center_x = true, place_center_y=false, place_center_z=true}
```
is equivalent to
```lua
{place_center_x = true, noplace_center_y=true, place_center_z=true}
```
which is equivalent to
```lua
"place_center_x, noplace_center_y, place_center_z"
```
or even
```lua
"place_center_x, place_center_z"
```
since, by default, no schematic attributes are set.
Formspec Formspec
-------- --------
Formspec defines a menu. It is a string, with a somewhat strange format. Formspec defines a menu. It is a string, with a somewhat strange format.
Spaces and newlines can be inserted between the blocks, as is used in the For details, refer to `lua_api.md`.
examples.
### Examples
#### Chest
size[8,9]
list[context;main;0,0;8,4;]
list[current_player;main;0,5;8,4;]
#### Furnace
size[8,9]
list[context;fuel;2,3;1,1;]
list[context;src;2,1;1,1;]
list[context;dst;5,1;2,2;]
list[current_player;main;0,5;8,4;]
#### Minecraft-like player inventory
size[8,7.5]
image[1,0.6;1,2;player.png]
list[current_player;main;0,3.5;8,4;]
list[current_player;craft;3,0;3,3;]
list[current_player;craftpreview;7,1;1,1;]
### Elements
#### `size[<W>,<H>,<fixed_size>]`
* Define the size of the menu in inventory slots
* `fixed_size`: `true`/`false` (optional)
* deprecated: `invsize[<W>,<H>;]`
#### `container[<X>,<Y>]`
* Start of a container block, moves all physical elements in the container by (X, Y)
* Must have matching container_end
* Containers can be nested, in which case the offsets are added
(child containers are relative to parent containers)
#### `container_end[]`
* End of a container, following elements are no longer relative to this container
#### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;]`
* Show an inventory list
#### `list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;<starting item index>]`
* Show an inventory list
#### `listring[<inventory location>;<list name>]`
* Allows to create a ring of inventory lists
* Shift-clicking on items in one element of the ring
will send them to the next inventory list inside the ring
* The first occurrence of an element inside the ring will
determine the inventory where items will be sent to
#### `listring[]`
* Shorthand for doing `listring[<inventory location>;<list name>]`
for the last two inventory lists added by list[...]
#### `listcolors[<slot_bg_normal>;<slot_bg_hover>]`
* Sets background color of slots as `ColorString`
* Sets background color of slots on mouse hovering
#### `listcolors[<slot_bg_normal>;<slot_bg_hover>;<slot_border>]`
* Sets background color of slots as `ColorString`
* Sets background color of slots on mouse hovering
* Sets color of slots border
#### `listcolors[<slot_bg_normal>;<slot_bg_hover>;<slot_border>;<tooltip_bgcolor>;<tooltip_fontcolor>]`
* Sets background color of slots as `ColorString`
* Sets background color of slots on mouse hovering
* Sets color of slots border
* Sets default background color of tooltips
* Sets default font color of tooltips
#### `tooltip[<gui_element_name>;<tooltip_text>;<bgcolor>,<fontcolor>]`
* Adds tooltip for an element
* `<bgcolor>` tooltip background color as `ColorString` (optional)
* `<fontcolor>` tooltip font color as `ColorString` (optional)
#### `image[<X>,<Y>;<W>,<H>;<texture name>]`
* Show an image
* Position and size units are inventory slots
#### `item_image[<X>,<Y>;<W>,<H>;<item name>]`
* Show an inventory image of registered item/node
* Position and size units are inventory slots
#### `bgcolor[<color>;<fullscreen>]`
* Sets background color of formspec as `ColorString`
* If `true`, the background color is drawn fullscreen (does not affect the size of the formspec)
#### `background[<X>,<Y>;<W>,<H>;<texture name>]`
* Use a background. Inventory rectangles are not drawn then.
* Position and size units are inventory slots
* Example for formspec 8x4 in 16x resolution: image shall be sized
8 times 16px times 4 times 16px.
#### `background[<X>,<Y>;<W>,<H>;<texture name>;<auto_clip>]`
* Use a background. Inventory rectangles are not drawn then.
* Position and size units are inventory slots
* Example for formspec 8x4 in 16x resolution:
image shall be sized 8 times 16px times 4 times 16px
* If `true` the background is clipped to formspec size
(`x` and `y` are used as offset values, `w` and `h` are ignored)
#### `pwdfield[<X>,<Y>;<W>,<H>;<name>;<label>]`
* Textual password style field; will be sent to server when a button is clicked
* When enter is pressed in field, fields.key_enter_field will be sent with the name
of this field.
* `x` and `y` position the field relative to the top left of the menu
* `w` and `h` are the size of the field
* Fields are a set height, but will be vertically centered on `h`
* Position and size units are inventory slots
* `name` is the name of the field as returned in fields to `on_receive_fields`
* `label`, if not blank, will be text printed on the top left above the field
* See field_close_on_enter to stop enter closing the formspec
#### `field[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]`
* Textual field; will be sent to server when a button is clicked
* When enter is pressed in field, fields.key_enter_field will be sent with the name
of this field.
* `x` and `y` position the field relative to the top left of the menu
* `w` and `h` are the size of the field
* Fields are a set height, but will be vertically centered on `h`
* Position and size units are inventory slots
* `name` is the name of the field as returned in fields to `on_receive_fields`
* `label`, if not blank, will be text printed on the top left above the field
* `default` is the default value of the field
* `default` may contain variable references such as `${text}'` which
will fill the value from the metadata value `text`
* **Note**: no extra text or more than a single variable is supported ATM.
* See field_close_on_enter to stop enter closing the formspec
#### `field[<name>;<label>;<default>]`
* As above, but without position/size units
* When enter is pressed in field, fields.key_enter_field will be sent with the name
of this field.
* Special field for creating simple forms, such as sign text input
* Must be used without a `size[]` element
* A "Proceed" button will be added automatically
* See field_close_on_enter to stop enter closing the formspec
#### `field_close_on_enter[<name>;<close_on_enter>]`
* <name> is the name of the field
* if <close_on_enter> is false, pressing enter in the field will submit the form but not close it
* defaults to true when not specified (ie: no tag for a field)
#### `textarea[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]`
* Same as fields above, but with multi-line input
#### `label[<X>,<Y>;<label>]`
* `x` and `y` work as per field
* `label` is the text on the label
* Position and size units are inventory slots
#### `vertlabel[<X>,<Y>;<label>]`
* Textual label drawn vertically
* `x` and `y` work as per field
* `label` is the text on the label
* Position and size units are inventory slots
#### `button[<X>,<Y>;<W>,<H>;<name>;<label>]`
* Clickable button. When clicked, fields will be sent.
* `x`, `y` and `name` work as per field
* `w` and `h` are the size of the button
* Fixed button height. It will be vertically centered on `h`
* `label` is the text on the button
* Position and size units are inventory slots
#### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
* `x`, `y`, `w`, `h`, and `name` work as per button
* `texture name` is the filename of an image
* Position and size units are inventory slots
#### `image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>;<noclip>;<drawborder>;<pressed texture name>]`
* `x`, `y`, `w`, `h`, and `name` work as per button
* `texture name` is the filename of an image
* Position and size units are inventory slots
* `noclip=true` means the image button doesn't need to be within specified formsize
* `drawborder`: draw button border or not
* `pressed texture name` is the filename of an image on pressed state
#### `item_image_button[<X>,<Y>;<W>,<H>;<item name>;<name>;<label>]`
* `x`, `y`, `w`, `h`, `name` and `label` work as per button
* `item name` is the registered name of an item/node,
tooltip will be made out of its description
to override it use tooltip element
* Position and size units are inventory slots
#### `button_exit[<X>,<Y>;<W>,<H>;<name>;<label>]`
* When clicked, fields will be sent and the form will quit.
#### `image_button_exit[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]`
* When clicked, fields will be sent and the form will quit.
#### `textlist[<X>,<Y>;<W>,<H>;<name>;<listelem 1>,<listelem 2>,...,<listelem n>]`
* Scrollable item list showing arbitrary text elements
* `x` and `y` position the itemlist relative to the top left of the menu
* `w` and `h` are the size of the itemlist
* `name` fieldname sent to server on doubleclick value is current selected element
* `listelements` can be prepended by #color in hexadecimal format RRGGBB (only),
* if you want a listelement to start with "#" write "##".
#### `textlist[<X>,<Y>;<W>,<H>;<name>;<listelem 1>,<listelem 2>,...,<listelem n>;<selected idx>;<transparent>]`
* Scrollable itemlist showing arbitrary text elements
* `x` and `y` position the item list relative to the top left of the menu
* `w` and `h` are the size of the item list
* `name` fieldname sent to server on doubleclick value is current selected element
* `listelements` can be prepended by #RRGGBB (only) in hexadecimal format
* if you want a listelement to start with "#" write "##"
* Index to be selected within textlist
* `true`/`false`: draw transparent background
* See also `minetest.explode_textlist_event` (main menu: `engine.explode_textlist_event`)
#### `tabheader[<X>,<Y>;<name>;<caption 1>,<caption 2>,...,<caption n>;<current_tab>;<transparent>;<draw_border>]`
* Show a tab**header** at specific position (ignores formsize)
* `x` and `y` position the itemlist relative to the top left of the menu
* `name` fieldname data is transferred to Lua
* `caption 1`...: name shown on top of tab
* `current_tab`: index of selected tab 1...
* `transparent` (optional): show transparent
* `draw_border` (optional): draw border
#### `box[<X>,<Y>;<W>,<H>;<color>]`
* Simple colored semitransparent box
* `x` and `y` position the box relative to the top left of the menu
* `w` and `h` are the size of box
* `color` is color specified as a `ColorString`
#### `dropdown[<X>,<Y>;<W>;<name>;<item 1>,<item 2>, ...,<item n>;<selected idx>]`
* Show a dropdown field
* **Important note**: There are two different operation modes:
1. handle directly on change (only changed dropdown is submitted)
2. read the value on pressing a button (all dropdown values are available)
* `x` and `y` position of dropdown
* Width of dropdown
* Fieldname data is transferred to Lua
* Items to be shown in dropdown
* Index of currently selected dropdown item
#### `checkbox[<X>,<Y>;<name>;<label>;<selected>]`
* Show a checkbox
* `x` and `y`: position of checkbox
* `name` fieldname data is transferred to Lua
* `label` to be shown left of checkbox
* `selected` (optional): `true`/`false`
#### `scrollbar[<X>,<Y>;<W>,<H>;<orientation>;<name>;<value>]`
* Show a scrollbar
* There are two ways to use it:
1. handle the changed event (only changed scrollbar is available)
2. read the value on pressing a button (all scrollbars are available)
* `x` and `y`: position of trackbar
* `w` and `h`: width and height
* `orientation`: `vertical`/`horizontal`
* Fieldname data is transferred to Lua
* Value this trackbar is set to (`0`-`1000`)
* See also `minetest.explode_scrollbar_event` (main menu: `engine.explode_scrollbar_event`)
#### `table[<X>,<Y>;<W>,<H>;<name>;<cell 1>,<cell 2>,...,<cell n>;<selected idx>]`
* Show scrollable table using options defined by the previous `tableoptions[]`
* Displays cells as defined by the previous `tablecolumns[]`
* `x` and `y`: position the itemlist relative to the top left of the menu
* `w` and `h` are the size of the itemlist
* `name`: fieldname sent to server on row select or doubleclick
* `cell 1`...`cell n`: cell contents given in row-major order
* `selected idx`: index of row to be selected within table (first row = `1`)
* See also `minetest.explode_table_event` (main menu: `engine.explode_table_event`)
#### `tableoptions[<opt 1>;<opt 2>;...]`
* Sets options for `table[]`
* `color=#RRGGBB`
* default text color (`ColorString`), defaults to `#FFFFFF`
* `background=#RRGGBB`
* table background color (`ColorString`), defaults to `#000000`
* `border=<true/false>`
* should the table be drawn with a border? (default: `true`)
* `highlight=#RRGGBB`
* highlight background color (`ColorString`), defaults to `#466432`
* `highlight_text=#RRGGBB`
* highlight text color (`ColorString`), defaults to `#FFFFFF`
* `opendepth=<value>`
* all subtrees up to `depth < value` are open (default value = `0`)
* only useful when there is a column of type "tree"
#### `tablecolumns[<type 1>,<opt 1a>,<opt 1b>,...;<type 2>,<opt 2a>,<opt 2b>;...]`
* Sets columns for `table[]`
* Types: `text`, `image`, `color`, `indent`, `tree`
* `text`: show cell contents as text
* `image`: cell contents are an image index, use column options to define images
* `color`: cell contents are a ColorString and define color of following cell
* `indent`: cell contents are a number and define indentation of following cell
* `tree`: same as indent, but user can open and close subtrees (treeview-like)
* Column options:
* `align=<value>`
* for `text` and `image`: content alignment within cells.
Available values: `left` (default), `center`, `right`, `inline`
* `width=<value>`
* for `text` and `image`: minimum width in em (default: `0`)
* for `indent` and `tree`: indent width in em (default: `1.5`)
* `padding=<value>`: padding left of the column, in em (default `0.5`).
Exception: defaults to 0 for indent columns
* `tooltip=<value>`: tooltip text (default: empty)
* `image` column options:
* `0=<value>` sets image for image index 0
* `1=<value>` sets image for image index 1
* `2=<value>` sets image for image index 2
* and so on; defined indices need not be contiguous empty or
non-numeric cells are treated as `0`.
* `color` column options:
* `span=<value>`: number of following columns to affect (default: infinite)
**Note**: do _not_ use an element name starting with `key_`; those names are reserved to
pass key press events to formspec!
Spatial Vectors Spatial Vectors
--------------- ---------------
* `vector.new(a[, b, c])`: returns a vector:
* A copy of `a` if `a` is a vector.
* `{x = a, y = b, z = c}`, if all `a, b, c` are defined
* `vector.direction(p1, p2)`: returns a vector
* `vector.distance(p1, p2)`: returns a number
* `vector.length(v)`: returns a number
* `vector.normalize(v)`: returns a vector
* `vector.floor(v)`: returns a vector, each dimension rounded down
* `vector.round(v)`: returns a vector, each dimension rounded to nearest int
* `vector.apply(v, func)`: returns a vector
* `vector.combine(v, w, func)`: returns a vector
* `vector.equals(v1, v2)`: returns a boolean
For the following functions `x` can be either a vector or a number: Refer to `lua_api.md`.
* `vector.add(v, x)`: returns a vector
* `vector.subtract(v, x)`: returns a vector
* `vector.multiply(v, x)`: returns a scaled vector or Schur product
* `vector.divide(v, x)`: returns a scaled vector or Schur quotient
Helper functions Helper functions
---------------- ----------------
@ -770,6 +397,10 @@ Call these functions only at load time!
* `minetest.after(time, func, ...)` * `minetest.after(time, func, ...)`
* Call the function `func` after `time` seconds, may be fractional * Call the function `func` after `time` seconds, may be fractional
* Optional: Variable number of arguments that are passed to `func` * Optional: Variable number of arguments that are passed to `func`
* Jobs set for earlier times are executed earlier. If multiple jobs expire
at exactly the same time, then they expire in the order in which they were
registered. This basically just applies to jobs registered on the same
step with the exact same delay.
* `minetest.get_us_time()` * `minetest.get_us_time()`
* Returns time with microsecond precision. May not return wall time. * Returns time with microsecond precision. May not return wall time.
* `minetest.get_timeofday()` * `minetest.get_timeofday()`
@ -1152,6 +783,10 @@ Methods:
* See [`HUD definition`](#hud-definition-hud_add-hud_get) * See [`HUD definition`](#hud-definition-hud_add-hud_get)
* `hud_get(id)` * `hud_get(id)`
* returns the [`definition`](#hud-definition-hud_add-hud_get) of the HUD with that ID number or `nil`, if non-existent. * returns the [`definition`](#hud-definition-hud_add-hud_get) of the HUD with that ID number or `nil`, if non-existent.
* `hud_get_all()`:
* Returns a table in the form `{ [id] = HUD definition, [id] = ... }`.
* A mod should keep track of its introduced IDs and only use this to access foreign elements.
* It is discouraged to change foreign HUD elements.
* `hud_remove(id)` * `hud_remove(id)`
* remove the HUD element of the specified id, returns `true` on success * remove the HUD element of the specified id, returns `true` on success
* `hud_change(id, stat, value)` * `hud_change(id, stat, value)`
@ -1319,6 +954,7 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or
``` ```
### Server info ### Server info
```lua ```lua
{ {
address = "minetest.example.org", -- The domain name/IP address of a remote server or "" for a local server. address = "minetest.example.org", -- The domain name/IP address of a remote server or "" for a local server.
@ -1330,32 +966,7 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or
### HUD Definition (`hud_add`, `hud_get`) ### HUD Definition (`hud_add`, `hud_get`)
```lua Refer to `lua_api.md`.
{
type = "image", -- see HUD element types, default "text"
-- ^ 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},
-- ^ Left corner position of element, default `{x=0,y=0}`.
name = "<name>", -- default ""
scale = {x=2, y=2}, -- default {x=0,y=0}
text = "<text>", -- default ""
number = 2, -- default 0
item = 3, -- default 0
-- ^ Selected item in inventory. 0 for no item selected.
direction = 0, -- default 0
-- ^ Direction: 0: left-right, 1: right-left, 2: top-bottom, 3: bottom-top
alignment = {x=0, y=0}, -- default {x=0, y=0}
-- ^ See "HUD Element Types"
offset = {x=0, y=0}, -- default {x=0, y=0}
-- ^ See "HUD Element Types"
size = { x=100, y=100 }, -- default {x=0, y=0}
-- ^ Size of element in pixels
style = 0,
-- ^ For "text" elements sets font style: bitfield with 1 = bold, 2 = italic, 4 = monospace
}
```
Escape sequences Escape sequences
---------------- ----------------
@ -1383,177 +994,17 @@ The following functions provide escape sequences:
`ColorString` `ColorString`
------------- -------------
`#RGB` defines a color in hexadecimal format.
`#RGBA` defines a color in hexadecimal format and alpha channel. Refer to `lua_api.md`.
`#RRGGBB` defines a color in hexadecimal format.
`#RRGGBBAA` defines a color in hexadecimal format and alpha channel.
Named colors are also supported and are equivalent to
[CSS Color Module Level 4](http://dev.w3.org/csswg/css-color/#named-colors).
To specify the value of the alpha channel, append `#A` or `#AA` to the end of
the color name (e.g. `colorname#08`).
`Color` `Color`
------------- -------------
`{a = alpha, r = red, g = green, b = blue}` defines an ARGB8 color. `{a = alpha, r = red, g = green, b = blue}` defines an ARGB8 color.
HUD element types
-----------------
The position field is used for all element types.
To account for differing resolutions, the position coordinates are the percentage
of the screen, ranging in value from `0` to `1`.
The name field is not yet used, but should contain a description of what the
HUD element represents. The direction field is the direction in which something
is drawn.
`0` draws from left to right, `1` draws from right to left, `2` draws from
top to bottom, and `3` draws from bottom to top.
The `alignment` field specifies how the item will be aligned. It ranges from `-1` to `1`,
with `0` being the center, `-1` is moved to the left/up, and `1` is to the right/down.
Fractional values can be used.
The `offset` field specifies a pixel offset from the position. Contrary to position,
the offset is not scaled to screen size. This allows for some precisely-positioned
items in the HUD.
**Note**: `offset` _will_ adapt to screen DPI as well as user defined scaling factor!
Below are the specific uses for fields in each type; fields not listed for that type are ignored.
**Note**: Future revisions to the HUD API may be incompatible; the HUD API is still
in the experimental stages.
### `image`
Displays an image on the HUD.
* `scale`: The scale of the image, with 1 being the original texture size.
Only the X coordinate scale is used (positive values).
Negative values represent that percentage of the screen it
should take; e.g. `x=-100` means 100% (width).
* `text`: The name of the texture that is displayed.
* `alignment`: The alignment of the image.
* `offset`: offset in pixels from position.
### `text`
Displays text on the HUD.
* `scale`: Defines the bounding rectangle of the text.
A value such as `{x=100, y=100}` should work.
* `text`: The text to be displayed in the HUD element.
* `number`: An integer containing the RGB value of the color used to draw the text.
Specify `0xFFFFFF` for white text, `0xFF0000` for red, and so on.
* `alignment`: The alignment of the text.
* `offset`: offset in pixels from position.
### `statbar`
Displays a horizontal bar made up of half-images.
* `text`: The name of the texture that is used.
* `number`: The number of half-textures that are displayed.
If odd, will end with a vertically center-split texture.
* `direction`
* `offset`: offset in pixels from position.
* `size`: If used, will force full-image size to this value (override texture pack image size)
### `inventory`
* `text`: The name of the inventory list to be displayed.
* `number`: Number of items in the inventory to be displayed.
* `item`: Position of item that is selected.
* `direction`
* `offset`: offset in pixels from position.
### `waypoint`
Displays distance to selected world position.
* `name`: The name of the waypoint.
* `text`: Distance suffix. Can be blank.
* `precision`: Waypoint precision, integer >= 0. Defaults to 10.
If set to 0, distance is not shown. Shown value is `floor(distance*precision)/precision`.
When the precision is an integer multiple of 10, there will be `log_10(precision)` digits after the decimal point.
`precision = 1000`, for example, will show 3 decimal places (eg: `0.999`).
`precision = 2` will show multiples of `0.5`; precision = 5 will show multiples of `0.2` and so on:
`precision = n` will show multiples of `1/n`
* `number:` An integer containing the RGB value of the color used to draw the
text.
* `world_pos`: World position of the waypoint.
* `offset`: offset in pixels from position.
* `alignment`: The alignment of the waypoint.
### `image_waypoint`
Same as `image`, but does not accept a `position`; the position is instead determined by `world_pos`, the world position of the waypoint.
* `scale`: The scale of the image, with 1 being the original texture size.
Only the X coordinate scale is used (positive values).
Negative values represent that percentage of the screen it
should take; e.g. `x=-100` means 100% (width).
* `text`: The name of the texture that is displayed.
* `alignment`: The alignment of the image.
* `world_pos`: World position of the waypoint.
* `offset`: offset in pixels from position.
### Particle definition (`add_particle`) ### Particle definition (`add_particle`)
```lua As documented in `lua_api.md`, except for obvious reasons, the `playername` field is not supported.
{
pos = {x=0, y=0, z=0},
velocity = {x=0, y=0, z=0},
acceleration = {x=0, y=0, z=0},
-- ^ Spawn particle at pos with velocity and acceleration
expirationtime = 1,
-- ^ Disappears after expirationtime seconds
size = 1,
collisiondetection = false,
-- ^ collisiondetection: if true collides with physical objects
collision_removal = false,
-- ^ collision_removal: if true then particle is removed when it collides,
-- ^ requires collisiondetection = true to have any effect
vertical = false,
-- ^ vertical: if true faces player using y axis only
texture = "image.png",
-- ^ Uses texture (string)
animation = {Tile Animation definition},
-- ^ optional, specifies how to animate the particle texture
glow = 0
-- ^ optional, specify particle self-luminescence in darkness
}
```
### `ParticleSpawner` definition (`add_particlespawner`) ### `ParticleSpawner` definition (`add_particlespawner`)
```lua As documented in `lua_api.md`, except for obvious reasons, the `playername` field is not supported.
{
amount = 1,
time = 1,
-- ^ If time is 0 has infinite lifespan and spawns the amount on a per-second base
minpos = {x=0, y=0, z=0},
maxpos = {x=0, y=0, z=0},
minvel = {x=0, y=0, z=0},
maxvel = {x=0, y=0, z=0},
minacc = {x=0, y=0, z=0},
maxacc = {x=0, y=0, z=0},
minexptime = 1,
maxexptime = 1,
minsize = 1,
maxsize = 1,
-- ^ The particle's properties are random values in between the bounds:
-- ^ minpos/maxpos, minvel/maxvel (velocity), minacc/maxacc (acceleration),
-- ^ minsize/maxsize, minexptime/maxexptime (expirationtime)
collisiondetection = false,
-- ^ collisiondetection: if true uses collision detection
collision_removal = false,
-- ^ collision_removal: if true then particle is removed when it collides,
-- ^ requires collisiondetection = true to have any effect
vertical = false,
-- ^ vertical: if true faces player using y axis only
texture = "image.png",
-- ^ Uses texture (string)
}
```

@ -7,8 +7,12 @@
| GCC | 7.5+ | or Clang 7.0.1+ | | GCC | 7.5+ | or Clang 7.0.1+ |
| CMake | 3.5+ | | | CMake | 3.5+ | |
| IrrlichtMt | - | Custom version of Irrlicht, see https://github.com/minetest/irrlicht | | IrrlichtMt | - | Custom version of Irrlicht, see https://github.com/minetest/irrlicht |
| libjpeg | - | (via IrrlichtMt) |
| libpng | - | (via IrrlichtMt) |
| SDL | 2.x | (via IrrlichtMt) |
| Freetype | 2.0+ | | | Freetype | 2.0+ | |
| SQLite3 | 3+ | | | SQLite3 | 3+ | |
| Zlib | - | |
| Zstd | 1.0+ | | | Zstd | 1.0+ | |
| LuaJIT | 2.0+ | Bundled Lua 5.1 is used if not present | | LuaJIT | 2.0+ | Bundled Lua 5.1 is used if not present |
| GMP | 5.0.0+ | Bundled mini-GMP is used if not present | | GMP | 5.0.0+ | Bundled mini-GMP is used if not present |
@ -18,27 +22,27 @@
For Debian/Ubuntu users: For Debian/Ubuntu users:
sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxi-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev gettext sudo apt install g++ make libc6-dev cmake libpng-dev libjpeg-dev libxi-dev libgl1-mesa-dev libsqlite3-dev libogg-dev libvorbis-dev libopenal-dev libcurl4-gnutls-dev libfreetype6-dev zlib1g-dev libgmp-dev libjsoncpp-dev libzstd-dev libluajit-5.1-dev gettext libsdl2-dev
For Fedora users: For Fedora users:
sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libpng-devel libjpeg-devel libvorbis-devel libXi-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel gettext sudo dnf install make automake gcc gcc-c++ kernel-devel cmake libcurl-devel openal-soft-devel libpng-devel libjpeg-devel libvorbis-devel libXi-devel libogg-devel freetype-devel mesa-libGL-devel zlib-devel jsoncpp-devel gmp-devel sqlite-devel luajit-devel leveldb-devel ncurses-devel spatialindex-devel libzstd-devel gettext SDL2-devel
For openSUSE users: For openSUSE users:
sudo zypper install gcc cmake libjpeg8-devel libpng16-devel openal-soft-devel libcurl-devel sqlite3-devel luajit-devel libzstd-devel Mesa-libGL-devel libXi-devel libvorbis-devel freetype2-devel sudo zypper install gcc cmake libjpeg8-devel libpng16-devel openal-soft-devel libcurl-devel sqlite3-devel luajit-devel libzstd-devel Mesa-libGL-devel libXi-devel libvorbis-devel freetype2-devel SDL2-devel
For Arch users: For Arch users:
sudo pacman -S --needed 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 sdl2
For Alpine users: For Alpine users:
sudo apk add build-base cmake libpng-dev jpeg-dev libxi-dev mesa-dev sqlite-dev libogg-dev libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev gmp-dev jsoncpp-dev luajit-dev zstd-dev gettext sudo apk add build-base cmake libpng-dev jpeg-dev libxi-dev mesa-dev sqlite-dev libogg-dev libvorbis-dev openal-soft-dev curl-dev freetype-dev zlib-dev gmp-dev jsoncpp-dev luajit-dev zstd-dev gettext sdl2-dev
For Void users: For Void users:
sudo xbps-install cmake libpng-devel jpeg-devel libXi-devel mesa sqlite-devel libogg-devel libvorbis-devel libopenal-devel libcurl-devel freetype-devel zlib-devel gmp-devel jsoncpp-devel LuaJIT-devel libzstd-devel gettext sudo xbps-install cmake libpng-devel jpeg-devel libXi-devel mesa sqlite-devel libogg-devel libvorbis-devel libopenal-devel libcurl-devel freetype-devel zlib-devel gmp-devel jsoncpp-devel LuaJIT-devel libzstd-devel gettext SDL2-devel
## Download ## Download

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

24
doc/developing/README.md Normal file

@ -0,0 +1,24 @@
# Developer documentation
## Wiki
Some important development docs are found in the wiki: https://dev.minetest.net/
Notable pages:
- [Releasing Minetest](https://dev.minetest.net/Releasing_Minetest)
- [Engine translations](https://dev.minetest.net/Translation#Maintaining_engine_translations)
- [Changelog](https://dev.minetest.net/Changelog)
- [Organisation](https://dev.minetest.net/Organisation)
- [Code style guidelines](https://dev.minetest.net/Code_style_guidelines)
## In this folder
- [Developing minetestserver with Docker](docker.md)
- [Miscellaneous](misc.md)
## IRC
Oftentimes knowledge hasn't been written down (yet) and your best bet is to ask someone experienced and/or the core developers.
Feel free to join the [#minetest-dev IRC](https://wiki.minetest.net/IRC) and ask questions related to **engine development**.

65
doc/developing/misc.md Normal file

@ -0,0 +1,65 @@
# Miscellaneous
## Sign the Android APK from CI
The [Github Actions Workflow](https://github.com/minetest/minetest/actions?query=workflow%3Aandroid+event%3Apush)
automatically produces an APK for each architecture.
Before installing them onto a device they however need to be signed.
This requires an installation of the Android SDK and `adb`.
```bash
.../android-sdk/build-tools/30.0.3/apksigner sign --ks ~/.android/debug.keystore \
app-arm64-v8a-release-unsigned.apk
# Enter 'android' (without quotes) when asked for a password
```
Note that the `debug.keystore` will not exist if you have never compiled an
Android app on your system (probably).
After that installing it will work:
```bash
adb install -r -d ./app-arm64-v8a-release-unsigned.apk
```
## How to get debug output from Minetest on Android
In case debug.txt isn't enough (e.g. when debugging a crash), you can get debug
output using logcat:
`adb logcat -s 'Minetest:*' '*:F'`
Note that you can do this even *after* the app has crashed,
since Android keeps an internal buffer.
A segmentation fault for example looks like this:
```
01-10 17:20:22.215 19308 20560 F libc : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 20560 (MinetestNativeT), pid 19308 (netest.minetest)
01-10 17:20:22.287 20576 20576 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
01-10 17:20:22.287 20576 20576 F DEBUG : Build fingerprint: '...'
01-10 17:20:22.287 20576 20576 F DEBUG : Revision: '4'
01-10 17:20:22.287 20576 20576 F DEBUG : ABI: 'arm64'
01-10 17:20:22.288 20576 20576 F DEBUG : Timestamp: 2024-01-10 17:20:22+0100
01-10 17:20:22.288 20576 20576 F DEBUG : pid: 19308, tid: 20560, name: MinetestNativeT >>> net.minetest.minetest <<<
01-10 17:20:22.288 20576 20576 F DEBUG : uid: 10385
01-10 17:20:22.288 20576 20576 F DEBUG : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
[ ... more information follows ... ]
```
## Profiling Minetest on Linux
We will be using a tool called "perf", which you can get by installing `perf` or `linux-perf` or `linux-tools-common`.
For best results build Minetest and Irrlicht with debug symbols
(`-DCMAKE_BUILD_TYPE=RelWithDebInfo` or `-DCMAKE_BUILD_TYPE=Debug`).
Run the client (or server) like this and do whatever you wanted to test:
```bash
perf record -z --call-graph dwarf -- ./bin/minetest
```
This will leave a file called "perf.data".
You can open this file with perf built-in tools but much more interesting
is the visualization using a GUI tool: **[Hotspot](https://github.com/KDAB/hotspot)**.
It will give you flamegraphs, per-thread, per-function views and much more.

@ -246,15 +246,15 @@ The format is documented in `builtin/settingtypes.txt`.
It is parsed by the main menu settings dialogue to list mod-specific It is parsed by the main menu settings dialogue to list mod-specific
settings in the "Mods" category. settings in the "Mods" category.
`minetest.settings` can be used to read custom or engine settings.
See [`Settings`].
### `init.lua` ### `init.lua`
The main Lua script. Running this script should register everything it The main Lua script. Running this script should register everything it
wants to register. Subsequent execution depends on minetest calling the wants to register. Subsequent execution depends on Minetest calling the
registered callbacks. registered callbacks.
`minetest.settings` can be used to read custom or existing settings at load
time, if necessary. (See [`Settings`])
### `textures`, `sounds`, `media`, `models`, `locale` ### `textures`, `sounds`, `media`, `models`, `locale`
Media files (textures, sounds, whatever) that will be transferred to the Media files (textures, sounds, whatever) that will be transferred to the
@ -1270,11 +1270,15 @@ The function of `param2` is determined by `paramtype2` in node definition.
* The rotation of the node is stored in `param2` * The rotation of the node is stored in `param2`
* Node is 'mounted'/facing towards one of 6 directions * Node is 'mounted'/facing towards one of 6 directions
* You can make this value by using `minetest.dir_to_wallmounted()` * You can make this value by using `minetest.dir_to_wallmounted()`
* Values range 0 - 5 * Values range 0 - 7
* The value denotes at which direction the node is "mounted": * The value denotes at which direction the node is "mounted":
0 = y+, 1 = y-, 2 = x+, 3 = x-, 4 = z+, 5 = z- 0 = y+, 1 = y-, 2 = x+, 3 = x-, 4 = z+, 5 = z-
6 = y+, but rotated by 90°
7 = y-, but rotated by -90°
* By default, on placement the param2 is automatically set to the * By default, on placement the param2 is automatically set to the
appropriate rotation, depending on which side was pointed at appropriate rotation (0 to 5), depending on which side was
pointed at. With the node field `wallmounted_rotate_vertical = true`,
the param2 values 6 and 7 might additionally be set
* `paramtype2 = "facedir"` * `paramtype2 = "facedir"`
* Supported drawtypes: "normal", "nodebox", "mesh" * Supported drawtypes: "normal", "nodebox", "mesh"
* The rotation of the node is stored in `param2`. * The rotation of the node is stored in `param2`.
@ -1678,10 +1682,12 @@ type are ignored.
Displays an image on the HUD. Displays an image on the HUD.
* `scale`: The scale of the image, with 1 being the original texture size. * `scale`: The scale of the image, with `{x = 1, y = 1}` being the original texture size.
Only the X coordinate scale is used (positive values). The `x` and `y` fields apply to the respective axes.
Negative values represent that percentage of the screen it Positive values scale the source image.
should take; e.g. `x=-100` means 100% (width). Negative values represent percentages relative to screen dimensions.
Example: `{x = -20, y = 3}` means the image will be drawn 20% of screen width wide,
and 3 times as high as the source image is.
* `text`: The name of the texture that is displayed. * `text`: The name of the texture that is displayed.
* `alignment`: The alignment of the image. * `alignment`: The alignment of the image.
* `offset`: offset in pixels from position. * `offset`: offset in pixels from position.
@ -1748,10 +1754,12 @@ Displays distance to selected world position.
Same as `image`, but does not accept a `position`; the position is instead determined by `world_pos`, the world position of the waypoint. Same as `image`, but does not accept a `position`; the position is instead determined by `world_pos`, the world position of the waypoint.
* `scale`: The scale of the image, with 1 being the original texture size. * `scale`: The scale of the image, with `{x = 1, y = 1}` being the original texture size.
Only the X coordinate scale is used (positive values). The `x` and `y` fields apply to the respective axes.
Negative values represent that percentage of the screen it Positive values scale the source image.
should take; e.g. `x=-100` means 100% (width). Negative values represent percentages relative to screen dimensions.
Example: `{x = -20, y = 3}` means the image will be drawn 20% of screen width wide,
and 3 times as high as the source image is.
* `text`: The name of the texture that is displayed. * `text`: The name of the texture that is displayed.
* `alignment`: The alignment of the image. * `alignment`: The alignment of the image.
* `world_pos`: World position of the waypoint. * `world_pos`: World position of the waypoint.
@ -5280,6 +5288,20 @@ Utilities
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) -- In HUD definitions the field `type` is used and `hud_elem_type` is deprecated (5.9.0)
hud_def_type_field = true, hud_def_type_field = true,
-- PseudoRandom and PcgRandom state is restorable
-- PseudoRandom has get_state method
-- PcgRandom has get_state and set_state methods (5.9.0)
random_state_restore = true,
-- minetest.after guarantees that coexisting jobs are executed primarily
-- in order of expiry and secondarily in order of registration (5.9.0)
after_order_expiry_registration = true,
-- wallmounted nodes mounted at floor or ceiling may additionally
-- be rotated by 90° with special param2 values (5.9.0)
wallmounted_rotate = true,
-- Availability of the `pointabilities` property in the item definition (5.9.0)
item_specific_pointabilities = true,
-- Nodes `pointable` property can be `"blocking"` (5.9.0)
blocking_pointability_type = true,
} }
``` ```
@ -5768,7 +5790,7 @@ Setting-related
--------------- ---------------
* `minetest.settings`: Settings object containing all of the settings from the * `minetest.settings`: Settings object containing all of the settings from the
main config file (`minetest.conf`). main config file (`minetest.conf`). See [`Settings`].
* `minetest.setting_get_pos(name)`: Loads a setting from the main settings and * `minetest.setting_get_pos(name)`: Loads a setting from the main settings and
parses it as a position (in the format `(1,2,3)`). Returns a position or nil. parses it as a position (in the format `(1,2,3)`). Returns a position or nil.
@ -5819,8 +5841,20 @@ Authentication
* `name`: string; if omitted, all auth data should be considered modified * `name`: string; if omitted, all auth data should be considered modified
* `minetest.set_player_password(name, password_hash)`: Set password hash of * `minetest.set_player_password(name, password_hash)`: Set password hash of
player `name`. player `name`.
* `minetest.set_player_privs(name, {priv1=true,...})`: Set privileges of player * `minetest.set_player_privs(name, privs)`: Set privileges of player `name`.
`name`. * `privs` is a **set** of privileges:
A table where the keys are names of privileges and the values are `true`.
* Example: `minetest.set_player_privs("singleplayer", {interact = true, fly = true})`.
This **sets** the player privileges to `interact` and `fly`;
`singleplayer` will only have these two privileges afterwards.
* `minetest.change_player_privs(name, changes)`: Helper to grant or revoke privileges.
* `changes`: Table of changes to make.
A field `[privname] = true` grants a privilege,
whereas `[privname] = false` revokes a privilege.
* Example: `minetest.change_player_privs("singleplayer", {interact = true, fly = false})`
will grant singleplayer the `interact` privilege
and revoke singleplayer's `fly` privilege.
All other privileges will remain unchanged.
* `minetest.auth_reload()` * `minetest.auth_reload()`
* See `reload()` in authentication handler definition * See `reload()` in authentication handler definition
@ -6450,6 +6484,8 @@ Timing
* `minetest.after(time, func, ...)`: returns job table to use as below. * `minetest.after(time, func, ...)`: returns job table to use as below.
* Call the function `func` after `time` seconds, may be fractional * Call the function `func` after `time` seconds, may be fractional
* Optional: Variable number of arguments that are passed to `func` * Optional: Variable number of arguments that are passed to `func`
* Jobs set for earlier times are executed earlier. If multiple jobs expire
at exactly the same time, then they are executed in registration order.
* `job:cancel()` * `job:cancel()`
* Cancels the job function from being called * Cancels the job function from being called
@ -7799,6 +7835,10 @@ child will follow movement and rotation of that bone.
* `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
`"type"` (or the deprecated `"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_get_all()`:
* Returns a table in the form `{ [id] = HUD definition, [id] = ... }`.
* A mod should keep track of its introduced IDs and only use this to access foreign elements.
* It is discouraged to change foreign HUD elements.
* `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
* `hotbar` * `hotbar`
@ -7852,8 +7892,7 @@ child will follow movement and rotation of that bone.
whether `set_sky` accepts this format. Check the legacy format otherwise. whether `set_sky` accepts this format. Check the legacy format otherwise.
* Passing no arguments resets the sky to its default values. * Passing no arguments resets the sky to its default values.
* `sky_parameters` is a table with the following optional fields: * `sky_parameters` is a table with the following optional fields:
* `base_color`: ColorSpec, changes fog in "skybox" and "plain". * `base_color`: ColorSpec, meaning depends on `type` (default: `#ffffff`)
(default: `#ffffff`)
* `body_orbit_tilt`: Float, rotation angle of sun/moon orbit in degrees. * `body_orbit_tilt`: Float, rotation angle of sun/moon orbit in degrees.
By default, orbit is controlled by a client-side setting, and this field is not set. By default, orbit is controlled by a client-side setting, and this field is not set.
After a value is assigned, it can only be changed to another float value. After a value is assigned, it can only be changed to another float value.
@ -7910,6 +7949,9 @@ child will follow movement and rotation of that bone.
Any value between [0.0, 0.99] set the fog_start as a fraction of the viewing_range. Any value between [0.0, 0.99] set the fog_start as a fraction of the viewing_range.
Any value < 0, resets the behavior to being client-controlled. Any value < 0, resets the behavior to being client-controlled.
(default: -1) (default: -1)
* `fog_color`: ColorSpec, override the color of the fog.
Unlike `base_color` above this will apply regardless of the skybox type.
(default: `"#00000000"`, which means no override)
* `set_sky(base_color, type, {texture names}, clouds)` * `set_sky(base_color, type, {texture names}, clouds)`
* Deprecated. Use `set_sky(sky_parameters)` * Deprecated. Use `set_sky(sky_parameters)`
* `base_color`: ColorSpec, defaults to white * `base_color`: ColorSpec, defaults to white
@ -8048,7 +8090,7 @@ child will follow movement and rotation of that bone.
* `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`.
* `respawn()`: Respawns the player using the same mechanism as the death screen, * `respawn()`: Respawns the player using the same mechanism as the death screen,
including calling on_respawnplayer callbacks. including calling `on_respawnplayer` callbacks.
`PcgRandom` `PcgRandom`
----------- -----------
@ -8057,7 +8099,9 @@ A 32-bit pseudorandom number generator.
Uses PCG32, an algorithm of the permuted congruential generator family, Uses PCG32, an algorithm of the permuted congruential generator family,
offering very strong randomness. offering very strong randomness.
It can be created via `PcgRandom(seed)` or `PcgRandom(seed, sequence)`. * constructor `PcgRandom(seed, [seq])`
* `seed`: 64-bit unsigned seed
* `seq`: 64-bit unsigned sequence, optional
### Methods ### Methods
@ -8069,6 +8113,8 @@ It can be created via `PcgRandom(seed)` or `PcgRandom(seed, sequence)`.
* `mean = (max - min) / 2`, and * `mean = (max - min) / 2`, and
* `variance = (((max - min + 1) ^ 2) - 1) / (12 * num_trials)` * `variance = (((max - min + 1) ^ 2) - 1) / (12 * num_trials)`
* Increasing `num_trials` improves accuracy of the approximation * Increasing `num_trials` improves accuracy of the approximation
* `get_state()`: return generator state encoded in string
* `set_state(state_string)`: restore generator state from encoded string
`PerlinNoise` `PerlinNoise`
------------- -------------
@ -8152,14 +8198,22 @@ Can be obtained using `player:get_meta()`.
A 16-bit pseudorandom number generator. A 16-bit pseudorandom number generator.
Uses a well-known LCG algorithm introduced by K&R. Uses a well-known LCG algorithm introduced by K&R.
It can be created via `PseudoRandom(seed)`. **Note**:
`PseudoRandom` is slower and has worse random distribution than `PcgRandom`.
Use `PseudoRandom` only if you need output to match the well-known LCG algorithm introduced by K&R.
Otherwise, use `PcgRandom`.
* constructor `PseudoRandom(seed)`
* `seed`: 32-bit signed number
### Methods ### Methods
* `next()`: return next integer random number [`0`...`32767`] * `next()`: return next integer random number [`0`...`32767`]
* `next(min, max)`: return next integer random number [`min`...`max`] * `next(min, max)`: return next integer random number [`min`...`max`]
* `((max - min) == 32767) or ((max-min) <= 6553))` must be true * Either `max - min == 32767` or `max - min <= 6553` must be true
due to the simple implementation making bad distribution otherwise. due to the simple implementation making a bad distribution otherwise.
* `get_state()`: return state of pseudorandom generator as number
* use returned number as seed in PseudoRandom constructor to restore
`Raycast` `Raycast`
--------- ---------
@ -8225,37 +8279,47 @@ secure random device cannot be found on the system.
An interface to read config files in the format of `minetest.conf`. An interface to read config files in the format of `minetest.conf`.
It can be created via `Settings(filename)`. `minetest.settings` is a `Settings` instance that can be used to access the
main config file (`minetest.conf`). Instances for other config files can be
created via `Settings(filename)`.
Engine settings on the `minetest.settings` object have internal defaults that
will be returned if a setting is unset.
The engine does *not* (yet) read `settingtypes.txt` for this purpose. This
means that no defaults will be returned for mod settings.
### Methods ### Methods
* `get(key)`: returns a value * `get(key)`: returns a value
* Returns `nil` if `key` is not found.
* `get_bool(key, [default])`: returns a boolean * `get_bool(key, [default])`: returns a boolean
* `default` is the value returned if `key` is not found. * `default` is the value returned if `key` is not found.
* Returns `nil` if `key` is not found and `default` not specified. * Returns `nil` if `key` is not found and `default` not specified.
* `get_np_group(key)`: returns a NoiseParams table * `get_np_group(key)`: returns a NoiseParams table
* Returns `nil` if `key` is not found.
* `get_flags(key)`: * `get_flags(key)`:
* Returns `{flag = true/false, ...}` according to the set flags. * Returns `{flag = true/false, ...}` according to the set flags.
* Is currently limited to mapgen flags `mg_flags` and mapgen-specific * Is currently limited to mapgen flags `mg_flags` and mapgen-specific
flags like `mgv5_spflags`. flags like `mgv5_spflags`.
* Returns `nil` if `key` is not found.
* `set(key, value)` * `set(key, value)`
* Setting names can't contain whitespace or any of `="{}#`. * Setting names can't contain whitespace or any of `="{}#`.
* Setting values can't contain the sequence `\n"""`. * Setting values can't contain the sequence `\n"""`.
* Setting names starting with "secure." can't be set on the main settings * Setting names starting with "secure." can't be set on the main settings
object (`minetest.settings`). object (`minetest.settings`).
* `set_bool(key, value)` * `set_bool(key, value)`
* See documentation for set() above. * See documentation for `set()` above.
* `set_np_group(key, value)` * `set_np_group(key, value)`
* `value` is a NoiseParams table. * `value` is a NoiseParams table.
* Also, see documentation for set() above. * Also, see documentation for `set()` above.
* `remove(key)`: returns a boolean (`true` for success) * `remove(key)`: returns a boolean (`true` for success)
* `get_names()`: returns `{key1,...}` * `get_names()`: returns `{key1,...}`
* `has(key)`: * `has(key)`:
* Returns a boolean indicating whether `key` exists. * Returns a boolean indicating whether `key` exists.
* Note that for the main settings object (`minetest.settings`), `get(key)` * In contrast to the various getter functions, `has()` doesn't consider
might return a value even if `has(key)` returns `false`. That's because any default values.
`get` can fall back to the so-called parent of the `Settings` object, i.e. * This means that on the main settings object (`minetest.settings`),
the default values. `get(key)` might return a value even if `has(key)` returns `false`.
* `write()`: returns a boolean (`true` for success) * `write()`: returns a boolean (`true` for success)
* Writes changes to file. * Writes changes to file.
* `to_table()`: returns `{[key1]=value1,...}` * `to_table()`: returns `{[key1]=value1,...}`
@ -8336,7 +8400,9 @@ Player properties need to be saved manually.
pointable = true, pointable = true,
-- Whether the object can be pointed at -- Can be `true` if it is pointable, `false` if it can be pointed through,
-- or `"blocking"` if it is pointable but not selectable.
-- Can be overridden by the `pointabilities` of the held item.
visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem" / "item", visual = "cube" / "sprite" / "upright_sprite" / "mesh" / "wielditem" / "item",
-- "cube" is a node-sized cube. -- "cube" is a node-sized cube.
@ -8700,6 +8766,27 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- If true, item can point to all liquid nodes (`liquidtype ~= "none"`), -- If true, item can point to all liquid nodes (`liquidtype ~= "none"`),
-- even those for which `pointable = false` -- even those for which `pointable = false`
pointabilities = {
nodes = {
["default:stone"] = "blocking",
["group:leaves"] = false,
},
objects = {
["modname:entityname"] = true,
["group:ghosty"] = true, -- (an armor group)
}
},
-- Contains lists to override the `pointable` property of pointed nodes and objects.
-- The index can be a node/entity name or a group with the prefix `"group:"`.
-- (For objects `armor_groups` are used and for players the entity name is irrelevant.)
-- If multiple fields fit, the following priority order is applied:
-- 1. value of matching node/entity name
-- 2. `true` for any group
-- 3. `false` for any group
-- 4. `"blocking"` for any group
-- 5. `liquids_pointable` if it is a liquid node
-- 6. `pointable` property of the node or object
light_source = 0, light_source = 0,
-- When used for nodes: Defines amount of light emitted by node. -- When used for nodes: Defines amount of light emitted by node.
-- Otherwise: Defines texture glow when viewed as a dropped item -- Otherwise: Defines texture glow when viewed as a dropped item
@ -8741,6 +8828,20 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- Otherwise should be name of node which the client immediately places -- Otherwise should be name of node which the client immediately places
-- upon digging. Server will always update with actual result shortly. -- upon digging. Server will always update with actual result shortly.
touch_interaction = {
-- Only affects touchscreen clients.
-- Defines the meaning of short and long taps with the item in hand.
-- The fields in this table have two valid values:
-- * "long_dig_short_place" (long tap = dig, short tap = place)
-- * "short_dig_long_place" (short tap = dig, long tap = place)
-- The field to be used is selected according to the current
-- `pointed_thing`.
pointed_nothing = "long_dig_short_place",
pointed_node = "long_dig_short_place",
pointed_object = "short_dig_long_place",
},
sound = { sound = {
-- Definition of item sounds to be played at various events. -- Definition of item sounds to be played at various events.
-- All fields in this table are optional. -- All fields in this table are optional.
@ -8892,6 +8993,13 @@ Used by `minetest.register_node`.
place_param2 = 0, place_param2 = 0,
-- Value for param2 that is set when player places node -- Value for param2 that is set when player places node
wallmounted_rotate_vertical = false,
-- If true, place_param2 is nil, and this is a wallmounted node,
-- this node might use the special 90° rotation when placed
-- on the floor or ceiling, depending on the direction.
-- See the explanation about wallmounted for details.
-- Otherwise, the rotation is always the same on vertical placement.
is_ground_content = true, is_ground_content = true,
-- If false, the cave generator and dungeon generator will not carve -- If false, the cave generator and dungeon generator will not carve
-- through this node. -- through this node.
@ -8904,7 +9012,11 @@ Used by `minetest.register_node`.
walkable = true, -- If true, objects collide with node walkable = true, -- If true, objects collide with node
pointable = true, -- If true, can be pointed at pointable = true,
-- Can be `true` if it is pointable, `false` if it can be pointed through,
-- or `"blocking"` if it is pointable but not selectable.
-- Can be overridden by the `pointabilities` of the held item.
-- A client may be able to point non-pointable nodes, since it isn't checked server-side.
diggable = true, -- If false, can never be dug diggable = true, -- If false, can never be dug
@ -10647,8 +10759,8 @@ Used by `minetest.register_authentication_handler`.
set_privileges = function(name, privileges), set_privileges = function(name, privileges),
-- Set privileges of player `name`. -- Set privileges of player `name`.
-- `privileges` is in table form, auth data should be created if not -- `privileges` is in table form: keys are privilege names, values are `true`;
-- present. -- auth data should be created if not present.
reload = function(), reload = function(),
-- Reload authentication data from the storage location. -- Reload authentication data from the storage location.

@ -17,6 +17,7 @@ markdown_extensions:
- pymdownx.superfences - pymdownx.superfences
- pymdownx.highlight: - pymdownx.highlight:
css_class: codehilite css_class: codehilite
- gfm_admonition
plugins: plugins:
- search: - search:
separator: '[\s\-\.\(]+' separator: '[\s\-\.\(]+'

@ -1,3 +1,4 @@
mkdocs~=1.4.3 mkdocs~=1.4.3
pygments~=2.15.1 pygments~=2.15.1
pymdown-extensions~=10.3 pymdown-extensions~=10.3
markdown-gfm-admonition~=0.1.0

@ -27,6 +27,7 @@ read_globals = {
"Settings", "Settings",
"check", "check",
"PseudoRandom", "PseudoRandom",
"PcgRandom",
string = {fields = {"split", "trim"}}, string = {fields = {"split", "trim"}},
table = {fields = {"copy", "getn", "indexof", "insert_all"}}, table = {fields = {"copy", "getn", "indexof", "insert_all"}},

@ -1,3 +1,4 @@
dofile(minetest.get_modpath("testentities").."/visuals.lua") dofile(minetest.get_modpath("testentities").."/visuals.lua")
dofile(minetest.get_modpath("testentities").."/selectionbox.lua") dofile(minetest.get_modpath("testentities").."/selectionbox.lua")
dofile(minetest.get_modpath("testentities").."/armor.lua") dofile(minetest.get_modpath("testentities").."/armor.lua")
dofile(minetest.get_modpath("testentities").."/pointable.lua")

@ -0,0 +1,23 @@
-- Pointability test Entities
-- Register wrapper for compactness
local function register_pointable_testentity(name, pointable)
local texture = "testnodes_"..name..".png"
minetest.register_entity("testentities:"..name, {
initial_properties = {
visual = "cube",
visual_size = {x = 0.6, y = 0.6, z = 0.6},
textures = {
texture, texture, texture, texture, texture, texture
},
pointable = pointable,
},
on_activate = function(self)
self.object:set_armor_groups({[name.."_test"] = 1})
end
})
end
register_pointable_testentity("pointable", true)
register_pointable_testentity("not_pointable", false)
register_pointable_testentity("blocking_pointable", "blocking")

@ -152,7 +152,8 @@ minetest.register_chatcommand("hudwaypoints", {
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}, -- 20% of screen width, 3x image height
scale = {x = -20, y = 3},
offset = {x = 0, y = -32} offset = {x = 0, y = -32}
} }
if not player_waypoints[name] then if not player_waypoints[name] then
@ -209,3 +210,23 @@ minetest.register_on_leaveplayer(function(player)
player_font_huds[player:get_player_name()] = nil player_font_huds[player:get_player_name()] = nil
player_waypoints[player:get_player_name()] = nil player_waypoints[player:get_player_name()] = nil
end) end)
minetest.register_chatcommand("hudprint", {
description = "Writes all used Lua HUD elements into chat.",
func = function(name, params)
local player = minetest.get_player_by_name(name)
if not player then
return false, "No player."
end
local s = "HUD elements:"
for k, elem in pairs(player:hud_get_all()) do
local ename = dump(elem.name)
local etype = dump(elem.type)
local epos = "{x="..elem.position.x..", y="..elem.position.y.."}"
s = s.."\n["..k.."] type = "..etype.." | name = "..ename.." | pos = ".. epos
end
return true, s
end
})

@ -163,7 +163,7 @@ minetest.register_node("testnodes:torchlike", {
minetest.register_node("testnodes:torchlike_wallmounted", { minetest.register_node("testnodes:torchlike_wallmounted", {
description = S("Wallmounted \"torchlike\" Drawtype Test Node").."\n".. description = S("Wallmounted \"torchlike\" Drawtype Test Node").."\n"..
S("param2 = wallmounted rotation (0..5)"), S("param2 = wallmounted rotation (0..7)"),
drawtype = "torchlike", drawtype = "torchlike",
paramtype = "light", paramtype = "light",
paramtype2 = "wallmounted", paramtype2 = "wallmounted",
@ -179,6 +179,24 @@ minetest.register_node("testnodes:torchlike_wallmounted", {
groups = { dig_immediate = 3 }, groups = { dig_immediate = 3 },
}) })
minetest.register_node("testnodes:torchlike_wallmounted_rot", {
description = S("Wallmounted Rotatable Torchlike Drawtype Test Node"),
drawtype = "torchlike",
paramtype = "light",
paramtype2 = "wallmounted",
wallmounted_rotate_vertical = true,
tiles = {
"testnodes_torchlike_floor.png^[colorize:#FFFF00:40",
"testnodes_torchlike_ceiling.png^[colorize:#FFFF00:40",
"testnodes_torchlike_wall.png^[colorize:#FFFF00:40",
},
walkable = false,
sunlight_propagates = true,
groups = { dig_immediate = 3 },
})
minetest.register_node("testnodes:signlike", { minetest.register_node("testnodes:signlike", {
description = S("Floor \"signlike\" Drawtype Test Node").."\n".. description = S("Floor \"signlike\" Drawtype Test Node").."\n"..
S("Always on floor"), S("Always on floor"),
@ -186,16 +204,14 @@ minetest.register_node("testnodes:signlike", {
paramtype = "light", paramtype = "light",
tiles = { "testnodes_signlike.png^[colorize:#FF0000:64" }, tiles = { "testnodes_signlike.png^[colorize:#FF0000:64" },
walkable = false, walkable = false,
groups = { dig_immediate = 3 },
sunlight_propagates = true, sunlight_propagates = true,
groups = { dig_immediate = 3 },
}) })
minetest.register_node("testnodes:signlike_wallmounted", { minetest.register_node("testnodes:signlike_wallmounted", {
description = S("Wallmounted \"signlike\" Drawtype Test Node").."\n".. description = S("Wallmounted \"signlike\" Drawtype Test Node").."\n"..
S("param2 = wallmounted rotation (0..5)"), S("param2 = wallmounted rotation (0..7)"),
drawtype = "signlike", drawtype = "signlike",
paramtype = "light", paramtype = "light",
paramtype2 = "wallmounted", paramtype2 = "wallmounted",
@ -207,6 +223,22 @@ minetest.register_node("testnodes:signlike_wallmounted", {
sunlight_propagates = true, sunlight_propagates = true,
}) })
minetest.register_node("testnodes:signlike_rot", {
description = S("Wallmounted Rotatable Signlike Drawtype Test Node"),
drawtype = "signlike",
paramtype = "light",
paramtype2 = "wallmounted",
wallmounted_rotate_vertical = true,
tiles = { "testnodes_signlike.png^[colorize:#FFFF00:40" },
walkable = false,
groups = { dig_immediate = 3 },
sunlight_propagates = true,
})
minetest.register_node("testnodes:plantlike", { minetest.register_node("testnodes:plantlike", {
description = S("\"plantlike\" Drawtype Test Node"), description = S("\"plantlike\" Drawtype Test Node"),
drawtype = "plantlike", drawtype = "plantlike",
@ -235,7 +267,7 @@ minetest.register_node("testnodes:plantlike_waving", {
minetest.register_node("testnodes:plantlike_wallmounted", { minetest.register_node("testnodes:plantlike_wallmounted", {
description = S("Wallmounted \"plantlike\" Drawtype Test Node").."\n".. description = S("Wallmounted \"plantlike\" Drawtype Test Node").."\n"..
S("param2 = wallmounted rotation (0..5)"), S("param2 = wallmounted rotation (0..7)"),
drawtype = "plantlike", drawtype = "plantlike",
paramtype = "light", paramtype = "light",
paramtype2 = "wallmounted", paramtype2 = "wallmounted",
@ -366,7 +398,7 @@ minetest.register_node("testnodes:plantlike_rooted", {
minetest.register_node("testnodes:plantlike_rooted_wallmounted", { minetest.register_node("testnodes:plantlike_rooted_wallmounted", {
description = S("Wallmounted \"rooted_plantlike\" Drawtype Test Node").."\n".. description = S("Wallmounted \"rooted_plantlike\" Drawtype Test Node").."\n"..
S("param2 = wallmounted rotation (0..5)"), S("param2 = wallmounted rotation (0..7)"),
drawtype = "plantlike_rooted", drawtype = "plantlike_rooted",
paramtype = "light", paramtype = "light",
paramtype2 = "wallmounted", paramtype2 = "wallmounted",

@ -92,7 +92,7 @@ minetest.register_node("testnodes:mesh_color4dir", {
-- Wallmounted mesh: pyramid -- Wallmounted mesh: pyramid
minetest.register_node("testnodes:mesh_wallmounted", { minetest.register_node("testnodes:mesh_wallmounted", {
description = S("Wallmounted Mesh Test Node").."\n".. description = S("Wallmounted Mesh Test Node").."\n"..
S("param2 = wallmounted rotation (0..5)"), S("param2 = wallmounted rotation (0..7)"),
drawtype = "mesh", drawtype = "mesh",
mesh = "testnodes_pyramid.obj", mesh = "testnodes_pyramid.obj",
tiles = {"testnodes_mesh_stripes9.png"}, tiles = {"testnodes_mesh_stripes9.png"},
@ -105,7 +105,7 @@ minetest.register_node("testnodes:mesh_wallmounted", {
minetest.register_node("testnodes:mesh_colorwallmounted", { minetest.register_node("testnodes:mesh_colorwallmounted", {
description = S("Color Wallmounted Mesh Test Node").."\n".. description = S("Color Wallmounted Mesh Test Node").."\n"..
S("param2 = color + wallmounted rotation (0..5, 8..13, ...)"), S("param2 = color + wallmounted rotation (0..7, 8..15, ...)"),
drawtype = "mesh", drawtype = "mesh",
mesh = "testnodes_pyramid.obj", mesh = "testnodes_pyramid.obj",
tiles = {"testnodes_mesh_stripes10.png"}, tiles = {"testnodes_mesh_stripes10.png"},

@ -180,3 +180,63 @@ minetest.register_node("testnodes:facedir_to_connect_to", {
paramtype2 = "facedir", paramtype2 = "facedir",
connect_sides = {"left", "top"}, connect_sides = {"left", "top"},
}) })
-- 3D sign and button:
-- These are example nodes for more realistic example uses
-- of wallmounted_rotate_vertical
minetest.register_node("testnodes:sign3d", {
description = S("Nodebox Sign, Nodebox Type \"fixed\""),
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "wallmounted",
wallmounted_rotate_vertical = true,
sunlight_propagates = true,
walkable = false,
tiles = {
"testnodes_sign3d.png",
},
groups = { dig_immediate = 3 },
node_box = {
type = "fixed",
fixed = {-0.4375, -0.5, -0.3125, 0.4375, -0.4375, 0.3125},
},
})
minetest.register_node("testnodes:sign3d_wallmounted", {
description = S("Nodebox Sign, Nodebox Type \"wallmounted\""),
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "wallmounted",
wallmounted_rotate_vertical = true,
sunlight_propagates = true,
walkable = false,
tiles = {
"testnodes_sign3d.png^[colorize:#ff0000:127",
},
groups = { dig_immediate = 3 },
node_box = {
type = "wallmounted",
wall_top = {-0.4375, 0.4375, -0.3125, 0.4375, 0.5, 0.3125},
wall_bottom = {-0.4375, -0.5, -0.3125, 0.4375, -0.4375, 0.3125},
wall_side = {-0.5, -0.3125, -0.4375, -0.4375, 0.3125, 0.4375},
},
})
minetest.register_node("testnodes:button", {
description = S("Button Nodebox Test Node"),
drawtype = "nodebox",
paramtype = "light",
paramtype2 = "wallmounted",
wallmounted_rotate_vertical = true,
sunlight_propagates = true,
walkable = false,
tiles = {
"testnodes_nodebox.png",
},
groups = { dig_immediate = 3 },
node_box = {
type = "fixed",
fixed = { -4/16, -8/16, -2/16, 4/16, -6/16, 2/16 },
},
})

@ -80,7 +80,7 @@ minetest.register_node("testnodes:4dir_nodebox", {
minetest.register_node("testnodes:wallmounted", { minetest.register_node("testnodes:wallmounted", {
description = S("Wallmounted Test Node").."\n".. description = S("Wallmounted Test Node").."\n"..
S("param2 = wallmounted rotation (0..5)"), S("param2 = wallmounted rotation (0..7)"),
paramtype2 = "wallmounted", paramtype2 = "wallmounted",
tiles = { tiles = {
"testnodes_1w.png", "testnodes_1w.png",
@ -94,9 +94,25 @@ minetest.register_node("testnodes:wallmounted", {
groups = { dig_immediate = 3 }, groups = { dig_immediate = 3 },
}) })
minetest.register_node("testnodes:wallmounted_rot", {
description = S("Wallmounted Rotatable Test Node"),
paramtype2 = "wallmounted",
wallmounted_rotate_vertical = true,
tiles = {
"testnodes_1w.png^[colorize:#FFFF00:40",
"testnodes_2w.png^[colorize:#FFFF00:40",
"testnodes_3w.png^[colorize:#FFFF00:40",
"testnodes_4w.png^[colorize:#FFFF00:40",
"testnodes_5w.png^[colorize:#FFFF00:40",
"testnodes_6w.png^[colorize:#FFFF00:40",
},
groups = { dig_immediate = 3 },
})
minetest.register_node("testnodes:wallmounted_nodebox", { minetest.register_node("testnodes:wallmounted_nodebox", {
description = S("Wallmounted Nodebox Test Node").."\n".. description = S("Wallmounted Nodebox Test Node").."\n"..
S("param2 = wallmounted rotation (0..5)"), S("param2 = wallmounted rotation (0..7)"),
paramtype2 = "wallmounted", paramtype2 = "wallmounted",
paramtype = "light", paramtype = "light",
tiles = { tiles = {
@ -118,6 +134,30 @@ minetest.register_node("testnodes:wallmounted_nodebox", {
groups = { dig_immediate = 3 }, groups = { dig_immediate = 3 },
}) })
minetest.register_node("testnodes:wallmounted_nodebox_rot", {
description = S("Wallmounted Rotatable Nodebox Test Node"),
paramtype2 = "wallmounted",
wallmounted_rotate_vertical = true,
paramtype = "light",
tiles = {
"testnodes_1w.png^[colorize:#FFFF00:40",
"testnodes_2w.png^[colorize:#FFFF00:40",
"testnodes_3w.png^[colorize:#FFFF00:40",
"testnodes_4w.png^[colorize:#FFFF00:40",
"testnodes_5w.png^[colorize:#FFFF00:40",
"testnodes_6w.png^[colorize:#FFFF00:40",
},
drawtype = "nodebox",
node_box = {
type = "wallmounted",
wall_top = { -0.5, 0, -0.5, 0.5, 0.5, 0.5 },
wall_bottom = { -0.5, -0.5, -0.5, 0.5, 0, 0.5 },
wall_side = { -0.5, -0.5, -0.5, 0, 0.5, 0.5 },
},
groups = { dig_immediate = 3 },
})
minetest.register_node("testnodes:color", { minetest.register_node("testnodes:color", {
description = S("Color Test Node").."\n".. description = S("Color Test Node").."\n"..
S("param2 = color (0..255)"), S("param2 = color (0..255)"),
@ -212,7 +252,7 @@ minetest.register_node("testnodes:color4dir_nodebox", {
minetest.register_node("testnodes:colorwallmounted", { minetest.register_node("testnodes:colorwallmounted", {
description = S("Color Wallmounted Test Node").."\n".. description = S("Color Wallmounted Test Node").."\n"..
S("param2 = color + wallmounted rotation (0..5, 8..13, ...)"), S("param2 = color + wallmounted rotation (0..7, 8..15, ...)"),
paramtype2 = "colorwallmounted", paramtype2 = "colorwallmounted",
paramtype = "light", paramtype = "light",
palette = "testnodes_palette_wallmounted.png", palette = "testnodes_palette_wallmounted.png",
@ -230,7 +270,7 @@ minetest.register_node("testnodes:colorwallmounted", {
minetest.register_node("testnodes:colorwallmounted_nodebox", { minetest.register_node("testnodes:colorwallmounted_nodebox", {
description = S("Color Wallmounted Nodebox Test Node").."\n".. description = S("Color Wallmounted Nodebox Test Node").."\n"..
S("param2 = color + wallmounted rotation (0..5, 8..13, ...)"), S("param2 = color + wallmounted rotation (0..7, 8..15, ...)"),
paramtype2 = "colorwallmounted", paramtype2 = "colorwallmounted",
paramtype = "light", paramtype = "light",
palette = "testnodes_palette_wallmounted.png", palette = "testnodes_palette_wallmounted.png",

@ -61,8 +61,8 @@ minetest.register_node("testnodes:attached", {
-- when the node it attaches to is gone. -- when the node it attaches to is gone.
minetest.register_node("testnodes:attached_wallmounted", { minetest.register_node("testnodes:attached_wallmounted", {
description = S("Wallmounted Attached Node").."\n".. description = S("Wallmounted Attached Node").."\n"..
S("Attaches to wall; drops as item if neighbor node is gone").."\n".. S("Attaches to solid node it was placed on; drops as item if neighbor node is gone").."\n"..
S("param2 = wallmounted rotation (0..5)"), S("param2 = wallmounted rotation (0..7)"),
paramtype2 = "wallmounted", paramtype2 = "wallmounted",
tiles = { tiles = {
"testnodes_attachedw_top.png", "testnodes_attachedw_top.png",
@ -72,9 +72,29 @@ minetest.register_node("testnodes:attached_wallmounted", {
groups = { attached_node = 1, dig_immediate = 3 }, groups = { attached_node = 1, dig_immediate = 3 },
}) })
-- This node attaches to the side of a node and drops as item
-- when the node it attaches to is gone.
-- Also adds vertical 90° rotation variants.
minetest.register_node("testnodes:attached_wallmounted_rot", {
description = S("Rotatable Wallmounted Attached Node").."\n"..
S("Attaches to solid node it was placed on; drops as item if neighbor node is gone").."\n"..
S("param2 = wallmounted rotation (0..7)").."\n"..
S("May be rotated by 90° if placed at floor or ceiling"),
paramtype2 = "wallmounted",
tiles = {
"testnodes_attachedwr_top.png",
"testnodes_attachedwr_bottom.png",
"testnodes_attachedwr_side.png",
},
wallmounted_rotate_vertical = true,
groups = { attached_node = 1, dig_immediate = 3 },
})
-- Wallmounted node that always attaches to the floor -- Wallmounted node that always attaches to the floor
minetest.register_node("testnodes:attached_wallmounted_floor", { minetest.register_node("testnodes:attached_wallmounted_floor", {
description = S("Floor-Attached Wallmounted Node"), description = S("Floor-Attached Wallmounted Node").."\n"..
S("Drops as item if no solid node below (regardless of rotation)").."\n"..
S("param2 = wallmounted rotation (visual only) (0..7)"),
paramtype2 = "wallmounted", paramtype2 = "wallmounted",
tiles = { tiles = {
"testnodes_attached_top.png", "testnodes_attached_top.png",
@ -85,10 +105,28 @@ minetest.register_node("testnodes:attached_wallmounted_floor", {
color = "#FF8080", color = "#FF8080",
}) })
-- Wallmounted node that always attaches to the floor.
-- Also adds 90° rotation variants.
minetest.register_node("testnodes:attached_wallmounted_floor_rot", {
description = S("Rotatable Floor-Attached Wallmounted Node").."\n"..
S("Drops as item if no solid node below (regardless of rotation)").."\n"..
S("param2 = wallmounted rotation (visual only) (0..7)").."\n"..
S("May be rotated by 90° if placed at floor or ceiling"),
paramtype2 = "wallmounted",
tiles = {
"testnodes_attachedfr_top.png",
"testnodes_attachedfr_bottom.png",
"testnodes_attachedfr_side.png",
},
wallmounted_rotate_vertical = true,
groups = { attached_node = 3, dig_immediate = 3 },
})
-- This node attaches to the ceiling and drops as item -- This node attaches to the ceiling and drops as item
-- when the ceiling is gone. -- when the ceiling is gone.
minetest.register_node("testnodes:attached_top", { minetest.register_node("testnodes:attached_top", {
description = S("Ceiling-Attached Node"), description = S("Ceiling-Attached Node").."\n"..
S("Drops as item if no solid node above"),
tiles = { tiles = {
"testnodes_attached_bottom.png", "testnodes_attached_bottom.png",
"testnodes_attached_top.png", "testnodes_attached_top.png",
@ -99,7 +137,9 @@ minetest.register_node("testnodes:attached_top", {
-- Same as wallmounted attached, but for facedir -- Same as wallmounted attached, but for facedir
minetest.register_node("testnodes:attached_facedir", { minetest.register_node("testnodes:attached_facedir", {
description = S("Facedir Attached Node"), description = S("Facedir Attached Node").."\n"..
S("Attaches to a neighboring solid node; drops as item if that node is gone").."\n"..
S("param2 = facedir rotation (0..23)"),
paramtype2 = "facedir", paramtype2 = "facedir",
tiles = { tiles = {
"testnodes_attachedf_side.png^[transformR180", "testnodes_attachedf_side.png^[transformR180",
@ -114,7 +154,9 @@ minetest.register_node("testnodes:attached_facedir", {
-- Same as facedir attached, but for 4dir -- Same as facedir attached, but for 4dir
minetest.register_node("testnodes:attached_4dir", { minetest.register_node("testnodes:attached_4dir", {
description = S("4dir Attached Node"), description = S("4dir Attached Node").."\n"..
S("Attaches to the side of a solid node; drops as item if that node is gone").."\n"..
S("param2 = 4dir rotation (0..3)"),
paramtype2 = "4dir", paramtype2 = "4dir",
tiles = { tiles = {
"testnodes_attached4_side.png^[transformR180", "testnodes_attached4_side.png^[transformR180",
@ -621,3 +663,23 @@ minetest.register_node("testnodes:post_effect_color_shaded_true", {
is_ground_content = false, is_ground_content = false,
groups = {dig_immediate=3}, groups = {dig_immediate=3},
}) })
-- Pointability
-- Register wrapper for compactness
local function register_pointable_test_node(name, description, pointable)
local texture = "testnodes_"..name..".png"
minetest.register_node("testnodes:"..name, {
description = S(description),
tiles = {texture},
drawtype = "glasslike_framed",
paramtype = "light",
walkable = false,
pointable = pointable,
groups = {dig_immediate=3, [name.."_test"]=1},
})
end
register_pointable_test_node("pointable", "Pointable Node", true)
register_pointable_test_node("not_pointable", "Not Pointable Node", false)
register_pointable_test_node("blocking_pointable", "Blocking Pointable Node", "blocking")

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

@ -7,6 +7,20 @@ dofile(minetest.get_modpath("testtools") .. "/light.lua")
dofile(minetest.get_modpath("testtools") .. "/privatizer.lua") dofile(minetest.get_modpath("testtools") .. "/privatizer.lua")
dofile(minetest.get_modpath("testtools") .. "/particles.lua") dofile(minetest.get_modpath("testtools") .. "/particles.lua")
local pointabilities_nodes = {
nodes = {
["group:blocking_pointable_test"] = true,
["group:not_pointable_test"] = true,
},
}
local pointabilities_objects = {
objects = {
["group:blocking_pointable_test"] = true,
["group:not_pointable_test"] = true,
},
}
minetest.register_tool("testtools:param2tool", { minetest.register_tool("testtools:param2tool", {
description = S("Param2 Tool") .."\n".. description = S("Param2 Tool") .."\n"..
S("Modify param2 value of nodes") .."\n".. S("Modify param2 value of nodes") .."\n"..
@ -16,6 +30,7 @@ minetest.register_tool("testtools:param2tool", {
S("Sneak+Place: -8"), S("Sneak+Place: -8"),
inventory_image = "testtools_param2tool.png", inventory_image = "testtools_param2tool.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing) local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type ~= "node" or (not pos) then if pointed_thing.type ~= "node" or (not pos) then
@ -58,6 +73,7 @@ minetest.register_tool("testtools:node_setter", {
S("Place in air: Manually select a node"), S("Place in air: Manually select a node"),
inventory_image = "testtools_node_setter.png", inventory_image = "testtools_node_setter.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing) local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type == "nothing" then if pointed_thing.type == "nothing" then
@ -118,6 +134,10 @@ minetest.register_tool("testtools:remover", {
S("Punch: Remove pointed node or object"), S("Punch: Remove pointed node or object"),
inventory_image = "testtools_remover.png", inventory_image = "testtools_remover.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = {
nodes = pointabilities_nodes.nodes,
objects = pointabilities_objects.objects,
},
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
local pos = minetest.get_pointed_thing_position(pointed_thing) local pos = minetest.get_pointed_thing_position(pointed_thing)
if pointed_thing.type == "node" and pos ~= nil then if pointed_thing.type == "node" and pos ~= nil then
@ -139,6 +159,7 @@ minetest.register_tool("testtools:falling_node_tool", {
S("Place: Move pointed node 2 units upwards, then make it fall"), S("Place: Move pointed node 2 units upwards, then make it fall"),
inventory_image = "testtools_falling_node_tool.png", inventory_image = "testtools_falling_node_tool.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_nodes,
on_place = function(itemstack, user, pointed_thing) on_place = function(itemstack, user, pointed_thing)
-- Teleport node 1-2 units upwards (if possible) and make it fall -- Teleport node 1-2 units upwards (if possible) and make it fall
local pos = minetest.get_pointed_thing_position(pointed_thing) local pos = minetest.get_pointed_thing_position(pointed_thing)
@ -192,6 +213,7 @@ minetest.register_tool("testtools:rotator", {
S("Aux1+Punch: Roll"), S("Aux1+Punch: Roll"),
inventory_image = "testtools_entity_rotator.png", inventory_image = "testtools_entity_rotator.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type ~= "object" then if pointed_thing.type ~= "object" then
return return
@ -250,6 +272,7 @@ minetest.register_tool("testtools:object_mover", {
S("Sneak+Place: Decrease distance"), S("Sneak+Place: Decrease distance"),
inventory_image = "testtools_object_mover.png", inventory_image = "testtools_object_mover.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_place = mover_config, on_place = mover_config,
on_secondary_use = mover_config, on_secondary_use = mover_config,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
@ -296,6 +319,7 @@ minetest.register_tool("testtools:entity_scaler", {
S("Sneak+Punch: Decrease scale"), S("Sneak+Punch: Decrease scale"),
inventory_image = "testtools_entity_scaler.png", inventory_image = "testtools_entity_scaler.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
if pointed_thing.type ~= "object" then if pointed_thing.type ~= "object" then
return return
@ -355,6 +379,7 @@ minetest.register_tool("testtools:branding_iron", {
S("Devices that accept the returned name also accept \"player:<playername>\" for players."), S("Devices that accept the returned name also accept \"player:<playername>\" for players."),
inventory_image = "testtools_branding_iron.png", inventory_image = "testtools_branding_iron.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(_itemstack, user, pointed_thing) on_use = function(_itemstack, user, pointed_thing)
local obj local obj
local msg local msg
@ -499,6 +524,7 @@ minetest.register_tool("testtools:object_editor", {
S("Punch air: Edit yourself"), S("Punch air: Edit yourself"),
inventory_image = "testtools_object_editor.png", inventory_image = "testtools_object_editor.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
if user and user:is_player() then if user and user:is_player() then
local name = user:get_player_name() local name = user:get_player_name()
@ -586,6 +612,7 @@ minetest.register_tool("testtools:object_attacher", {
S("Aux1+Sneak+Place: Decrease attachment rotation"), S("Aux1+Sneak+Place: Decrease attachment rotation"),
inventory_image = "testtools_object_attacher.png", inventory_image = "testtools_object_attacher.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_place = attacher_config, on_place = attacher_config,
on_secondary_use = attacher_config, on_secondary_use = attacher_config,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
@ -679,6 +706,7 @@ minetest.register_tool("testtools:children_getter", {
S("Punch air to show your own 'children'"), S("Punch air to show your own 'children'"),
inventory_image = "testtools_children_getter.png", inventory_image = "testtools_children_getter.png",
groups = { testtool = 1, disable_repair = 1 }, groups = { testtool = 1, disable_repair = 1 },
pointabilities = pointabilities_objects,
on_use = function(itemstack, user, pointed_thing) on_use = function(itemstack, user, pointed_thing)
if user and user:is_player() then if user and user:is_player() then
local name = user:get_player_name() local name = user:get_player_name()
@ -998,3 +1026,41 @@ minetest.register_on_leaveplayer(function(player)
meta_latest_keylist[name] = nil meta_latest_keylist[name] = nil
node_meta_posses[name] = nil node_meta_posses[name] = nil
end) end)
-- Pointing Staffs
minetest.register_tool("testtools:blocked_pointing_staff", {
description = S("Blocked Pointing Staff").."\n"..
S("Can point the Blocking Pointable Node/Object and "..
"the Pointable Node/Object is point blocking."),
inventory_image = "testtools_blocked_pointing_staff.png",
pointabilities = {
nodes = {
["testnodes:blocking_pointable"] = true,
["group:pointable_test"] = "blocking"
},
objects = {
["testentities:blocking_pointable"] = true,
["group:pointable_test"] = "blocking"
}
}
})
minetest.register_tool("testtools:ultimate_pointing_staff", {
description = S("Ultimate Pointing Staff").."\n"..
S("Can point all pointable test nodes, objects and liquids."),
inventory_image = "testtools_ultimate_pointing_staff.png",
liquids_pointable = true,
pointabilities = {
nodes = {
["group:blocking_pointable_test"] = true,
["group:pointable_test"] = true,
["testnodes:not_pointable"] = true
},
objects = {
["group:blocking_pointable_test"] = true,
["group:pointable_test"] = true,
["testentities:not_pointable"] = true
}
}
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

@ -1,10 +1,37 @@
local function test_random() local function test_pseudo_random()
-- Try out PseudoRandom -- We have comprehensive unit tests in C++, this is just to make sure the API code isn't messing up
local pseudo = PseudoRandom(13) local gen1 = PseudoRandom(13)
assert(pseudo:next() == 22290) assert(gen1:next() == 22290)
assert(pseudo:next() == 13854) assert(gen1:next() == 13854)
local gen2 = PseudoRandom(gen1:get_state())
for n = 0, 16 do
assert(gen1:next() == gen2:next())
end
local pr3 = PseudoRandom(-101)
assert(pr3:next(0, 100) == 35)
-- unusual case that is normally disallowed:
assert(pr3:next(10000, 42767) == 12485)
end end
unittests.register("test_random", test_random) unittests.register("test_pseudo_random", test_pseudo_random)
local function test_pcg_random()
-- We have comprehensive unit tests in C++, this is just to make sure the API code isn't messing up
local gen1 = PcgRandom(55)
for n = 0, 16 do
gen1:next()
end
local gen2 = PcgRandom(26)
gen2:set_state(gen1:get_state())
for n = 16, 32 do
assert(gen1:next() == gen2:next())
end
end
unittests.register("test_pcg_random", test_pcg_random)
local function test_dynamic_media(cb, player) local function test_dynamic_media(cb, player)
if core.get_player_information(player:get_player_name()).protocol_version < 40 then if core.get_player_information(player:get_player_name()).protocol_version < 40 then

@ -1,6 +1,6 @@
/* /*
* Catch v2.13.9 * Catch v2.13.10
* Generated: 2022-04-12 22:37:23.260201 * Generated: 2022-10-16 11:01:23.452308
* ---------------------------------------------------------- * ----------------------------------------------------------
* This file has been merged from multiple headers. Please don't edit it directly * This file has been merged from multiple headers. Please don't edit it directly
* Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved. * Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved.
@ -15,7 +15,7 @@
#define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MAJOR 2
#define CATCH_VERSION_MINOR 13 #define CATCH_VERSION_MINOR 13
#define CATCH_VERSION_PATCH 9 #define CATCH_VERSION_PATCH 10
#ifdef __clang__ #ifdef __clang__
# pragma clang system_header # pragma clang system_header
@ -7395,8 +7395,6 @@ namespace Catch {
template <typename T, bool Destruct> template <typename T, bool Destruct>
struct ObjectStorage struct ObjectStorage
{ {
using TStorage = typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type;
ObjectStorage() : data() {} ObjectStorage() : data() {}
ObjectStorage(const ObjectStorage& other) ObjectStorage(const ObjectStorage& other)
@ -7439,7 +7437,7 @@ namespace Catch {
return *static_cast<T*>(static_cast<void*>(&data)); return *static_cast<T*>(static_cast<void*>(&data));
} }
TStorage data; struct { alignas(T) unsigned char data[sizeof(T)]; } data;
}; };
} }
@ -7949,7 +7947,7 @@ namespace Catch {
#if defined(__i386__) || defined(__x86_64__) #if defined(__i386__) || defined(__x86_64__)
#define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */
#elif defined(__aarch64__) #elif defined(__aarch64__)
#define CATCH_TRAP() __asm__(".inst 0xd4200000") #define CATCH_TRAP() __asm__(".inst 0xd43e0000")
#endif #endif
#elif defined(CATCH_PLATFORM_IPHONE) #elif defined(CATCH_PLATFORM_IPHONE)
@ -13558,7 +13556,7 @@ namespace Catch {
// Handle list request // Handle list request
if( Option<std::size_t> listed = list( m_config ) ) if( Option<std::size_t> listed = list( m_config ) )
return static_cast<int>( *listed ); return (std::min) (MaxExitCode, static_cast<int>(*listed));
TestGroup tests { m_config }; TestGroup tests { m_config };
auto const totals = tests.execute(); auto const totals = tests.execute();
@ -15391,7 +15389,7 @@ namespace Catch {
} }
Version const& libraryVersion() { Version const& libraryVersion() {
static Version version( 2, 13, 9, "", 0 ); static Version version( 2, 13, 10, "", 0 );
return version; return version;
} }
@ -16314,7 +16312,7 @@ class Duration {
Unit m_units; Unit m_units;
public: public:
explicit Duration(double inNanoseconds, Unit units = Unit::Microseconds) explicit Duration(double inNanoseconds, Unit units = Unit::Auto)
: m_inNanoseconds(inNanoseconds), : m_inNanoseconds(inNanoseconds),
m_units(units) { m_units(units) {
if (m_units == Unit::Auto) { if (m_units == Unit::Auto) {
@ -16364,7 +16362,7 @@ public:
} }
friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& {
return os << std::fixed << duration.value() << ' ' << duration.unitsAsString(); return os << duration.value() << ' ' << duration.unitsAsString();
} }
}; };
} // end anon namespace } // end anon namespace
@ -17526,12 +17524,20 @@ namespace Catch {
#ifndef __OBJC__ #ifndef __OBJC__
#ifndef CATCH_INTERNAL_CDECL
#ifdef _MSC_VER
#define CATCH_INTERNAL_CDECL __cdecl
#else
#define CATCH_INTERNAL_CDECL
#endif
#endif
#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) #if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
// Standard C/C++ Win32 Unicode wmain entry point // Standard C/C++ Win32 Unicode wmain entry point
extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { extern "C" int CATCH_INTERNAL_CDECL wmain (int argc, wchar_t * argv[], wchar_t * []) {
#else #else
// Standard C/C++ main entry point // Standard C/C++ main entry point
int main (int argc, char * argv[]) { int CATCH_INTERNAL_CDECL main (int argc, char * argv[]) {
#endif #endif
return Catch::Session().run( argc, argv ); return Catch::Session().run( argc, argv );

@ -1,27 +1,39 @@
/* mini-gmp, a minimalistic implementation of a GNU GMP subset. /* mini-gmp, a minimalistic implementation of a GNU GMP subset.
Contributed to the GNU project by Niels Möller Contributed to the GNU project by Niels Möller
Additional functionalities and improvements by Marco Bodrato.
Copyright 1991-1997, 1999-2019 Free Software Foundation, Inc. Copyright 1991-1997, 1999-2022 Free Software Foundation, Inc.
This file is part of the GNU MP Library. This file is part of the GNU MP Library.
The GNU MP Library is free software; you can redistribute it and/or modify The GNU MP Library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by it under the terms of either:
the Free Software Foundation; either version 3 of the License, or (at your
option) any later version. * the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your
option) any later version.
or
* the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any
later version.
or both in parallel, as here.
The GNU MP Library is distributed in the hope that it will be useful, but The GNU MP Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
License for more details. for more details.
You should have received a copy of the GNU Lesser General Public License You should have received copies of the GNU General Public License and the
along with the GNU MP Library. If not, see http://www.gnu.org/licenses/. */ GNU Lesser General Public License along with the GNU MP Library. If not,
see https://www.gnu.org/licenses/. */
/* NOTE: All functions in this file which are not declared in /* NOTE: All functions in this file which are not declared in
mini-gmp.h are internal, and are not intended to be compatible mini-gmp.h are internal, and are not intended to be compatible
neither with GMP nor with future versions of mini-gmp. */ with GMP or with future versions of mini-gmp. */
/* Much of the material copied from GMP files, including: gmp-impl.h, /* Much of the material copied from GMP files, including: gmp-impl.h,
longlong.h, mpn/generic/add_n.c, mpn/generic/addmul_1.c, longlong.h, mpn/generic/add_n.c, mpn/generic/addmul_1.c,
@ -43,7 +55,7 @@ along with the GNU MP Library. If not, see http://www.gnu.org/licenses/. */
#include <float.h> #include <float.h>
#endif #endif
/* Macros */ /* Macros */
#define GMP_LIMB_BITS (sizeof(mp_limb_t) * CHAR_BIT) #define GMP_LIMB_BITS (sizeof(mp_limb_t) * CHAR_BIT)
@ -79,6 +91,7 @@ along with the GNU MP Library. If not, see http://www.gnu.org/licenses/. */
#define gmp_assert_nocarry(x) do { \ #define gmp_assert_nocarry(x) do { \
mp_limb_t __cy = (x); \ mp_limb_t __cy = (x); \
assert (__cy == 0); \ assert (__cy == 0); \
(void) (__cy); \
} while (0) } while (0)
#define gmp_clz(count, x) do { \ #define gmp_clz(count, x) do { \
@ -137,6 +150,7 @@ along with the GNU MP Library. If not, see http://www.gnu.org/licenses/. */
mp_limb_t __x0, __x1, __x2, __x3; \ mp_limb_t __x0, __x1, __x2, __x3; \
unsigned __ul, __vl, __uh, __vh; \ unsigned __ul, __vl, __uh, __vh; \
mp_limb_t __u = (u), __v = (v); \ mp_limb_t __u = (u), __v = (v); \
assert (sizeof (unsigned) * 2 >= sizeof (mp_limb_t)); \
\ \
__ul = __u & GMP_LLIMB_MASK; \ __ul = __u & GMP_LLIMB_MASK; \
__uh = __u >> (GMP_LIMB_BITS / 2); \ __uh = __u >> (GMP_LIMB_BITS / 2); \
@ -158,12 +172,19 @@ along with the GNU MP Library. If not, see http://www.gnu.org/licenses/. */
} \ } \
} while (0) } while (0)
/* If mp_limb_t is of size smaller than int, plain u*v implies
automatic promotion to *signed* int, and then multiply may overflow
and cause undefined behavior. Explicitly cast to unsigned int for
that case. */
#define gmp_umullo_limb(u, v) \
((sizeof(mp_limb_t) >= sizeof(int)) ? (u)*(v) : (unsigned int)(u) * (v))
#define gmp_udiv_qrnnd_preinv(q, r, nh, nl, d, di) \ #define gmp_udiv_qrnnd_preinv(q, r, nh, nl, d, di) \
do { \ do { \
mp_limb_t _qh, _ql, _r, _mask; \ mp_limb_t _qh, _ql, _r, _mask; \
gmp_umul_ppmm (_qh, _ql, (nh), (di)); \ gmp_umul_ppmm (_qh, _ql, (nh), (di)); \
gmp_add_ssaaaa (_qh, _ql, _qh, _ql, (nh) + 1, (nl)); \ gmp_add_ssaaaa (_qh, _ql, _qh, _ql, (nh) + 1, (nl)); \
_r = (nl) - _qh * (d); \ _r = (nl) - gmp_umullo_limb (_qh, (d)); \
_mask = -(mp_limb_t) (_r > _ql); /* both > and >= are OK */ \ _mask = -(mp_limb_t) (_r > _ql); /* both > and >= are OK */ \
_qh += _mask; \ _qh += _mask; \
_r += _mask & (d); \ _r += _mask & (d); \
@ -184,7 +205,7 @@ along with the GNU MP Library. If not, see http://www.gnu.org/licenses/. */
gmp_add_ssaaaa ((q), _q0, (q), _q0, (n2), (n1)); \ gmp_add_ssaaaa ((q), _q0, (q), _q0, (n2), (n1)); \
\ \
/* Compute the two most significant limbs of n - q'd */ \ /* Compute the two most significant limbs of n - q'd */ \
(r1) = (n1) - (d1) * (q); \ (r1) = (n1) - gmp_umullo_limb ((d1), (q)); \
gmp_sub_ddmmss ((r1), (r0), (r1), (n0), (d1), (d0)); \ gmp_sub_ddmmss ((r1), (r0), (r1), (n0), (d1), (d0)); \
gmp_umul_ppmm (_t1, _t0, (d0), (q)); \ gmp_umul_ppmm (_t1, _t0, (d0), (q)); \
gmp_sub_ddmmss ((r1), (r0), (r1), (r0), _t1, _t0); \ gmp_sub_ddmmss ((r1), (r0), (r1), (r0), _t1, _t0); \
@ -340,20 +361,27 @@ mp_set_memory_functions (void *(*alloc_func) (size_t),
gmp_free_func = free_func; gmp_free_func = free_func;
} }
#define gmp_xalloc(size) ((*gmp_allocate_func)((size))) #define gmp_alloc(size) ((*gmp_allocate_func)((size)))
#define gmp_free(p) ((*gmp_free_func) ((p), 0)) #define gmp_free(p, size) ((*gmp_free_func) ((p), (size)))
#define gmp_realloc(ptr, old_size, size) ((*gmp_reallocate_func)(ptr, old_size, size))
static mp_ptr static mp_ptr
gmp_xalloc_limbs (mp_size_t size) gmp_alloc_limbs (mp_size_t size)
{ {
return (mp_ptr) gmp_xalloc (size * sizeof (mp_limb_t)); return (mp_ptr) gmp_alloc (size * sizeof (mp_limb_t));
} }
static mp_ptr static mp_ptr
gmp_xrealloc_limbs (mp_ptr old, mp_size_t size) gmp_realloc_limbs (mp_ptr old, mp_size_t old_size, mp_size_t size)
{ {
assert (size > 0); assert (size > 0);
return (mp_ptr) (*gmp_reallocate_func) (old, 0, size * sizeof (mp_limb_t)); return (mp_ptr) gmp_realloc (old, old_size * sizeof (mp_limb_t), size * sizeof (mp_limb_t));
}
static void
gmp_free_limbs (mp_ptr old, mp_size_t size)
{
gmp_free (old, size * sizeof (mp_limb_t));
} }
@ -765,6 +793,7 @@ mpn_invert_3by2 (mp_limb_t u1, mp_limb_t u0)
mp_limb_t p, ql; mp_limb_t p, ql;
unsigned ul, uh, qh; unsigned ul, uh, qh;
assert (sizeof (unsigned) * 2 >= sizeof (mp_limb_t));
/* For notation, let b denote the half-limb base, so that B = b^2. /* For notation, let b denote the half-limb base, so that B = b^2.
Split u1 = b uh + ul. */ Split u1 = b uh + ul. */
ul = u1 & GMP_LLIMB_MASK; ul = u1 & GMP_LLIMB_MASK;
@ -945,11 +974,17 @@ mpn_div_qr_1_preinv (mp_ptr qp, mp_srcptr np, mp_size_t nn,
mp_limb_t d, di; mp_limb_t d, di;
mp_limb_t r; mp_limb_t r;
mp_ptr tp = NULL; mp_ptr tp = NULL;
mp_size_t tn = 0;
if (inv->shift > 0) if (inv->shift > 0)
{ {
/* Shift, reusing qp area if possible. In-place shift if qp == np. */ /* Shift, reusing qp area if possible. In-place shift if qp == np. */
tp = qp ? qp : gmp_xalloc_limbs (nn); tp = qp;
if (!tp)
{
tn = nn;
tp = gmp_alloc_limbs (tn);
}
r = mpn_lshift (tp, np, nn, inv->shift); r = mpn_lshift (tp, np, nn, inv->shift);
np = tp; np = tp;
} }
@ -966,8 +1001,8 @@ mpn_div_qr_1_preinv (mp_ptr qp, mp_srcptr np, mp_size_t nn,
if (qp) if (qp)
qp[nn] = q; qp[nn] = q;
} }
if ((inv->shift > 0) && (tp != qp)) if (tn)
gmp_free (tp); gmp_free_limbs (tp, tn);
return r >> inv->shift; return r >> inv->shift;
} }
@ -1125,13 +1160,13 @@ mpn_div_qr (mp_ptr qp, mp_ptr np, mp_size_t nn, mp_srcptr dp, mp_size_t dn)
mpn_div_qr_invert (&inv, dp, dn); mpn_div_qr_invert (&inv, dp, dn);
if (dn > 2 && inv.shift > 0) if (dn > 2 && inv.shift > 0)
{ {
tp = gmp_xalloc_limbs (dn); tp = gmp_alloc_limbs (dn);
gmp_assert_nocarry (mpn_lshift (tp, dp, dn, inv.shift)); gmp_assert_nocarry (mpn_lshift (tp, dp, dn, inv.shift));
dp = tp; dp = tp;
} }
mpn_div_qr_preinv (qp, np, nn, dp, dn, &inv); mpn_div_qr_preinv (qp, np, nn, dp, dn, &inv);
if (tp) if (tp)
gmp_free (tp); gmp_free_limbs (tp, dn);
} }
@ -1307,29 +1342,26 @@ mpn_set_str_bits (mp_ptr rp, const unsigned char *sp, size_t sn,
unsigned bits) unsigned bits)
{ {
mp_size_t rn; mp_size_t rn;
size_t j; mp_limb_t limb;
unsigned shift; unsigned shift;
for (j = sn, rn = 0, shift = 0; j-- > 0; ) for (limb = 0, rn = 0, shift = 0; sn-- > 0; )
{ {
if (shift == 0) limb |= (mp_limb_t) sp[sn] << shift;
shift += bits;
if (shift >= GMP_LIMB_BITS)
{ {
rp[rn++] = sp[j]; shift -= GMP_LIMB_BITS;
shift += bits; rp[rn++] = limb;
} /* Next line is correct also if shift == 0,
else bits == 8, and mp_limb_t == unsigned char. */
{ limb = (unsigned int) sp[sn] >> (bits - shift);
rp[rn-1] |= (mp_limb_t) sp[j] << shift;
shift += bits;
if (shift >= GMP_LIMB_BITS)
{
shift -= GMP_LIMB_BITS;
if (shift > 0)
rp[rn++] = (mp_limb_t) sp[j] >> (bits - shift);
}
} }
} }
rn = mpn_normalized_size (rp, rn); if (limb != 0)
rp[rn++] = limb;
else
rn = mpn_normalized_size (rp, rn);
return rn; return rn;
} }
@ -1417,14 +1449,14 @@ mpz_init2 (mpz_t r, mp_bitcnt_t bits)
r->_mp_alloc = rn; r->_mp_alloc = rn;
r->_mp_size = 0; r->_mp_size = 0;
r->_mp_d = gmp_xalloc_limbs (rn); r->_mp_d = gmp_alloc_limbs (rn);
} }
void void
mpz_clear (mpz_t r) mpz_clear (mpz_t r)
{ {
if (r->_mp_alloc) if (r->_mp_alloc)
gmp_free (r->_mp_d); gmp_free_limbs (r->_mp_d, r->_mp_alloc);
} }
static mp_ptr static mp_ptr
@ -1433,9 +1465,9 @@ mpz_realloc (mpz_t r, mp_size_t size)
size = GMP_MAX (size, 1); size = GMP_MAX (size, 1);
if (r->_mp_alloc) if (r->_mp_alloc)
r->_mp_d = gmp_xrealloc_limbs (r->_mp_d, size); r->_mp_d = gmp_realloc_limbs (r->_mp_d, r->_mp_alloc, size);
else else
r->_mp_d = gmp_xalloc_limbs (size); r->_mp_d = gmp_alloc_limbs (size);
r->_mp_alloc = size; r->_mp_alloc = size;
if (GMP_ABS (r->_mp_size) > size) if (GMP_ABS (r->_mp_size) > size)
@ -1530,8 +1562,7 @@ mpz_init_set (mpz_t r, const mpz_t x)
int int
mpz_fits_slong_p (const mpz_t u) mpz_fits_slong_p (const mpz_t u)
{ {
return (LONG_MAX + LONG_MIN == 0 || mpz_cmp_ui (u, LONG_MAX) <= 0) && return mpz_cmp_si (u, LONG_MAX) <= 0 && mpz_cmp_si (u, LONG_MIN) >= 0;
mpz_cmpabs_ui (u, GMP_NEG_CAST (unsigned long int, LONG_MIN)) <= 0;
} }
static int static int
@ -1554,6 +1585,30 @@ mpz_fits_ulong_p (const mpz_t u)
return us >= 0 && mpn_absfits_ulong_p (u->_mp_d, us); return us >= 0 && mpn_absfits_ulong_p (u->_mp_d, us);
} }
int
mpz_fits_sint_p (const mpz_t u)
{
return mpz_cmp_si (u, INT_MAX) <= 0 && mpz_cmp_si (u, INT_MIN) >= 0;
}
int
mpz_fits_uint_p (const mpz_t u)
{
return u->_mp_size >= 0 && mpz_cmpabs_ui (u, UINT_MAX) <= 0;
}
int
mpz_fits_sshort_p (const mpz_t u)
{
return mpz_cmp_si (u, SHRT_MAX) <= 0 && mpz_cmp_si (u, SHRT_MIN) >= 0;
}
int
mpz_fits_ushort_p (const mpz_t u)
{
return u->_mp_size >= 0 && mpz_cmpabs_ui (u, USHRT_MAX) <= 0;
}
long int long int
mpz_get_si (const mpz_t u) mpz_get_si (const mpz_t u)
{ {
@ -1891,9 +1946,8 @@ mpz_neg (mpz_t r, const mpz_t u)
void void
mpz_swap (mpz_t u, mpz_t v) mpz_swap (mpz_t u, mpz_t v)
{ {
MP_SIZE_T_SWAP (u->_mp_size, v->_mp_size);
MP_SIZE_T_SWAP (u->_mp_alloc, v->_mp_alloc); MP_SIZE_T_SWAP (u->_mp_alloc, v->_mp_alloc);
MP_PTR_SWAP (u->_mp_d, v->_mp_d); MPN_PTR_SWAP (u->_mp_d, u->_mp_size, v->_mp_d, v->_mp_size);
} }
@ -2676,7 +2730,7 @@ mpz_make_odd (mpz_t r)
assert (r->_mp_size > 0); assert (r->_mp_size > 0);
/* Count trailing zeros, equivalent to mpn_scan1, because we know that there is a 1 */ /* Count trailing zeros, equivalent to mpn_scan1, because we know that there is a 1 */
shift = mpn_common_scan (r->_mp_d[0], 0, r->_mp_d, 0, 0); shift = mpn_scan1 (r->_mp_d, 0);
mpz_tdiv_q_2exp (r, r, shift); mpz_tdiv_q_2exp (r, r, shift);
return shift; return shift;
@ -2733,9 +2787,13 @@ mpz_gcd (mpz_t g, const mpz_t u, const mpz_t v)
if (tv->_mp_size == 1) if (tv->_mp_size == 1)
{ {
mp_limb_t vl = tv->_mp_d[0]; mp_limb_t *gp;
mp_limb_t ul = mpz_tdiv_ui (tu, vl);
mpz_set_ui (g, mpn_gcd_11 (ul, vl)); mpz_tdiv_r (tu, tu, tv);
gp = MPZ_REALLOC (g, 1); /* gp = mpz_limbs_modify (g, 1); */
*gp = mpn_gcd_11 (tu->_mp_d[0], tv->_mp_d[0]);
g->_mp_size = *gp != 0; /* mpz_limbs_finish (g, 1); */
break; break;
} }
mpz_sub (tu, tu, tv); mpz_sub (tu, tu, tv);
@ -2824,7 +2882,6 @@ mpz_gcdext (mpz_t g, mpz_t s, mpz_t t, const mpz_t u, const mpz_t v)
* s0 = 0, s1 = 2^vz * s0 = 0, s1 = 2^vz
*/ */
mpz_setbit (t0, uz);
mpz_tdiv_qr (t1, tu, tu, tv); mpz_tdiv_qr (t1, tu, tu, tv);
mpz_mul_2exp (t1, t1, uz); mpz_mul_2exp (t1, t1, uz);
@ -2835,8 +2892,7 @@ mpz_gcdext (mpz_t g, mpz_t s, mpz_t t, const mpz_t u, const mpz_t v)
{ {
mp_bitcnt_t shift; mp_bitcnt_t shift;
shift = mpz_make_odd (tu); shift = mpz_make_odd (tu);
mpz_mul_2exp (t0, t0, shift); mpz_setbit (t0, uz + shift);
mpz_mul_2exp (s0, s0, shift);
power += shift; power += shift;
for (;;) for (;;)
@ -2874,6 +2930,8 @@ mpz_gcdext (mpz_t g, mpz_t s, mpz_t t, const mpz_t u, const mpz_t v)
power += shift; power += shift;
} }
} }
else
mpz_setbit (t0, uz);
/* Now tv = odd part of gcd, and -s0 and t0 are corresponding /* Now tv = odd part of gcd, and -s0 and t0 are corresponding
cofactors. */ cofactors. */
@ -3048,7 +3106,7 @@ mpz_powm (mpz_t r, const mpz_t b, const mpz_t e, const mpz_t m)
if (en == 0) if (en == 0)
{ {
mpz_set_ui (r, 1); mpz_set_ui (r, mpz_cmpabs_ui (m, 1));
return; return;
} }
@ -3062,7 +3120,7 @@ mpz_powm (mpz_t r, const mpz_t b, const mpz_t e, const mpz_t m)
one, using a *normalized* m. */ one, using a *normalized* m. */
minv.shift = 0; minv.shift = 0;
tp = gmp_xalloc_limbs (mn); tp = gmp_alloc_limbs (mn);
gmp_assert_nocarry (mpn_lshift (tp, mp, mn, shift)); gmp_assert_nocarry (mpn_lshift (tp, mp, mn, shift));
mp = tp; mp = tp;
} }
@ -3128,7 +3186,7 @@ mpz_powm (mpz_t r, const mpz_t b, const mpz_t e, const mpz_t m)
tr->_mp_size = mpn_normalized_size (tr->_mp_d, mn); tr->_mp_size = mpn_normalized_size (tr->_mp_d, mn);
} }
if (tp) if (tp)
gmp_free (tp); gmp_free_limbs (tp, mn);
mpz_swap (r, tr); mpz_swap (r, tr);
mpz_clear (tr); mpz_clear (tr);
@ -3150,6 +3208,7 @@ void
mpz_rootrem (mpz_t x, mpz_t r, const mpz_t y, unsigned long z) mpz_rootrem (mpz_t x, mpz_t r, const mpz_t y, unsigned long z)
{ {
int sgn; int sgn;
mp_bitcnt_t bc;
mpz_t t, u; mpz_t t, u;
sgn = y->_mp_size < 0; sgn = y->_mp_size < 0;
@ -3168,7 +3227,8 @@ mpz_rootrem (mpz_t x, mpz_t r, const mpz_t y, unsigned long z)
mpz_init (u); mpz_init (u);
mpz_init (t); mpz_init (t);
mpz_setbit (t, mpz_sizeinbase (y, 2) / z + 1); bc = (mpz_sizeinbase (y, 2) - 1) / z + 1;
mpz_setbit (t, bc);
if (z == 2) /* simplify sqrt loop: z-1 == 1 */ if (z == 2) /* simplify sqrt loop: z-1 == 1 */
do { do {
@ -3339,13 +3399,15 @@ gmp_jacobi_coprime (mp_limb_t a, mp_limb_t b)
gmp_ctz(c, a); gmp_ctz(c, a);
a >>= 1; a >>= 1;
do for (;;)
{ {
a >>= c; a >>= c;
/* (2/b) = -1 if b = 3 or 5 mod 8 */ /* (2/b) = -1 if b = 3 or 5 mod 8 */
bit ^= c & (b ^ (b >> 1)); bit ^= c & (b ^ (b >> 1));
if (a < b) if (a < b)
{ {
if (a == 0)
return bit & 1 ? -1 : 1;
bit ^= a & b; bit ^= a & b;
a = b - a; a = b - a;
b -= a; b -= a;
@ -3359,9 +3421,6 @@ gmp_jacobi_coprime (mp_limb_t a, mp_limb_t b)
gmp_ctz(c, a); gmp_ctz(c, a);
++c; ++c;
} }
while (b > 0);
return bit & 1 ? -1 : 1;
} }
static void static void
@ -3407,7 +3466,7 @@ gmp_lucas_mod (mpz_t V, mpz_t Qk, long Q,
gmp_lucas_step_k_2k (V, Qk, n); gmp_lucas_step_k_2k (V, Qk, n);
/* A step k->k+1 is performed if the bit in $n$ is 1 */ /* A step k->k+1 is performed if the bit in $n$ is 1 */
/* mpz_tstbit(n,bs) or the the bit is 0 in $n$ but */ /* mpz_tstbit(n,bs) or the bit is 0 in $n$ but */
/* should be 1 in $n+1$ (bs == b0) */ /* should be 1 in $n+1$ (bs == b0) */
if (b0 == bs || mpz_tstbit (n, bs)) if (b0 == bs || mpz_tstbit (n, bs))
{ {
@ -3476,7 +3535,8 @@ gmp_stronglucas (const mpz_t x, mpz_t Qk)
mpz_init (V); mpz_init (V);
/* n-(D/n) = n+1 = d*2^{b0}, with d = (n>>b0) | 1 */ /* n-(D/n) = n+1 = d*2^{b0}, with d = (n>>b0) | 1 */
b0 = mpz_scan0 (n, 0); b0 = mpn_common_scan (~ n->_mp_d[0], 0, n->_mp_d, n->_mp_size, GMP_LIMB_MAX);
/* b0 = mpz_scan0 (n, 0); */
/* D= P^2 - 4Q; P = 1; Q = (1-D)/4 */ /* D= P^2 - 4Q; P = 1; Q = (1-D)/4 */
Q = (D & 2) ? (long) (D >> 2) + 1 : -(long) (D >> 2); Q = (D & 2) ? (long) (D >> 2) + 1 : -(long) (D >> 2);
@ -3508,11 +3568,6 @@ gmp_millerrabin (const mpz_t n, const mpz_t nm1, mpz_t y,
mpz_powm_ui (y, y, 2, n); mpz_powm_ui (y, y, 2, n);
if (mpz_cmp (y, nm1) == 0) if (mpz_cmp (y, nm1) == 0)
return 1; return 1;
/* y == 1 means that the previous y was a non-trivial square root
of 1 (mod n). y == 0 means that n is a power of the base.
In either case, n is not prime. */
if (mpz_cmp_ui (y, 1) <= 0)
return 0;
} }
return 0; return 0;
} }
@ -3558,7 +3613,8 @@ mpz_probab_prime_p (const mpz_t n, int reps)
/* Find q and k, where q is odd and n = 1 + 2**k * q. */ /* Find q and k, where q is odd and n = 1 + 2**k * q. */
mpz_abs (nm1, n); mpz_abs (nm1, n);
nm1->_mp_d[0] -= 1; nm1->_mp_d[0] -= 1;
k = mpz_scan1 (nm1, 0); /* Count trailing zeros, equivalent to mpn_scan1, because we know that there is a 1 */
k = mpn_scan1 (nm1->_mp_d, 0);
mpz_tdiv_q_2exp (q, nm1, k); mpz_tdiv_q_2exp (q, nm1, k);
/* BPSW test */ /* BPSW test */
@ -4133,7 +4189,7 @@ mpz_scan0 (const mpz_t u, mp_bitcnt_t starting_bit)
size_t size_t
mpz_sizeinbase (const mpz_t u, int base) mpz_sizeinbase (const mpz_t u, int base)
{ {
mp_size_t un; mp_size_t un, tn;
mp_srcptr up; mp_srcptr up;
mp_ptr tp; mp_ptr tp;
mp_bitcnt_t bits; mp_bitcnt_t bits;
@ -4166,20 +4222,21 @@ mpz_sizeinbase (const mpz_t u, int base)
10. */ 10. */
} }
tp = gmp_xalloc_limbs (un); tp = gmp_alloc_limbs (un);
mpn_copyi (tp, up, un); mpn_copyi (tp, up, un);
mpn_div_qr_1_invert (&bi, base); mpn_div_qr_1_invert (&bi, base);
tn = un;
ndigits = 0; ndigits = 0;
do do
{ {
ndigits++; ndigits++;
mpn_div_qr_1_preinv (tp, tp, un, &bi); mpn_div_qr_1_preinv (tp, tp, tn, &bi);
un -= (tp[un-1] == 0); tn -= (tp[tn-1] == 0);
} }
while (un > 0); while (tn > 0);
gmp_free (tp); gmp_free_limbs (tp, un);
return ndigits; return ndigits;
} }
@ -4189,7 +4246,7 @@ mpz_get_str (char *sp, int base, const mpz_t u)
unsigned bits; unsigned bits;
const char *digits; const char *digits;
mp_size_t un; mp_size_t un;
size_t i, sn; size_t i, sn, osn;
digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
if (base > 1) if (base > 1)
@ -4210,15 +4267,19 @@ mpz_get_str (char *sp, int base, const mpz_t u)
sn = 1 + mpz_sizeinbase (u, base); sn = 1 + mpz_sizeinbase (u, base);
if (!sp) if (!sp)
sp = (char *) gmp_xalloc (1 + sn); {
osn = 1 + sn;
sp = (char *) gmp_alloc (osn);
}
else
osn = 0;
un = GMP_ABS (u->_mp_size); un = GMP_ABS (u->_mp_size);
if (un == 0) if (un == 0)
{ {
sp[0] = '0'; sp[0] = '0';
sp[1] = '\0'; sn = 1;
return sp; goto ret;
} }
i = 0; i = 0;
@ -4237,17 +4298,20 @@ mpz_get_str (char *sp, int base, const mpz_t u)
mp_ptr tp; mp_ptr tp;
mpn_get_base_info (&info, base); mpn_get_base_info (&info, base);
tp = gmp_xalloc_limbs (un); tp = gmp_alloc_limbs (un);
mpn_copyi (tp, u->_mp_d, un); mpn_copyi (tp, u->_mp_d, un);
sn = i + mpn_get_str_other ((unsigned char *) sp + i, base, &info, tp, un); sn = i + mpn_get_str_other ((unsigned char *) sp + i, base, &info, tp, un);
gmp_free (tp); gmp_free_limbs (tp, un);
} }
for (; i < sn; i++) for (; i < sn; i++)
sp[i] = digits[(unsigned char) sp[i]]; sp[i] = digits[(unsigned char) sp[i]];
ret:
sp[sn] = '\0'; sp[sn] = '\0';
if (osn && osn != sn + 1)
sp = (char*) gmp_realloc (sp, osn, sn + 1);
return sp; return sp;
} }
@ -4257,7 +4321,7 @@ mpz_set_str (mpz_t r, const char *sp, int base)
unsigned bits, value_of_a; unsigned bits, value_of_a;
mp_size_t rn, alloc; mp_size_t rn, alloc;
mp_ptr rp; mp_ptr rp;
size_t dn; size_t dn, sn;
int sign; int sign;
unsigned char *dp; unsigned char *dp;
@ -4295,7 +4359,8 @@ mpz_set_str (mpz_t r, const char *sp, int base)
r->_mp_size = 0; r->_mp_size = 0;
return -1; return -1;
} }
dp = (unsigned char *) gmp_xalloc (strlen (sp)); sn = strlen(sp);
dp = (unsigned char *) gmp_alloc (sn);
value_of_a = (base > 36) ? 36 : 10; value_of_a = (base > 36) ? 36 : 10;
for (dn = 0; *sp; sp++) for (dn = 0; *sp; sp++)
@ -4315,7 +4380,7 @@ mpz_set_str (mpz_t r, const char *sp, int base)
if (digit >= (unsigned) base) if (digit >= (unsigned) base)
{ {
gmp_free (dp); gmp_free (dp, sn);
r->_mp_size = 0; r->_mp_size = 0;
return -1; return -1;
} }
@ -4325,7 +4390,7 @@ mpz_set_str (mpz_t r, const char *sp, int base)
if (!dn) if (!dn)
{ {
gmp_free (dp); gmp_free (dp, sn);
r->_mp_size = 0; r->_mp_size = 0;
return -1; return -1;
} }
@ -4349,7 +4414,7 @@ mpz_set_str (mpz_t r, const char *sp, int base)
rn -= rp[rn-1] == 0; rn -= rp[rn-1] == 0;
} }
assert (rn <= alloc); assert (rn <= alloc);
gmp_free (dp); gmp_free (dp, sn);
r->_mp_size = sign ? - rn : rn; r->_mp_size = sign ? - rn : rn;
@ -4367,13 +4432,15 @@ size_t
mpz_out_str (FILE *stream, int base, const mpz_t x) mpz_out_str (FILE *stream, int base, const mpz_t x)
{ {
char *str; char *str;
size_t len; size_t len, n;
str = mpz_get_str (NULL, base, x); str = mpz_get_str (NULL, base, x);
if (!str)
return 0;
len = strlen (str); len = strlen (str);
len = fwrite (str, 1, len, stream); n = fwrite (str, 1, len, stream);
gmp_free (str); gmp_free (str, len + 1);
return len; return n;
} }
@ -4462,7 +4529,7 @@ mpz_export (void *r, size_t *countp, int order, size_t size, int endian,
mp_size_t un; mp_size_t un;
if (nails != 0) if (nails != 0)
gmp_die ("mpz_import: Nails not supported."); gmp_die ("mpz_export: Nails not supported.");
assert (order == 1 || order == -1); assert (order == 1 || order == -1);
assert (endian >= -1 && endian <= 1); assert (endian >= -1 && endian <= 1);
@ -4477,7 +4544,7 @@ mpz_export (void *r, size_t *countp, int order, size_t size, int endian,
ptrdiff_t word_step; ptrdiff_t word_step;
/* The current (partial) limb. */ /* The current (partial) limb. */
mp_limb_t limb; mp_limb_t limb;
/* The number of bytes left to to in this limb. */ /* The number of bytes left to do in this limb. */
size_t bytes; size_t bytes;
/* The index where the limb was read. */ /* The index where the limb was read. */
mp_size_t i; mp_size_t i;
@ -4501,7 +4568,7 @@ mpz_export (void *r, size_t *countp, int order, size_t size, int endian,
count = (k + (un-1) * sizeof (mp_limb_t) + size - 1) / size; count = (k + (un-1) * sizeof (mp_limb_t) + size - 1) / size;
if (!r) if (!r)
r = gmp_xalloc (count * size); r = gmp_alloc (count * size);
if (endian == 0) if (endian == 0)
endian = gmp_detect_endian (); endian = gmp_detect_endian ();

@ -1,21 +1,32 @@
/* mini-gmp, a minimalistic implementation of a GNU GMP subset. /* mini-gmp, a minimalistic implementation of a GNU GMP subset.
Copyright 2011-2015, 2017, 2019 Free Software Foundation, Inc. Copyright 2011-2015, 2017, 2019-2021 Free Software Foundation, Inc.
This file is part of the GNU MP Library. This file is part of the GNU MP Library.
The GNU MP Library is free software; you can redistribute it and/or modify The GNU MP Library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by it under the terms of either:
the Free Software Foundation; either version 3 of the License, or (at your
option) any later version. * the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your
option) any later version.
or
* the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any
later version.
or both in parallel, as here.
The GNU MP Library is distributed in the hope that it will be useful, but The GNU MP Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
License for more details. for more details.
You should have received a copy of the GNU Lesser General Public License You should have received copies of the GNU General Public License and the
along with the GNU MP Library. If not, see http://www.gnu.org/licenses/. */ GNU Lesser General Public License along with the GNU MP Library. If not,
see https://www.gnu.org/licenses/. */
/* About mini-gmp: This is a minimal implementation of a subset of the /* About mini-gmp: This is a minimal implementation of a subset of the
GMP interface. It is intended for inclusion into applications which GMP interface. It is intended for inclusion into applications which
@ -233,6 +244,10 @@ mp_bitcnt_t mpz_scan1 (const mpz_t, mp_bitcnt_t);
int mpz_fits_slong_p (const mpz_t); int mpz_fits_slong_p (const mpz_t);
int mpz_fits_ulong_p (const mpz_t); int mpz_fits_ulong_p (const mpz_t);
int mpz_fits_sint_p (const mpz_t);
int mpz_fits_uint_p (const mpz_t);
int mpz_fits_sshort_p (const mpz_t);
int mpz_fits_ushort_p (const mpz_t);
long int mpz_get_si (const mpz_t); long int mpz_get_si (const mpz_t);
unsigned long int mpz_get_ui (const mpz_t); unsigned long int mpz_get_ui (const mpz_t);
double mpz_get_d (const mpz_t); double mpz_get_d (const mpz_t);
@ -280,7 +295,9 @@ int mpz_init_set_str (mpz_t, const char *, int);
|| defined (_MSL_STDIO_H) /* Metrowerks */ \ || defined (_MSL_STDIO_H) /* Metrowerks */ \
|| defined (_STDIO_H_INCLUDED) /* QNX4 */ \ || defined (_STDIO_H_INCLUDED) /* QNX4 */ \
|| defined (_ISO_STDIO_ISO_H) /* Sun C++ */ \ || defined (_ISO_STDIO_ISO_H) /* Sun C++ */ \
|| defined (__STDIO_LOADED) /* VMS */ || defined (__STDIO_LOADED) /* VMS */ \
|| defined (_STDIO) /* HPE NonStop */ \
|| defined (__DEFINED_FILE) /* musl */
size_t mpz_out_str (FILE *, int, const mpz_t); size_t mpz_out_str (FILE *, int, const mpz_t);
#endif #endif

@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
cd .. cd ..
git clone https://github.com/open-source-parsers/jsoncpp -b 1.9.4 --depth 1 git clone https://github.com/open-source-parsers/jsoncpp -b 1.9.5 --depth 1
cd jsoncpp cd jsoncpp
python amalgamate.py ./amalgamate.py
cp -R dist/json ../json cp -R dist/json ../json
cp dist/jsoncpp.cpp .. cp dist/jsoncpp.cpp ..

@ -94,10 +94,10 @@ license you like.
// 3. /CMakeLists.txt // 3. /CMakeLists.txt
// IMPORTANT: also update the SOVERSION!! // IMPORTANT: also update the SOVERSION!!
#define JSONCPP_VERSION_STRING "1.9.4" #define JSONCPP_VERSION_STRING "1.9.5"
#define JSONCPP_VERSION_MAJOR 1 #define JSONCPP_VERSION_MAJOR 1
#define JSONCPP_VERSION_MINOR 9 #define JSONCPP_VERSION_MINOR 9
#define JSONCPP_VERSION_PATCH 3 #define JSONCPP_VERSION_PATCH 5
#define JSONCPP_VERSION_QUALIFIER #define JSONCPP_VERSION_QUALIFIER
#define JSONCPP_VERSION_HEXA \ #define JSONCPP_VERSION_HEXA \
((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \
@ -162,11 +162,10 @@ public:
* Release memory which was allocated for N items at pointer P. * Release memory which was allocated for N items at pointer P.
* *
* The memory block is filled with zeroes before being released. * The memory block is filled with zeroes before being released.
* The pointer argument is tagged as "volatile" to prevent the
* compiler optimizing out this critical step.
*/ */
void deallocate(volatile pointer p, size_type n) { void deallocate(pointer p, size_type n) {
std::memset(p, 0, n * sizeof(T)); // memset_s is used because memset may be optimized away by the compiler
memset_s(p, n * sizeof(T), 0, n * sizeof(T));
// free using "global operator delete" // free using "global operator delete"
::operator delete(p); ::operator delete(p);
} }

@ -93,10 +93,10 @@ license you like.
// 3. /CMakeLists.txt // 3. /CMakeLists.txt
// IMPORTANT: also update the SOVERSION!! // IMPORTANT: also update the SOVERSION!!
#define JSONCPP_VERSION_STRING "1.9.4" #define JSONCPP_VERSION_STRING "1.9.5"
#define JSONCPP_VERSION_MAJOR 1 #define JSONCPP_VERSION_MAJOR 1
#define JSONCPP_VERSION_MINOR 9 #define JSONCPP_VERSION_MINOR 9
#define JSONCPP_VERSION_PATCH 3 #define JSONCPP_VERSION_PATCH 5
#define JSONCPP_VERSION_QUALIFIER #define JSONCPP_VERSION_QUALIFIER
#define JSONCPP_VERSION_HEXA \ #define JSONCPP_VERSION_HEXA \
((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \ ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | \
@ -161,11 +161,10 @@ public:
* Release memory which was allocated for N items at pointer P. * Release memory which was allocated for N items at pointer P.
* *
* The memory block is filled with zeroes before being released. * The memory block is filled with zeroes before being released.
* The pointer argument is tagged as "volatile" to prevent the
* compiler optimizing out this critical step.
*/ */
void deallocate(volatile pointer p, size_type n) { void deallocate(pointer p, size_type n) {
std::memset(p, 0, n * sizeof(T)); // memset_s is used because memset may be optimized away by the compiler
memset_s(p, n * sizeof(T), 0, n * sizeof(T));
// free using "global operator delete" // free using "global operator delete"
::operator delete(p); ::operator delete(p);
} }
@ -575,7 +574,7 @@ public:
// be used by... // be used by...
#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) #if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
#pragma warning(push) #pragma warning(push)
#pragma warning(disable : 4251) #pragma warning(disable : 4251 4275)
#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) #endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
#pragma pack(push, 8) #pragma pack(push, 8)
@ -788,10 +787,10 @@ private:
CZString(ArrayIndex index); CZString(ArrayIndex index);
CZString(char const* str, unsigned length, DuplicationPolicy allocate); CZString(char const* str, unsigned length, DuplicationPolicy allocate);
CZString(CZString const& other); CZString(CZString const& other);
CZString(CZString&& other); CZString(CZString&& other) noexcept;
~CZString(); ~CZString();
CZString& operator=(const CZString& other); CZString& operator=(const CZString& other);
CZString& operator=(CZString&& other); CZString& operator=(CZString&& other) noexcept;
bool operator<(CZString const& other) const; bool operator<(CZString const& other) const;
bool operator==(CZString const& other) const; bool operator==(CZString const& other) const;
@ -869,13 +868,13 @@ public:
Value(bool value); Value(bool value);
Value(std::nullptr_t ptr) = delete; Value(std::nullptr_t ptr) = delete;
Value(const Value& other); Value(const Value& other);
Value(Value&& other); Value(Value&& other) noexcept;
~Value(); ~Value();
/// \note Overwrite existing comments. To preserve comments, use /// \note Overwrite existing comments. To preserve comments, use
/// #swapPayload(). /// #swapPayload().
Value& operator=(const Value& other); Value& operator=(const Value& other);
Value& operator=(Value&& other); Value& operator=(Value&& other) noexcept;
/// Swap everything. /// Swap everything.
void swap(Value& other); void swap(Value& other);
@ -1160,9 +1159,9 @@ private:
public: public:
Comments() = default; Comments() = default;
Comments(const Comments& that); Comments(const Comments& that);
Comments(Comments&& that); Comments(Comments&& that) noexcept;
Comments& operator=(const Comments& that); Comments& operator=(const Comments& that);
Comments& operator=(Comments&& that); Comments& operator=(Comments&& that) noexcept;
bool has(CommentPlacement slot) const; bool has(CommentPlacement slot) const;
String get(CommentPlacement slot) const; String get(CommentPlacement slot) const;
void set(CommentPlacement slot, String comment); void set(CommentPlacement slot, String comment);
@ -1443,8 +1442,8 @@ public:
* because the returned references/pointers can be used * because the returned references/pointers can be used
* to change state of the base class. * to change state of the base class.
*/ */
reference operator*() { return deref(); } reference operator*() const { return const_cast<reference>(deref()); }
pointer operator->() { return &deref(); } pointer operator->() const { return const_cast<pointer>(&deref()); }
}; };
inline void swap(Value& a, Value& b) { a.swap(b); } inline void swap(Value& a, Value& b) { a.swap(b); }
@ -1507,8 +1506,7 @@ namespace Json {
* \deprecated Use CharReader and CharReaderBuilder. * \deprecated Use CharReader and CharReaderBuilder.
*/ */
class JSONCPP_DEPRECATED( class JSON_API Reader {
"Use CharReader and CharReaderBuilder instead.") JSON_API Reader {
public: public:
using Char = char; using Char = char;
using Location = const Char*; using Location = const Char*;
@ -1525,13 +1523,13 @@ public:
}; };
/** \brief Constructs a Reader allowing all features for parsing. /** \brief Constructs a Reader allowing all features for parsing.
* \deprecated Use CharReader and CharReaderBuilder.
*/ */
JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead")
Reader(); Reader();
/** \brief Constructs a Reader allowing the specified feature set for parsing. /** \brief Constructs a Reader allowing the specified feature set for parsing.
* \deprecated Use CharReader and CharReaderBuilder.
*/ */
JSONCPP_DEPRECATED("Use CharReader and CharReaderBuilder instead")
Reader(const Features& features); Reader(const Features& features);
/** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a>
@ -1798,6 +1796,9 @@ public:
* - `"allowSpecialFloats": false or true` * - `"allowSpecialFloats": false or true`
* - If true, special float values (NaNs and infinities) are allowed and * - If true, special float values (NaNs and infinities) are allowed and
* their values are lossfree restorable. * their values are lossfree restorable.
* - `"skipBom": false or true`
* - If true, if the input starts with the Unicode byte order mark (BOM),
* it is skipped.
* *
* You can examine 'settings_` yourself to see the defaults. You can also * You can examine 'settings_` yourself to see the defaults. You can also
* write and read them just like any JSON Value. * write and read them just like any JSON Value.
@ -2001,6 +2002,8 @@ public:
* - Number of precision digits for formatting of real values. * - Number of precision digits for formatting of real values.
* - "precisionType": "significant"(default) or "decimal" * - "precisionType": "significant"(default) or "decimal"
* - Type of precision for formatting of real values. * - Type of precision for formatting of real values.
* - "emitUTF8": false or true
* - If true, outputs raw UTF8 strings instead of escaping them.
* You can examine 'settings_` yourself * You can examine 'settings_` yourself
* to see the defaults. You can also write and read them just like any * to see the defaults. You can also write and read them just like any
@ -2036,7 +2039,7 @@ public:
/** \brief Abstract class for writers. /** \brief Abstract class for writers.
* \deprecated Use StreamWriter. (And really, this is an implementation detail.) * \deprecated Use StreamWriter. (And really, this is an implementation detail.)
*/ */
class JSONCPP_DEPRECATED("Use StreamWriter instead") JSON_API Writer { class JSON_API Writer {
public: public:
virtual ~Writer(); virtual ~Writer();
@ -2056,7 +2059,7 @@ public:
#pragma warning(push) #pragma warning(push)
#pragma warning(disable : 4996) // Deriving from deprecated class #pragma warning(disable : 4996) // Deriving from deprecated class
#endif #endif
class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API FastWriter class JSON_API FastWriter
: public Writer { : public Writer {
public: public:
FastWriter(); FastWriter();
@ -2116,7 +2119,7 @@ private:
#pragma warning(push) #pragma warning(push)
#pragma warning(disable : 4996) // Deriving from deprecated class #pragma warning(disable : 4996) // Deriving from deprecated class
#endif #endif
class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API class JSON_API
StyledWriter : public Writer { StyledWriter : public Writer {
public: public:
StyledWriter(); StyledWriter();
@ -2185,7 +2188,7 @@ private:
#pragma warning(push) #pragma warning(push)
#pragma warning(disable : 4996) // Deriving from deprecated class #pragma warning(disable : 4996) // Deriving from deprecated class
#endif #endif
class JSONCPP_DEPRECATED("Use StreamWriterBuilder instead") JSON_API class JSON_API
StyledStreamWriter { StyledStreamWriter {
public: public:
/** /**

@ -202,14 +202,18 @@ template <typename Iter> void fixNumericLocaleInput(Iter begin, Iter end) {
* Return iterator that would be the new end of the range [begin,end), if we * Return iterator that would be the new end of the range [begin,end), if we
* were to delete zeros in the end of string, but not the last zero before '.'. * were to delete zeros in the end of string, but not the last zero before '.'.
*/ */
template <typename Iter> Iter fixZerosInTheEnd(Iter begin, Iter end) { template <typename Iter>
Iter fixZerosInTheEnd(Iter begin, Iter end, unsigned int precision) {
for (; begin != end; --end) { for (; begin != end; --end) {
if (*(end - 1) != '0') { if (*(end - 1) != '0') {
return end; return end;
} }
// Don't delete the last zero before the decimal point. // Don't delete the last zero before the decimal point.
if (begin != (end - 1) && *(end - 2) == '.') { if (begin != (end - 1) && begin != (end - 2) && *(end - 2) == '.') {
return end; if (precision) {
return end;
}
return end - 2;
} }
} }
return end; return end;
@ -338,8 +342,7 @@ bool Reader::parse(std::istream& is, Value& root, bool collectComments) {
// Since String is reference-counted, this at least does not // Since String is reference-counted, this at least does not
// create an extra copy. // create an extra copy.
String doc; String doc(std::istreambuf_iterator<char>(is), {});
std::getline(is, doc, static_cast<char> EOF);
return parse(doc.data(), doc.data() + doc.size(), root, collectComments); return parse(doc.data(), doc.data() + doc.size(), root, collectComments);
} }
@ -2155,7 +2158,7 @@ bool CharReaderBuilder::validate(Json::Value* invalid) const {
if (valid_keys.count(key)) if (valid_keys.count(key))
continue; continue;
if (invalid) if (invalid)
(*invalid)[std::move(key)] = *si; (*invalid)[key] = *si;
else else
return false; return false;
} }
@ -2670,7 +2673,7 @@ Value::CZString::CZString(const CZString& other) {
storage_.length_ = other.storage_.length_; storage_.length_ = other.storage_.length_;
} }
Value::CZString::CZString(CZString&& other) Value::CZString::CZString(CZString&& other) noexcept
: cstr_(other.cstr_), index_(other.index_) { : cstr_(other.cstr_), index_(other.index_) {
other.cstr_ = nullptr; other.cstr_ = nullptr;
} }
@ -2696,7 +2699,7 @@ Value::CZString& Value::CZString::operator=(const CZString& other) {
return *this; return *this;
} }
Value::CZString& Value::CZString::operator=(CZString&& other) { Value::CZString& Value::CZString::operator=(CZString&& other) noexcept {
cstr_ = other.cstr_; cstr_ = other.cstr_;
index_ = other.index_; index_ = other.index_;
other.cstr_ = nullptr; other.cstr_ = nullptr;
@ -2844,7 +2847,7 @@ Value::Value(const Value& other) {
dupMeta(other); dupMeta(other);
} }
Value::Value(Value&& other) { Value::Value(Value&& other) noexcept {
initBasic(nullValue); initBasic(nullValue);
swap(other); swap(other);
} }
@ -2859,7 +2862,7 @@ Value& Value::operator=(const Value& other) {
return *this; return *this;
} }
Value& Value::operator=(Value&& other) { Value& Value::operator=(Value&& other) noexcept {
other.swap(*this); other.swap(*this);
return *this; return *this;
} }
@ -3323,7 +3326,8 @@ void Value::resize(ArrayIndex newSize) {
if (newSize == 0) if (newSize == 0)
clear(); clear();
else if (newSize > oldSize) else if (newSize > oldSize)
this->operator[](newSize - 1); for (ArrayIndex i = oldSize; i < newSize; ++i)
(*this)[i];
else { else {
for (ArrayIndex index = newSize; index < oldSize; ++index) { for (ArrayIndex index = newSize; index < oldSize; ++index) {
value_.map_->erase(index); value_.map_->erase(index);
@ -3784,14 +3788,15 @@ bool Value::isObject() const { return type() == objectValue; }
Value::Comments::Comments(const Comments& that) Value::Comments::Comments(const Comments& that)
: ptr_{cloneUnique(that.ptr_)} {} : ptr_{cloneUnique(that.ptr_)} {}
Value::Comments::Comments(Comments&& that) : ptr_{std::move(that.ptr_)} {} Value::Comments::Comments(Comments&& that) noexcept
: ptr_{std::move(that.ptr_)} {}
Value::Comments& Value::Comments::operator=(const Comments& that) { Value::Comments& Value::Comments::operator=(const Comments& that) {
ptr_ = cloneUnique(that.ptr_); ptr_ = cloneUnique(that.ptr_);
return *this; return *this;
} }
Value::Comments& Value::Comments::operator=(Comments&& that) { Value::Comments& Value::Comments::operator=(Comments&& that) noexcept {
ptr_ = std::move(that.ptr_); ptr_ = std::move(that.ptr_);
return *this; return *this;
} }
@ -3807,13 +3812,11 @@ String Value::Comments::get(CommentPlacement slot) const {
} }
void Value::Comments::set(CommentPlacement slot, String comment) { void Value::Comments::set(CommentPlacement slot, String comment) {
if (!ptr_) { if (slot >= CommentPlacement::numberOfCommentPlacement)
return;
if (!ptr_)
ptr_ = std::unique_ptr<Array>(new Array()); ptr_ = std::unique_ptr<Array>(new Array());
} (*ptr_)[slot] = std::move(comment);
// check comments array boundry.
if (slot < CommentPlacement::numberOfCommentPlacement) {
(*ptr_)[slot] = std::move(comment);
}
} }
void Value::setComment(String comment, CommentPlacement placement) { void Value::setComment(String comment, CommentPlacement placement) {
@ -4127,7 +4130,7 @@ Value& Path::make(Value& root) const {
#if !defined(isnan) #if !defined(isnan)
// IEEE standard states that NaN values will not compare to themselves // IEEE standard states that NaN values will not compare to themselves
#define isnan(x) (x != x) #define isnan(x) ((x) != (x))
#endif #endif
#if !defined(__APPLE__) #if !defined(__APPLE__)
@ -4213,16 +4216,18 @@ String valueToString(double value, bool useSpecialFloats,
buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end()); buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end());
// strip the zero padding from the right
if (precisionType == PrecisionType::decimalPlaces) {
buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end()), buffer.end());
}
// try to ensure we preserve the fact that this was given to us as a double on // try to ensure we preserve the fact that this was given to us as a double on
// input // input
if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) { if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) {
buffer += ".0"; buffer += ".0";
} }
// strip the zero padding from the right
if (precisionType == PrecisionType::decimalPlaces) {
buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end(), precision),
buffer.end());
}
return buffer; return buffer;
} }
} // namespace } // namespace
@ -4329,7 +4334,7 @@ static void appendHex(String& result, unsigned ch) {
result.append("\\u").append(toHex16Bit(ch)); result.append("\\u").append(toHex16Bit(ch));
} }
static String valueToQuotedStringN(const char* value, unsigned length, static String valueToQuotedStringN(const char* value, size_t length,
bool emitUTF8 = false) { bool emitUTF8 = false) {
if (value == nullptr) if (value == nullptr)
return ""; return "";
@ -4407,7 +4412,7 @@ static String valueToQuotedStringN(const char* value, unsigned length,
} }
String valueToQuotedString(const char* value) { String valueToQuotedString(const char* value) {
return valueToQuotedStringN(value, static_cast<unsigned int>(strlen(value))); return valueToQuotedStringN(value, strlen(value));
} }
// Class Writer // Class Writer
@ -4456,7 +4461,7 @@ void FastWriter::writeValue(const Value& value) {
char const* end; char const* end;
bool ok = value.getString(&str, &end); bool ok = value.getString(&str, &end);
if (ok) if (ok)
document_ += valueToQuotedStringN(str, static_cast<unsigned>(end - str)); document_ += valueToQuotedStringN(str, static_cast<size_t>(end - str));
break; break;
} }
case booleanValue: case booleanValue:
@ -4479,8 +4484,7 @@ void FastWriter::writeValue(const Value& value) {
const String& name = *it; const String& name = *it;
if (it != members.begin()) if (it != members.begin())
document_ += ','; document_ += ',';
document_ += valueToQuotedStringN(name.data(), document_ += valueToQuotedStringN(name.data(), name.length());
static_cast<unsigned>(name.length()));
document_ += yamlCompatibilityEnabled_ ? ": " : ":"; document_ += yamlCompatibilityEnabled_ ? ": " : ":";
writeValue(value[name]); writeValue(value[name]);
} }
@ -4525,7 +4529,7 @@ void StyledWriter::writeValue(const Value& value) {
char const* end; char const* end;
bool ok = value.getString(&str, &end); bool ok = value.getString(&str, &end);
if (ok) if (ok)
pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end - str))); pushValue(valueToQuotedStringN(str, static_cast<size_t>(end - str)));
else else
pushValue(""); pushValue("");
break; break;
@ -4566,7 +4570,7 @@ void StyledWriter::writeValue(const Value& value) {
} }
void StyledWriter::writeArrayValue(const Value& value) { void StyledWriter::writeArrayValue(const Value& value) {
unsigned size = value.size(); size_t size = value.size();
if (size == 0) if (size == 0)
pushValue("[]"); pushValue("[]");
else { else {
@ -4575,7 +4579,7 @@ void StyledWriter::writeArrayValue(const Value& value) {
writeWithIndent("["); writeWithIndent("[");
indent(); indent();
bool hasChildValue = !childValues_.empty(); bool hasChildValue = !childValues_.empty();
unsigned index = 0; ArrayIndex index = 0;
for (;;) { for (;;) {
const Value& childValue = value[index]; const Value& childValue = value[index];
writeCommentBeforeValue(childValue); writeCommentBeforeValue(childValue);
@ -4598,7 +4602,7 @@ void StyledWriter::writeArrayValue(const Value& value) {
{ {
assert(childValues_.size() == size); assert(childValues_.size() == size);
document_ += "[ "; document_ += "[ ";
for (unsigned index = 0; index < size; ++index) { for (size_t index = 0; index < size; ++index) {
if (index > 0) if (index > 0)
document_ += ", "; document_ += ", ";
document_ += childValues_[index]; document_ += childValues_[index];
@ -4743,7 +4747,7 @@ void StyledStreamWriter::writeValue(const Value& value) {
char const* end; char const* end;
bool ok = value.getString(&str, &end); bool ok = value.getString(&str, &end);
if (ok) if (ok)
pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end - str))); pushValue(valueToQuotedStringN(str, static_cast<size_t>(end - str)));
else else
pushValue(""); pushValue("");
break; break;
@ -5017,8 +5021,8 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) {
char const* end; char const* end;
bool ok = value.getString(&str, &end); bool ok = value.getString(&str, &end);
if (ok) if (ok)
pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end - str), pushValue(
emitUTF8_)); valueToQuotedStringN(str, static_cast<size_t>(end - str), emitUTF8_));
else else
pushValue(""); pushValue("");
break; break;
@ -5041,8 +5045,8 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) {
String const& name = *it; String const& name = *it;
Value const& childValue = value[name]; Value const& childValue = value[name];
writeCommentBeforeValue(childValue); writeCommentBeforeValue(childValue);
writeWithIndent(valueToQuotedStringN( writeWithIndent(
name.data(), static_cast<unsigned>(name.length()), emitUTF8_)); valueToQuotedStringN(name.data(), name.length(), emitUTF8_));
*sout_ << colonSymbol_; *sout_ << colonSymbol_;
writeValue(childValue); writeValue(childValue);
if (++it == members.end()) { if (++it == members.end()) {
@ -5276,7 +5280,7 @@ bool StreamWriterBuilder::validate(Json::Value* invalid) const {
if (valid_keys.count(key)) if (valid_keys.count(key))
continue; continue;
if (invalid) if (invalid)
(*invalid)[std::move(key)] = *si; (*invalid)[key] = *si;
else else
return false; return false;
} }

@ -1 +1 @@
1.9.0mt13 1.9.0mt14

53
misc/make_redirects.sh Executable file

@ -0,0 +1,53 @@
#!/bin/bash
set -e
rm -rf public
mkdir public
redirect() {
dir=$(dirname "public$1")
mkdir -p $dir
cp misc/redirect.html "public$1"
url=${1/\/index.html/\/}
sed -i "s|URL|$url|g" "public$1"
}
redirect /inventory/index.html
redirect /definition-tables/index.html
redirect /aliases/index.html
redirect /items/index.html
redirect /registered-definitions/index.html
redirect /colors/index.html
redirect /decoration-types/index.html
redirect /hud/index.html
redirect /index.html
redirect /spatial-vectors/index.html
redirect /metadata/index.html
redirect /perlin-noise/index.html
redirect /translations/index.html
redirect /tool-capabilities/index.html
redirect /l-system-trees/index.html
redirect /entity-damage-mechanism/index.html
redirect /escape-sequences/index.html
redirect /registered-entities/index.html
redirect /flag-specifier-format/index.html
redirect /minetest-namespace-reference/index.html
redirect /ores/index.html
redirect /search.html
redirect /representations-of-simple-things/index.html
redirect /nodes/index.html
redirect /lua-voxel-manipulator/index.html
redirect /helper-functions/index.html
redirect /formspec/index.html
redirect /games/index.html
redirect /sounds/index.html
redirect /textures/index.html
redirect /map-terminology-and-coordinates/index.html
redirect /schematics/index.html
redirect /groups/index.html
redirect /privileges/index.html
redirect /class-reference/index.html
redirect /mods/index.html
redirect /mapgen-objects/index.html

14
misc/redirect.html Normal file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Minetest API documentation</title>
<link rel="canonical" href="https://api.minetest.netURL">
<meta http-equiv="refresh" content="0; url=https://api.minetest.netURL">
</head>
<body>
<h1>Redirecting&hellip;</h1>
<a href="https://api.minetest.netURL">Click here if you are not redirected.</a>
</body>
</html>

@ -47,7 +47,7 @@ option(ENABLE_CURL "Enable cURL support for fetching media" TRUE)
set(USE_CURL FALSE) set(USE_CURL FALSE)
if(ENABLE_CURL) if(ENABLE_CURL)
find_package(CURL) find_package(CURL 7.28.0)
if (CURL_FOUND) if (CURL_FOUND)
message(STATUS "cURL support enabled.") message(STATUS "cURL support enabled.")
set(USE_CURL TRUE) set(USE_CURL TRUE)
@ -326,6 +326,9 @@ if(HAVE_LINK_ATOMIC)
set(PLATFORM_LIBS ${PLATFORM_LIBS} "-latomic") set(PLATFORM_LIBS ${PLATFORM_LIBS} "-latomic")
endif() endif()
include(CheckSymbolExists)
check_symbol_exists(strlcpy "string.h" HAVE_STRLCPY)
check_include_files(endian.h HAVE_ENDIAN_H) check_include_files(endian.h HAVE_ENDIAN_H)
configure_file( configure_file(
@ -363,9 +366,7 @@ set(common_SRCS
${mapgen_SRCS} ${mapgen_SRCS}
${server_SRCS} ${server_SRCS}
${content_SRCS} ${content_SRCS}
ban.cpp
chat.cpp chat.cpp
clientiface.cpp
collision.cpp collision.cpp
content_mapnode.cpp content_mapnode.cpp
content_nodemeta.cpp content_nodemeta.cpp
@ -410,12 +411,10 @@ set(common_SRCS
raycast.cpp raycast.cpp
reflowscan.cpp reflowscan.cpp
remoteplayer.cpp remoteplayer.cpp
rollback.cpp
rollback_interface.cpp rollback_interface.cpp
serialization.cpp serialization.cpp
server.cpp server.cpp
serverenvironment.cpp serverenvironment.cpp
serverlist.cpp
settings.cpp settings.cpp
staticobject.cpp staticobject.cpp
terminal_chat_console.cpp terminal_chat_console.cpp
@ -693,7 +692,6 @@ endif()
# Set some optimizations and tweaks # Set some optimizations and tweaks
include(CheckCSourceCompiles) include(CheckCSourceCompiles)
include(CheckSymbolExists)
set(CMAKE_REQUIRED_INCLUDES ${LUA_INCLUDE_DIR}) set(CMAKE_REQUIRED_INCLUDES ${LUA_INCLUDE_DIR})
if(USE_LUAJIT) if(USE_LUAJIT)

@ -19,10 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once #pragma once
#include <map>
#include <memory> #include <memory>
#include <vector>
#include "debug.h" #include "debug.h"
#include "util/container.h"
#include "irrlichttypes.h" #include "irrlichttypes.h"
#include "util/basic_macros.h" #include "util/basic_macros.h"
@ -52,24 +51,19 @@ public:
void clear() void clear()
{ {
while (!m_active_objects.empty()) // on_destruct could add new objects so this has to be a loop
removeObject(m_active_objects.begin()->first); do {
for (auto &it : m_active_objects.iter()) {
if (!it.second)
continue;
m_active_objects.remove(it.first);
}
} while (!m_active_objects.empty());
} }
T *getActiveObject(u16 id) T *getActiveObject(u16 id)
{ {
auto it = m_active_objects.find(id); return m_active_objects.get(id).get();
return it != m_active_objects.end() ? it->second.get() : nullptr;
}
std::vector<u16> getAllIds() const
{
std::vector<u16> ids;
ids.reserve(m_active_objects.size());
for (auto &it : m_active_objects) {
ids.push_back(it.first);
}
return ids;
} }
protected: protected:
@ -88,11 +82,9 @@ protected:
bool isFreeId(u16 id) const bool isFreeId(u16 id) const
{ {
return id != 0 && m_active_objects.find(id) == m_active_objects.end(); return id != 0 && !m_active_objects.get(id);
} }
// ordered to fix #10985 // Note that this is ordered to fix #10985
// Note: ActiveObjects can access the ActiveObjectMgr. Only erase objects using ModifySafeMap<u16, std::unique_ptr<T>> m_active_objects;
// removeObject()!
std::map<u16, std::unique_ptr<T>> m_active_objects;
}; };

@ -3,6 +3,7 @@ set (BENCHMARK_SRCS
${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 ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapblock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapmodify.cpp
PARENT_SCOPE) PARENT_SCOPE)
set (BENCHMARK_CLIENT_SRCS set (BENCHMARK_CLIENT_SRCS

@ -0,0 +1,152 @@
/*
Minetest
Copyright (C) 2023 sfan5
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 "util/container.h"
// Testing the standard library is not useful except to compare
//#define TEST_STDLIB
using TestMap = ModifySafeMap<u16, void*>;
static inline void fill(TestMap &map, size_t n)
{
map.clear();
for (size_t i = 0; i < n; i++)
map.put(i, reinterpret_cast<TestMap::mapped_type>(0x40000U + i));
}
static inline void pollute(TestMap &map)
{
auto dummy = reinterpret_cast<TestMap::mapped_type>(123);
// produce some garbage to avoid best case behaviour
map.put(0xffff, dummy);
for (auto it : map.iter()) {
(void)it;
map.remove(0xffff);
break;
}
}
static inline void remove(TestMap &map, size_t offset, size_t count)
{
for (size_t i = 0; i < count; i++)
map.remove(static_cast<TestMap::key_type>(i + offset));
}
#define BENCH_ITERATE_(_label, _count, _best) \
BENCHMARK_ADVANCED(_label)(Catch::Benchmark::Chronometer meter) { \
TestMap map; \
fill(map, _count); \
if (!_best) pollute(map); \
meter.measure([&] { \
size_t x = map.size(); \
for (auto &it : map.iter()) { \
if (!it.second) \
continue; \
x ^= reinterpret_cast<intptr_t>(it.second); \
} \
return x; \
}); \
};
#define BENCH_ITERATE(_count) \
BENCH_ITERATE_("iterate_" #_count, _count, 0) \
BENCH_ITERATE_("iterate_bestcase_" #_count, _count, 1)
#define BENCH_REMOVE(_count) \
BENCHMARK_ADVANCED("remove_" #_count)(Catch::Benchmark::Chronometer meter) { \
TestMap map; \
fill(map, _count); \
meter.measure([&] { \
for (auto it : map.iter()) { \
(void)it; \
remove(map, (_count) / 7, (_count) / 2); /* delete half */ \
break; \
} \
}); \
};
TEST_CASE("ModifySafeMap") {
BENCH_ITERATE(50)
BENCH_ITERATE(400)
BENCH_ITERATE(1000)
BENCH_REMOVE(50)
BENCH_REMOVE(400)
BENCH_REMOVE(1000)
}
using TestMap2 = std::map<u16, void*>;
static inline void fill2(TestMap2 &map, size_t n)
{
map.clear();
for (size_t i = 0; i < n; i++)
map.emplace(i, reinterpret_cast<TestMap2::mapped_type>(0x40000U + i));
}
static inline void remove2(TestMap2 &map, size_t offset, size_t count)
{
for (size_t i = 0; i < count; i++)
map.erase(static_cast<TestMap2::key_type>(i + offset));
}
#define BENCH2_ITERATE(_count) \
BENCHMARK_ADVANCED("iterate_" #_count)(Catch::Benchmark::Chronometer meter) { \
TestMap2 map; \
fill2(map, _count); \
meter.measure([&] { \
size_t x = map.size(); \
/* mirrors what ActiveObjectMgr::step used to do */ \
std::vector<TestMap2::key_type> keys; \
keys.reserve(x); \
for (auto &it : map) \
keys.push_back(it.first); \
for (auto key : keys) { \
auto it = map.find(key); \
if (it == map.end()) \
continue; \
x ^= reinterpret_cast<intptr_t>(it->second); \
} \
return x; \
}); \
};
#define BENCH2_REMOVE(_count) \
BENCHMARK_ADVANCED("remove_" #_count)(Catch::Benchmark::Chronometer meter) { \
TestMap2 map; \
fill2(map, _count); \
meter.measure([&] { \
/* no overhead so no fake iteration */ \
remove2(map, (_count) / 7, (_count) / 2); \
}); \
};
#ifdef TEST_STDLIB
TEST_CASE("std::map") {
BENCH2_ITERATE(50)
BENCH2_ITERATE(400)
BENCH2_ITERATE(1000)
BENCH2_REMOVE(50)
BENCH2_REMOVE(400)
BENCH2_REMOVE(1000)
}
#endif

@ -775,9 +775,9 @@ void ChatPrompt::clampView()
ChatBackend::ChatBackend(): ChatBackend::ChatBackend():
m_console_buffer(500), m_console_buffer(1500),
m_recent_buffer(6), m_recent_buffer(6),
m_prompt(L"]", 500) m_prompt(L"]", 1500)
{ {
} }

@ -37,17 +37,14 @@ ActiveObjectMgr::~ActiveObjectMgr()
void ActiveObjectMgr::step( void ActiveObjectMgr::step(
float dtime, const std::function<void(ClientActiveObject *)> &f) float dtime, const std::function<void(ClientActiveObject *)> &f)
{ {
g_profiler->avg("ActiveObjectMgr: CAO count [#]", m_active_objects.size()); size_t count = 0;
for (auto &ao_it : m_active_objects.iter()) {
// Same as in server activeobjectmgr. if (!ao_it.second)
std::vector<u16> ids = getAllIds(); continue;
count++;
for (u16 id : ids) { f(ao_it.second.get());
auto it = m_active_objects.find(id);
if (it == m_active_objects.end())
continue; // obj was removed
f(it->second.get());
} }
g_profiler->avg("ActiveObjectMgr: CAO count [#]", count);
} }
bool ActiveObjectMgr::registerObject(std::unique_ptr<ClientActiveObject> obj) bool ActiveObjectMgr::registerObject(std::unique_ptr<ClientActiveObject> obj)
@ -71,7 +68,7 @@ bool ActiveObjectMgr::registerObject(std::unique_ptr<ClientActiveObject> obj)
} }
infostream << "Client::ActiveObjectMgr::registerObject(): " infostream << "Client::ActiveObjectMgr::registerObject(): "
<< "added (id=" << obj->getId() << ")" << std::endl; << "added (id=" << obj->getId() << ")" << std::endl;
m_active_objects[obj->getId()] = std::move(obj); m_active_objects.put(obj->getId(), std::move(obj));
return true; return true;
} }
@ -79,16 +76,14 @@ void ActiveObjectMgr::removeObject(u16 id)
{ {
verbosestream << "Client::ActiveObjectMgr::removeObject(): " verbosestream << "Client::ActiveObjectMgr::removeObject(): "
<< "id=" << id << std::endl; << "id=" << id << std::endl;
auto it = m_active_objects.find(id);
if (it == m_active_objects.end()) { std::unique_ptr<ClientActiveObject> obj = m_active_objects.take(id);
if (!obj) {
infostream << "Client::ActiveObjectMgr::removeObject(): " infostream << "Client::ActiveObjectMgr::removeObject(): "
<< "id=" << id << " not found" << std::endl; << "id=" << id << " not found" << std::endl;
return; return;
} }
std::unique_ptr<ClientActiveObject> obj = std::move(it->second);
m_active_objects.erase(it);
obj->removeFromScene(true); obj->removeFromScene(true);
} }
@ -96,8 +91,10 @@ void ActiveObjectMgr::getActiveObjects(const v3f &origin, f32 max_d,
std::vector<DistanceSortedActiveObject> &dest) std::vector<DistanceSortedActiveObject> &dest)
{ {
f32 max_d2 = max_d * max_d; f32 max_d2 = max_d * max_d;
for (auto &ao_it : m_active_objects) { for (auto &ao_it : m_active_objects.iter()) {
ClientActiveObject *obj = ao_it.second.get(); ClientActiveObject *obj = ao_it.second.get();
if (!obj)
continue;
f32 d2 = (obj->getPosition() - origin).getLengthSQ(); f32 d2 = (obj->getPosition() - origin).getLengthSQ();
@ -114,8 +111,10 @@ std::vector<DistanceSortedActiveObject> ActiveObjectMgr::getActiveSelectableObje
f32 max_d = shootline.getLength(); f32 max_d = shootline.getLength();
v3f dir = shootline.getVector().normalize(); v3f dir = shootline.getVector().normalize();
for (auto &ao_it : m_active_objects) { for (auto &ao_it : m_active_objects.iter()) {
ClientActiveObject *obj = ao_it.second.get(); ClientActiveObject *obj = ao_it.second.get();
if (!obj)
continue;
aabb3f selection_box; aabb3f selection_box;
if (!obj->getSelectionBox(&selection_box)) if (!obj->getSelectionBox(&selection_box))

@ -83,8 +83,10 @@ u32 PacketCounter::sum() const
void PacketCounter::print(std::ostream &o) const void PacketCounter::print(std::ostream &o) const
{ {
for (const auto &it : m_packets) { for (const auto &it : m_packets) {
auto name = it.first >= TOCLIENT_NUM_MSG_TYPES ? "?" auto name = it.first >= TOCLIENT_NUM_MSG_TYPES ? nullptr
: toClientCommandTable[it.first].name; : toClientCommandTable[it.first].name;
if (!name)
name = "?";
o << "cmd " << it.first << " (" << name << ") count " o << "cmd " << it.first << " (" << name << ") count "
<< it.second << std::endl; << it.second << std::endl;
} }
@ -991,27 +993,25 @@ inline void Client::handleCommand(NetworkPacket* pkt)
void Client::ProcessData(NetworkPacket *pkt) void Client::ProcessData(NetworkPacket *pkt)
{ {
ToClientCommand command = (ToClientCommand) pkt->getCommand(); ToClientCommand command = (ToClientCommand) pkt->getCommand();
u32 sender_peer_id = pkt->getPeerId();
//infostream<<"Client: received command="<<command<<std::endl; m_packetcounter.add(static_cast<u16>(command));
m_packetcounter.add((u16)command);
g_profiler->graphAdd("client_received_packets", 1); g_profiler->graphAdd("client_received_packets", 1);
/* /*
If this check is removed, be sure to change the queue If this check is removed, be sure to change the queue
system to know the ids system to know the ids
*/ */
if(sender_peer_id != PEER_ID_SERVER) { if (pkt->getPeerId() != PEER_ID_SERVER) {
infostream << "Client::ProcessData(): Discarding data not " infostream << "Client::ProcessData(): Discarding data not "
"coming from server: peer_id=" << sender_peer_id << " command=" << pkt->getCommand() "coming from server: peer_id=" << static_cast<int>(pkt->getPeerId())
<< std::endl; << " command=" << static_cast<unsigned>(command) << std::endl;
return; return;
} }
// Command must be handled into ToClientCommandHandler // Command must be handled into ToClientCommandHandler
if (command >= TOCLIENT_NUM_MSG_TYPES) { if (command >= TOCLIENT_NUM_MSG_TYPES) {
infostream << "Client: Ignoring unknown command " infostream << "Client: Ignoring unknown command "
<< command << std::endl; << static_cast<unsigned>(command) << std::endl;
return; return;
} }
@ -1020,31 +1020,26 @@ void Client::ProcessData(NetworkPacket *pkt)
* But we must use the new ToClientConnectionState in the future, * But we must use the new ToClientConnectionState in the future,
* as a byte mask * as a byte mask
*/ */
if(toClientCommandTable[command].state == TOCLIENT_STATE_NOT_CONNECTED) { if (toClientCommandTable[command].state == TOCLIENT_STATE_NOT_CONNECTED) {
handleCommand(pkt); handleCommand(pkt);
return; return;
} }
if(m_server_ser_ver == SER_FMT_VER_INVALID) { if (m_server_ser_ver == SER_FMT_VER_INVALID) {
infostream << "Client: Server serialization" infostream << "Client: Server serialization"
" format invalid or not initialized." " format invalid. Skipping incoming command "
" Skipping incoming command=" << command << std::endl; << static_cast<unsigned>(command) << std::endl;
return; return;
} }
/*
Handle runtime commands
*/
handleCommand(pkt); handleCommand(pkt);
} }
void Client::Send(NetworkPacket* pkt) void Client::Send(NetworkPacket* pkt)
{ {
m_con->Send(PEER_ID_SERVER, auto &scf = serverCommandFactoryTable[pkt->getCommand()];
serverCommandFactoryTable[pkt->getCommand()].channel, FATAL_ERROR_IF(!scf.name, "packet type missing in table");
pkt, m_con->Send(PEER_ID_SERVER, scf.channel, pkt, scf.reliable);
serverCommandFactoryTable[pkt->getCommand()].reliable);
} }
// Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 bytes // Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 bytes

@ -47,10 +47,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class CAOShaderConstantSetter : public IShaderConstantSetter class CAOShaderConstantSetter : public IShaderConstantSetter
{ {
public: public:
CAOShaderConstantSetter():
m_emissive_color_setting("emissiveColor")
{}
~CAOShaderConstantSetter() override = default; ~CAOShaderConstantSetter() override = default;
void onSetConstants(video::IMaterialRendererServices *services) override void onSetConstants(video::IMaterialRendererServices *services) override
@ -74,7 +70,8 @@ public:
private: private:
video::SColor m_emissive_color; video::SColor m_emissive_color;
CachedPixelShaderSetting<float, 4> m_emissive_color_setting; CachedPixelShaderSetting<float, 4>
m_emissive_color_setting{"emissiveColor"};
}; };
class CAOShaderConstantSetterFactory : public IShaderConstantSetterFactory class CAOShaderConstantSetterFactory : public IShaderConstantSetterFactory
@ -492,7 +489,8 @@ ClientEnvEvent ClientEnvironment::getClientEnvEvent()
void ClientEnvironment::getSelectedActiveObjects( void ClientEnvironment::getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map, const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects) std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities)
{ {
auto allObjects = m_ao_manager.getActiveSelectableObjects(shootline_on_map); auto allObjects = m_ao_manager.getActiveSelectableObjects(shootline_on_map);
const v3f line_vector = shootline_on_map.getVector(); const v3f line_vector = shootline_on_map.getVector();
@ -519,9 +517,23 @@ void ClientEnvironment::getSelectedActiveObjects(
current_raw_normal = current_normal; current_raw_normal = current_normal;
} }
if (collision) { if (collision) {
current_intersection += obj->getPosition(); PointabilityType pointable;
objects.emplace_back(obj->getId(), current_intersection, current_normal, current_raw_normal, if (pointabilities) {
(current_intersection - shootline_on_map.start).getLengthSQ()); if (gcao->isPlayer()) {
pointable = pointabilities->matchPlayer(gcao->getGroups()).value_or(
gcao->getProperties().pointable);
} else {
pointable = pointabilities->matchObject(gcao->getName(),
gcao->getGroups()).value_or(gcao->getProperties().pointable);
}
} else {
pointable = gcao->getProperties().pointable;
}
if (pointable != PointabilityType::POINTABLE_NOT) {
current_intersection += obj->getPosition();
objects.emplace_back(obj->getId(), current_intersection, current_normal, current_raw_normal,
(current_intersection - shootline_on_map.start).getLengthSQ(), pointable);
}
} }
} }
} }

@ -131,7 +131,8 @@ public:
virtual void getSelectedActiveObjects( virtual void getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map, const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects std::vector<PointedThing> &objects,
const std::optional<Pointabilities> &pointabilities
); );
const std::set<std::string> &getPlayerNames() { return m_player_names; } const std::set<std::string> &getPlayerNames() { return m_player_names; }

@ -27,13 +27,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "chat.h" #include "chat.h"
#include "gettext.h" #include "gettext.h"
#include "profiler.h" #include "profiler.h"
#include "serverlist.h"
#include "gui/guiEngine.h" #include "gui/guiEngine.h"
#include "fontengine.h" #include "fontengine.h"
#include "clientlauncher.h" #include "clientlauncher.h"
#include "version.h" #include "version.h"
#include "renderingengine.h" #include "renderingengine.h"
#include "network/networkexceptions.h" #include "network/networkexceptions.h"
#include <IGUISpriteBank.h>
#include <ICameraSceneNode.h>
#if USE_SOUND #if USE_SOUND
#include "sound/sound_openal.h" #include "sound/sound_openal.h"
@ -106,13 +107,6 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
return false; return false;
} }
// Speed tests (done after irrlicht is loaded to get timer)
if (cmd_args.getFlag("speedtests")) {
dstream << "Running speed tests" << std::endl;
speed_tests();
return true;
}
if (m_rendering_engine->get_video_driver() == NULL) { if (m_rendering_engine->get_video_driver() == NULL) {
errorstream << "Could not initialize video driver." << std::endl; errorstream << "Could not initialize video driver." << std::endl;
return false; return false;
@ -213,7 +207,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
m_rendering_engine->get_raw_device()-> m_rendering_engine->get_raw_device()->
setWindowCaption((utf8_to_wide(PROJECT_NAME_C) + setWindowCaption((utf8_to_wide(PROJECT_NAME_C) +
L" " + utf8_to_wide(g_version_hash) + L" " + utf8_to_wide(g_version_hash) +
L" [" + wstrgettext("Main Menu") + L"]").c_str()); L" [" + wstrgettext("Main Menu") + L"]" +
L" [" + m_rendering_engine->getVideoDriver()->getName() + L"]" ).c_str());
try { // This is used for catching disconnects try { // This is used for catching disconnects
@ -247,11 +242,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
} }
// Break out of menu-game loop to shut down cleanly // Break out of menu-game loop to shut down cleanly
if (!m_rendering_engine->run() || *kill) { if (!m_rendering_engine->run() || *kill)
if (!g_settings_path.empty())
g_settings->updateConfigFile(g_settings_path.c_str());
break; break;
}
m_rendering_engine->get_video_driver()->setTextureCreationFlag( m_rendering_engine->get_video_driver()->setTextureCreationFlag(
video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map")); video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map"));
@ -275,6 +267,10 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
error_message = gettext("Connection error (timed out?)"); error_message = gettext("Connection error (timed out?)");
errorstream << error_message << std::endl; errorstream << error_message << std::endl;
} }
catch (ShaderException &e) {
error_message = e.what();
errorstream << error_message << std::endl;
}
#ifdef NDEBUG #ifdef NDEBUG
catch (std::exception &e) { catch (std::exception &e) {
@ -292,6 +288,16 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
receiver->m_touchscreengui = NULL; receiver->m_touchscreengui = NULL;
#endif #endif
/* Save the settings when leaving the game.
* This makes sure that setting changes made in-game are persisted even
* in case of a later unclean exit from the mainmenu.
* This is especially useful on Android because closing the app from the
* "Recents screen" results in an unclean exit.
* Caveat: This means that the settings are saved twice when exiting Minetest.
*/
if (!g_settings_path.empty())
g_settings->updateConfigFile(g_settings_path.c_str());
// If no main menu, show error and exit // If no main menu, show error and exit
if (skip_main_menu) { if (skip_main_menu) {
if (!error_message.empty()) { if (!error_message.empty()) {
@ -380,7 +386,7 @@ bool ClientLauncher::launch_game(std::string &error_message,
if (cmd_args.exists("password-file")) { if (cmd_args.exists("password-file")) {
std::ifstream passfile(cmd_args.get("password-file")); std::ifstream passfile(cmd_args.get("password-file"));
if (passfile.good()) { if (passfile.good()) {
getline(passfile, start_data.password); std::getline(passfile, start_data.password);
} else { } else {
error_message = gettext("Provided password file " error_message = gettext("Provided password file "
"failed to open: ") "failed to open: ")
@ -437,8 +443,6 @@ bool ClientLauncher::launch_game(std::string &error_message,
int world_index = menudata.selected_world; int world_index = menudata.selected_world;
if (world_index >= 0 && world_index < (int)worldspecs.size()) { if (world_index >= 0 && world_index < (int)worldspecs.size()) {
g_settings->set("selected_world_path",
worldspecs[world_index].path);
start_data.world_spec = worldspecs[world_index]; start_data.world_spec = worldspecs[world_index];
} }
@ -510,15 +514,7 @@ bool ClientLauncher::launch_game(std::string &error_message,
return false; return false;
} }
if (porting::signal_handler_killstatus()) return true;
return true;
if (!start_data.game_spec.isValid()) {
error_message = gettext("Invalid gamespec.");
error_message += " (world.gameid=" + worldspec.gameid + ")";
errorstream << error_message << std::endl;
return false;
}
} }
start_data.world_path = start_data.world_spec.path; start_data.world_path = start_data.world_spec.path;
@ -542,118 +538,27 @@ void ClientLauncher::main_menu(MainMenuData *menudata)
} }
infostream << "Waited for other menus" << std::endl; infostream << "Waited for other menus" << std::endl;
#ifndef ANDROID auto *cur_control = m_rendering_engine->get_raw_device()->getCursorControl();
// Cursor can be non-visible when coming from the game if (cur_control) {
m_rendering_engine->get_raw_device()->getCursorControl()->setVisible(true); // Cursor can be non-visible when coming from the game
cur_control->setVisible(true);
// Set absolute mouse mode // Set absolute mouse mode
m_rendering_engine->get_raw_device()->getCursorControl()->setRelativeMode(false); cur_control->setRelativeMode(false);
#endif }
/* show main menu */ /* show main menu */
GUIEngine mymenu(&input->joystick, guiroot, m_rendering_engine, &g_menumgr, menudata, *kill); GUIEngine mymenu(&input->joystick, guiroot, m_rendering_engine, &g_menumgr, menudata, *kill);
/* leave scene manager in a clean state */ /* leave scene manager in a clean state */
m_rendering_engine->get_scene_manager()->clear(); m_rendering_engine->get_scene_manager()->clear();
}
/* Save the settings when leaving the mainmenu.
void ClientLauncher::speed_tests() * This makes sure that setting changes made in the mainmenu are persisted
{ * even in case of a later unclean exit from the game.
// volatile to avoid some potential compiler optimisations * This is especially useful on Android because closing the app from the
volatile static s16 temp16; * "Recents screen" results in an unclean exit.
volatile static f32 tempf; * Caveat: This means that the settings are saved twice when exiting Minetest.
// Silence compiler warning */
(void)temp16; if (!g_settings_path.empty())
static v3f tempv3f1; g_settings->updateConfigFile(g_settings_path.c_str());
static v3f tempv3f2;
static std::string tempstring;
static std::string tempstring2;
tempv3f1 = v3f();
tempv3f2 = v3f();
tempstring.clear();
tempstring2.clear();
{
infostream << "The following test should take around 20ms." << std::endl;
TimeTaker timer("Testing std::string speed");
const u32 jj = 10000;
for (u32 j = 0; j < jj; j++) {
tempstring.clear();
tempstring2.clear();
const u32 ii = 10;
for (u32 i = 0; i < ii; i++) {
tempstring2 += "asd";
}
for (u32 i = 0; i < ii+1; i++) {
tempstring += "asd";
if (tempstring == tempstring2)
break;
}
}
}
infostream << "All of the following tests should take around 100ms each."
<< std::endl;
{
TimeTaker timer("Testing floating-point conversion speed");
tempf = 0.001;
for (u32 i = 0; i < 4000000; i++) {
temp16 += tempf;
tempf += 0.001;
}
}
{
TimeTaker timer("Testing floating-point vector speed");
tempv3f1 = v3f(1, 2, 3);
tempv3f2 = v3f(4, 5, 6);
for (u32 i = 0; i < 10000000; i++) {
tempf += tempv3f1.dotProduct(tempv3f2);
tempv3f2 += v3f(7, 8, 9);
}
}
{
TimeTaker timer("Testing std::map speed");
std::map<v2s16, f32> map1;
tempf = -324;
const s16 ii = 300;
for (s16 y = 0; y < ii; y++) {
for (s16 x = 0; x < ii; x++) {
map1[v2s16(x, y)] = tempf;
tempf += 1;
}
}
for (s16 y = ii - 1; y >= 0; y--) {
for (s16 x = 0; x < ii; x++) {
tempf = map1[v2s16(x, y)];
}
}
}
{
infostream << "Around 5000/ms should do well here." << std::endl;
TimeTaker timer("Testing mutex speed");
std::mutex m;
u32 n = 0;
u32 i = 0;
do {
n += 10000;
for (; i < n; i++) {
m.lock();
m.unlock();
}
}
// Do at least 10ms
while(timer.getTimerTime() < 10);
u32 dtime = timer.stop();
u32 per_ms = n / dtime;
infostream << "Done. " << dtime << "ms, " << per_ms << "/ms" << std::endl;
}
} }

@ -44,8 +44,6 @@ private:
void main_menu(MainMenuData *menudata); void main_menu(MainMenuData *menudata);
void speed_tests();
bool skip_main_menu = false; bool skip_main_menu = false;
bool random_input = false; bool random_input = false;
RenderingEngine *m_rendering_engine = nullptr; RenderingEngine *m_rendering_engine = nullptr;

@ -329,7 +329,7 @@ void ClientMap::updateDrawList()
MapBlockVect sectorblocks; MapBlockVect sectorblocks;
for (auto &sector_it : m_sectors) { for (auto &sector_it : m_sectors) {
MapSector *sector = sector_it.second; const MapSector *sector = sector_it.second;
v2s16 sp = sector->getPos(); v2s16 sp = sector->getPos();
blocks_loaded += sector->size(); blocks_loaded += sector->size();
@ -339,18 +339,16 @@ void ClientMap::updateDrawList()
continue; continue;
} }
sectorblocks.clear();
sector->getBlocks(sectorblocks);
// Loop through blocks in sector // Loop through blocks in sector
for (MapBlock *block : sectorblocks) { for (const auto &entry : sector->getBlocks()) {
MapBlock *block = entry.second.get();
MapBlockMesh *mesh = block->mesh; MapBlockMesh *mesh = block->mesh;
// Calculate the coordinates for range and frustum culling // Calculate the coordinates for range and frustum culling
v3f mesh_sphere_center; v3f mesh_sphere_center;
f32 mesh_sphere_radius; f32 mesh_sphere_radius;
v3s16 block_pos_nodes = block->getPos() * MAP_BLOCKSIZE; v3s16 block_pos_nodes = block->getPosRelative();
if (mesh) { if (mesh) {
mesh_sphere_center = intToFloat(block_pos_nodes, BS) mesh_sphere_center = intToFloat(block_pos_nodes, BS)
@ -649,7 +647,7 @@ void ClientMap::touchMapBlocks()
u32 blocks_in_range_with_mesh = 0; u32 blocks_in_range_with_mesh = 0;
for (const auto &sector_it : m_sectors) { for (const auto &sector_it : m_sectors) {
MapSector *sector = sector_it.second; const MapSector *sector = sector_it.second;
v2s16 sp = sector->getPos(); v2s16 sp = sector->getPos();
blocks_loaded += sector->size(); blocks_loaded += sector->size();
@ -659,21 +657,19 @@ void ClientMap::touchMapBlocks()
continue; continue;
} }
MapBlockVect sectorblocks;
sector->getBlocks(sectorblocks);
/* /*
Loop through blocks in sector Loop through blocks in sector
*/ */
for (MapBlock *block : sectorblocks) { for (const auto &entry : sector->getBlocks()) {
MapBlock *block = entry.second.get();
MapBlockMesh *mesh = block->mesh; MapBlockMesh *mesh = block->mesh;
// Calculate the coordinates for range and frustum culling // Calculate the coordinates for range and frustum culling
v3f mesh_sphere_center; v3f mesh_sphere_center;
f32 mesh_sphere_radius; f32 mesh_sphere_radius;
v3s16 block_pos_nodes = block->getPos() * MAP_BLOCKSIZE; v3s16 block_pos_nodes = block->getPosRelative();
if (mesh) { if (mesh) {
mesh_sphere_center = intToFloat(block_pos_nodes, BS) mesh_sphere_center = intToFloat(block_pos_nodes, BS)
@ -1249,11 +1245,6 @@ void ClientMap::updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir,
{ {
ScopeProfiler sp(g_profiler, "CM::updateDrawListShadow()", SPT_AVG); ScopeProfiler sp(g_profiler, "CM::updateDrawListShadow()", SPT_AVG);
v3s16 cam_pos_nodes = floatToInt(shadow_light_pos, BS);
v3s16 p_blocks_min;
v3s16 p_blocks_max;
getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max, radius + length);
for (auto &i : m_drawlist_shadow) { for (auto &i : m_drawlist_shadow) {
MapBlock *block = i.second; MapBlock *block = i.second;
block->refDrop(); block->refDrop();
@ -1266,25 +1257,23 @@ void ClientMap::updateDrawListShadow(v3f shadow_light_pos, v3f shadow_light_dir,
u32 blocks_in_range_with_mesh = 0; u32 blocks_in_range_with_mesh = 0;
for (auto &sector_it : m_sectors) { for (auto &sector_it : m_sectors) {
MapSector *sector = sector_it.second; const MapSector *sector = sector_it.second;
if (!sector) if (!sector)
continue; continue;
blocks_loaded += sector->size(); blocks_loaded += sector->size();
MapBlockVect sectorblocks;
sector->getBlocks(sectorblocks);
/* /*
Loop through blocks in sector Loop through blocks in sector
*/ */
for (MapBlock *block : sectorblocks) { for (const auto &entry : sector->getBlocks()) {
MapBlock *block = entry.second.get();
MapBlockMesh *mesh = block->mesh; MapBlockMesh *mesh = block->mesh;
if (!mesh) { if (!mesh) {
// Ignore if mesh doesn't exist // Ignore if mesh doesn't exist
continue; continue;
} }
v3f block_pos = intToFloat(block->getPos() * MAP_BLOCKSIZE, BS) + mesh->getBoundingSphereCenter(); v3f block_pos = intToFloat(block->getPosRelative(), BS) + mesh->getBoundingSphereCenter();
v3f projection = shadow_light_pos + shadow_light_dir * shadow_light_dir.dotProduct(block_pos - shadow_light_pos); v3f projection = shadow_light_pos + shadow_light_dir * shadow_light_dir.dotProduct(block_pos - shadow_light_pos);
if (projection.getDistanceFrom(block_pos) > (radius + mesh->getBoundingRadius())) if (projection.getDistanceFrom(block_pos) > (radius + mesh->getBoundingRadius()))
continue; continue;

@ -165,9 +165,11 @@ void Clouds::render()
driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density, driver->getFog(fog_color, fog_type, fog_start, fog_end, fog_density,
fog_pixelfog, fog_rangefog); fog_pixelfog, fog_rangefog);
// Set our own fog // Set our own fog, unless it was already disabled
driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5, if (fog_start < FOG_RANGE_ALL) {
driver->setFog(fog_color, fog_type, cloud_full_radius * 0.5,
cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog); cloud_full_radius*1.2, fog_density, fog_pixelfog, fog_rangefog);
}
// Read noise // Read noise

@ -411,8 +411,7 @@ GenericCAO::~GenericCAO()
bool GenericCAO::getSelectionBox(aabb3f *toset) const bool GenericCAO::getSelectionBox(aabb3f *toset) const
{ {
if (!m_prop.is_visible || !m_is_visible || m_is_local_player if (!m_prop.is_visible || !m_is_visible || m_is_local_player) {
|| !m_prop.pointable) {
return false; return false;
} }
*toset = m_selection_box; *toset = m_selection_box;
@ -620,6 +619,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl; infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl;
m_material_type_param = 0.5f; // May cut off alpha < 128 depending on m_material_type
if (m_enable_shaders) { if (m_enable_shaders) {
IShaderSource *shader_source = m_client->getShaderSource(); IShaderSource *shader_source = m_client->getShaderSource();
MaterialType material_type; MaterialType material_type;
@ -634,8 +635,12 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr)
u32 shader_id = shader_source->getShader("object_shader", material_type, NDT_NORMAL); u32 shader_id = shader_source->getShader("object_shader", material_type, NDT_NORMAL);
m_material_type = shader_source->getShaderInfo(shader_id).material; m_material_type = shader_source->getShaderInfo(shader_id).material;
} else { } else {
m_material_type = (m_prop.use_texture_alpha) ? if (m_prop.use_texture_alpha) {
video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; m_material_type = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
m_material_type_param = 1.0f / 256.f; // minimal alpha for texture rendering
} else {
m_material_type = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
}
} }
auto grabMatrixNode = [this] { auto grabMatrixNode = [this] {
@ -1341,7 +1346,7 @@ void GenericCAO::updateTextures(std::string mod)
video::SMaterial &material = m_spritenode->getMaterial(0); video::SMaterial &material = m_spritenode->getMaterial(0);
material.MaterialType = m_material_type; material.MaterialType = m_material_type;
material.MaterialTypeParam = 0.5f; material.MaterialTypeParam = m_material_type_param;
material.setTexture(0, tsrc->getTextureForMesh(texturestring)); material.setTexture(0, tsrc->getTextureForMesh(texturestring));
// This allows setting per-material colors. However, until a real lighting // This allows setting per-material colors. However, until a real lighting
@ -1377,7 +1382,7 @@ void GenericCAO::updateTextures(std::string mod)
// Set material flags and texture // Set material flags and texture
video::SMaterial &material = m_animated_meshnode->getMaterial(i); video::SMaterial &material = m_animated_meshnode->getMaterial(i);
material.MaterialType = m_material_type; material.MaterialType = m_material_type;
material.MaterialTypeParam = 0.5f; material.MaterialTypeParam = m_material_type_param;
material.TextureLayers[0].Texture = texture; material.TextureLayers[0].Texture = texture;
material.Lighting = true; material.Lighting = true;
material.BackfaceCulling = m_prop.backface_culling; material.BackfaceCulling = m_prop.backface_culling;
@ -1421,7 +1426,7 @@ void GenericCAO::updateTextures(std::string mod)
// Set material flags and texture // Set material flags and texture
video::SMaterial &material = m_meshnode->getMaterial(i); video::SMaterial &material = m_meshnode->getMaterial(i);
material.MaterialType = m_material_type; material.MaterialType = m_material_type;
material.MaterialTypeParam = 0.5f; material.MaterialTypeParam = m_material_type_param;
material.Lighting = false; material.Lighting = false;
material.setTexture(0, tsrc->getTextureForMesh(texturestring)); material.setTexture(0, tsrc->getTextureForMesh(texturestring));
material.getTextureMatrix(0).makeIdentity(); material.getTextureMatrix(0).makeIdentity();

@ -130,6 +130,7 @@ private:
bool m_is_visible = false; bool m_is_visible = false;
// Material // Material
video::E_MATERIAL_TYPE m_material_type; video::E_MATERIAL_TYPE m_material_type;
f32 m_material_type_param;
// Settings // Settings
bool m_enable_shaders = false; bool m_enable_shaders = false;
@ -173,6 +174,8 @@ public:
inline const ObjectProperties &getProperties() const { return m_prop; } inline const ObjectProperties &getProperties() const { return m_prop; }
inline const std::string &getName() const { return m_name; }
scene::ISceneNode *getSceneNode() const override; scene::ISceneNode *getSceneNode() const override;
scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const override; scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() const override;
@ -207,6 +210,11 @@ public:
return m_is_local_player; return m_is_local_player;
} }
inline bool isPlayer() const
{
return m_is_player;
}
inline bool isVisible() const inline bool isVisible() const
{ {
return m_is_visible; return m_is_visible;

@ -1008,7 +1008,9 @@ void MapblockMeshGenerator::drawTorchlikeNode()
switch (wall) { switch (wall) {
case DWM_YP: tileindex = 1; break; // ceiling case DWM_YP: tileindex = 1; break; // ceiling
case DWM_YN: tileindex = 0; break; // floor case DWM_YN: tileindex = 0; break; // floor
default: tileindex = 2; // side (or invalid—should we care?) case DWM_S1: tileindex = 1; break; // ceiling, but rotated
case DWM_S2: tileindex = 0; break; // floor, but rotated
default: tileindex = 2; // side (or invalid, shouldn't happen)
} }
useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); useTile(tileindex, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING);
@ -1044,6 +1046,17 @@ void MapblockMeshGenerator::drawTorchlikeNode()
case DWM_ZN: case DWM_ZN:
vertex.X += -size + BS/2; vertex.X += -size + BS/2;
vertex.rotateXZBy(-90); vertex.rotateXZBy(-90);
break;
case DWM_S1:
// same as DWM_YP, but rotated 90°
vertex.Y += -size + BS/2;
vertex.rotateXZBy(45);
break;
case DWM_S2:
// same as DWM_YN, but rotated -90°
vertex.Y += size - BS/2;
vertex.rotateXZBy(-45);
break;
} }
} }
drawQuad(vertices); drawQuad(vertices);
@ -1077,6 +1090,10 @@ void MapblockMeshGenerator::drawSignlikeNode()
vertex.rotateXZBy( 90); break; vertex.rotateXZBy( 90); break;
case DWM_ZN: case DWM_ZN:
vertex.rotateXZBy(-90); break; vertex.rotateXZBy(-90); break;
case DWM_S1:
vertex.rotateXYBy( 90); vertex.rotateXZBy(90); break;
case DWM_S2:
vertex.rotateXYBy(-90); vertex.rotateXZBy(-90); break;
} }
} }
drawQuad(vertices); drawQuad(vertices);

@ -74,6 +74,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "script/scripting_client.h" #include "script/scripting_client.h"
#include "hud.h" #include "hud.h"
#include "clientdynamicinfo.h" #include "clientdynamicinfo.h"
#include <IAnimatedMeshSceneNode.h>
#if USE_SOUND #if USE_SOUND
#include "client/sound/sound_openal.h" #include "client/sound/sound_openal.h"
@ -373,43 +374,55 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
bool *m_force_fog_off; bool *m_force_fog_off;
f32 *m_fog_range; f32 *m_fog_range;
bool m_fog_enabled; bool m_fog_enabled;
CachedPixelShaderSetting<float, 4> m_sky_bg_color; CachedPixelShaderSetting<float, 4> m_fog_color{"fogColor"};
CachedPixelShaderSetting<float> m_fog_distance; CachedPixelShaderSetting<float> m_fog_distance{"fogDistance"};
CachedPixelShaderSetting<float> m_fog_shading_parameter; CachedPixelShaderSetting<float>
CachedVertexShaderSetting<float> m_animation_timer_vertex; m_fog_shading_parameter{"fogShadingParameter"};
CachedPixelShaderSetting<float> m_animation_timer_pixel; CachedVertexShaderSetting<float> m_animation_timer_vertex{"animationTimer"};
CachedVertexShaderSetting<float> m_animation_timer_delta_vertex; CachedPixelShaderSetting<float> m_animation_timer_pixel{"animationTimer"};
CachedPixelShaderSetting<float> m_animation_timer_delta_pixel; CachedVertexShaderSetting<float>
CachedPixelShaderSetting<float, 3> m_day_light; m_animation_timer_delta_vertex{"animationTimerDelta"};
CachedPixelShaderSetting<float, 4> m_star_color; CachedPixelShaderSetting<float>
CachedPixelShaderSetting<float, 3> m_eye_position_pixel; m_animation_timer_delta_pixel{"animationTimerDelta"};
CachedVertexShaderSetting<float, 3> m_eye_position_vertex; CachedPixelShaderSetting<float, 3> m_day_light{"dayLight"};
CachedPixelShaderSetting<float, 3> m_minimap_yaw; CachedPixelShaderSetting<float, 4> m_star_color{"starColor"};
CachedPixelShaderSetting<float, 3> m_camera_offset_pixel; CachedPixelShaderSetting<float, 3> m_eye_position_pixel{"eyePosition"};
CachedPixelShaderSetting<float, 3> m_camera_offset_vertex; CachedVertexShaderSetting<float, 3> m_eye_position_vertex{"eyePosition"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture0; CachedPixelShaderSetting<float, 3> m_minimap_yaw{"yawVec"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture1; CachedPixelShaderSetting<float, 3> m_camera_offset_pixel{"cameraOffset"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture2; CachedPixelShaderSetting<float, 3> m_camera_offset_vertex{"cameraOffset"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture3; CachedPixelShaderSetting<SamplerLayer_t> m_texture0{"texture0"};
CachedVertexShaderSetting<float, 2> m_texel_size0_vertex; CachedPixelShaderSetting<SamplerLayer_t> m_texture1{"texture1"};
CachedPixelShaderSetting<float, 2> m_texel_size0_pixel; CachedPixelShaderSetting<SamplerLayer_t> m_texture2{"texture2"};
CachedPixelShaderSetting<SamplerLayer_t> m_texture3{"texture3"};
CachedVertexShaderSetting<float, 2> m_texel_size0_vertex{"texelSize0"};
CachedPixelShaderSetting<float, 2> m_texel_size0_pixel{"texelSize0"};
std::array<float, 2> m_texel_size0_values; std::array<float, 2> m_texel_size0_values;
CachedStructPixelShaderSetting<float, 7> m_exposure_params_pixel; CachedStructPixelShaderSetting<float, 7> m_exposure_params_pixel{
"exposureParams",
std::array<const char*, 7> {
"luminanceMin", "luminanceMax", "exposureCorrection",
"speedDarkBright", "speedBrightDark", "centerWeightPower",
"compensationFactor"
}};
float m_user_exposure_compensation; float m_user_exposure_compensation;
bool m_bloom_enabled; bool m_bloom_enabled;
CachedPixelShaderSetting<float> m_bloom_intensity_pixel; CachedPixelShaderSetting<float> m_bloom_intensity_pixel{"bloomIntensity"};
float m_bloom_intensity; float m_bloom_intensity;
CachedPixelShaderSetting<float> m_bloom_strength_pixel; CachedPixelShaderSetting<float> m_bloom_strength_pixel{"bloomStrength"};
float m_bloom_strength; float m_bloom_strength;
CachedPixelShaderSetting<float> m_bloom_radius_pixel; CachedPixelShaderSetting<float> m_bloom_radius_pixel{"bloomRadius"};
float m_bloom_radius; float m_bloom_radius;
CachedPixelShaderSetting<float> m_saturation_pixel; CachedPixelShaderSetting<float> m_saturation_pixel{"saturation"};
bool m_volumetric_light_enabled; bool m_volumetric_light_enabled;
CachedPixelShaderSetting<float, 3> m_sun_position_pixel; CachedPixelShaderSetting<float, 3>
CachedPixelShaderSetting<float> m_sun_brightness_pixel; m_sun_position_pixel{"sunPositionScreen"};
CachedPixelShaderSetting<float, 3> m_moon_position_pixel; CachedPixelShaderSetting<float> m_sun_brightness_pixel{"sunBrightness"};
CachedPixelShaderSetting<float> m_moon_brightness_pixel; CachedPixelShaderSetting<float, 3>
CachedPixelShaderSetting<float> m_volumetric_light_strength_pixel; m_moon_position_pixel{"moonPositionScreen"};
CachedPixelShaderSetting<float> m_moon_brightness_pixel{"moonBrightness"};
CachedPixelShaderSetting<float>
m_volumetric_light_strength_pixel{"volumetricLightStrength"};
public: public:
void onSettingsChange(const std::string &name) void onSettingsChange(const std::string &name)
@ -438,41 +451,7 @@ public:
m_sky(sky), m_sky(sky),
m_client(client), m_client(client),
m_force_fog_off(force_fog_off), m_force_fog_off(force_fog_off),
m_fog_range(fog_range), m_fog_range(fog_range)
m_sky_bg_color("skyBgColor"),
m_fog_distance("fogDistance"),
m_fog_shading_parameter("fogShadingParameter"),
m_animation_timer_vertex("animationTimer"),
m_animation_timer_pixel("animationTimer"),
m_animation_timer_delta_vertex("animationTimerDelta"),
m_animation_timer_delta_pixel("animationTimerDelta"),
m_day_light("dayLight"),
m_star_color("starColor"),
m_eye_position_pixel("eyePosition"),
m_eye_position_vertex("eyePosition"),
m_minimap_yaw("yawVec"),
m_camera_offset_pixel("cameraOffset"),
m_camera_offset_vertex("cameraOffset"),
m_texture0("texture0"),
m_texture1("texture1"),
m_texture2("texture2"),
m_texture3("texture3"),
m_texel_size0_vertex("texelSize0"),
m_texel_size0_pixel("texelSize0"),
m_exposure_params_pixel("exposureParams",
std::array<const char*, 7> {
"luminanceMin", "luminanceMax", "exposureCorrection",
"speedDarkBright", "speedBrightDark", "centerWeightPower", "compensationFactor"
}),
m_bloom_intensity_pixel("bloomIntensity"),
m_bloom_strength_pixel("bloomStrength"),
m_bloom_radius_pixel("bloomRadius"),
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);
@ -496,20 +475,13 @@ public:
void onSetConstants(video::IMaterialRendererServices *services) override void onSetConstants(video::IMaterialRendererServices *services) override
{ {
// Background color video::SColorf fogcolorf(m_sky->getFogColor());
video::SColor bgcolor = m_sky->getBgColor(); float fogcolorfa[4] = {
video::SColorf bgcolorf(bgcolor); fogcolorf.r, fogcolorf.g, fogcolorf.b, fogcolorf.a,
float bgcolorfa[4] = {
bgcolorf.r,
bgcolorf.g,
bgcolorf.b,
bgcolorf.a,
}; };
m_sky_bg_color.set(bgcolorfa, services); m_fog_color.set(fogcolorfa, services);
// Fog distance
float fog_distance = 10000 * BS; float fog_distance = 10000 * BS;
if (m_fog_enabled && !*m_force_fog_off) if (m_fog_enabled && !*m_force_fog_off)
fog_distance = *m_fog_range; fog_distance = *m_fog_range;
@ -862,6 +834,7 @@ protected:
* the camera position. This also gives the maximal distance * the camera position. This also gives the maximal distance
* of the search. * of the search.
* @param[in] liquids_pointable if false, liquids are ignored * @param[in] liquids_pointable if false, liquids are ignored
* @param[in] pointabilities item specific pointable overriding
* @param[in] look_for_object if false, objects are ignored * @param[in] look_for_object if false, objects are ignored
* @param[in] camera_offset offset of the camera * @param[in] camera_offset offset of the camera
* @param[out] selected_object the selected object or * @param[out] selected_object the selected object or
@ -869,6 +842,7 @@ protected:
*/ */
PointedThing updatePointedThing( PointedThing updatePointedThing(
const core::line3d<f32> &shootline, bool liquids_pointable, const core::line3d<f32> &shootline, bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities,
bool look_for_object, const v3s16 &camera_offset); bool look_for_object, const v3s16 &camera_offset);
void handlePointingAtNothing(const ItemStack &playerItem); void handlePointingAtNothing(const ItemStack &playerItem);
void handlePointingAtNode(const PointedThing &pointed, void handlePointingAtNode(const PointedThing &pointed,
@ -1002,7 +976,6 @@ private:
bool *kill; bool *kill;
std::string *error_message; std::string *error_message;
bool *reconnect_requested; bool *reconnect_requested;
scene::ISceneNode *skybox;
PausedNodesList paused_animated_nodes; PausedNodesList paused_animated_nodes;
bool simple_singleplayer_mode; bool simple_singleplayer_mode;
@ -1541,7 +1514,6 @@ bool Game::createClient(const GameStartData &start_data)
*/ */
sky = new Sky(-1, m_rendering_engine, texture_src, shader_src); sky = new Sky(-1, m_rendering_engine, texture_src, shader_src);
scsf->setSky(sky); scsf->setSky(sky);
skybox = NULL; // This is used/set later on in the main run loop
/* Pre-calculated values /* Pre-calculated values
*/ */
@ -1771,7 +1743,7 @@ bool Game::getServerContent(bool *aborted)
// End condition // End condition
if (client->mediaReceived() && client->itemdefReceived() && if (client->mediaReceived() && client->itemdefReceived() &&
client->nodedefReceived()) { client->nodedefReceived()) {
break; return true;
} }
// Error conditions // Error conditions
@ -1830,7 +1802,9 @@ bool Game::getServerContent(bool *aborted)
} }
} }
return true; *aborted = true;
infostream << "Connect aborted [device]" << std::endl;
return false;
} }
@ -2635,23 +2609,27 @@ void Game::checkZoomEnabled()
void Game::updateCameraDirection(CameraOrientation *cam, float dtime) void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
{ {
#ifndef __ANDROID__ auto *cur_control = device->getCursorControl();
if (isMenuActive())
device->getCursorControl()->setRelativeMode(false); /* With CIrrDeviceSDL on Linux and Windows, enabling relative mouse mode
else somehow results in simulated mouse events being generated from touch events,
device->getCursorControl()->setRelativeMode(true); although SDL_HINT_MOUSE_TOUCH_EVENTS and SDL_HINT_TOUCH_MOUSE_EVENTS are set to 0.
Since Minetest has its own code to synthesize mouse events from touch events,
this results in duplicated input. To avoid that, we don't enable relative
mouse mode if we're in touchscreen mode. */
#ifndef HAVE_TOUCHSCREENGUI
if (cur_control)
cur_control->setRelativeMode(!isMenuActive());
#endif #endif
if ((device->isWindowActive() && device->isWindowFocused() if ((device->isWindowActive() && device->isWindowFocused()
&& !isMenuActive()) || input->isRandom()) { && !isMenuActive()) || input->isRandom()) {
#ifndef __ANDROID__ if (cur_control && !input->isRandom()) {
if (!input->isRandom()) {
// Mac OSX gets upset if this is set every frame // Mac OSX gets upset if this is set every frame
if (device->getCursorControl()->isVisible()) if (cur_control->isVisible())
device->getCursorControl()->setVisible(false); cur_control->setVisible(false);
} }
#endif
if (m_first_loop_after_window_activation) { if (m_first_loop_after_window_activation) {
m_first_loop_after_window_activation = false; m_first_loop_after_window_activation = false;
@ -2663,15 +2641,11 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
} }
} else { } else {
#ifndef ANDROID
// Mac OSX gets upset if this is set every frame // Mac OSX gets upset if this is set every frame
if (!device->getCursorControl()->isVisible()) if (cur_control && !cur_control->isVisible())
device->getCursorControl()->setVisible(true); cur_control->setVisible(true);
#endif
m_first_loop_after_window_activation = true; m_first_loop_after_window_activation = true;
} }
} }
@ -3048,6 +3022,9 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca
CASE_SET(HUD_STAT_TEXT2, text2, sdata); CASE_SET(HUD_STAT_TEXT2, text2, sdata);
CASE_SET(HUD_STAT_STYLE, style, data); CASE_SET(HUD_STAT_STYLE, style, data);
case HudElementStat_END:
break;
} }
#undef CASE_SET #undef CASE_SET
@ -3061,10 +3038,6 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
// Whether clouds are visible in front of a custom skybox. // Whether clouds are visible in front of a custom skybox.
sky->setCloudsEnabled(event->set_sky->clouds); sky->setCloudsEnabled(event->set_sky->clouds);
if (skybox) {
skybox->remove();
skybox = NULL;
}
// Clear the old textures out in case we switch rendering type. // Clear the old textures out in case we switch rendering type.
sky->clearSkyboxTextures(); sky->clearSkyboxTextures();
// Handle according to type // Handle according to type
@ -3124,6 +3097,8 @@ 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));
sky->setFogColor(event->set_sky->fog_color);
delete event->set_sky; delete event->set_sky;
} }
@ -3361,12 +3336,18 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
PointedThing pointed = updatePointedThing(shootline, PointedThing pointed = updatePointedThing(shootline,
selected_def.liquids_pointable, selected_def.liquids_pointable,
selected_def.pointabilities,
!runData.btn_down_for_dig, !runData.btn_down_for_dig,
camera_offset); camera_offset);
if (pointed != runData.pointed_old) if (pointed != runData.pointed_old)
infostream << "Pointing at " << pointed.dump() << std::endl; infostream << "Pointing at " << pointed.dump() << std::endl;
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed));
#endif
// Note that updating the selection mesh every frame is not particularly efficient, // Note that updating the selection mesh every frame is not particularly efficient,
// but the halo rendering code is already inefficient so there's no point in optimizing it here // but the halo rendering code is already inefficient so there's no point in optimizing it here
hud->updateSelectionMesh(camera_offset); hud->updateSelectionMesh(camera_offset);
@ -3467,6 +3448,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
PointedThing Game::updatePointedThing( PointedThing Game::updatePointedThing(
const core::line3d<f32> &shootline, const core::line3d<f32> &shootline,
bool liquids_pointable, bool liquids_pointable,
const std::optional<Pointabilities> &pointabilities,
bool look_for_object, bool look_for_object,
const v3s16 &camera_offset) const v3s16 &camera_offset)
{ {
@ -3483,7 +3465,7 @@ PointedThing Game::updatePointedThing(
runData.selected_object = NULL; runData.selected_object = NULL;
hud->pointing_at_object = false; hud->pointing_at_object = false;
RaycastState s(shootline, look_for_object, liquids_pointable); RaycastState s(shootline, look_for_object, liquids_pointable, pointabilities);
PointedThing result; PointedThing result;
env.continueRaycast(&s, &result); env.continueRaycast(&s, &result);
if (result.type == POINTEDTHING_OBJECT) { if (result.type == POINTEDTHING_OBJECT) {
@ -3725,7 +3707,36 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
v3s16 dir = nodepos - neighborpos; v3s16 dir = nodepos - neighborpos;
if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) { if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
predicted_node.setParam2(dir.Y < 0 ? 1 : 0); // If you change this code, also change builtin/game/item.lua
u8 predicted_param2 = dir.Y < 0 ? 1 : 0;
if (selected_def.wallmounted_rotate_vertical) {
bool rotate90 = false;
v3f fnodepos = v3f(neighborpos.X, neighborpos.Y, neighborpos.Z);
v3f ppos = client->getEnv().getLocalPlayer()->getPosition() / BS;
v3f pdir = fnodepos - ppos;
switch (predicted_f.drawtype) {
case NDT_TORCHLIKE: {
rotate90 = !((pdir.X < 0 && pdir.Z > 0) ||
(pdir.X > 0 && pdir.Z < 0));
if (dir.Y > 0) {
rotate90 = !rotate90;
}
break;
};
case NDT_SIGNLIKE: {
rotate90 = abs(pdir.X) < abs(pdir.Z);
break;
}
default: {
rotate90 = abs(pdir.X) > abs(pdir.Z);
break;
}
}
if (rotate90) {
predicted_param2 += 6;
}
}
predicted_node.setParam2(predicted_param2);
} else if (abs(dir.X) > abs(dir.Z)) { } else if (abs(dir.X) > abs(dir.Z)) {
predicted_node.setParam2(dir.X < 0 ? 3 : 2); predicted_node.setParam2(dir.X < 0 ? 3 : 2);
} else { } else {
@ -4035,7 +4046,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
draw_control->wanted_range = MYMIN(draw_control->wanted_range, sky->getFogDistance()); draw_control->wanted_range = MYMIN(draw_control->wanted_range, sky->getFogDistance());
} }
if (draw_control->range_all && sky->getFogDistance() < 0) { if (draw_control->range_all && sky->getFogDistance() < 0) {
runData.fog_range = 100000 * BS; runData.fog_range = FOG_RANGE_ALL;
} else { } else {
runData.fog_range = draw_control->wanted_range * BS; runData.fog_range = draw_control->wanted_range * BS;
} }
@ -4196,7 +4207,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
/* /*
==================== Drawing begins ==================== ==================== Drawing begins ====================
*/ */
if (RenderingEngine::shouldRender()) if (device->isWindowVisible())
drawScene(graph, stats); drawScene(graph, stats);
/* /*
==================== End scene ==================== ==================== End scene ====================
@ -4277,7 +4288,7 @@ void Game::updateShadows()
void Game::drawScene(ProfilerGraph *graph, RunStats *stats) void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
{ {
const video::SColor bg_color = this->sky->getBgColor(); const video::SColor fog_color = this->sky->getFogColor();
const video::SColor sky_color = this->sky->getSkyColor(); const video::SColor sky_color = this->sky->getSkyColor();
/* /*
@ -4285,21 +4296,21 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
*/ */
if (this->m_cache_enable_fog) { if (this->m_cache_enable_fog) {
this->driver->setFog( this->driver->setFog(
bg_color, fog_color,
video::EFT_FOG_LINEAR, video::EFT_FOG_LINEAR,
this->runData.fog_range * this->sky->getFogStart(), this->runData.fog_range * this->sky->getFogStart(),
this->runData.fog_range * 1.0f, this->runData.fog_range * 1.0f,
0.01f, 0.f, // unused
false, // pixel fog false, // pixel fog
true // range fog true // range fog
); );
} else { } else {
this->driver->setFog( this->driver->setFog(
bg_color, fog_color,
video::EFT_FOG_LINEAR, video::EFT_FOG_LINEAR,
100000 * BS, FOG_RANGE_ALL,
110000 * BS, FOG_RANGE_ALL + 100 * BS,
0.01f, 0.f, // unused
false, // pixel fog false, // pixel fog
false // range fog false // range fog
); );
@ -4473,8 +4484,8 @@ void Game::showPauseMenu()
static const std::string control_text = strgettext("Controls:\n" static const std::string control_text = strgettext("Controls:\n"
"No menu open:\n" "No menu open:\n"
"- slide finger: look around\n" "- slide finger: look around\n"
"- tap: place/use\n" "- tap: place/punch/use (default)\n"
"- long tap: dig/punch/use\n" "- long tap: dig/use (default)\n"
"Menu/inventory open:\n" "Menu/inventory open:\n"
"- double tap (outside):\n" "- double tap (outside):\n"
" --> close\n" " --> close\n"

@ -416,7 +416,9 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
(e->number >> 0) & 0xFF); (e->number >> 0) & 0xFF);
std::wstring text = unescape_translate(utf8_to_wide(e->name)); std::wstring text = unescape_translate(utf8_to_wide(e->name));
const std::string &unit = e->text; const std::string &unit = e->text;
// waypoints reuse the item field to store precision, item = precision + 1 // Waypoints reuse the item field to store precision,
// item = precision + 1 and item = 0 <=> precision = 10 for backwards compatibility.
// Also see `push_hud_element`.
u32 item = e->item; u32 item = e->item;
float precision = (item == 0) ? 10.0f : (item - 1.f); float precision = (item == 0) ? 10.0f : (item - 1.f);
bool draw_precision = precision > 0; bool draw_precision = precision > 0;

@ -54,7 +54,6 @@ MeshUpdateQueue::MeshUpdateQueue(Client *client):
{ {
m_cache_enable_shaders = g_settings->getBool("enable_shaders"); m_cache_enable_shaders = g_settings->getBool("enable_shaders");
m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); m_cache_smooth_lighting = g_settings->getBool("smooth_lighting");
m_meshgen_block_cache_size = g_settings->getS32("meshgen_block_cache_size");
} }
MeshUpdateQueue::~MeshUpdateQueue() MeshUpdateQueue::~MeshUpdateQueue()

@ -87,7 +87,6 @@ private:
// TODO: Add callback to update these when g_settings changes // TODO: Add callback to update these when g_settings changes
bool m_cache_enable_shaders; bool m_cache_enable_shaders;
bool m_cache_smooth_lighting; bool m_cache_smooth_lighting;
int m_meshgen_block_cache_size;
void fillDataFromMapBlocks(QueuedMeshUpdate *q); void fillDataFromMapBlocks(QueuedMeshUpdate *q);
void cleanupCache(); void cleanupCache();

@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/ */
#include <optional> #include <optional>
#include <IrrlichtDevice.h> #include <irrlicht.h>
#include "fontengine.h" #include "fontengine.h"
#include "client.h" #include "client.h"
#include "clouds.h" #include "clouds.h"
@ -292,15 +292,16 @@ std::vector<video::E_DRIVER_TYPE> RenderingEngine::getSupportedVideoDrivers()
// Order by preference (best first) // Order by preference (best first)
static const video::E_DRIVER_TYPE glDrivers[] = { static const video::E_DRIVER_TYPE glDrivers[] = {
video::EDT_OPENGL, video::EDT_OPENGL,
video::EDT_OPENGL3,
video::EDT_OGLES2, video::EDT_OGLES2,
video::EDT_OGLES1, video::EDT_OGLES1,
video::EDT_NULL, video::EDT_NULL,
}; };
std::vector<video::E_DRIVER_TYPE> drivers; std::vector<video::E_DRIVER_TYPE> drivers;
for (u32 i = 0; i < ARRLEN(glDrivers); i++) { for (video::E_DRIVER_TYPE driver: glDrivers) {
if (IrrlichtDevice::isDriverSupported(glDrivers[i])) if (IrrlichtDevice::isDriverSupported(driver))
drivers.push_back(glDrivers[i]); drivers.push_back(driver);
} }
return drivers; return drivers;
@ -328,6 +329,7 @@ const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_
static const std::unordered_map<int, VideoDriverInfo> driver_info_map = { static const std::unordered_map<int, VideoDriverInfo> driver_info_map = {
{(int)video::EDT_NULL, {"null", "NULL Driver"}}, {(int)video::EDT_NULL, {"null", "NULL Driver"}},
{(int)video::EDT_OPENGL, {"opengl", "OpenGL"}}, {(int)video::EDT_OPENGL, {"opengl", "OpenGL"}},
{(int)video::EDT_OPENGL3, {"opengl3", "OpenGL 3+"}},
{(int)video::EDT_OGLES1, {"ogles1", "OpenGL ES1"}}, {(int)video::EDT_OGLES1, {"ogles1", "OpenGL ES1"}},
{(int)video::EDT_OGLES2, {"ogles2", "OpenGL ES2"}}, {(int)video::EDT_OGLES2, {"ogles2", "OpenGL ES2"}},
}; };

@ -43,6 +43,9 @@ class Minimap;
class RenderingCore; class RenderingCore;
// Instead of a mechanism to disable fog we just set it to be really far away
#define FOG_RANGE_ALL (100000 * BS)
class RenderingEngine class RenderingEngine
{ {
public: public:
@ -138,17 +141,6 @@ 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;

@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <IShaderConstantSetCallBack.h> #include <IShaderConstantSetCallBack.h>
#include "client/renderingengine.h" #include "client/renderingengine.h"
#include "EShaderTypes.h" #include "EShaderTypes.h"
#include "gettext.h"
#include "log.h" #include "log.h"
#include "gamedef.h" #include "gamedef.h"
#include "client/tile.h" #include "client/tile.h"
@ -210,51 +211,36 @@ public:
class MainShaderConstantSetter : public IShaderConstantSetter class MainShaderConstantSetter : public IShaderConstantSetter
{ {
CachedVertexShaderSetting<f32, 16> m_world_view_proj; CachedVertexShaderSetting<f32, 16> m_world_view_proj{"mWorldViewProj"};
CachedVertexShaderSetting<f32, 16> m_world; CachedVertexShaderSetting<f32, 16> m_world{"mWorld"};
// Shadow-related // Shadow-related
CachedPixelShaderSetting<f32, 16> m_shadow_view_proj; CachedPixelShaderSetting<f32, 16> m_shadow_view_proj{"m_ShadowViewProj"};
CachedPixelShaderSetting<f32, 3> m_light_direction; CachedPixelShaderSetting<f32, 3> m_light_direction{"v_LightDirection"};
CachedPixelShaderSetting<f32> m_texture_res; CachedPixelShaderSetting<f32> m_texture_res{"f_textureresolution"};
CachedPixelShaderSetting<f32> m_shadow_strength; CachedPixelShaderSetting<f32> m_shadow_strength{"f_shadow_strength"};
CachedPixelShaderSetting<f32> m_time_of_day; CachedPixelShaderSetting<f32> m_time_of_day{"f_timeofday"};
CachedPixelShaderSetting<f32> m_shadowfar; CachedPixelShaderSetting<f32> m_shadowfar{"f_shadowfar"};
CachedPixelShaderSetting<f32, 4> m_camera_pos; CachedPixelShaderSetting<f32, 4> m_camera_pos{"CameraPos"};
CachedPixelShaderSetting<s32> m_shadow_texture; CachedPixelShaderSetting<s32> m_shadow_texture{"ShadowMapSampler"};
CachedVertexShaderSetting<f32> m_perspective_bias0_vertex; CachedVertexShaderSetting<f32>
CachedPixelShaderSetting<f32> m_perspective_bias0_pixel; m_perspective_bias0_vertex{"xyPerspectiveBias0"};
CachedVertexShaderSetting<f32> m_perspective_bias1_vertex; CachedPixelShaderSetting<f32>
CachedPixelShaderSetting<f32> m_perspective_bias1_pixel; m_perspective_bias0_pixel{"xyPerspectiveBias0"};
CachedVertexShaderSetting<f32> m_perspective_zbias_vertex; CachedVertexShaderSetting<f32>
CachedPixelShaderSetting<f32> m_perspective_zbias_pixel; m_perspective_bias1_vertex{"xyPerspectiveBias1"};
CachedPixelShaderSetting<f32>
m_perspective_bias1_pixel{"xyPerspectiveBias1"};
CachedVertexShaderSetting<f32>
m_perspective_zbias_vertex{"zPerspectiveBias"};
CachedPixelShaderSetting<f32> m_perspective_zbias_pixel{"zPerspectiveBias"};
// Modelview matrix // Modelview matrix
CachedVertexShaderSetting<float, 16> m_world_view; CachedVertexShaderSetting<float, 16> m_world_view{"mWorldView"};
// Texture matrix // Texture matrix
CachedVertexShaderSetting<float, 16> m_texture; CachedVertexShaderSetting<float, 16> m_texture{"mTexture"};
public: public:
MainShaderConstantSetter() :
m_world_view_proj("mWorldViewProj")
, m_world("mWorld")
, m_shadow_view_proj("m_ShadowViewProj")
, m_light_direction("v_LightDirection")
, m_texture_res("f_textureresolution")
, m_shadow_strength("f_shadow_strength")
, m_time_of_day("f_timeofday")
, m_shadowfar("f_shadowfar")
, m_camera_pos("CameraPos")
, m_shadow_texture("ShadowMapSampler")
, m_perspective_bias0_vertex("xyPerspectiveBias0")
, m_perspective_bias0_pixel("xyPerspectiveBias0")
, m_perspective_bias1_vertex("xyPerspectiveBias1")
, m_perspective_bias1_pixel("xyPerspectiveBias1")
, m_perspective_zbias_vertex("zPerspectiveBias")
, m_perspective_zbias_pixel("zPerspectiveBias")
, m_world_view("mWorldView")
, m_texture("mTexture")
{}
~MainShaderConstantSetter() = default; ~MainShaderConstantSetter() = default;
virtual void onSetConstants(video::IMaterialRendererServices *services) override virtual void onSetConstants(video::IMaterialRendererServices *services) override
@ -276,7 +262,7 @@ public:
worldViewProj *= worldView; worldViewProj *= worldView;
m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services); m_world_view_proj.set(*reinterpret_cast<float(*)[16]>(worldViewProj.pointer()), services);
if (driver->getDriverType() == video::EDT_OGLES2) { if (driver->getDriverType() == video::EDT_OGLES2 || driver->getDriverType() == video::EDT_OPENGL3) {
core::matrix4 texture = driver->getTransform(video::ETS_TEXTURE_0); core::matrix4 texture = driver->getTransform(video::ETS_TEXTURE_0);
m_world_view.set(*reinterpret_cast<float(*)[16]>(worldView.pointer()), services); m_world_view.set(*reinterpret_cast<float(*)[16]>(worldView.pointer()), services);
m_texture.set(*reinterpret_cast<float(*)[16]>(texture.pointer()), services); m_texture.set(*reinterpret_cast<float(*)[16]>(texture.pointer()), services);
@ -603,23 +589,25 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
video::IVideoDriver *driver = RenderingEngine::get_video_driver(); video::IVideoDriver *driver = RenderingEngine::get_video_driver();
if (!driver->queryFeature(video::EVDF_ARB_GLSL)) { if (!driver->queryFeature(video::EVDF_ARB_GLSL)) {
errorstream << "Shaders are enabled but GLSL is not supported by the driver\n"; throw ShaderException(gettext("Shaders are enabled but GLSL is not "
return shaderinfo; "supported by the driver."));
} }
video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices(); video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
// Create shaders header // Create shaders header
bool use_gles = driver->getDriverType() == video::EDT_OGLES2; bool fully_programmable = driver->getDriverType() == video::EDT_OGLES2 || driver->getDriverType() == video::EDT_OPENGL3;
std::stringstream shaders_header; std::stringstream shaders_header;
shaders_header shaders_header
<< std::noboolalpha << std::noboolalpha
<< std::showpoint // for GLSL ES << std::showpoint // for GLSL ES
; ;
std::string vertex_header, fragment_header, geometry_header; std::string vertex_header, fragment_header, geometry_header;
if (use_gles) { if (fully_programmable) {
shaders_header << R"( if (driver->getDriverType() == video::EDT_OPENGL3) {
#version 100 shaders_header << "#version 150\n";
)"; } else {
shaders_header << "#version 100\n";
}
vertex_header = R"( vertex_header = R"(
precision mediump float; precision mediump float;
@ -673,7 +661,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
abort(); abort();
} }
bool use_discard = use_gles; bool use_discard = fully_programmable;
// For renderers that should use discard instead of GL_ALPHA_TEST // For renderers that should use discard instead of GL_ALPHA_TEST
const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER)); const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER));
if (strstr(renderer, "GC7000")) if (strstr(renderer, "GC7000"))
@ -805,7 +793,9 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
dumpShaderProgram(warningstream, "Vertex", vertex_shader); dumpShaderProgram(warningstream, "Vertex", vertex_shader);
dumpShaderProgram(warningstream, "Fragment", fragment_shader); dumpShaderProgram(warningstream, "Fragment", fragment_shader);
dumpShaderProgram(warningstream, "Geometry", geometry_shader); dumpShaderProgram(warningstream, "Geometry", geometry_shader);
return shaderinfo; throw ShaderException(
fmtgettext("Failed to compile the \"%s\" shader.", name.c_str()) +
strgettext("\nCheck debug.txt for details."));
} }
// Apply the newly created material type // Apply the newly created material type

@ -39,16 +39,12 @@ private:
class shadowScreenQuadCB : public video::IShaderConstantSetCallBack class shadowScreenQuadCB : public video::IShaderConstantSetCallBack
{ {
public: public:
shadowScreenQuadCB() :
m_sm_client_map_setting("ShadowMapClientMap"),
m_sm_client_map_trans_setting("ShadowMapClientMapTraslucent"),
m_sm_dynamic_sampler_setting("ShadowMapSamplerdynamic")
{}
virtual void OnSetConstants(video::IMaterialRendererServices *services, virtual void OnSetConstants(video::IMaterialRendererServices *services,
s32 userData); s32 userData);
private: private:
CachedPixelShaderSetting<s32> m_sm_client_map_setting; CachedPixelShaderSetting<s32> m_sm_client_map_setting{"ShadowMapClientMap"};
CachedPixelShaderSetting<s32> m_sm_client_map_trans_setting; CachedPixelShaderSetting<s32>
CachedPixelShaderSetting<s32> m_sm_dynamic_sampler_setting; m_sm_client_map_trans_setting{"ShadowMapClientMapTraslucent"};
CachedPixelShaderSetting<s32>
m_sm_dynamic_sampler_setting{"ShadowMapSamplerdynamic"};
}; };

@ -26,17 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class ShadowDepthShaderCB : public video::IShaderConstantSetCallBack class ShadowDepthShaderCB : public video::IShaderConstantSetCallBack
{ {
public: public:
ShadowDepthShaderCB() :
m_light_mvp_setting("LightMVP"),
m_map_resolution_setting("MapResolution"),
m_max_far_setting("MaxFar"),
m_color_map_sampler_setting("ColorMapSampler"),
m_perspective_bias0("xyPerspectiveBias0"),
m_perspective_bias1("xyPerspectiveBias1"),
m_perspective_zbias("zPerspectiveBias"),
m_cam_pos_setting("CameraPos")
{}
void OnSetMaterial(const video::SMaterial &material) override {} void OnSetMaterial(const video::SMaterial &material) override {}
void OnSetConstants(video::IMaterialRendererServices *services, void OnSetConstants(video::IMaterialRendererServices *services,
@ -47,12 +36,13 @@ public:
v3f CameraPos; v3f CameraPos;
private: private:
CachedVertexShaderSetting<f32, 16> m_light_mvp_setting; CachedVertexShaderSetting<f32, 16> m_light_mvp_setting{"LightMVP"};
CachedVertexShaderSetting<f32> m_map_resolution_setting; CachedVertexShaderSetting<f32> m_map_resolution_setting{"MapResolution"};
CachedVertexShaderSetting<f32> m_max_far_setting; CachedVertexShaderSetting<f32> m_max_far_setting{"MaxFar"};
CachedPixelShaderSetting<s32> m_color_map_sampler_setting; CachedPixelShaderSetting<s32>
CachedVertexShaderSetting<f32> m_perspective_bias0; m_color_map_sampler_setting{"ColorMapSampler"};
CachedVertexShaderSetting<f32> m_perspective_bias1; CachedVertexShaderSetting<f32> m_perspective_bias0{"xyPerspectiveBias0"};
CachedVertexShaderSetting<f32> m_perspective_zbias; CachedVertexShaderSetting<f32> m_perspective_bias1{"xyPerspectiveBias1"};
CachedVertexShaderSetting<f32, 4> m_cam_pos_setting; CachedVertexShaderSetting<f32> m_perspective_zbias{"zPerspectiveBias"};
CachedVertexShaderSetting<f32, 4> m_cam_pos_setting{"CameraPos"};
}; };

@ -54,12 +54,12 @@ public:
float getBrightness() { return m_brightness; } float getBrightness() { return m_brightness; }
const video::SColor &getBgColor() const video::SColor getBgColor() const
{ {
return m_visible ? m_bgcolor : m_fallback_bg_color; return m_visible ? m_bgcolor : m_fallback_bg_color;
} }
const video::SColor &getSkyColor() const video::SColor getSkyColor() const
{ {
return m_visible ? m_skycolor : m_fallback_bg_color; return m_visible ? m_skycolor : m_fallback_bg_color;
} }
@ -90,6 +90,7 @@ public:
const video::SColorf &getCloudColor() const { return m_cloudcolor_f; } const video::SColorf &getCloudColor() const { return m_cloudcolor_f; }
void setVisible(bool visible) { m_visible = visible; } void setVisible(bool visible) { m_visible = visible; }
// Set only from set_sky API // Set only from set_sky API
void setCloudsEnabled(bool clouds_enabled) { m_clouds_enabled = clouds_enabled; } void setCloudsEnabled(bool clouds_enabled) { m_clouds_enabled = clouds_enabled; }
void setFallbackBgColor(video::SColor fallback_bg_color) void setFallbackBgColor(video::SColor fallback_bg_color)
@ -111,17 +112,23 @@ public:
const std::string &use_sun_tint); const std::string &use_sun_tint);
void setInClouds(bool clouds) { m_in_clouds = clouds; } void setInClouds(bool clouds) { m_in_clouds = clouds; }
void clearSkyboxTextures() { m_sky_params.textures.clear(); } void clearSkyboxTextures() { m_sky_params.textures.clear(); }
void addTextureToSkybox(const std::string &texture, int material_id, void addTextureToSkybox(const std::string &texture, int material_id,
ITextureSource *tsrc); ITextureSource *tsrc);
const video::SColorf &getCurrentStarColor() const { return m_star_color; } const video::SColorf &getCurrentStarColor() const { return m_star_color; }
// Note: the Sky class doesn't use these values. It just stores them.
void setFogDistance(s16 fog_distance) { m_sky_params.fog_distance = fog_distance; } void setFogDistance(s16 fog_distance) { m_sky_params.fog_distance = fog_distance; }
s16 getFogDistance() const { return m_sky_params.fog_distance; } s16 getFogDistance() const { return m_sky_params.fog_distance; }
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; } void setFogColor(video::SColor v) { m_sky_params.fog_color = v; }
float getVolumetricLightStrength() const { return m_sky_params.volumetric_light_strength; } video::SColor getFogColor() const {
if (m_sky_params.fog_color.getAlpha() > 0)
return m_sky_params.fog_color;
return getBgColor();
}
private: private:
aabb3f m_box; aabb3f m_box;

@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <algorithm> #include <algorithm>
#include <ICameraSceneNode.h> #include <ICameraSceneNode.h>
#include <IVideoDriver.h> #include <IVideoDriver.h>
#include <IFileSystem.h>
#include "util/string.h" #include "util/string.h"
#include "util/container.h" #include "util/container.h"
#include "util/thread.h" #include "util/thread.h"
@ -38,7 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
/* /*
A cache from texture name to texture path A cache from texture name to texture path
*/ */
MutexedMap<std::string, std::string> g_texturename_to_path_cache; static MutexedMap<std::string, std::string> g_texturename_to_path_cache;
/* /*
Replaces the filename extension. Replaces the filename extension.
@ -185,11 +186,11 @@ struct TextureInfo
TextureInfo( TextureInfo(
const std::string &name_, const std::string &name_,
video::ITexture *texture_, video::ITexture *texture_,
std::set<std::string> &sourceImages_ std::set<std::string> &&sourceImages_
): ):
name(name_), name(name_),
texture(texture_), texture(texture_),
sourceImages(sourceImages_) sourceImages(std::move(sourceImages_))
{ {
} }
}; };
@ -486,8 +487,6 @@ TextureSource::~TextureSource()
u32 TextureSource::getTextureId(const std::string &name) u32 TextureSource::getTextureId(const std::string &name)
{ {
//infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
{ {
/* /*
See if texture already exists See if texture already exists
@ -552,16 +551,16 @@ static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
// color alpha with the destination alpha. // color alpha with the destination alpha.
// Otherwise, any pixels that are not fully transparent get the color alpha. // Otherwise, any pixels that are not fully transparent get the color alpha.
static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size, static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
const video::SColor &color, int ratio, bool keep_alpha); const video::SColor color, int ratio, bool keep_alpha);
// paint a texture using the given color // paint a texture using the given color
static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size, static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
const video::SColor &color); const video::SColor color);
// Perform a Screen blend with the given color. The opposite effect of a // Perform a Screen blend with the given color. The opposite effect of a
// Multiply blend. // Multiply blend.
static void apply_screen(video::IImage *dst, v2u32 dst_pos, v2u32 size, static void apply_screen(video::IImage *dst, v2u32 dst_pos, v2u32 size,
const video::SColor &color); const video::SColor color);
// Adjust the hue, saturation, and lightness of destination. Like // Adjust the hue, saturation, and lightness of destination. Like
// "Hue-Saturation" in GIMP. // "Hue-Saturation" in GIMP.
@ -607,8 +606,6 @@ void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
*/ */
u32 TextureSource::generateTexture(const std::string &name) u32 TextureSource::generateTexture(const std::string &name)
{ {
//infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
// Empty name means texture 0 // Empty name means texture 0
if (name.empty()) { if (name.empty()) {
infostream<<"generateTexture(): name is empty"<<std::endl; infostream<<"generateTexture(): name is empty"<<std::endl;
@ -620,8 +617,7 @@ u32 TextureSource::generateTexture(const std::string &name)
See if texture already exists See if texture already exists
*/ */
MutexAutoLock lock(m_textureinfo_cache_mutex); MutexAutoLock lock(m_textureinfo_cache_mutex);
std::map<std::string, u32>::iterator n; auto n = m_name_to_id.find(name);
n = m_name_to_id.find(name);
if (n != m_name_to_id.end()) { if (n != m_name_to_id.end()) {
return n->second; return n->second;
} }
@ -660,8 +656,8 @@ u32 TextureSource::generateTexture(const std::string &name)
MutexAutoLock lock(m_textureinfo_cache_mutex); MutexAutoLock lock(m_textureinfo_cache_mutex);
u32 id = m_textureinfo_cache.size(); u32 id = m_textureinfo_cache.size();
TextureInfo ti(name, tex, source_image_names); TextureInfo ti(name, tex, std::move(source_image_names));
m_textureinfo_cache.push_back(ti); m_textureinfo_cache.emplace_back(std::move(ti));
m_name_to_id[name] = id; m_name_to_id[name] = id;
return id; return id;
@ -707,7 +703,7 @@ video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *
const bool filter_needed = const bool filter_needed =
m_setting_mipmap || m_setting_trilinear_filter || m_setting_mipmap || m_setting_trilinear_filter ||
m_setting_bilinear_filter || m_setting_anisotropic_filter; m_setting_bilinear_filter || m_setting_anisotropic_filter;
if (filter_needed) if (filter_needed && !name.empty())
return getTexture(name + "^[applyfiltersformesh", id); return getTexture(name + "^[applyfiltersformesh", id);
return getTexture(name, id); return getTexture(name, id);
} }
@ -779,19 +775,12 @@ void TextureSource::processQueue()
GetRequest<std::string, u32, std::thread::id, u8> GetRequest<std::string, u32, std::thread::id, u8>
request = m_get_texture_queue.pop(); request = m_get_texture_queue.pop();
/*infostream<<"TextureSource::processQueue(): "
<<"got texture request with "
<<"name=\""<<request.key<<"\""
<<std::endl;*/
m_get_texture_queue.pushResult(request, generateTexture(request.key)); m_get_texture_queue.pushResult(request, generateTexture(request.key));
} }
} }
void TextureSource::insertSourceImage(const std::string &name, video::IImage *img) void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
{ {
//infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
sanity_check(std::this_thread::get_id() == m_main_thread); sanity_check(std::this_thread::get_id() == m_main_thread);
m_sourcecache.insert(name, img, true); m_sourcecache.insert(name, img, true);
@ -838,8 +827,7 @@ void TextureSource::rebuildImagesAndTextures()
void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti) void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti)
{ {
if (ti.name.empty()) assert(!ti.name.empty());
return; // this shouldn't happen, just a precaution
// replaces the previous sourceImages // replaces the previous sourceImages
// shouldn't really need to be done, but can't hurt // shouldn't really need to be done, but can't hurt
@ -856,7 +844,7 @@ void TextureSource::rebuildTexture(video::IVideoDriver *driver, TextureInfo &ti)
video::ITexture *t_old = ti.texture; video::ITexture *t_old = ti.texture;
// Replace texture // Replace texture
ti.texture = t; ti.texture = t;
ti.sourceImages = source_image_names; ti.sourceImages = std::move(source_image_names);
if (t_old) if (t_old)
m_texture_trash.push_back(t_old); m_texture_trash.push_back(t_old);
@ -1074,6 +1062,12 @@ video::IImage* TextureSource::generateImage(const std::string &name, std::set<st
if (baseimg == NULL) { if (baseimg == NULL) {
errorstream << "generateImage(): baseimg is NULL (attempted to" errorstream << "generateImage(): baseimg is NULL (attempted to"
" create texture \"" << name << "\")" << std::endl; " create texture \"" << name << "\")" << std::endl;
} else if (baseimg->getDimension().Width == 0 ||
baseimg->getDimension().Height == 0) {
errorstream << "generateImage(): zero-sized image was created?! "
"(attempted to create texture \"" << name << "\")" << std::endl;
baseimg->drop();
baseimg = nullptr;
} }
return baseimg; return baseimg;
@ -1182,6 +1176,31 @@ void blitBaseImage(video::IImage* &src, video::IImage* &dst)
blit_with_alpha(src, dst, pos_from, pos_to, dim_dst); blit_with_alpha(src, dst, pos_from, pos_to, dim_dst);
} }
#define CHECK_BASEIMG() \
do { \
if (!baseimg) { \
errorstream << "generateImagePart(): baseimg == NULL" \
<< " for part_of_name=\"" << part_of_name \
<< "\", cancelling." << std::endl; \
return false; \
} \
} while(0)
#define COMPLAIN_INVALID(description) \
do { \
errorstream << "generateImagePart(): invalid " << (description) \
<< " for part_of_name=\"" << part_of_name \
<< "\", cancelling." << std::endl; \
return false; \
} while(0)
#define CHECK_DIM(w, h) \
do { \
if ((w) <= 0 || (h) <= 0 || (w) >= 0xffff || (h) >= 0xffff) { \
COMPLAIN_INVALID("width or height"); \
} \
} while(0)
bool TextureSource::generateImagePart(std::string part_of_name, bool TextureSource::generateImagePart(std::string part_of_name,
video::IImage *& baseimg, std::set<std::string> &source_image_names) video::IImage *& baseimg, std::set<std::string> &source_image_names)
{ {
@ -1189,48 +1208,44 @@ bool TextureSource::generateImagePart(std::string part_of_name,
video::IVideoDriver *driver = RenderingEngine::get_video_driver(); video::IVideoDriver *driver = RenderingEngine::get_video_driver();
sanity_check(driver); sanity_check(driver);
if (baseimg && (baseimg->getDimension().Width == 0 ||
baseimg->getDimension().Height == 0)) {
errorstream << "generateImagePart(): baseimg is zero-sized?!"
<< std::endl;
baseimg->drop();
baseimg = nullptr;
}
// Stuff starting with [ are special commands // Stuff starting with [ are special commands
if (part_of_name.empty() || part_of_name[0] != '[') { if (part_of_name.empty() || part_of_name[0] != '[') {
source_image_names.insert(part_of_name); source_image_names.insert(part_of_name);
video::IImage *image = m_sourcecache.getOrLoad(part_of_name); video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
if (image == NULL) { if (!image) {
if (!part_of_name.empty()) { // Do not create the dummy texture
if (part_of_name.empty())
return true;
// Do not create normalmap dummies // Do not create normalmap dummies
if (part_of_name.find("_normal.png") != std::string::npos) { if (str_ends_with(part_of_name, "_normal.png")) {
warningstream << "generateImage(): Could not load normal map \"" warningstream << "generateImagePart(): Could not load normal map \""
<< part_of_name << "\"" << std::endl; << part_of_name << "\"" << std::endl;
return true; return true;
}
errorstream << "generateImage(): Could not load image \""
<< part_of_name << "\" while building texture; "
"Creating a dummy image" << std::endl;
} }
// Just create a dummy image errorstream << "generateImagePart(): Could not load image \""
//core::dimension2d<u32> dim(2,2); << part_of_name << "\" while building texture; "
"Creating a dummy image" << std::endl;
core::dimension2d<u32> dim(1,1); core::dimension2d<u32> dim(1,1);
image = driver->createImage(video::ECF_A8R8G8B8, dim); image = driver->createImage(video::ECF_A8R8G8B8, dim);
sanity_check(image != NULL); sanity_check(image != NULL);
/*image->setPixel(0,0, video::SColor(255,255,0,0));
image->setPixel(1,0, video::SColor(255,0,255,0));
image->setPixel(0,1, video::SColor(255,0,0,255));
image->setPixel(1,1, video::SColor(255,255,0,255));*/
image->setPixel(0,0, video::SColor(255,myrand()%256, image->setPixel(0,0, video::SColor(255,myrand()%256,
myrand()%256,myrand()%256)); myrand()%256,myrand()%256));
/*image->setPixel(1,0, video::SColor(255,myrand()%256,
myrand()%256,myrand()%256));
image->setPixel(0,1, video::SColor(255,myrand()%256,
myrand()%256,myrand()%256));
image->setPixel(1,1, video::SColor(255,myrand()%256,
myrand()%256,myrand()%256));*/
} }
// If base image is NULL, load as base. // If base image is NULL, load as base.
if (baseimg == NULL) if (baseimg == NULL)
{ {
//infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
/* /*
Copy it this way to get an alpha channel. Copy it this way to get an alpha channel.
Otherwise images with alpha cannot be blitted on Otherwise images with alpha cannot be blitted on
@ -1245,17 +1260,13 @@ bool TextureSource::generateImagePart(std::string part_of_name,
{ {
blitBaseImage(image, baseimg); blitBaseImage(image, baseimg);
} }
//cleanup
image->drop(); image->drop();
} }
else else
{ {
// A special texture modification // A special texture modification
/*infostream<<"generateImage(): generating special "
<<"modification \""<<part_of_name<<"\""
<<std::endl;*/
/* /*
[crack:N:P [crack:N:P
[cracko:N:P [cracko:N:P
@ -1264,12 +1275,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
if (str_starts_with(part_of_name, "[crack")) if (str_starts_with(part_of_name, "[crack"))
{ {
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream<<"generateImagePart(): baseimg == NULL "
<<"for part_of_name=\""<<part_of_name
<<"\", cancelling."<<std::endl;
return false;
}
// Crack image number and overlay option // Crack image number and overlay option
// Format: crack[o][:<tiles>]:<frame_count>:<frame> // Format: crack[o][:<tiles>]:<frame_count>:<frame>
@ -1317,6 +1323,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
sf.next(":"); sf.next(":");
u32 w0 = stoi(sf.next("x")); u32 w0 = stoi(sf.next("x"));
u32 h0 = stoi(sf.next(":")); u32 h0 = stoi(sf.next(":"));
CHECK_DIM(w0, h0);
core::dimension2d<u32> dim(w0,h0); core::dimension2d<u32> dim(w0,h0);
if (baseimg == NULL) { if (baseimg == NULL) {
baseimg = driver->createImage(video::ECF_A8R8G8B8, dim); baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
@ -1326,9 +1333,13 @@ bool TextureSource::generateImagePart(std::string part_of_name,
u32 x = stoi(sf.next(",")); u32 x = stoi(sf.next(","));
u32 y = stoi(sf.next("=")); u32 y = stoi(sf.next("="));
std::string filename = unescape_string(sf.next_esc(":", escape), escape); std::string filename = unescape_string(sf.next_esc(":", escape), escape);
if (x >= w0 || y >= h0)
COMPLAIN_INVALID("X or Y offset");
infostream<<"Adding \""<<filename infostream<<"Adding \""<<filename
<<"\" to combined ("<<x<<","<<y<<")" <<"\" to combined ("<<x<<","<<y<<")"
<<std::endl; <<std::endl;
video::IImage *img = generateImage(filename, source_image_names); video::IImage *img = generateImage(filename, source_image_names);
if (img) { if (img) {
core::dimension2d<u32> dim = img->getDimension(); core::dimension2d<u32> dim = img->getDimension();
@ -1337,10 +1348,6 @@ bool TextureSource::generateImagePart(std::string part_of_name,
driver->createImage(video::ECF_A8R8G8B8, dim); driver->createImage(video::ECF_A8R8G8B8, dim);
img->copyTo(img2); img->copyTo(img2);
img->drop(); img->drop();
/*img2->copyToWithAlpha(baseimg, pos_base,
core::rect<s32>(v2s32(0,0), dim),
video::SColor(255,255,255,255),
NULL);*/
blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim); blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
img2->drop(); img2->drop();
} else { } else {
@ -1357,8 +1364,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
else if (str_starts_with(part_of_name, "[fill")) else if (str_starts_with(part_of_name, "[fill"))
{ {
s32 x = 0; u32 x = 0;
s32 y = 0; u32 y = 0;
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
@ -1377,6 +1384,13 @@ bool TextureSource::generateImagePart(std::string part_of_name,
} }
core::dimension2d<u32> dim(width, height); core::dimension2d<u32> dim(width, height);
CHECK_DIM(dim.Width, dim.Height);
if (baseimg) {
auto basedim = baseimg->getDimension();
if (x >= basedim.Width || y >= basedim.Height)
COMPLAIN_INVALID("X or Y offset");
}
video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim); video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
img->fill(color); img->fill(color);
@ -1392,12 +1406,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
else if (str_starts_with(part_of_name, "[brighten")) else if (str_starts_with(part_of_name, "[brighten"))
{ {
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream<<"generateImagePart(): baseimg==NULL "
<<"for part_of_name=\""<<part_of_name
<<"\", cancelling."<<std::endl;
return false;
}
brighten(baseimg); brighten(baseimg);
} }
@ -1410,13 +1419,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
else if (str_starts_with(part_of_name, "[noalpha")) else if (str_starts_with(part_of_name, "[noalpha"))
{ {
if (baseimg == NULL){ CHECK_BASEIMG();
errorstream<<"generateImagePart(): baseimg==NULL "
<<"for part_of_name=\""<<part_of_name
<<"\", cancelling."<<std::endl;
return false;
}
core::dimension2d<u32> dim = baseimg->getDimension(); core::dimension2d<u32> dim = baseimg->getDimension();
// Set alpha to full // Set alpha to full
@ -1434,12 +1437,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
else if (str_starts_with(part_of_name, "[makealpha:")) else if (str_starts_with(part_of_name, "[makealpha:"))
{ {
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream<<"generateImagePart(): baseimg == NULL "
<<"for part_of_name=\""<<part_of_name
<<"\", cancelling."<<std::endl;
return false;
}
Strfnd sf(part_of_name.substr(11)); Strfnd sf(part_of_name.substr(11));
u32 r1 = stoi(sf.next(",")); u32 r1 = stoi(sf.next(","));
@ -1448,12 +1446,6 @@ bool TextureSource::generateImagePart(std::string part_of_name,
core::dimension2d<u32> dim = baseimg->getDimension(); core::dimension2d<u32> dim = baseimg->getDimension();
/*video::IImage *oldbaseimg = baseimg;
baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
oldbaseimg->copyTo(baseimg);
oldbaseimg->drop();*/
// Set alpha to full
for (u32 y=0; y<dim.Height; y++) for (u32 y=0; y<dim.Height; y++)
for (u32 x=0; x<dim.Width; x++) for (u32 x=0; x<dim.Width; x++)
{ {
@ -1489,12 +1481,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
else if (str_starts_with(part_of_name, "[transform")) else if (str_starts_with(part_of_name, "[transform"))
{ {
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream<<"generateImagePart(): baseimg == NULL "
<<"for part_of_name=\""<<part_of_name
<<"\", cancelling."<<std::endl;
return false;
}
u32 transform = parseImageTransform(part_of_name.substr(10)); u32 transform = parseImageTransform(part_of_name.substr(10));
core::dimension2d<u32> dim = imageTransformDimension( core::dimension2d<u32> dim = imageTransformDimension(
@ -1516,7 +1503,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
else if (str_starts_with(part_of_name, "[inventorycube")) else if (str_starts_with(part_of_name, "[inventorycube"))
{ {
if (baseimg != NULL){ if (baseimg) {
errorstream<<"generateImagePart(): baseimg != NULL " errorstream<<"generateImagePart(): baseimg != NULL "
<<"for part_of_name=\""<<part_of_name <<"for part_of_name=\""<<part_of_name
<<"\", cancelling."<<std::endl; <<"\", cancelling."<<std::endl;
@ -1539,8 +1526,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
errorstream << "generateImagePart(): Failed to create textures" errorstream << "generateImagePart(): Failed to create textures"
<< " for inventorycube \"" << part_of_name << "\"" << " for inventorycube \"" << part_of_name << "\""
<< std::endl; << std::endl;
baseimg = generateImage(imagename_top, source_image_names); return false;
return true;
} }
baseimg = createInventoryCubeImage(img_top, img_left, img_right); baseimg = createInventoryCubeImage(img_top, img_left, img_right);
@ -1560,30 +1546,26 @@ bool TextureSource::generateImagePart(std::string part_of_name,
{ {
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
u32 percent = stoi(sf.next(":")); u32 percent = stoi(sf.next(":"), 0, 100);
std::string filename = unescape_string(sf.next_esc(":", escape), escape); std::string filename = unescape_string(sf.next_esc(":", escape), escape);
if (baseimg == NULL)
baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
video::IImage *img = generateImage(filename, source_image_names); video::IImage *img = generateImage(filename, source_image_names);
if (img) if (img) {
{
core::dimension2d<u32> dim = img->getDimension(); core::dimension2d<u32> dim = img->getDimension();
if (!baseimg)
baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
core::position2d<s32> pos_base(0, 0); core::position2d<s32> pos_base(0, 0);
video::IImage *img2 =
driver->createImage(video::ECF_A8R8G8B8, dim);
img->copyTo(img2);
img->drop();
core::position2d<s32> clippos(0, 0); core::position2d<s32> clippos(0, 0);
clippos.Y = dim.Height * (100-percent) / 100; clippos.Y = dim.Height * (100-percent) / 100;
core::dimension2d<u32> clipdim = dim; core::dimension2d<u32> clipdim = dim;
clipdim.Height = clipdim.Height * percent / 100 + 1; clipdim.Height = clipdim.Height * percent / 100 + 1;
core::rect<s32> cliprect(clippos, clipdim); core::rect<s32> cliprect(clippos, clipdim);
img2->copyToWithAlpha(baseimg, pos_base, img->copyToWithAlpha(baseimg, pos_base,
core::rect<s32>(v2s32(0,0), dim), core::rect<s32>(v2s32(0,0), dim),
video::SColor(255,255,255,255), video::SColor(255,255,255,255),
&cliprect); &cliprect);
img2->drop(); img->drop();
} }
} }
/* /*
@ -1593,6 +1575,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
else if (str_starts_with(part_of_name, "[verticalframe:")) else if (str_starts_with(part_of_name, "[verticalframe:"))
{ {
CHECK_BASEIMG();
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
u32 frame_count = stoi(sf.next(":")); u32 frame_count = stoi(sf.next(":"));
@ -1604,25 +1588,14 @@ bool TextureSource::generateImagePart(std::string part_of_name,
<< "\", using frame_count = 1 instead." << std::endl; << "\", using frame_count = 1 instead." << std::endl;
frame_count = 1; frame_count = 1;
} }
if (frame_index >= frame_count)
if (baseimg == NULL){ frame_index = frame_count - 1;
errorstream<<"generateImagePart(): baseimg != NULL "
<<"for part_of_name=\""<<part_of_name
<<"\", cancelling."<<std::endl;
return false;
}
v2u32 frame_size = baseimg->getDimension(); v2u32 frame_size = baseimg->getDimension();
frame_size.Y /= frame_count; frame_size.Y /= frame_count;
video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
frame_size); frame_size);
if (!img){
errorstream<<"generateImagePart(): Could not create image "
<<"for part_of_name=\""<<part_of_name
<<"\", cancelling."<<std::endl;
return false;
}
// Fill target image with transparency // Fill target image with transparency
img->fill(video::SColor(0,0,0,0)); img->fill(video::SColor(0,0,0,0));
@ -1644,12 +1617,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
else if (str_starts_with(part_of_name, "[mask:")) else if (str_starts_with(part_of_name, "[mask:"))
{ {
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream << "generateImage(): baseimg == NULL "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
std::string filename = unescape_string(sf.next_esc(":", escape), escape); std::string filename = unescape_string(sf.next_esc(":", escape), escape);
@ -1660,8 +1629,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
img->getDimension()); img->getDimension());
img->drop(); img->drop();
} else { } else {
errorstream << "generateImage(): Failed to load \"" errorstream << "generateImagePart(): Failed to load image \""
<< filename << "\"."; << filename << "\" for [mask" << std::endl;
} }
} }
/* /*
@ -1680,12 +1649,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
sf.next(":"); sf.next(":");
std::string color_str = sf.next(":"); std::string color_str = sf.next(":");
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream << "generateImagePart(): baseimg != NULL "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
video::SColor color; video::SColor color;
@ -1711,12 +1675,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
std::string color_str = sf.next(":"); std::string color_str = sf.next(":");
std::string ratio_str = sf.next(":"); std::string ratio_str = sf.next(":");
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream << "generateImagePart(): baseimg != NULL "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
video::SColor color; video::SColor color;
int ratio = -1; int ratio = -1;
@ -1741,12 +1700,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
/* IMPORTANT: When changing this, getTextureForMesh() needs to be /* IMPORTANT: When changing this, getTextureForMesh() needs to be
* updated too. */ * updated too. */
if (!baseimg) { CHECK_BASEIMG();
errorstream << "generateImagePart(): baseimg == NULL "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
// Apply the "clean transparent" filter, if needed // Apply the "clean transparent" filter, if needed
if (m_setting_mipmap || m_setting_bilinear_filter || if (m_setting_mipmap || m_setting_bilinear_filter ||
@ -1768,12 +1722,6 @@ bool TextureSource::generateImagePart(std::string part_of_name,
* equal to the target minimum. If e.g. this is a vertical frames * equal to the target minimum. If e.g. this is a vertical frames
* animation, the short dimension will be the real size. * animation, the short dimension will be the real size.
*/ */
if (dim.Width == 0 || dim.Height == 0) {
errorstream << "generateImagePart(): Illegal 0 dimension "
<< "for part_of_name=\""<< part_of_name
<< "\", cancelling." << std::endl;
return false;
}
u32 xscale = scaleto / dim.Width; u32 xscale = scaleto / dim.Width;
u32 yscale = scaleto / dim.Height; u32 yscale = scaleto / dim.Height;
const s32 scale = std::max(xscale, yscale); const s32 scale = std::max(xscale, yscale);
@ -1797,21 +1745,16 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
else if (str_starts_with(part_of_name, "[resize")) else if (str_starts_with(part_of_name, "[resize"))
{ {
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream << "generateImagePart(): baseimg == NULL "
<< "for part_of_name=\""<< part_of_name
<< "\", cancelling." << std::endl;
return false;
}
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
u32 width = stoi(sf.next("x")); u32 width = stoi(sf.next("x"));
u32 height = stoi(sf.next("")); u32 height = stoi(sf.next(""));
core::dimension2d<u32> dim(width, height); CHECK_DIM(width, height);
video::IImage *image = RenderingEngine::get_video_driver()-> video::IImage *image = driver->
createImage(video::ECF_A8R8G8B8, dim); createImage(video::ECF_A8R8G8B8, {width, height});
baseimg->copyToScaling(image); baseimg->copyToScaling(image);
baseimg->drop(); baseimg->drop();
baseimg = image; baseimg = image;
@ -1824,12 +1767,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
255 means totally opaque. 255 means totally opaque.
*/ */
else if (str_starts_with(part_of_name, "[opacity:")) { else if (str_starts_with(part_of_name, "[opacity:")) {
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream << "generateImagePart(): baseimg == NULL "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
@ -1854,12 +1792,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
will be inverted. will be inverted.
*/ */
else if (str_starts_with(part_of_name, "[invert:")) { else if (str_starts_with(part_of_name, "[invert:")) {
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream << "generateImagePart(): baseimg == NULL "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
@ -1891,13 +1824,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
from the base image it assumes to be a from the base image it assumes to be a
tilesheet with dimensions W,H (in tiles). tilesheet with dimensions W,H (in tiles).
*/ */
else if (part_of_name.substr(0,7) == "[sheet:") { else if (str_starts_with(part_of_name, "[sheet:")) {
if (baseimg == NULL) { CHECK_BASEIMG();
errorstream << "generateImagePart(): baseimg != NULL "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
@ -1906,26 +1834,21 @@ 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) { CHECK_DIM(w0, h0);
errorstream << "generateImagePart(): invalid width or height " if (x0 >= w0 || y0 >= h0)
<< "for part_of_name=\"" << part_of_name COMPLAIN_INVALID("tile position (X,Y)");
<< "\", 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));
if (tile_dim.Width == 0)
tile_dim.Width = 1;
if (tile_dim.Height == 0)
tile_dim.Height = 1;
video::IImage *img = driver->createImage( video::IImage *img = driver->createImage(
video::ECF_A8R8G8B8, tile_dim); video::ECF_A8R8G8B8, tile_dim);
if (!img) {
errorstream << "generateImagePart(): Could not create image "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
img->fill(video::SColor(0,0,0,0)); img->fill(video::SColor(0,0,0,0));
v2u32 vdim(tile_dim); v2u32 vdim(tile_dim);
core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim); core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
baseimg->copyToWithAlpha(img, v2s32(0), rect, baseimg->copyToWithAlpha(img, v2s32(0), rect,
@ -1942,15 +1865,12 @@ bool TextureSource::generateImagePart(std::string part_of_name,
to produce a valid string. to produce a valid string.
*/ */
else if (str_starts_with(part_of_name, "[png:")) { else if (str_starts_with(part_of_name, "[png:")) {
Strfnd sf(part_of_name);
sf.next(":");
std::string png; std::string png;
{ {
std::string blob = sf.next(""); std::string blob = part_of_name.substr(5);
if (!base64_is_valid(blob)) { if (!base64_is_valid(blob)) {
errorstream << "generateImagePart(): " errorstream << "generateImagePart(): "
<< "malformed base64 in '[png'" << "malformed base64 in [png" << std::endl;
<< std::endl;
return false; return false;
} }
png = base64_decode(blob); png = base64_decode(blob);
@ -1995,12 +1915,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
else if (str_starts_with(part_of_name, "[hsl:") || else if (str_starts_with(part_of_name, "[hsl:") ||
str_starts_with(part_of_name, "[colorizehsl:")) { str_starts_with(part_of_name, "[colorizehsl:")) {
if (baseimg == nullptr) { CHECK_BASEIMG();
errorstream << "generateImagePart(): baseimg == NULL "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
bool colorize = str_starts_with(part_of_name, "[colorizehsl:"); bool colorize = str_starts_with(part_of_name, "[colorizehsl:");
@ -2037,12 +1952,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
else if (str_starts_with(part_of_name, "[overlay:") || else if (str_starts_with(part_of_name, "[overlay:") ||
str_starts_with(part_of_name, "[hardlight:")) { str_starts_with(part_of_name, "[hardlight:")) {
if (baseimg == nullptr) { CHECK_BASEIMG();
errorstream << "generateImage(): baseimg == NULL "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
std::string filename = unescape_string(sf.next_esc(":", escape), escape); std::string filename = unescape_string(sf.next_esc(":", escape), escape);
@ -2056,8 +1967,8 @@ bool TextureSource::generateImagePart(std::string part_of_name,
img->getDimension(), hardlight); img->getDimension(), hardlight);
img->drop(); img->drop();
} else { } else {
errorstream << "generateImage(): Failed to load \"" errorstream << "generateImage(): Failed to load image \""
<< filename << "\"."; << filename << "\" for [overlay or [hardlight" << std::endl;
} }
} }
/* /*
@ -2071,12 +1982,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/ */
else if (str_starts_with(part_of_name, "[contrast:")) { else if (str_starts_with(part_of_name, "[contrast:")) {
if (baseimg == nullptr) { CHECK_BASEIMG();
errorstream << "generateImagePart(): baseimg == NULL "
<< "for part_of_name=\"" << part_of_name
<< "\", cancelling." << std::endl;
return false;
}
Strfnd sf(part_of_name); Strfnd sf(part_of_name);
sf.next(":"); sf.next(":");
@ -2096,6 +2002,12 @@ bool TextureSource::generateImagePart(std::string part_of_name,
return true; return true;
} }
#undef CHECK_BASEIMG
#undef COMPLAIN_INVALID
#undef CHECK_DIM
/* /*
Calculate the color of a single pixel drawn on top of another pixel. Calculate the color of a single pixel drawn on top of another pixel.
@ -2104,7 +2016,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a pixel with alpha=64 drawn atop a pixel with alpha=128 should yield a
pixel with alpha=160, while getInterpolated would yield alpha=96. pixel with alpha=160, while getInterpolated would yield alpha=96.
*/ */
static inline video::SColor blitPixel(const video::SColor &src_c, const video::SColor &dst_c, u32 ratio) static inline video::SColor blitPixel(const video::SColor src_c, const video::SColor dst_c, u32 ratio)
{ {
if (dst_c.getAlpha() == 0) if (dst_c.getAlpha() == 0)
return src_c; return src_c;
@ -2197,7 +2109,7 @@ static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst
Apply color to destination, using a weighted interpolation blend Apply color to destination, using a weighted interpolation blend
*/ */
static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size, static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
const video::SColor &color, int ratio, bool keep_alpha) const video::SColor color, int ratio, bool keep_alpha)
{ {
u32 alpha = color.getAlpha(); u32 alpha = color.getAlpha();
video::SColor dst_c; video::SColor dst_c;
@ -2235,7 +2147,7 @@ static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
Apply color to destination, using a Multiply blend mode Apply color to destination, using a Multiply blend mode
*/ */
static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size, static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
const video::SColor &color) const video::SColor color)
{ {
video::SColor dst_c; video::SColor dst_c;
@ -2256,7 +2168,7 @@ static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
Apply color to destination, using a Screen blend mode Apply color to destination, using a Screen blend mode
*/ */
static void apply_screen(video::IImage *dst, v2u32 dst_pos, v2u32 size, static void apply_screen(video::IImage *dst, v2u32 dst_pos, v2u32 size,
const video::SColor &color) const video::SColor color)
{ {
video::SColor dst_c; video::SColor dst_c;
@ -2466,6 +2378,10 @@ video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver) core::dimension2d<u32> size, u8 tiles, video::IVideoDriver *driver)
{ {
core::dimension2d<u32> strip_size = crack->getDimension(); core::dimension2d<u32> strip_size = crack->getDimension();
if (tiles == 0 || strip_size.getArea() == 0)
return nullptr;
core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width); core::dimension2d<u32> frame_size(strip_size.Width, strip_size.Width);
core::dimension2d<u32> tile_size(size / tiles); core::dimension2d<u32> tile_size(size / tiles);
s32 frame_count = strip_size.Height / strip_size.Width; s32 frame_count = strip_size.Height / strip_size.Width;
@ -2474,7 +2390,7 @@ video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size); core::rect<s32> frame(v2s32(0, frame_index * frame_size.Height), frame_size);
video::IImage *result = nullptr; video::IImage *result = nullptr;
// extract crack frame // extract crack frame
video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size); video::IImage *crack_tile = driver->createImage(video::ECF_A8R8G8B8, tile_size);
if (!crack_tile) if (!crack_tile)
return nullptr; return nullptr;
@ -2491,7 +2407,7 @@ video::IImage *create_crack_image(video::IImage *crack, s32 frame_index,
if (tiles == 1) if (tiles == 1)
return crack_tile; return crack_tile;
// tile it // tile it
result = driver->createImage(video::ECF_A8R8G8B8, size); result = driver->createImage(video::ECF_A8R8G8B8, size);
if (!result) if (!result)
goto exit__has_tile; goto exit__has_tile;
@ -2695,7 +2611,7 @@ namespace {
return v / 12.92f; return v / 12.92f;
} }
v3f srgb_to_linear(const video::SColor &col_srgb) v3f srgb_to_linear(const video::SColor col_srgb)
{ {
v3f col(col_srgb.getRed(), col_srgb.getGreen(), col_srgb.getBlue()); v3f col(col_srgb.getRed(), col_srgb.getGreen(), col_srgb.getBlue());
col /= 255.0f; col /= 255.0f;
@ -2705,7 +2621,7 @@ namespace {
return col; return col;
} }
video::SColor linear_to_srgb(const v3f &col_linear) video::SColor linear_to_srgb(const v3f col_linear)
{ {
v3f col; v3f col;
col.X = linear_to_srgb_component(col_linear.X); col.X = linear_to_srgb_component(col_linear.X);

@ -30,6 +30,7 @@
#cmakedefine01 USE_SYSTEM_JSONCPP #cmakedefine01 USE_SYSTEM_JSONCPP
#cmakedefine01 USE_REDIS #cmakedefine01 USE_REDIS
#cmakedefine01 HAVE_ENDIAN_H #cmakedefine01 HAVE_ENDIAN_H
#cmakedefine01 HAVE_STRLCPY
#cmakedefine01 CURSES_HAVE_CURSES_H #cmakedefine01 CURSES_HAVE_CURSES_H
#cmakedefine01 CURSES_HAVE_NCURSES_H #cmakedefine01 CURSES_HAVE_NCURSES_H
#cmakedefine01 CURSES_HAVE_NCURSES_NCURSES_H #cmakedefine01 CURSES_HAVE_NCURSES_NCURSES_H

@ -42,11 +42,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define CONNECTION_TIMEOUT 30 #define CONNECTION_TIMEOUT 30
#define RESEND_TIMEOUT_MIN 0.1
#define RESEND_TIMEOUT_MAX 3.0
// resend_timeout = avg_rtt * this
#define RESEND_TIMEOUT_FACTOR 4
/* /*
Server Server
*/ */

@ -46,7 +46,6 @@ void set_default_settings()
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");
settings->setDefault("meshgen_block_cache_size", "20");
settings->setDefault("enable_vbo", "true"); settings->setDefault("enable_vbo", "true");
settings->setDefault("free_move", "false"); settings->setDefault("free_move", "false");
settings->setDefault("pitch_move", "false"); settings->setDefault("pitch_move", "false");

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