diff --git a/.gitattributes b/.gitattributes
index 06b76c6c8..ecd9a7a29 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -3,3 +3,5 @@
*.cpp diff=cpp
*.h diff=cpp
+
+*.gltf binary
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 2d0c72907..a2ec10a0a 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -88,7 +88,7 @@ jobs:
- name: Install deps
run: |
source ./util/ci/common.sh
- install_linux_deps clang-7 llvm
+ install_linux_deps clang-7 llvm-7
- name: Build
run: |
@@ -102,6 +102,11 @@ jobs:
run: |
./bin/minetest --run-unittests
+ # Do this here because we have ASan and error paths are sensitive to dangling pointers
+ - name: Test error cases
+ run: |
+ ./util/test_error_cases.sh
+
# Current clang version
clang_18:
runs-on: ubuntu-24.04
diff --git a/.github/workflows/lua.yml b/.github/workflows/lua.yml
index 0073eca2e..1f83c7688 100644
--- a/.github/workflows/lua.yml
+++ b/.github/workflows/lua.yml
@@ -35,7 +35,7 @@ jobs:
- name: Integration test + devtest
run: |
- ./util/test_multiplayer.sh
+ serverconf="profiler.load=true" ./util/test_multiplayer.sh
luacheck:
name: "Builtin Luacheck and Unit Tests"
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 34556ce8c..731d7d719 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -29,7 +29,7 @@ on:
jobs:
build:
- # use macOS 13 since it's the last one that still runs on x86
+ # use lowest possible macOS running on x86_64 supported by brew to support more users
runs-on: macos-13
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/whitespace_checks.yml b/.github/workflows/whitespace_checks.yml
new file mode 100644
index 000000000..cc16e7b22
--- /dev/null
+++ b/.github/workflows/whitespace_checks.yml
@@ -0,0 +1,45 @@
+name: whitespace_checks
+
+# Check whitespaces of the following file types
+# Not checked: .lua, .yml, .properties, .conf, .java, .py, .svg, .gradle, .xml, ...
+# (luacheck already checks .lua files)
+on:
+ push:
+ paths:
+ - '**.txt'
+ - '**.md'
+ - '**.[ch]'
+ - '**.cpp'
+ - '**.hpp'
+ - '**.sh'
+ - '**.cmake'
+ - '**.glsl'
+ pull_request:
+ paths:
+ - '**.txt'
+ - '**.md'
+ - '**.[ch]'
+ - '**.cpp'
+ - '**.hpp'
+ - '**.sh'
+ - '**.cmake'
+ - '**.glsl'
+
+jobs:
+ trailing_whitespaces:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ # Line endings are already ensured by .gitattributes
+ - name: Check trailing whitespaces
+ run: if git ls-files | grep -E '\.txt$|\.md$|\.[ch]$|\.cpp$|\.hpp$|\.sh$|\.cmake$|\.glsl$' | xargs grep -n '\s$'; then echo -e "\033[0;31mFound trailing whitespace"; (exit 1); else (exit 0); fi
+
+ tabs_lua_api_files:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ # Some files should not contain tabs
+ - name: Check tabs in Lua API files
+ run: if grep -n $'\t' doc/lua_api.md doc/client_lua_api.md; then echo -e "\033[0;31mFound tab in markdown file"; (exit 1); else (exit 0); fi
+
+
diff --git a/.gitignore b/.gitignore
index 8ff758720..c7879380b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ tags
!tags/
gtags.files
.idea
+.qtcreator/
# Codelite
*.project
# Visual Studio Code & plugins
@@ -109,6 +110,8 @@ src/cmake_config_githash.h
*.layout
*.o
*.a
+*.dump
+*.dmp
*.ninja
.ninja*
*.gch
diff --git a/.luacheckrc b/.luacheckrc
index f184e6d59..afc136c7c 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -37,6 +37,12 @@ files["builtin/client/register.lua"] = {
}
}
+files["builtin/common/math.lua"] = {
+ globals = {
+ "math",
+ },
+}
+
files["builtin/common/misc_helpers.lua"] = {
globals = {
"dump", "dump2", "table", "math", "string",
@@ -46,7 +52,7 @@ files["builtin/common/misc_helpers.lua"] = {
}
files["builtin/common/vector.lua"] = {
- globals = { "vector" },
+ globals = { "vector", "math" },
}
files["builtin/game/voxelarea.lua"] = {
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6623fa828..a9a0ef000 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -56,6 +56,11 @@ if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE)
endif()
set(ENABLE_LTO ${DEFAULT_ENABLE_LTO} CACHE BOOL "Use Link Time Optimization")
+set(BUILD_WITH_TRACY FALSE CACHE BOOL
+ "Fetch and build with the Tracy profiler client")
+set(FETCH_TRACY_GIT_TAG "master" CACHE STRING
+ "Git tag for fetching Tracy client. Match with your server (gui) version")
+
set(DEFAULT_RUN_IN_PLACE FALSE)
if(WIN32)
set(DEFAULT_RUN_IN_PLACE TRUE)
@@ -283,6 +288,8 @@ if(BUILD_UNITTESTS OR BUILD_BENCHMARKS)
add_subdirectory(lib/catch2)
endif()
+add_subdirectory(lib/tiniergltf)
+
# Subdirectories
# Be sure to add all relevant definitions above this
add_subdirectory(src)
@@ -368,3 +375,19 @@ if(BUILD_DOCUMENTATION)
)
endif()
endif()
+
+# Fetch Tracy
+if(BUILD_WITH_TRACY)
+ include(FetchContent)
+
+ message(STATUS "Fetching Tracy (${FETCH_TRACY_GIT_TAG})...")
+ FetchContent_Declare(
+ tracy
+ GIT_REPOSITORY https://github.com/wolfpld/tracy.git
+ GIT_TAG ${FETCH_TRACY_GIT_TAG}
+ GIT_SHALLOW TRUE
+ GIT_PROGRESS TRUE
+ )
+ FetchContent_MakeAvailable(tracy)
+ message(STATUS "Fetching Tracy - done")
+endif()
diff --git a/LICENSE.txt b/LICENSE.txt
index de76c7a80..03ca35100 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -57,12 +57,10 @@ srifqi:
textures/base/pack/minimap_btn.png
Zughy:
- textures/base/pack/cdb_add.png
textures/base/pack/cdb_downloading.png
textures/base/pack/cdb_queued.png
textures/base/pack/cdb_update.png
textures/base/pack/cdb_update_cropped.png
- textures/base/pack/cdb_viewonline.png
textures/base/pack/settings_btn.png
textures/base/pack/settings_info.png
textures/base/pack/settings_reset.png
@@ -79,7 +77,6 @@ kilbith:
textures/base/pack/progress_bar_bg.png
SmallJoker:
- textures/base/pack/cdb_clear.png
textures/base/pack/server_favorite_delete.png (based on server_favorite.png)
DS:
diff --git a/README.md b/README.md
index 5724359d6..919cb144c 100644
--- a/README.md
+++ b/README.md
@@ -119,6 +119,7 @@ Command-line options
Compiling
---------
+- [Compiling - common information](doc/compiling/README.md)
- [Compiling on GNU/Linux](doc/compiling/linux.md)
- [Compiling on Windows](doc/compiling/windows.md)
- [Compiling on MacOS](doc/compiling/macos.md)
diff --git a/android/app/build.gradle b/android/app/build.gradle
index fe6c4ab0d..cefc473af 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -8,7 +8,7 @@ android {
compileSdk 34
targetSdkVersion 34
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
- versionCode project.versionCode
+ versionCode versionMajor * 1000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
}
buildFeatures {
@@ -116,18 +116,6 @@ clean {
delete new File("src/main/assets", "Minetest.zip")
}
-// Map for the version code that gives each ABI a value.
-import com.android.build.OutputFile
-
-def abiCodes = ['armeabi-v7a': 0, 'arm64-v8a': 1]
-android.applicationVariants.all { variant ->
- variant.outputs.each {
- output ->
- def abiName = output.getFilter(OutputFile.ABI)
- output.versionCodeOverride = abiCodes.get(abiName, 0) + variant.versionCode
- }
-}
-
dependencies {
implementation project(':native')
implementation 'androidx.appcompat:appcompat:1.6.1'
diff --git a/android/build.gradle b/android/build.gradle
index 69fd26625..2017c536c 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,13 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
project.ext.set("versionMajor", 5) // Version Major
-project.ext.set("versionMinor", 10) // Version Minor
+project.ext.set("versionMinor", 10) // Version Minor
project.ext.set("versionPatch", 0) // Version Patch
// ^ keep in sync with cmake
-project.ext.set("versionCode", 48) // Android Version Code
-// NOTE: +2 after each release!
-// +1 for ARM and +1 for ARM64 APK's, because
-// each APK must have a larger `versionCode` than the previous
+
+project.ext.set("versionBuild", 0) // Version Build
+// ^ fourth version number to allow releasing Android-only fixes and beta versions
buildscript {
ext.ndk_version = '26.2.11394342'
diff --git a/android/native/build.gradle b/android/native/build.gradle
index 0be7c1d99..806dda2f0 100644
--- a/android/native/build.gradle
+++ b/android/native/build.gradle
@@ -8,7 +8,7 @@ android {
compileSdk 34
targetSdkVersion 34
externalNativeBuild {
- cmake {
+ cmake {
arguments "-DANDROID_STL=c++_shared",
"-DENABLE_CURL=1", "-DENABLE_SOUND=1",
"-DENABLE_GETTEXT=1",
diff --git a/builtin/client/death_formspec.lua b/builtin/client/death_formspec.lua
deleted file mode 100644
index c25c799ab..000000000
--- a/builtin/client/death_formspec.lua
+++ /dev/null
@@ -1,15 +0,0 @@
--- CSM death formspec. Only used when clientside modding is enabled, otherwise
--- handled by the engine.
-
-core.register_on_death(function()
- local formspec = "size[11,5.5]bgcolor[#320000b4;true]" ..
- "label[4.85,1.35;" .. fgettext("You died") ..
- "]button_exit[4,3;3,0.5;btn_respawn;".. fgettext("Respawn") .."]"
- core.show_formspec("bultin:death", formspec)
-end)
-
-core.register_on_formspec_input(function(formname, fields)
- if formname == "bultin:death" then
- core.send_respawn()
- end
-end)
diff --git a/builtin/client/init.lua b/builtin/client/init.lua
index 301a8050c..8d01c99a4 100644
--- a/builtin/client/init.lua
+++ b/builtin/client/init.lua
@@ -9,6 +9,5 @@ dofile(commonpath .. "mod_storage.lua")
dofile(commonpath .. "chatcommands.lua")
dofile(commonpath .. "information_formspecs.lua")
dofile(clientpath .. "chatcommands.lua")
-dofile(clientpath .. "death_formspec.lua")
dofile(clientpath .. "misc.lua")
assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions
diff --git a/builtin/common/information_formspecs.lua b/builtin/common/information_formspecs.lua
index 3fa397d25..270631fc9 100644
--- a/builtin/common/information_formspecs.lua
+++ b/builtin/common/information_formspecs.lua
@@ -69,7 +69,7 @@ local function build_chatcommands_formspec(name, sel, copy)
description = cmds[2].description
if copy then
local msg = S("Command: @1 @2",
- core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params)
+ core.colorize("#0FF", (INIT == "client" and "." or "/") .. cmds[1]), cmds[2].params)
if INIT == "client" then
core.display_chat_message(msg)
else
diff --git a/builtin/common/item_s.lua b/builtin/common/item_s.lua
index 72a722ed1..673c83877 100644
--- a/builtin/common/item_s.lua
+++ b/builtin/common/item_s.lua
@@ -166,20 +166,19 @@ function core.is_colored_paramtype(ptype)
end
function core.strip_param2_color(param2, paramtype2)
- if not core.is_colored_paramtype(paramtype2) then
+ if paramtype2 == "color" then
+ return param2
+ elseif paramtype2 == "colorfacedir" then
+ return math.floor(param2 / 32) * 32
+ elseif paramtype2 == "color4dir" then
+ return math.floor(param2 / 4) * 4
+ elseif paramtype2 == "colorwallmounted" then
+ return math.floor(param2 / 8) * 8
+ elseif paramtype2 == "colordegrotate" then
+ return math.floor(param2 / 32) * 32
+ else
return nil
end
- if paramtype2 == "colorfacedir" then
- param2 = math.floor(param2 / 32) * 32
- elseif paramtype2 == "color4dir" then
- param2 = math.floor(param2 / 4) * 4
- elseif paramtype2 == "colorwallmounted" then
- param2 = math.floor(param2 / 8) * 8
- elseif paramtype2 == "colordegrotate" then
- param2 = math.floor(param2 / 32) * 32
- end
- -- paramtype2 == "color" requires no modification.
- return param2
end
-- Content ID caching
diff --git a/builtin/common/math.lua b/builtin/common/math.lua
new file mode 100644
index 000000000..84c6c619b
--- /dev/null
+++ b/builtin/common/math.lua
@@ -0,0 +1,41 @@
+--[[
+ Math utils.
+--]]
+
+function math.hypot(x, y)
+ return math.sqrt(x * x + y * y)
+end
+
+function math.sign(x, tolerance)
+ tolerance = tolerance or 0
+ if x > tolerance then
+ return 1
+ elseif x < -tolerance then
+ return -1
+ end
+ return 0
+end
+
+function math.factorial(x)
+ assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer")
+ if x >= 171 then
+ -- 171! is greater than the biggest double, no need to calculate
+ return math.huge
+ end
+ local v = 1
+ for k = 2, x do
+ v = v * k
+ end
+ return v
+end
+
+function math.round(x)
+ if x < 0 then
+ local int = math.ceil(x)
+ local frac = x - int
+ return int - ((frac <= -0.5) and 1 or 0)
+ end
+ local int = math.floor(x)
+ local frac = x - int
+ return int + ((frac >= 0.5) and 1 or 0)
+end
diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua
index fb38c1b35..d0942b2d2 100644
--- a/builtin/common/misc_helpers.lua
+++ b/builtin/common/misc_helpers.lua
@@ -3,6 +3,7 @@
--------------------------------------------------------------------------------
-- Localize functions to avoid table lookups (better performance).
local string_sub, string_find = string.sub, string.find
+local math = math
--------------------------------------------------------------------------------
local function basic_dump(o)
@@ -220,47 +221,6 @@ function string:trim()
return self:match("^%s*(.-)%s*$")
end
---------------------------------------------------------------------------------
-function math.hypot(x, y)
- return math.sqrt(x * x + y * y)
-end
-
---------------------------------------------------------------------------------
-function math.sign(x, tolerance)
- tolerance = tolerance or 0
- if x > tolerance then
- return 1
- elseif x < -tolerance then
- return -1
- end
- return 0
-end
-
---------------------------------------------------------------------------------
-function math.factorial(x)
- assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer")
- if x >= 171 then
- -- 171! is greater than the biggest double, no need to calculate
- return math.huge
- end
- local v = 1
- for k = 2, x do
- v = v * k
- end
- return v
-end
-
-function math.round(x)
- if x < 0 then
- local int = math.ceil(x)
- local frac = x - int
- return int - ((frac <= -0.5) and 1 or 0)
- end
- local int = math.floor(x)
- local frac = x - int
- return int + ((frac >= 0.5) and 1 or 0)
-end
-
local formspec_escapes = {
["\\"] = "\\\\",
["["] = "\\[",
@@ -275,6 +235,16 @@ function core.formspec_escape(text)
end
+local hypertext_escapes = {
+ ["\\"] = "\\\\",
+ ["<"] = "\\<",
+ [">"] = "\\>",
+}
+function core.hypertext_escape(text)
+ return text and text:gsub("[\\<>]", hypertext_escapes)
+end
+
+
function core.wrap_text(text, max_length, as_table)
local result = {}
local line = {}
@@ -604,12 +574,14 @@ function core.strip_colors(str)
return (str:gsub(ESCAPE_CHAR .. "%([bc]@[^)]+%)", ""))
end
-function core.translate(textdomain, str, ...)
+local function translate(textdomain, str, num, ...)
local start_seq
- if textdomain == "" then
+ if textdomain == "" and num == "" then
start_seq = ESCAPE_CHAR .. "T"
- else
+ elseif num == "" then
start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. ")"
+ else
+ start_seq = ESCAPE_CHAR .. "(T@" .. textdomain .. "@" .. num .. ")"
end
local arg = {n=select('#', ...), ...}
local end_seq = ESCAPE_CHAR .. "E"
@@ -640,8 +612,31 @@ function core.translate(textdomain, str, ...)
return start_seq .. translated .. end_seq
end
+function core.translate(textdomain, str, ...)
+ return translate(textdomain, str, "", ...)
+end
+
+function core.translate_n(textdomain, str, str_plural, n, ...)
+ assert (type(n) == "number")
+ assert (n >= 0)
+ assert (math.floor(n) == n)
+
+ -- Truncate n if too large
+ local max = 1000000
+ if n >= 2 * max then
+ n = n % max + max
+ end
+ if n == 1 then
+ return translate(textdomain, str, "1", ...)
+ else
+ return translate(textdomain, str_plural, tostring(n), ...)
+ end
+end
+
function core.get_translator(textdomain)
- return function(str, ...) return core.translate(textdomain or "", str, ...) end
+ return
+ (function(str, ...) return core.translate(textdomain or "", str, ...) end),
+ (function(str, str_plural, n, ...) return core.translate_n(textdomain or "", str, str_plural, n, ...) end)
end
--------------------------------------------------------------------------------
@@ -702,6 +697,7 @@ function core.privs_to_string(privs, delim)
list[#list + 1] = priv
end
end
+ table.sort(list)
return table.concat(list, delim)
end
diff --git a/builtin/common/tests/after_spec.lua b/builtin/common/tests/after_spec.lua
index cca1871a5..f23bbd70d 100644
--- a/builtin/common/tests/after_spec.lua
+++ b/builtin/common/tests/after_spec.lua
@@ -1,6 +1,7 @@
_G.core = {}
_G.vector = {metatable = {}}
+dofile("builtin/common/math.lua")
dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua")
diff --git a/builtin/common/tests/math_spec.lua b/builtin/common/tests/math_spec.lua
new file mode 100644
index 000000000..108ab2b6a
--- /dev/null
+++ b/builtin/common/tests/math_spec.lua
@@ -0,0 +1,16 @@
+_G.core = {}
+dofile("builtin/common/math.lua")
+
+describe("math", function()
+ it("round()", function()
+ assert.equal(0, math.round(0))
+ assert.equal(10, math.round(10.3))
+ assert.equal(11, math.round(10.5))
+ assert.equal(11, math.round(10.7))
+ assert.equal(-10, math.round(-10.3))
+ assert.equal(-11, math.round(-10.5))
+ assert.equal(-11, math.round(-10.7))
+ assert.equal(0, math.round(0.49999999999999994))
+ assert.equal(0, math.round(-0.49999999999999994))
+ end)
+end)
diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua
index 611c4b20f..10e2bf277 100644
--- a/builtin/common/tests/misc_helpers_spec.lua
+++ b/builtin/common/tests/misc_helpers_spec.lua
@@ -1,4 +1,5 @@
_G.core = {}
+dofile("builtin/common/math.lua")
dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua")
diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua
index 67dbd2d6b..9a0458be4 100644
--- a/builtin/common/tests/vector_spec.lua
+++ b/builtin/common/tests/vector_spec.lua
@@ -1,4 +1,5 @@
_G.vector = {}
+dofile("builtin/common/math.lua")
dofile("builtin/common/vector.lua")
describe("vector", function()
@@ -113,12 +114,35 @@ describe("vector", function()
assert.equal(vector.new(0, 1, -1), a:round())
end)
+ it("ceil()", function()
+ local a = vector.new(0.1, 0.9, -0.5)
+ assert.equal(vector.new(1, 1, 0), vector.ceil(a))
+ assert.equal(vector.new(1, 1, 0), a:ceil())
+ end)
+
+ it("sign()", function()
+ local a = vector.new(-120.3, 0, 231.5)
+ assert.equal(vector.new(-1, 0, 1), vector.sign(a))
+ assert.equal(vector.new(-1, 0, 1), a:sign())
+ assert.equal(vector.new(0, 0, 1), vector.sign(a, 200))
+ assert.equal(vector.new(0, 0, 1), a:sign(200))
+ end)
+
+ it("abs()", function()
+ local a = vector.new(-123.456, 0, 13)
+ assert.equal(vector.new(123.456, 0, 13), vector.abs(a))
+ assert.equal(vector.new(123.456, 0, 13), a:abs())
+ end)
+
it("apply()", function()
local i = 0
local f = function(x)
i = i + 1
return x + i
end
+ local f2 = function(x, opt1, opt2, opt3)
+ return x + opt1 + opt2 + opt3
+ end
local a = vector.new(0.1, 0.9, -0.5)
assert.equal(vector.new(1, 1, 0), vector.apply(a, math.ceil))
assert.equal(vector.new(1, 1, 0), a:apply(math.ceil))
@@ -126,6 +150,9 @@ describe("vector", function()
assert.equal(vector.new(0.1, 0.9, 0.5), a:apply(math.abs))
assert.equal(vector.new(1.1, 2.9, 2.5), vector.apply(a, f))
assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f))
+ local b = vector.new(1, 2, 3)
+ assert.equal(vector.new(4, 5, 6), vector.apply(b, f2, 1, 1, 1))
+ assert.equal(vector.new(4, 5, 6), b:apply(f2, 1, 1, 1))
end)
it("combine()", function()
@@ -469,4 +496,13 @@ describe("vector", function()
assert.True(vector.in_area(vector.new(-10, -10, -10), vector.new(-10, -10, -10), vector.new(10, 10, 10)))
assert.False(vector.in_area(vector.new(-10, -10, -10), vector.new(10, 10, 10), vector.new(-11, -10, -10)))
end)
+
+ it("random_in_area()", function()
+ local min = vector.new(-100, -100, -100)
+ local max = vector.new(100, 100, 100)
+ for i = 1, 1000 do
+ local random = vector.random_in_area(min, max)
+ assert.True(vector.in_area(random, min, max))
+ end
+ end)
end)
diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua
index be82401c3..fb1d2a7d9 100644
--- a/builtin/common/vector.lua
+++ b/builtin/common/vector.lua
@@ -5,6 +5,7 @@ Note: The vector.*-functions must be able to accept old vectors that had no meta
-- localize functions
local setmetatable = setmetatable
+local math = math
vector = {}
@@ -97,18 +98,26 @@ function vector.floor(v)
end
function vector.round(v)
- return fast_new(
- math.round(v.x),
- math.round(v.y),
- math.round(v.z)
- )
+ return vector.apply(v, math.round)
end
-function vector.apply(v, func)
+function vector.ceil(v)
+ return vector.apply(v, math.ceil)
+end
+
+function vector.sign(v, tolerance)
+ return vector.apply(v, math.sign, tolerance)
+end
+
+function vector.abs(v)
+ return vector.apply(v, math.abs)
+end
+
+function vector.apply(v, func, ...)
return fast_new(
- func(v.x),
- func(v.y),
- func(v.z)
+ func(v.x, ...),
+ func(v.y, ...),
+ func(v.z, ...)
)
end
@@ -387,6 +396,14 @@ function vector.random_direction()
return fast_new(x/l, y/l, z/l)
end
+function vector.random_in_area(min, max)
+ return fast_new(
+ math.random(min.x, max.x),
+ math.random(min.y, max.y),
+ math.random(min.z, max.z)
+ )
+end
+
if rawget(_G, "core") and core.set_read_vector and core.set_push_vector then
local function read_vector(v)
return v.x, v.y, v.z
diff --git a/builtin/emerge/env.lua b/builtin/emerge/env.lua
index 2b32a0339..5beb6285c 100644
--- a/builtin/emerge/env.lua
+++ b/builtin/emerge/env.lua
@@ -23,6 +23,8 @@ core.add_node = core.set_node
-- we don't deal with metadata currently
core.swap_node = core.set_node
+core.bulk_swap_node = core.bulk_set_node
+
function core.remove_node(pos)
return core.vmanip:set_node_at(pos, {name="air"})
end
diff --git a/builtin/fstk/buttonbar.lua b/builtin/fstk/buttonbar.lua
index 59b7a586f..e53814751 100644
--- a/builtin/fstk/buttonbar.lua
+++ b/builtin/fstk/buttonbar.lua
@@ -19,7 +19,7 @@
local BASE_SPACING = 0.1
local function get_scroll_btn_width()
- return core.settings:get_bool("enable_touch") and 0.8 or 0.5
+ return core.settings:get_bool("touch_gui") and 0.8 or 0.5
end
local function buttonbar_formspec(self)
@@ -28,10 +28,8 @@ local function buttonbar_formspec(self)
end
local formspec = {
- "style_type[box;noclip=true]",
string.format("box[%f,%f;%f,%f;%s]", self.pos.x, self.pos.y, self.size.x,
self.size.y, self.bgcolor),
- "style_type[box;noclip=false]",
}
local btn_size = self.size.y - 2*BASE_SPACING
@@ -71,7 +69,7 @@ local function buttonbar_formspec(self)
y = self.pos.y + BASE_SPACING,
}
- table.insert(formspec, string.format("image_button[%f,%f;%f,%f;%s;%s;%s;true;false]tooltip[%s;%s]",
+ table.insert(formspec, string.format("image_button[%f,%f;%f,%f;%s;%s;%s;false;false]tooltip[%s;%s]",
btn_pos.x, btn_pos.y, btn_size, btn_size, btn.image, btn.name,
btn.caption, btn.name, btn.tooltip))
end
@@ -86,9 +84,6 @@ local function buttonbar_formspec(self)
y = self.pos.y + BASE_SPACING,
}
- table.insert(formspec, string.format("style[%s,%s;noclip=true]",
- self.btn_prev_name, self.btn_next_name))
-
table.insert(formspec, string.format("button[%f,%f;%f,%f;%s;<]",
btn_prev_pos.x, btn_prev_pos.y, get_scroll_btn_width(), btn_size,
self.btn_prev_name))
diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua
index f09c4df2d..42fc9ac18 100644
--- a/builtin/fstk/tabview.lua
+++ b/builtin/fstk/tabview.lua
@@ -66,11 +66,22 @@ local function get_formspec(self)
local content, prepend = tab.get_formspec(self, tab.name, tab.tabdata, tab.tabsize)
- local tsize = tab.tabsize or { width = self.width, height = self.height }
+ local TOUCH_GUI = core.settings:get_bool("touch_gui")
+
+ local orig_tsize = tab.tabsize or { width = self.width, height = self.height }
+ local tsize = { width = orig_tsize.width, height = orig_tsize.height }
+ tsize.height = tsize.height
+ + TABHEADER_H -- tabheader included in formspec size
+ + (TOUCH_GUI and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP)
+ + GAMEBAR_H -- gamebar included in formspec size
+
if self.parent == nil and not prepend then
prepend = string.format("size[%f,%f,%s]", tsize.width, tsize.height,
dump(self.fixed_size))
+ local anchor_pos = TABHEADER_H + orig_tsize.height / 2
+ prepend = prepend .. ("anchor[0.5,%f]"):format(anchor_pos / tsize.height)
+
if tab.formspec_version then
prepend = ("formspec_version[%d]"):format(tab.formspec_version) .. prepend
end
@@ -78,12 +89,15 @@ local function get_formspec(self)
local end_button_size = 0.75
- local tab_header_size = { width = tsize.width, height = 0.85 }
+ local tab_header_size = { width = tsize.width, height = TABHEADER_H }
if self.end_button then
tab_header_size.width = tab_header_size.width - end_button_size - 0.1
end
- local formspec = (prepend or "") .. self:tab_header(tab_header_size) .. content
+ local formspec = (prepend or "")
+ formspec = formspec .. ("bgcolor[;neither]container[0,%f]box[0,0;%f,%f;#0000008C]"):format(
+ TABHEADER_H, orig_tsize.width, orig_tsize.height)
+ formspec = formspec .. self:tab_header(tab_header_size) .. content
if self.end_button then
formspec = formspec ..
@@ -98,6 +112,8 @@ local function get_formspec(self)
self.end_button.name)
end
+ formspec = formspec .. "container_end[]"
+
return formspec
end
diff --git a/builtin/game/chat.lua b/builtin/game/chat.lua
index baab5212f..b7e2aea50 100644
--- a/builtin/game/chat.lua
+++ b/builtin/game/chat.lua
@@ -221,6 +221,7 @@ core.register_chatcommand("haspriv", {
return true, S("No online player has the \"@1\" privilege.",
param)
else
+ table.sort(players_with_priv)
return true, S("Players online with the \"@1\" privilege: @2",
param,
table.concat(players_with_priv, ", "))
diff --git a/builtin/game/death_screen.lua b/builtin/game/death_screen.lua
new file mode 100644
index 000000000..77f56aaff
--- /dev/null
+++ b/builtin/game/death_screen.lua
@@ -0,0 +1,31 @@
+local F = core.formspec_escape
+local S = core.get_translator("__builtin")
+
+function core.show_death_screen(player, _reason)
+ local fs = {
+ "formspec_version[1]",
+ "size[11,5.5,true]",
+ "bgcolor[#320000b4;true]",
+ "label[4.85,1.35;", F(S("You died")), "]",
+ "button_exit[4,3;3,0.5;btn_respawn;", F(S("Respawn")), "]",
+ }
+ core.show_formspec(player:get_player_name(), "__builtin:death", table.concat(fs, ""))
+end
+
+core.register_on_dieplayer(function(player, reason)
+ core.show_death_screen(player, reason)
+end)
+
+core.register_on_joinplayer(function(player)
+ if player:get_hp() == 0 then
+ core.show_death_screen(player, nil)
+ end
+end)
+
+core.register_on_player_receive_fields(function(player, formname, fields)
+ if formname == "__builtin:death" and fields.quit and player:get_hp() == 0 then
+ player:respawn()
+ core.log("action", player:get_player_name() .. " respawns at " ..
+ player:get_pos():to_string())
+ end
+end)
diff --git a/builtin/game/features.lua b/builtin/game/features.lua
index 22bf1859d..10884497c 100644
--- a/builtin/game/features.lua
+++ b/builtin/game/features.lua
@@ -42,6 +42,9 @@ core.features = {
node_interaction_actor = true,
moveresult_new_pos = true,
override_item_remove_fields = true,
+ hotbar_hud_element = true,
+ bulk_lbms = true,
+ abm_without_neighbors = true,
}
function core.has_feature(arg)
diff --git a/builtin/game/hud.lua b/builtin/game/hud.lua
index 1bc4b48d7..eaf037e68 100644
--- a/builtin/game/hud.lua
+++ b/builtin/game/hud.lua
@@ -251,11 +251,31 @@ register_builtin_hud_element("minimap", {
position = {x = 1, y = 0},
alignment = {x = -1, y = 1},
offset = {x = -10, y = 10},
- size = {x = 256, y = 256},
+ size = {x = 0, y = -25},
},
show_elem = function(player, flags)
+ local proto_ver = core.get_player_information(player:get_player_name()).protocol_version
-- Don't add a minimap for clients which already have it hardcoded in C++.
- return flags.minimap and
- core.get_player_information(player:get_player_name()).protocol_version >= 44
+ return flags.minimap and proto_ver >= 44
+ end,
+ update_def = function(player, elem_def)
+ local proto_ver = core.get_player_information(player:get_player_name()).protocol_version
+ -- Only use percentage when the client supports it.
+ elem_def.size = proto_ver >= 45 and {x = 0, y = -25} or {x = 256, y = 256}
+ end,
+})
+
+--- Hotbar
+
+register_builtin_hud_element("hotbar", {
+ elem_def = {
+ type = "hotbar",
+ position = {x = 0.5, y = 1},
+ direction = 0,
+ alignment = {x = 0, y = -1},
+ offset = {x = 0, y = -4}, -- Extra padding below.
+ },
+ show_elem = function(player, flags)
+ return flags.hotbar
end,
})
diff --git a/builtin/game/init.lua b/builtin/game/init.lua
index 4e16c686d..b3c64e729 100644
--- a/builtin/game/init.lua
+++ b/builtin/game/init.lua
@@ -38,6 +38,7 @@ dofile(gamepath .. "forceloading.lua")
dofile(gamepath .. "hud.lua")
dofile(gamepath .. "knockback.lua")
dofile(gamepath .. "async.lua")
+dofile(gamepath .. "death_screen.lua")
core.after(0, builtin_shared.cache_content_ids)
diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua
index a8a6700f9..25e19ddb0 100644
--- a/builtin/game/misc.lua
+++ b/builtin/game/misc.lua
@@ -6,14 +6,14 @@ local S = core.get_translator("__builtin")
-- Misc. API functions
--
--- @spec core.kick_player(String, String) :: Boolean
-function core.kick_player(player_name, reason)
+-- @spec core.kick_player(String, String, Boolean) :: Boolean
+function core.kick_player(player_name, reason, reconnect)
if type(reason) == "string" then
reason = "Kicked: " .. reason
else
reason = "Kicked."
end
- return core.disconnect_player(player_name, reason)
+ return core.disconnect_player(player_name, reason, reconnect)
end
function core.check_player_privs(name, ...)
@@ -298,3 +298,28 @@ do
return valid_object_iterator(core.get_objects_in_area(min_pos, max_pos))
end
end
+
+--
+-- Helper for LBM execution, called from C++
+--
+
+function core.run_lbm(id, pos_list, dtime_s)
+ local lbm = core.registered_lbms[id]
+ assert(lbm, "Entry with given id not found in registered_lbms table")
+ core.set_last_run_mod(lbm.mod_origin)
+ if lbm.bulk_action then
+ return lbm.bulk_action(pos_list, dtime_s)
+ end
+ -- emulate non-bulk LBMs
+ local expect = core.get_node(pos_list[1]).name
+ -- engine guarantees that
+ -- 1) all nodes are the same content type
+ -- 2) the list is up-to-date when we're called
+ assert(expect ~= "ignore")
+ for _, pos in ipairs(pos_list) do
+ local n = core.get_node(pos)
+ if n.name == expect then -- might have been changed by previous call
+ lbm.action(pos, n, dtime_s)
+ end
+ end
+end
diff --git a/builtin/game/register.lua b/builtin/game/register.lua
index 76cb72f87..3d99a6925 100644
--- a/builtin/game/register.lua
+++ b/builtin/game/register.lua
@@ -105,7 +105,12 @@ function core.register_lbm(spec)
-- Add to core.registered_lbms
check_modname_prefix(spec.name)
check_node_list(spec.nodenames, "nodenames")
- assert(type(spec.action) == "function", "Required field 'action' of type function")
+ local have = spec.action ~= nil
+ local have_bulk = spec.bulk_action ~= nil
+ assert(not have or type(spec.action) == "function", "Field 'action' must be a function")
+ assert(not have_bulk or type(spec.bulk_action) == "function", "Field 'bulk_action' must be a function")
+ assert(have ~= have_bulk, "Either 'action' or 'bulk_action' must be present")
+
core.registered_lbms[#core.registered_lbms + 1] = spec
spec.mod_origin = core.get_current_modname() or "??"
end
diff --git a/builtin/init.lua b/builtin/init.lua
index 49df70971..415087a54 100644
--- a/builtin/init.lua
+++ b/builtin/init.lua
@@ -42,6 +42,7 @@ local scriptdir = core.get_builtin_path()
local commonpath = scriptdir .. "common" .. DIR_DELIM
local asyncpath = scriptdir .. "async" .. DIR_DELIM
+dofile(commonpath .. "math.lua")
dofile(commonpath .. "vector.lua")
dofile(commonpath .. "strict.lua")
dofile(commonpath .. "serialize.lua")
diff --git a/builtin/locale/__builtin.be.tr b/builtin/locale/__builtin.be.tr
new file mode 100644
index 000000000..bd8ea999e
--- /dev/null
+++ b/builtin/locale/__builtin.be.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Вы загінулі
+Respawn=Адрадзіцца
diff --git a/builtin/locale/__builtin.bg.tr b/builtin/locale/__builtin.bg.tr
new file mode 100644
index 000000000..43fe8f31e
--- /dev/null
+++ b/builtin/locale/__builtin.bg.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Умряхте
+Respawn=Прераждане
diff --git a/builtin/locale/__builtin.ca.tr b/builtin/locale/__builtin.ca.tr
new file mode 100644
index 000000000..5cde155f9
--- /dev/null
+++ b/builtin/locale/__builtin.ca.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Has mort
+Respawn=Reaparèixer
diff --git a/builtin/locale/__builtin.cs.tr b/builtin/locale/__builtin.cs.tr
new file mode 100644
index 000000000..4f4b592e4
--- /dev/null
+++ b/builtin/locale/__builtin.cs.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Zemřel jsi
+Respawn=Oživit
diff --git a/builtin/locale/__builtin.cy.tr b/builtin/locale/__builtin.cy.tr
new file mode 100644
index 000000000..372da1a89
--- /dev/null
+++ b/builtin/locale/__builtin.cy.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Buest ti farw
+Respawn=Atgyfodi
diff --git a/builtin/locale/__builtin.da.tr b/builtin/locale/__builtin.da.tr
new file mode 100644
index 000000000..c34eceeb9
--- /dev/null
+++ b/builtin/locale/__builtin.da.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Du døde
+Respawn=Genopstå
diff --git a/builtin/locale/__builtin.de.tr b/builtin/locale/__builtin.de.tr
index 0552ef88b..3665a3f54 100644
--- a/builtin/locale/__builtin.de.tr
+++ b/builtin/locale/__builtin.de.tr
@@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Es wurden insgesamt @1 Datenpunkt(e) aufgeze
The output is limited to '@1'.=Die Ausgabe ist beschränkt auf „@1“.
Saving of profile failed: @1=Speichern des Profils fehlgeschlagen: @1
Profile saved to @1=Profil abgespeichert nach @1
+You died=Sie sind gestorben
+Respawn=Wiederbeleben
diff --git a/builtin/locale/__builtin.el.tr b/builtin/locale/__builtin.el.tr
new file mode 100644
index 000000000..c14180a62
--- /dev/null
+++ b/builtin/locale/__builtin.el.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Πέθανες
+Respawn=Επανεμφάνηση
diff --git a/builtin/locale/__builtin.eo.tr b/builtin/locale/__builtin.eo.tr
index c3fec6c37..f1fe5333b 100644
--- a/builtin/locale/__builtin.eo.tr
+++ b/builtin/locale/__builtin.eo.tr
@@ -244,3 +244,5 @@ 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
+You died=Vi mortis
+Respawn=Renaskiĝi
diff --git a/builtin/locale/__builtin.es.tr b/builtin/locale/__builtin.es.tr
new file mode 100644
index 000000000..3c4a66933
--- /dev/null
+++ b/builtin/locale/__builtin.es.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Has muerto
+Respawn=Reaparecer
diff --git a/builtin/locale/__builtin.et.tr b/builtin/locale/__builtin.et.tr
new file mode 100644
index 000000000..e0745c24a
--- /dev/null
+++ b/builtin/locale/__builtin.et.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Said surma
+Respawn=Ärka ellu
diff --git a/builtin/locale/__builtin.eu.tr b/builtin/locale/__builtin.eu.tr
new file mode 100644
index 000000000..77f510855
--- /dev/null
+++ b/builtin/locale/__builtin.eu.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Hil zara
+Respawn=Birsortu
diff --git a/builtin/locale/__builtin.fi.tr b/builtin/locale/__builtin.fi.tr
new file mode 100644
index 000000000..f7c0ee038
--- /dev/null
+++ b/builtin/locale/__builtin.fi.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Kuolit
+Respawn=Synny uudelleen
diff --git a/builtin/locale/__builtin.fil.tr b/builtin/locale/__builtin.fil.tr
new file mode 100644
index 000000000..0755234a1
--- /dev/null
+++ b/builtin/locale/__builtin.fil.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Namatay ka
+Respawn=Mag-respawn
diff --git a/builtin/locale/__builtin.fr.tr b/builtin/locale/__builtin.fr.tr
index 6c3a244dc..d36574afd 100644
--- a/builtin/locale/__builtin.fr.tr
+++ b/builtin/locale/__builtin.fr.tr
@@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=@1 échantillons ont été collectés.
The output is limited to '@1'.=La sortie est limitée à '@1'.
Saving of profile failed: @1=La sauvegarde du profil a échoué : @1
Profile saved to @1=Le profil a été sauvegardé dans @1
+You died=Vous êtes mort
+Respawn=Réapparaître
diff --git a/builtin/locale/__builtin.ga.tr b/builtin/locale/__builtin.ga.tr
new file mode 100644
index 000000000..e19959b10
--- /dev/null
+++ b/builtin/locale/__builtin.ga.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Fuair tú bás
+Respawn=Athsceith
diff --git a/builtin/locale/__builtin.gl.tr b/builtin/locale/__builtin.gl.tr
new file mode 100644
index 000000000..7f692f633
--- /dev/null
+++ b/builtin/locale/__builtin.gl.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Morreches
+Respawn=Reaparecer
diff --git a/builtin/locale/__builtin.hu.tr b/builtin/locale/__builtin.hu.tr
new file mode 100644
index 000000000..d7eebf7a0
--- /dev/null
+++ b/builtin/locale/__builtin.hu.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Meghaltál
+Respawn=Újraéledés
diff --git a/builtin/locale/__builtin.id.tr b/builtin/locale/__builtin.id.tr
index a541b0f8c..a7eba8c1c 100644
--- a/builtin/locale/__builtin.id.tr
+++ b/builtin/locale/__builtin.id.tr
@@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Total @1 sampel yang diambil.
The output is limited to '@1'.=Keluaran dibatasi ke '@1'.
Saving of profile failed: @1=Penyimpanan profil gagal: @1
Profile saved to @1=Profil disimpan ke @1
+You died=Anda mati
+Respawn=Bangkit kembali
diff --git a/builtin/locale/__builtin.it.tr b/builtin/locale/__builtin.it.tr
index aecb80a9a..24cf38104 100644
--- a/builtin/locale/__builtin.it.tr
+++ b/builtin/locale/__builtin.it.tr
@@ -245,3 +245,5 @@ A total of @1 sample(s) were taken.=Son stati ottenuti campioni per un totale di
The output is limited to '@1'.=L'output è limitato a '@1'.
Saving of profile failed: @1=Errore nel salvare il profilo: @1
Profile saved to @1=Profilo salvato in @1
+You died=Sei morto
+Respawn=Rinasci
diff --git a/builtin/locale/__builtin.ja.tr b/builtin/locale/__builtin.ja.tr
new file mode 100644
index 000000000..1138edddd
--- /dev/null
+++ b/builtin/locale/__builtin.ja.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=死んでしまった
+Respawn=リスポーン
diff --git a/builtin/locale/__builtin.jbo.tr b/builtin/locale/__builtin.jbo.tr
new file mode 100644
index 000000000..fe492bcdb
--- /dev/null
+++ b/builtin/locale/__builtin.jbo.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=.i do morsi
+Respawn=tolcanci
diff --git a/builtin/locale/__builtin.jv.tr b/builtin/locale/__builtin.jv.tr
new file mode 100644
index 000000000..282cc5476
--- /dev/null
+++ b/builtin/locale/__builtin.jv.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Panjenengan pejah
+Respawn=Bangkit Malilh
diff --git a/builtin/locale/__builtin.ko.tr b/builtin/locale/__builtin.ko.tr
new file mode 100644
index 000000000..0d5d35a90
--- /dev/null
+++ b/builtin/locale/__builtin.ko.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=사망했습니다
+Respawn=리스폰
diff --git a/builtin/locale/__builtin.kv.tr b/builtin/locale/__builtin.kv.tr
new file mode 100644
index 000000000..f985d2ac4
--- /dev/null
+++ b/builtin/locale/__builtin.kv.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Кулінныд
+Respawn=Ловзьыны
diff --git a/builtin/locale/__builtin.ky.tr b/builtin/locale/__builtin.ky.tr
new file mode 100644
index 000000000..9ad468aa0
--- /dev/null
+++ b/builtin/locale/__builtin.ky.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Сиз өлдүңүз.
+Respawn=Кайтадан жаралуу
diff --git a/builtin/locale/__builtin.lt.tr b/builtin/locale/__builtin.lt.tr
new file mode 100644
index 000000000..cc613162c
--- /dev/null
+++ b/builtin/locale/__builtin.lt.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Jūs numirėte
+Respawn=Prisikelti
diff --git a/builtin/locale/__builtin.lv.tr b/builtin/locale/__builtin.lv.tr
new file mode 100644
index 000000000..1da1fe3d8
--- /dev/null
+++ b/builtin/locale/__builtin.lv.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Jūs nomirāt
+Respawn=Atdzīvoties
diff --git a/builtin/locale/__builtin.lzh.tr b/builtin/locale/__builtin.lzh.tr
new file mode 100644
index 000000000..1af6aba98
--- /dev/null
+++ b/builtin/locale/__builtin.lzh.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=尔死矣
+Respawn=复生
diff --git a/builtin/locale/__builtin.mn.tr b/builtin/locale/__builtin.mn.tr
new file mode 100644
index 000000000..dd9a3465b
--- /dev/null
+++ b/builtin/locale/__builtin.mn.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Та үхсэн
+Respawn=Дахин төрөх
diff --git a/builtin/locale/__builtin.mr.tr b/builtin/locale/__builtin.mr.tr
new file mode 100644
index 000000000..e4fcfb9b1
--- /dev/null
+++ b/builtin/locale/__builtin.mr.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=तू मेलास
+Respawn=पुनर्जन्म
diff --git a/builtin/locale/__builtin.ms.tr b/builtin/locale/__builtin.ms.tr
index ebf794e97..65ac557bf 100644
--- a/builtin/locale/__builtin.ms.tr
+++ b/builtin/locale/__builtin.ms.tr
@@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Sebanyak @1 sampel telah diambil secara kese
The output is limited to '@1'.=Output dihadkan kepada '@1'.
Saving of profile failed: @1=Penyimpanan profil telah gagal: @1
Profile saved to @1=Profil telah disimpan ke @1
+You died=Anda telah meninggal
+Respawn=Jelma semula
diff --git a/builtin/locale/__builtin.nb.tr b/builtin/locale/__builtin.nb.tr
new file mode 100644
index 000000000..b02a2d282
--- /dev/null
+++ b/builtin/locale/__builtin.nb.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Du døde
+Respawn=Gjenoppstå
diff --git a/builtin/locale/__builtin.nl.tr b/builtin/locale/__builtin.nl.tr
new file mode 100644
index 000000000..bd23c04b0
--- /dev/null
+++ b/builtin/locale/__builtin.nl.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Je bent gestorven
+Respawn=Herboren worden
diff --git a/builtin/locale/__builtin.nn.tr b/builtin/locale/__builtin.nn.tr
new file mode 100644
index 000000000..240191f73
--- /dev/null
+++ b/builtin/locale/__builtin.nn.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Du døydde
+Respawn=Kom opp att
diff --git a/builtin/locale/__builtin.oc.tr b/builtin/locale/__builtin.oc.tr
new file mode 100644
index 000000000..34b6577ad
--- /dev/null
+++ b/builtin/locale/__builtin.oc.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Setz mòrt·a
+Respawn=Tornar
diff --git a/builtin/locale/__builtin.pl.tr b/builtin/locale/__builtin.pl.tr
new file mode 100644
index 000000000..ec0a9b7c1
--- /dev/null
+++ b/builtin/locale/__builtin.pl.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Nie żyjesz
+Respawn=Wróć do gry
diff --git a/builtin/locale/__builtin.pt.tr b/builtin/locale/__builtin.pt.tr
new file mode 100644
index 000000000..fbdb461e5
--- /dev/null
+++ b/builtin/locale/__builtin.pt.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Você morreu
+Respawn=Renascer
diff --git a/builtin/locale/__builtin.pt_BR.tr b/builtin/locale/__builtin.pt_BR.tr
index e12e9fae8..e48425bda 100644
--- a/builtin/locale/__builtin.pt_BR.tr
+++ b/builtin/locale/__builtin.pt_BR.tr
@@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Um total de @1 amostra(s) foi coletada.
The output is limited to '@1'.=A saída é limitada a '@1'.
Saving of profile failed: @1=Falha ao salvar o perfil: @1
Profile saved to @1=Perfil salvo em @1
+You died=Você morreu
+Respawn=Reviver
diff --git a/builtin/locale/__builtin.ro.tr b/builtin/locale/__builtin.ro.tr
new file mode 100644
index 000000000..7ff5e3c38
--- /dev/null
+++ b/builtin/locale/__builtin.ro.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Ai murit
+Respawn=Reînviere
diff --git a/builtin/locale/__builtin.ru.tr b/builtin/locale/__builtin.ru.tr
index d43fbc589..863cdd638 100644
--- a/builtin/locale/__builtin.ru.tr
+++ b/builtin/locale/__builtin.ru.tr
@@ -244,3 +244,5 @@ A total of @1 sample(s) were taken.=Всего было взято @1 образ
The output is limited to '@1'.=Вывод ограничен значением '@1'.
Saving of profile failed: @1=Не удалось сохранить данные профилирования: @1
Profile saved to @1=Данные профилирования сохранены в @1
+You died=Вы умерли
+Respawn=Возродиться
diff --git a/builtin/locale/__builtin.sk.tr b/builtin/locale/__builtin.sk.tr
new file mode 100644
index 000000000..e9e93f6a3
--- /dev/null
+++ b/builtin/locale/__builtin.sk.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Zomrel si
+Respawn=Oživiť
diff --git a/builtin/locale/__builtin.sl.tr b/builtin/locale/__builtin.sl.tr
new file mode 100644
index 000000000..6dc77c3b2
--- /dev/null
+++ b/builtin/locale/__builtin.sl.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Umrl si
+Respawn=Ponovno oživi
diff --git a/builtin/locale/__builtin.sr_Cyrl.tr b/builtin/locale/__builtin.sr_Cyrl.tr
new file mode 100644
index 000000000..68551e77a
--- /dev/null
+++ b/builtin/locale/__builtin.sr_Cyrl.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Умро си
+Respawn=Врати се у живот
diff --git a/builtin/locale/__builtin.sr_Latn.tr b/builtin/locale/__builtin.sr_Latn.tr
new file mode 100644
index 000000000..4adc496b4
--- /dev/null
+++ b/builtin/locale/__builtin.sr_Latn.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Umro/la si.
+Respawn=Vrati se u zivot
diff --git a/builtin/locale/__builtin.sv.tr b/builtin/locale/__builtin.sv.tr
new file mode 100644
index 000000000..115014506
--- /dev/null
+++ b/builtin/locale/__builtin.sv.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Du dog
+Respawn=Återuppstå
diff --git a/builtin/locale/__builtin.sw.tr b/builtin/locale/__builtin.sw.tr
new file mode 100644
index 000000000..92aa1f3e1
--- /dev/null
+++ b/builtin/locale/__builtin.sw.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Umekufa.
+Respawn=Respawn
diff --git a/builtin/locale/__builtin.tok.tr b/builtin/locale/__builtin.tok.tr
new file mode 100644
index 000000000..14d131d6a
--- /dev/null
+++ b/builtin/locale/__builtin.tok.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=sina moli
+Respawn=o kama sin
diff --git a/builtin/locale/__builtin.tr.tr b/builtin/locale/__builtin.tr.tr
new file mode 100644
index 000000000..dafec5630
--- /dev/null
+++ b/builtin/locale/__builtin.tr.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Öldün
+Respawn=Yeniden Canlan
diff --git a/builtin/locale/__builtin.tt.tr b/builtin/locale/__builtin.tt.tr
new file mode 100644
index 000000000..a162cd9b7
--- /dev/null
+++ b/builtin/locale/__builtin.tt.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Сез үлдегез
+Respawn=Тергезелергә
diff --git a/builtin/locale/__builtin.uk.tr b/builtin/locale/__builtin.uk.tr
new file mode 100644
index 000000000..e82a10bde
--- /dev/null
+++ b/builtin/locale/__builtin.uk.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Ви загинули
+Respawn=Відродитися
diff --git a/builtin/locale/__builtin.vi.tr b/builtin/locale/__builtin.vi.tr
new file mode 100644
index 000000000..53caac93c
--- /dev/null
+++ b/builtin/locale/__builtin.vi.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=Bạn đã bị chết
+Respawn=Hồi sinh
diff --git a/builtin/locale/__builtin.zh_CN.tr b/builtin/locale/__builtin.zh_CN.tr
new file mode 100644
index 000000000..5b1077429
--- /dev/null
+++ b/builtin/locale/__builtin.zh_CN.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=您已死亡
+Respawn=重生
diff --git a/builtin/locale/__builtin.zh_TW.tr b/builtin/locale/__builtin.zh_TW.tr
new file mode 100644
index 000000000..5b1077429
--- /dev/null
+++ b/builtin/locale/__builtin.zh_TW.tr
@@ -0,0 +1,3 @@
+# textdomain: __builtin
+You died=您已死亡
+Respawn=重生
diff --git a/builtin/mainmenu/content/contentdb.lua b/builtin/mainmenu/content/contentdb.lua
index e0479cb4c..5d6d6c482 100644
--- a/builtin/mainmenu/content/contentdb.lua
+++ b/builtin/mainmenu/content/contentdb.lua
@@ -182,6 +182,23 @@ function contentdb.get_package_by_id(id)
end
+function contentdb.calculate_package_id(type, author, name)
+ local id = author:lower() .. "/"
+ if (type == nil or type == "game") and #name > 5 and name:sub(#name - 4) == "_game" then
+ id = id .. name:sub(1, #name - 5)
+ else
+ id = id .. name
+ end
+ return id
+end
+
+
+function contentdb.get_package_by_info(author, name)
+ local id = contentdb.calculate_package_id(nil, author, name)
+ return contentdb.package_by_id[id]
+end
+
+
-- Create a coroutine from `fn` and provide results to `callback` when complete (dead).
-- Returns a resumer function.
local function make_callback_coroutine(fn, callback)
@@ -415,15 +432,7 @@ local function fetch_pkgs(params)
local aliases = {}
for _, package in pairs(packages) do
- local name_len = #package.name
- -- This must match what contentdb.update_paths() does!
- package.id = package.author:lower() .. "/"
- if package.type == "game" and name_len > 5 and package.name:sub(name_len - 4) == "_game" then
- package.id = package.id .. package.name:sub(1, name_len - 5)
- else
- package.id = package.id .. package.name
- end
-
+ package.id = params.calculate_package_id(package.type, package.author, package.name)
package.url_part = core.urlencode(package.author) .. "/" .. core.urlencode(package.name)
if package.aliases then
@@ -443,7 +452,7 @@ end
function contentdb.fetch_pkgs(callback)
contentdb.loading = true
- core.handle_async(fetch_pkgs, nil, function(result)
+ core.handle_async(fetch_pkgs, { calculate_package_id = contentdb.calculate_package_id }, function(result)
if result then
contentdb.load_ok = true
contentdb.load_error = false
@@ -581,3 +590,78 @@ function contentdb.filter_packages(query, by_type)
end
end
end
+
+
+function contentdb.get_full_package_info(package, callback)
+ assert(package)
+ if package.full_info then
+ callback(package.full_info)
+ return
+ end
+
+ local function fetch(params)
+ local version = core.get_version()
+ local base_url = core.settings:get("contentdb_url")
+
+ local languages
+ local current_language = core.get_language()
+ if current_language ~= "" then
+ languages = { current_language, "en;q=0.8" }
+ else
+ languages = { "en" }
+ end
+
+ local url = base_url ..
+ "/api/packages/" .. params.package.url_part .. "/for-client/?" ..
+ "protocol_version=" .. core.urlencode(core.get_max_supp_proto()) ..
+ "&engine_version=" .. core.urlencode(version.string) ..
+ "&formspec_version=" .. core.urlencode(core.get_formspec_version()) ..
+ "&include_images=false"
+ local http = core.get_http_api()
+ local response = http.fetch_sync({
+ url = url,
+ extra_headers = {
+ "Accept-Language: " .. table.concat(languages, ", ")
+ },
+ })
+ if not response.succeeded then
+ return nil
+ end
+
+ return core.parse_json(response.data)
+ end
+
+ local function my_callback(value)
+ package.full_info = value
+ callback(value)
+ end
+
+ if not core.handle_async(fetch, { package = package }, my_callback) then
+ core.log("error", "ERROR: async event failed")
+ callback(nil)
+ end
+end
+
+
+function contentdb.get_formspec_padding()
+ -- Padding is increased on Android to account for notches
+ -- TODO: use Android API to determine size of cut outs
+ return { x = PLATFORM == "Android" and 1 or 0.5, y = PLATFORM == "Android" and 0.25 or 0.5 }
+end
+
+
+function contentdb.get_formspec_size()
+ local window = core.get_window_info()
+ local size = { x = window.max_formspec_size.x, y = window.max_formspec_size.y }
+
+ -- Minimum formspec size
+ local min_x = 15.5
+ local min_y = 10
+ if size.x < min_x or size.y < min_y then
+ local scale = math.max(min_x / size.x, min_y / size.y)
+ size.x = size.x * scale
+ size.y = size.y * scale
+ end
+
+ return size
+end
diff --git a/builtin/mainmenu/content/dlg_contentdb.lua b/builtin/mainmenu/content/dlg_contentdb.lua
index 84ef96800..8f232e490 100644
--- a/builtin/mainmenu/content/dlg_contentdb.lua
+++ b/builtin/mainmenu/content/dlg_contentdb.lua
@@ -26,68 +26,20 @@ end
-- Filter
local search_string = ""
local cur_page = 1
-local num_per_page = 5
-local filter_type = 1
-local filter_types_titles = {
- fgettext("All packages"),
- fgettext("Games"),
- fgettext("Mods"),
- fgettext("Texture packs"),
-}
+local filter_type
-- Automatic package installation
local auto_install_spec = nil
-local filter_types_type = {
- nil,
- "game",
- "mod",
- "txp",
+
+local filter_type_names = {
+ { "type_all", nil },
+ { "type_game", "game" },
+ { "type_mod", "mod" },
+ { "type_txp", "txp" },
}
-local function install_or_update_package(this, package)
- local install_parent
- if package.type == "mod" then
- install_parent = core.get_modpath()
- elseif package.type == "game" then
- install_parent = core.get_gamepath()
- elseif package.type == "txp" then
- install_parent = core.get_texturepath()
- else
- error("Unknown package type: " .. package.type)
- end
-
- if package.queued or package.downloading then
- return
- end
-
- local function on_confirm()
- local dlg = create_install_dialog(package)
- dlg:set_parent(this)
- this:hide()
- dlg:show()
-
- dlg:load_deps()
- end
-
- if package.type == "mod" and #pkgmgr.games == 0 then
- local dlg = messagebox("install_game",
- fgettext("You need to install a game before you can install a mod"))
- dlg:set_parent(this)
- this:hide()
- dlg:show()
- elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
- local dlg = create_confirm_overwrite(package, on_confirm)
- dlg:set_parent(this)
- this:hide()
- dlg:show()
- else
- on_confirm()
- end
-end
-
-
-- Resolves the package specification stored in auto_install_spec into an actual package.
-- May only be called after the package list has been loaded successfully.
local function resolve_auto_install_spec()
@@ -145,7 +97,7 @@ end
local function sort_and_filter_pkgs()
contentdb.update_paths()
contentdb.sort_packages()
- contentdb.filter_packages(search_string, filter_types_type[filter_type])
+ contentdb.filter_packages(search_string, filter_type)
local auto_install_pkg = resolve_auto_install_spec()
if auto_install_pkg then
@@ -176,72 +128,151 @@ local function load()
end
-local function get_info_formspec(text)
- local H = 9.5
+local function get_info_formspec(size, padding, text)
return table.concat({
"formspec_version[6]",
- "size[15.75,9.5]",
- core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]",
+ "size[", size.x, ",", size.y, "]",
+ "padding[0,0]",
+ "bgcolor[;true]",
- "label[4,4.35;", text, "]",
- "container[0,", H - 0.8 - 0.375, "]",
- "button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]",
+ "label[", padding.x + 3.625, ",4.35;", text, "]",
+ "container[", padding.x, ",", size.y - 0.8 - padding.y, "]",
+ "button[0,0;2,0.8;back;", fgettext("Back"), "]",
"container_end[]",
})
end
+-- Determines how to fit `num_per_page` into `size` space
+local function fit_cells(num_per_page, size)
+ local cell_spacing = 0.5
+ local columns = 1
+ local cell_w, cell_h
+ -- Fit cells into the available height
+ while true do
+ cell_w = (size.x - (columns-1)*cell_spacing) / columns
+ cell_h = cell_w / 4
+
+ local required_height = math.ceil(num_per_page / columns) * (cell_h + cell_spacing) - cell_spacing
+ -- Add 0.1 to be more lenient
+ if required_height <= size.y + 0.1 then
+ break
+ end
+
+ columns = columns + 1
+ end
+
+ return cell_spacing, columns, cell_w, cell_h
+end
+
+
+local function calculate_num_per_page()
+ local size = contentdb.get_formspec_size()
+ local padding = contentdb.get_formspec_padding()
+ local window = core.get_window_info()
+
+ size.x = size.x - padding.x * 2
+ size.y = size.y - padding.y * 2 - 1.425 - 0.25 - 0.8
+
+ local coordToPx = window.size.x / window.max_formspec_size.x / window.real_gui_scaling
+
+ local num_per_page = 12
+ while num_per_page > 2 do
+ local _, _, cell_w, _ = fit_cells(num_per_page, size)
+ if cell_w * coordToPx > 350 then
+ break
+ end
+
+ num_per_page = num_per_page - 1
+ end
+ return num_per_page
+end
+
+
local function get_formspec(dlgdata)
+ local window_padding = contentdb.get_formspec_padding()
+ local size = contentdb.get_formspec_size()
+
if contentdb.loading then
- return get_info_formspec(fgettext("Loading..."))
+ return get_info_formspec(size, window_padding, fgettext("Loading..."))
end
if contentdb.load_error then
- return get_info_formspec(fgettext("No packages could be retrieved"))
+ return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved"))
end
assert(contentdb.load_ok)
contentdb.update_paths()
+ local num_per_page = dlgdata.num_per_page
dlgdata.pagemax = math.max(math.ceil(#contentdb.packages / num_per_page), 1)
if cur_page > dlgdata.pagemax then
cur_page = 1
end
- local W = 15.75
- local H = 9.5
+ local W = size.x - window_padding.x * 2
+ local H = size.y - window_padding.y * 2
+
+ local category_x = 0
+ local number_category_buttons = 4
+ local max_button_w = (W - 0.375 - 0.25 - 7) / number_category_buttons
+ local category_button_w = math.min(max_button_w, 3)
+ local function make_category_button(name, label, selected)
+ category_x = category_x + 1
+ local color = selected and mt_color_green or ""
+ return ("style[%s;bgcolor=%s]button[%f,0;%f,0.8;%s;%s]"):format(name, color,
+ (category_x - 1) * category_button_w, category_button_w, name, label)
+ end
+
+
+ local selected_type = filter_type
+
+ local search_box_width = W - 0.375 - 0.25 - 2*0.8
+ - number_category_buttons * category_button_w
local formspec = {
- "formspec_version[6]",
- "size[15.75,9.5]",
- core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "position[0.5,0.55]",
+ "formspec_version[7]",
+ "size[", size.x, ",", size.y, "]",
+ "padding[0,0]",
+ "bgcolor[;true]",
- "style[status,downloading,queued;border=false]",
+ "container[", window_padding.x, ",", window_padding.y, "]",
- "container[0.375,0.375]",
- "field[0,0;7.225,0.8;search_string;;", core.formspec_escape(search_string), "]",
+ -- Top-left: categories
+ make_category_button("type_all", fgettext("All"), selected_type == nil),
+ make_category_button("type_game", fgettext("Games"), selected_type == "game"),
+ make_category_button("type_mod", fgettext("Mods"), selected_type == "mod"),
+ make_category_button("type_txp", fgettext("Texture Packs"), selected_type == "txp"),
+
+ -- Top-right: Search
+ "container[", W - search_box_width - 0.8*2, ",0]",
+ "field[0,0;", search_box_width, ",0.8;search_string;;", core.formspec_escape(search_string), "]",
"field_enter_after_edit[search_string;true]",
- "image_button[7.3,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
- "image_button[8.125,0;0.8,0.8;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
- "dropdown[9.175,0;2.7875,0.8;type;", table.concat(filter_types_titles, ","), ";", filter_type, "]",
+ "image_button[", search_box_width, ",0;0.8,0.8;",
+ core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
+ "image_button[", search_box_width + 0.8, ",0;0.8,0.8;",
+ core.formspec_escape(defaulttexturedir .. "clear.png"), ";clear;]",
"container_end[]",
- -- Page nav buttons
- "container[0,", H - 0.8 - 0.375, "]",
- "button[0.375,0;5,0.8;back;", fgettext("Back to Main Menu"), "]",
+ -- Bottom strip start
+ "container[0,", H - 0.8, "]",
+ "button[0,0;2,0.8;back;", fgettext("Back"), "]",
- "container[", W - 0.375 - 0.8*4 - 2, ",0]",
- "image_button[0,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
- "image_button[0.8,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
+ -- Bottom-center: Page nav buttons
+ "container[", (W - 1*4 - 2) / 2, ",0]",
+ "image_button[0,0;1,0.8;", core.formspec_escape(defaulttexturedir), "start_icon.png;pstart;]",
+ "image_button[1,0;1,0.8;", core.formspec_escape(defaulttexturedir), "prev_icon.png;pback;]",
"style[pagenum;border=false]",
- "button[1.6,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
- "image_button[3.6,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]",
- "image_button[4.4,0;0.8,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
- "container_end[]",
+ "button[2,0;2,0.8;pagenum;", tonumber(cur_page), " / ", tonumber(dlgdata.pagemax), "]",
+ "image_button[4,0;1,0.8;", core.formspec_escape(defaulttexturedir), "next_icon.png;pnext;]",
+ "image_button[5,0;1,0.8;", core.formspec_escape(defaulttexturedir), "end_icon.png;pend;]",
+ "container_end[]", -- page nav end
- "container_end[]",
+ -- Bottom-right: updating
+ "container[", W - 3, ",0]",
+ "style[status,downloading,queued;border=false]",
}
if contentdb.number_downloading > 0 then
- formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;downloading;"
+ formspec[#formspec + 1] = "button[0,0;3,0.8;downloading;"
if #contentdb.download_queue > 0 then
formspec[#formspec + 1] = fgettext("$1 downloading,\n$2 queued",
contentdb.number_downloading, #contentdb.download_queue)
@@ -260,16 +291,19 @@ local function get_formspec(dlgdata)
end
if num_avail_updates == 0 then
- formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;status;"
+ formspec[#formspec + 1] = "button[0,0;3,0.8;status;"
formspec[#formspec + 1] = fgettext("No updates")
formspec[#formspec + 1] = "]"
else
- formspec[#formspec + 1] = "button[12.5875,0.375;2.7875,0.8;update_all;"
+ formspec[#formspec + 1] = "button[0,0;3,0.8;update_all;"
formspec[#formspec + 1] = fgettext("Update All [$1]", num_avail_updates)
formspec[#formspec + 1] = "]"
end
end
+ formspec[#formspec + 1] = "container_end[]" -- updating end
+ formspec[#formspec + 1] = "container_end[]" -- bottom strip end
+
if #contentdb.packages == 0 then
formspec[#formspec + 1] = "label[4,4.75;"
formspec[#formspec + 1] = fgettext("No results")
@@ -281,81 +315,85 @@ local function get_formspec(dlgdata)
formspec[#formspec + 1] = "tooltip[downloading;" .. fgettext("Downloading...") .. tooltip_colors
formspec[#formspec + 1] = "tooltip[queued;" .. fgettext("Queued") .. tooltip_colors
+ formspec[#formspec + 1] = "container[0,1.425]"
+
+ local cell_spacing, columns, cell_w, cell_h = fit_cells(num_per_page, {
+ x = W,
+ y = H - 1.425 - 0.25 - 0.8
+ })
+ local img_w = cell_h * 3 / 2
+
local start_idx = (cur_page - 1) * num_per_page + 1
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
local package = contentdb.packages[i]
- local container_y = (i - start_idx) * 1.375 + (2*0.375 + 0.8)
- formspec[#formspec + 1] = "container[0.375,"
- formspec[#formspec + 1] = container_y
- formspec[#formspec + 1] = "]"
- -- image
- formspec[#formspec + 1] = "image[0,0;1.5,1;"
- formspec[#formspec + 1] = core.formspec_escape(get_screenshot(package))
- formspec[#formspec + 1] = "]"
+ table.insert_all(formspec, {
+ "container[",
+ (cell_w + cell_spacing) * ((i - start_idx) % columns),
+ ",",
+ (cell_h + cell_spacing) * math.floor((i - start_idx) / columns),
+ "]",
- -- title
- formspec[#formspec + 1] = "label[1.875,0.1;"
- formspec[#formspec + 1] = core.formspec_escape(
- core.colorize(mt_color_green, package.title) ..
- core.colorize("#BFBFBF", " by " .. package.author))
- formspec[#formspec + 1] = "]"
+ "box[0,0;", cell_w, ",", cell_h, ";#ffffff11]",
- -- buttons
- local description_width = W - 2.625 - 2 * 0.7 - 2 * 0.15
+ -- image,
+ "image[0,0;", img_w, ",", cell_h, ";",
+ core.formspec_escape(get_screenshot(package, package.thumbnail, 2)), "]",
- local second_base = "image_button[-1.55,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
- local third_base = "image_button[-2.4,0;0.7,0.7;" .. core.formspec_escape(defaulttexturedir)
- formspec[#formspec + 1] = "container["
- formspec[#formspec + 1] = W - 0.375*2
- formspec[#formspec + 1] = ",0.1]"
+ "label[", img_w + 0.25 + 0.05, ",0.5;",
+ core.formspec_escape(
+ core.colorize(mt_color_green, package.title) ..
+ core.colorize("#BFBFBF", " by " .. package.author)), "]",
- if package.downloading then
- formspec[#formspec + 1] = "animated_image[-1.7,-0.15;1,1;downloading;"
- formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
- formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
- elseif package.queued then
- formspec[#formspec + 1] = second_base
- formspec[#formspec + 1] = "cdb_queued.png;queued;]"
- elseif not package.path then
- local elem_name = "install_" .. i .. ";"
- formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#71aa34]"
- formspec[#formspec + 1] = second_base .. "cdb_add.png;" .. elem_name .. "]"
- formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Install") .. tooltip_colors
- else
- if package.installed_release < package.release then
- -- The install_ action also handles updating
- local elem_name = "install_" .. i .. ";"
- formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#28ccdf]"
- formspec[#formspec + 1] = third_base .. "cdb_update.png;" .. elem_name .. "]"
- formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Update") .. tooltip_colors
+ "textarea[", img_w + 0.25, ",0.75;", cell_w - img_w - 0.25, ",", cell_h - 0.75, ";;;",
+ core.formspec_escape(package.short_description), "]",
- description_width = description_width - 0.7 - 0.15
- end
+ "style[view_", i, ";border=false]",
+ "style[view_", i, ":hovered;bgimg=", core.formspec_escape(defaulttexturedir .. "button_hover_semitrans.png"), "]",
+ "style[view_", i, ":pressed;bgimg=", core.formspec_escape(defaulttexturedir .. "button_press_semitrans.png"), "]",
+ "button[0,0;", cell_w, ",", cell_h, ";view_", i, ";]",
+ })
- local elem_name = "uninstall_" .. i .. ";"
- formspec[#formspec + 1] = "style[" .. elem_name .. "bgcolor=#a93b3b]"
- formspec[#formspec + 1] = second_base .. "cdb_clear.png;" .. elem_name .. "]"
- formspec[#formspec + 1] = "tooltip[" .. elem_name .. fgettext("Uninstall") .. tooltip_colors
+ if package.featured then
+ table.insert_all(formspec, {
+ "tooltip[0,0;0.8,0.8;", fgettext("Featured"), "]",
+ "image[0.2,0.2;0.4,0.4;", defaulttexturedir, "server_favorite.png]",
+ })
end
- local web_elem_name = "view_" .. i .. ";"
- formspec[#formspec + 1] = "image_button[-0.7,0;0.7,0.7;" ..
- core.formspec_escape(defaulttexturedir) .. "cdb_viewonline.png;" .. web_elem_name .. "]"
- formspec[#formspec + 1] = "tooltip[" .. web_elem_name ..
- fgettext("View more information in a web browser") .. tooltip_colors
- formspec[#formspec + 1] = "container_end[]"
+ table.insert_all(formspec, {
+ "container[", cell_w - 0.625,",", 0.25, "]",
+ })
- -- description
- formspec[#formspec + 1] = "textarea[1.855,0.3;"
- formspec[#formspec + 1] = tostring(description_width)
- formspec[#formspec + 1] = ",0.8;;;"
- formspec[#formspec + 1] = core.formspec_escape(package.short_description)
- formspec[#formspec + 1] = "]"
+ if package.downloading then
+ table.insert_all(formspec, {
+ "animated_image[0,0;0.5,0.5;downloading;", defaulttexturedir, "cdb_downloading.png;3;400;;]",
+ })
+ elseif package.queued then
+ table.insert_all(formspec, {
+ "image[0,0;0.5,0.5;", defaulttexturedir, "cdb_queued.png]",
+ })
+ elseif package.path then
+ if package.installed_release < package.release then
+ table.insert_all(formspec, {
+ "image[0,0;0.5,0.5;", defaulttexturedir, "cdb_update.png]",
+ })
+ else
+ table.insert_all(formspec, {
+ "image[0.1,0.1;0.3,0.3;", defaulttexturedir, "checkbox_64.png]",
+ })
+ end
+ end
- formspec[#formspec + 1] = "container_end[]"
+ table.insert_all(formspec, {
+ "container_end[]",
+ "container_end[]",
+ })
end
+ formspec[#formspec + 1] = "container_end[]"
+ formspec[#formspec + 1] = "container_end[]"
+
return table.concat(formspec)
end
@@ -364,14 +402,14 @@ local function handle_submit(this, fields)
if fields.search or fields.key_enter_field == "search_string" then
search_string = fields.search_string:trim()
cur_page = 1
- contentdb.filter_packages(search_string, filter_types_type[filter_type])
+ contentdb.filter_packages(search_string, filter_type)
return true
end
if fields.clear then
search_string = ""
cur_page = 1
- contentdb.filter_packages("", filter_types_type[filter_type])
+ contentdb.filter_packages("", filter_type)
return true
end
@@ -407,12 +445,11 @@ local function handle_submit(this, fields)
return true
end
- if fields.type then
- local new_type = table.indexof(filter_types_titles, fields.type)
- if new_type ~= filter_type then
- filter_type = new_type
+ for _, pair in ipairs(filter_type_names) do
+ if fields[pair[1]] then
+ filter_type = pair[2]
cur_page = 1
- contentdb.filter_packages(search_string, filter_types_type[filter_type])
+ contentdb.filter_packages(search_string, filter_type)
return true
end
end
@@ -428,32 +465,20 @@ local function handle_submit(this, fields)
return true
end
+ local num_per_page = this.data.num_per_page
local start_idx = (cur_page - 1) * num_per_page + 1
assert(start_idx ~= nil)
for i=start_idx, math.min(#contentdb.packages, start_idx+num_per_page-1) do
local package = contentdb.packages[i]
assert(package)
- if fields["install_" .. i] then
- install_or_update_package(this, package)
- return true
- end
-
- if fields["uninstall_" .. i] then
- local dlg = create_delete_content_dlg(package)
+ if fields["view_" .. i] or fields["title_" .. i] or fields["author_" .. i] then
+ local dlg = create_package_dialog(package)
dlg:set_parent(this)
this:hide()
dlg:show()
return true
end
-
- if fields["view_" .. i] then
- local url = ("%s/packages/%s?protocol_version=%d"):format(
- core.settings:get("contentdb_url"), package.url_part,
- core.get_max_supp_proto())
- core.open_url(url)
- return true
- end
end
return false
@@ -462,8 +487,8 @@ end
local function handle_events(event)
if event == "DialogShow" then
- -- On touchscreen, don't show the "MINETEST" header behind the dialog.
- mm_game_theme.set_engine(core.settings:get_bool("enable_touch"))
+ -- Don't show the "MINETEST" header behind the dialog.
+ mm_game_theme.set_engine(true)
-- If ContentDB is already loaded, auto-install packages here.
do_auto_install()
@@ -471,6 +496,11 @@ local function handle_events(event)
return true
end
+ if event == "WindowInfoChange" then
+ ui.update()
+ return true
+ end
+
return false
end
@@ -485,17 +515,7 @@ end
function create_contentdb_dlg(type, install_spec)
search_string = ""
cur_page = 1
- if type then
- -- table.indexof does not work on tables that contain `nil`
- for i, v in pairs(filter_types_type) do
- if v == type then
- filter_type = i
- break
- end
- end
- else
- filter_type = 1
- end
+ filter_type = type
-- Keep the old auto_install_spec if the caller doesn't specify one.
if install_spec then
@@ -504,8 +524,10 @@ function create_contentdb_dlg(type, install_spec)
load()
- return dialog_create("contentdb",
+ local dlg = dialog_create("contentdb",
get_formspec,
handle_submit,
handle_events)
+ dlg.data.num_per_page = calculate_num_per_page()
+ return dlg
end
diff --git a/builtin/mainmenu/content/dlg_install.lua b/builtin/mainmenu/content/dlg_install.lua
index 0549e23be..3f43bd23c 100644
--- a/builtin/mainmenu/content/dlg_install.lua
+++ b/builtin/mainmenu/content/dlg_install.lua
@@ -22,13 +22,13 @@ end
local function get_loading_formspec()
- local ENABLE_TOUCH = core.settings:get_bool("enable_touch")
- local w = ENABLE_TOUCH and 14 or 7
+ local TOUCH_GUI = core.settings:get_bool("touch_gui")
+ local w = TOUCH_GUI and 14 or 7
local formspec = {
"formspec_version[3]",
"size[", w, ",9.05]",
- ENABLE_TOUCH and "padding[0.01,0.01]" or "position[0.5,0.55]",
+ TOUCH_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]",
"label[3,4.525;", fgettext("Loading..."), "]",
}
return table.concat(formspec)
@@ -110,18 +110,18 @@ local function get_formspec(data)
message_bg = mt_color_orange
end
- local ENABLE_TOUCH = core.settings:get_bool("enable_touch")
+ local TOUCH_GUI = core.settings:get_bool("touch_gui")
- local w = ENABLE_TOUCH and 14 or 7
+ local w = TOUCH_GUI and 14 or 7
local padded_w = w - 2*0.375
- local dropdown_w = ENABLE_TOUCH and 10.2 or 4.25
+ local dropdown_w = TOUCH_GUI and 10.2 or 4.25
local button_w = (padded_w - 0.25) / 3
local button_pad = button_w / 2
local formspec = {
"formspec_version[3]",
"size[", w, ",9.05]",
- ENABLE_TOUCH and "padding[0.01,0.01]" or "position[0.5,0.55]",
+ TOUCH_GUI and "padding[0.01,0.01]" or "position[0.5,0.55]",
"style[title;border=false]",
"box[0,0;", w, ",0.8;#3333]",
"button[0,0;", w, ",0.8;title;", fgettext("Install $1", package.title) , "]",
@@ -244,3 +244,45 @@ function create_install_dialog(package)
return dlg
end
+
+
+function install_or_update_package(parent, package)
+ local install_parent
+ if package.type == "mod" then
+ install_parent = core.get_modpath()
+ elseif package.type == "game" then
+ install_parent = core.get_gamepath()
+ elseif package.type == "txp" then
+ install_parent = core.get_texturepath()
+ else
+ error("Unknown package type: " .. package.type)
+ end
+
+ if package.queued or package.downloading then
+ return
+ end
+
+ local function on_confirm()
+ local dlg = create_install_dialog(package)
+ dlg:set_parent(parent)
+ parent:hide()
+ dlg:show()
+
+ dlg:load_deps()
+ end
+
+ if package.type == "mod" and #pkgmgr.games == 0 then
+ local dlg = messagebox("install_game",
+ fgettext("You need to install a game before you can install a mod"))
+ dlg:set_parent(parent)
+ parent:hide()
+ dlg:show()
+ elseif not package.path and core.is_dir(install_parent .. DIR_DELIM .. package.name) then
+ local dlg = create_confirm_overwrite(package, on_confirm)
+ dlg:set_parent(parent)
+ parent:hide()
+ dlg:show()
+ else
+ on_confirm()
+ end
+end
diff --git a/builtin/mainmenu/content/dlg_package.lua b/builtin/mainmenu/content/dlg_package.lua
new file mode 100644
index 000000000..404e950c4
--- /dev/null
+++ b/builtin/mainmenu/content/dlg_package.lua
@@ -0,0 +1,333 @@
+--Minetest
+--Copyright (C) 2018-24 rubenwardy
+--
+--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.
+
+
+local function get_info_formspec(size, padding, text)
+ return table.concat({
+ "formspec_version[6]",
+ "size[", size.x, ",", size.y, "]",
+ "padding[0,0]",
+ "bgcolor[;true]",
+
+ "label[4,4.35;", text, "]",
+ "container[", padding.x, ",", size.y - 0.8 - padding.y, "]",
+ "button[0,0;2,0.8;back;", fgettext("Back"), "]",
+ "container_end[]",
+ })
+end
+
+
+local function get_formspec(data)
+ local window_padding = contentdb.get_formspec_padding()
+ local size = contentdb.get_formspec_size()
+ size.x = math.min(size.x, 20)
+ local W = size.x - window_padding.x * 2
+ local H = size.y - window_padding.y * 2
+
+ if not data.info then
+ if not data.loading and not data.loading_error then
+ data.loading = true
+
+ contentdb.get_full_package_info(data.package, function(info)
+ data.loading = false
+
+ if info == nil then
+ data.loading_error = true
+ ui.update()
+ return
+ end
+
+ if info.forums then
+ info.forums = "https://forum.minetest.net/viewtopic.php?t=" .. info.forums
+ end
+
+ assert(data.package.name == info.name)
+ data.info = info
+ ui.update()
+ end)
+ end
+
+ -- get_full_package_info can return cached info immediately, so
+ -- check to see if that happened
+ if not data.info then
+ if data.loading_error then
+ return get_info_formspec(size, window_padding, fgettext("No packages could be retrieved"))
+ end
+ return get_info_formspec(size, window_padding, fgettext("Loading..."))
+ end
+ end
+
+ -- Check installation status
+ contentdb.update_paths()
+
+ local info = data.info
+
+ local info_line =
+ fgettext("by $1 — $2 downloads — +$3 / $4 / -$5",
+ info.author, info.downloads,
+ info.reviews.positive, info.reviews.neutral, info.reviews.negative)
+
+ local bottom_buttons_y = H - 0.8
+
+ local formspec = {
+ "formspec_version[7]",
+ "size[", size.x, ",", size.y, "]",
+ "padding[0,0]",
+ "bgcolor[;true]",
+
+ "container[", window_padding.x, ",", window_padding.y, "]",
+
+ "button[0,", bottom_buttons_y, ";2,0.8;back;", fgettext("Back"), "]",
+ "button[", W - 3, ",", bottom_buttons_y, ";3,0.8;open_contentdb;", fgettext("ContentDB page"), "]",
+
+ "style_type[label;font_size=+24;font=bold]",
+ "label[0,0.4;", core.formspec_escape(info.title), "]",
+ "style_type[label;font_size=;font=]",
+
+ "label[0,1.2;", core.formspec_escape(info_line), "]",
+ }
+
+ table.insert_all(formspec, {
+ "container[", W - 6, ",0]"
+ })
+
+ local left_button_rect = "0,0;2.875,1"
+ local right_button_rect = "3.125,0;2.875,1"
+ if data.package.downloading then
+ formspec[#formspec + 1] = "animated_image[5,0;1,1;downloading;"
+ formspec[#formspec + 1] = core.formspec_escape(defaulttexturedir)
+ formspec[#formspec + 1] = "cdb_downloading.png;3;400;]"
+ elseif data.package.queued then
+ formspec[#formspec + 1] = "style[queued;border=false]"
+ formspec[#formspec + 1] = "image_button[5,0;1,1;" .. core.formspec_escape(defaulttexturedir)
+ formspec[#formspec + 1] = "cdb_queued.png;queued;]"
+ elseif not data.package.path then
+ formspec[#formspec + 1] = "style[install;bgcolor=green]"
+ formspec[#formspec + 1] = "button["
+ formspec[#formspec + 1] = right_button_rect
+ formspec[#formspec + 1] =";install;"
+ formspec[#formspec + 1] = fgettext("Install [$1]", info.download_size)
+ formspec[#formspec + 1] = "]"
+ else
+ if data.package.installed_release < data.package.release then
+ -- The install_ action also handles updating
+ formspec[#formspec + 1] = "style[install;bgcolor=#28ccdf]"
+ formspec[#formspec + 1] = "button["
+ formspec[#formspec + 1] = left_button_rect
+ formspec[#formspec + 1] = ";install;"
+ formspec[#formspec + 1] = fgettext("Update")
+ formspec[#formspec + 1] = "]"
+ end
+
+ formspec[#formspec + 1] = "style[uninstall;bgcolor=#a93b3b]"
+ formspec[#formspec + 1] = "button["
+ formspec[#formspec + 1] = right_button_rect
+ formspec[#formspec + 1] = ";uninstall;"
+ formspec[#formspec + 1] = fgettext("Uninstall")
+ formspec[#formspec + 1] = "]"
+ end
+
+ local current_tab = data.current_tab or 1
+ local tab_titles = {
+ fgettext("Description"),
+ fgettext("Information"),
+ }
+
+ local tab_body_height = bottom_buttons_y - 2.8
+
+ table.insert_all(formspec, {
+ "container_end[]",
+
+ "box[0,2.55;", W, ",", tab_body_height, ";#ffffff11]",
+
+ "tabheader[0,2.55;", W, ",0.8;tabs;",
+ table.concat(tab_titles, ","), ";", current_tab, ";true;true]",
+
+ "container[0,2.8]",
+ })
+
+ if current_tab == 1 then
+ -- Screenshots and description
+ local hypertext = "" .. core.hypertext_escape(info.short_description) .. "\n"
+ local winfo = core.get_window_info()
+ local fs_to_px = winfo.size.x / winfo.max_formspec_size.x
+ for i, ss in ipairs(info.screenshots) do
+ local path = get_screenshot(data.package, ss.url, 2)
+ hypertext = hypertext .. "
"
+ if i ~= #info.screenshots then
+ hypertext = hypertext .. "
"
+ end
+ end
+ hypertext = hypertext .. "\n" .. info.long_description.head
+
+ local first = true
+ local function add_link_button(label, name)
+ if info[name] then
+ if not first then
+ hypertext = hypertext .. " | "
+ end
+ hypertext = hypertext .. "" .. core.hypertext_escape(label) .. ""
+ info.long_description.links["link_" .. name] = info[name]
+ first = false
+ end
+ end
+
+ add_link_button(fgettext("Donate"), "donate_url")
+ add_link_button(fgettext("Website"), "website")
+ add_link_button(fgettext("Source"), "repo")
+ add_link_button(fgettext("Issue Tracker"), "issue_tracker")
+ add_link_button(fgettext("Translate"), "translation_url")
+ add_link_button(fgettext("Forum Topic"), "forums")
+
+ hypertext = hypertext .. "\n\n" .. info.long_description.body
+
+ hypertext = hypertext:gsub("
[Project founder]",
+ "sfan5 ",
+ "ShadowNinja ",
+ "Nathanaëlle Courant (Nore/Ekdohibs) ",
+ "Loic Blot (nerzhul/nrz) ",
+ "Andrew Ward (rubenwardy) ",
+ "Krock/SmallJoker ",
+ "Lars Hofhansl ",
+ "v-rob ",
+ "Desour/DS",
+ "srifqi",
+ "Gregor Parzefall (grorp)",
+ "Lars Müller (luatic)"
+ ],
+ "previous_core_developers": [
+ "BlockMen",
+ "Maciej Kasatkin (RealBadAngel) [RIP]",
+ "Lisa Milne (darkrose) ",
+ "proller",
+ "Ilya Zhuravlev (xyz) ",
+ "PilzAdam ",
+ "est31 ",
+ "kahrl ",
+ "Ryan Kwolek (kwolekr) ",
+ "sapier",
+ "Zeno",
+ "Auke Kok (sofar) ",
+ "Aaron Suen ",
+ "paramat",
+ "Pierre-Yves Rollo ",
+ "hecks",
+ "Jude Melton-Houghton (TurkeyMcMac) [RIP]",
+ "Hugues Ross ",
+ "Dmitry Kostenko (x2048) "
+ ],
+ "#": "Currently only https://github.com/orgs/minetest/teams/triagers/members",
+ "core_team": [
+ "Zughy [Issue triager]",
+ "wsor [Issue triager]",
+ "Hugo Locurcio (Calinou) [Issue triager]"
+ ],
+ "#": "For updating active/previous contributors, see the script in ./util/gather_git_credits.py",
+ "contributors": [
+ "cx384",
+ "numzero",
+ "AFCMS",
+ "sfence",
+ "Wuzzy",
+ "ROllerozxa",
+ "JosiahWI",
+ "OgelGames",
+ "David Heidelberg",
+ "1F616EMO",
+ "HybridDog",
+ "Bradley Pierce (Thresher)",
+ "savilli",
+ "Stvk imension",
+ "y5nw",
+ "chmodsayshello",
+ "jordan4ibanez",
+ "superfloh247"
+ ],
+ "previous_contributors": [
+ "Nils Dagsson Moskopp (erlehmann) [Minetest logo]",
+ "red-001 ",
+ "Giuseppe Bilotta",
+ "HybridDog",
+ "ClobberXD",
+ "Dániel Juhász (juhdanad) ",
+ "MirceaKitsune ",
+ "Jean-Patrick Guerrero (kilbith)",
+ "MoNTE48",
+ "Constantin Wenger (SpeedProg)",
+ "Ciaran Gultnieks (CiaranG)",
+ "Paul Ouellette (pauloue)",
+ "stujones11",
+ "Rogier ",
+ "Gregory Currie (gregorycu)",
+ "JacobF",
+ "Jeija "
+ ]
+}
diff --git a/builtin/mainmenu/dlg_config_world.lua b/builtin/mainmenu/dlg_config_world.lua
index e8f49b230..a8c5221de 100644
--- a/builtin/mainmenu/dlg_config_world.lua
+++ b/builtin/mainmenu/dlg_config_world.lua
@@ -126,7 +126,7 @@ local function get_formspec(data)
local retval =
"size[11.5,7.5,true]" ..
"label[0.5,0;" .. fgettext("World:") .. "]" ..
- "label[1.75,0;" .. data.worldspec.name .. "]"
+ "label[1.75,0;" .. core.formspec_escape(data.worldspec.name) .. "]"
if mod.is_modpack or mod.type == "game" then
local info = core.formspec_escape(
diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua
index 41885e298..dd35334c2 100644
--- a/builtin/mainmenu/init.lua
+++ b/builtin/mainmenu/init.lua
@@ -23,6 +23,13 @@ mt_color_dark_green = "#25C191"
mt_color_orange = "#FF8800"
mt_color_red = "#FF3300"
+MAIN_TAB_W = 15.5
+MAIN_TAB_H = 7.1
+TABHEADER_H = 0.85
+GAMEBAR_H = 1.25
+GAMEBAR_OFFSET_DESKTOP = 0.375
+GAMEBAR_OFFSET_TOUCH = 0.15
+
local menupath = core.get_mainmenu_path()
local basepath = core.get_builtin_path()
defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" ..
@@ -89,7 +96,7 @@ local function init_globals()
mm_game_theme.set_engine() -- This is just a fallback.
-- Create main tabview
- local tv_main = tabview_create("maintab", {x = 15.5, y = 7.1}, {x = 0, y = 0})
+ local tv_main = tabview_create("maintab", {x = MAIN_TAB_W, y = MAIN_TAB_H}, {x = 0, y = 0})
tv_main:set_autosave_tab(true)
tv_main:add(tabs.local_game)
diff --git a/builtin/mainmenu/settings/components.lua b/builtin/mainmenu/settings/components.lua
index bfe64285c..79253558b 100644
--- a/builtin/mainmenu/settings/components.lua
+++ b/builtin/mainmenu/settings/components.lua
@@ -67,6 +67,19 @@ function make.heading(text)
end
+function make.note(text)
+ return {
+ full_width = true,
+ get_formspec = function(self, avail_w)
+ -- Assuming label height 0.4:
+ -- Position at y=0 to eat 0.2 of the padding above, leave 0.05.
+ -- The returned used_height doesn't include padding.
+ return ("label[0,0;%s]"):format(core.colorize("#bbb", core.formspec_escape(text))), 0.2
+ end,
+ }
+end
+
+
--- Used for string and numeric style fields
---
--- @param converter Function to coerce values from strings.
diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua
index 73a72769b..d668fba50 100644
--- a/builtin/mainmenu/settings/dlg_settings.lua
+++ b/builtin/mainmenu/settings/dlg_settings.lua
@@ -110,7 +110,7 @@ local function load()
local change_keys = {
query_text = "Controls",
requires = {
- keyboard_mouse = true,
+ touch_controls = false,
},
get_formspec = function(self, avail_w)
local btn_w = math.min(avail_w, 3)
@@ -152,9 +152,24 @@ local function load()
table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys)
do
- local content = page_by_id.graphics_and_audio_shaders.content
+ local content = page_by_id.graphics_and_audio_effects.content
local idx = table.indexof(content, "enable_dynamic_shadows")
table.insert(content, idx, shadows_component)
+
+ idx = table.indexof(content, "enable_auto_exposure") + 1
+ local note = component_funcs.note(fgettext_ne("(The game will need to enable automatic exposure as well)"))
+ note.requires = get_setting_info("enable_auto_exposure").requires
+ table.insert(content, idx, note)
+
+ idx = table.indexof(content, "enable_bloom") + 1
+ note = component_funcs.note(fgettext_ne("(The game will need to enable bloom as well)"))
+ note.requires = get_setting_info("enable_bloom").requires
+ table.insert(content, idx, note)
+
+ idx = table.indexof(content, "enable_volumetric_lighting") + 1
+ note = component_funcs.note(fgettext_ne("(The game will need to enable volumetric lighting as well)"))
+ note.requires = get_setting_info("enable_volumetric_lighting").requires
+ table.insert(content, idx, note)
end
-- These must not be translated, as they need to show in the local
@@ -222,6 +237,12 @@ local function load()
zh_CN = "中文 (简体) [zh_CN]",
zh_TW = "正體中文 (繁體) [zh_TW]",
}
+
+ get_setting_info("touch_controls").option_labels = {
+ ["auto"] = fgettext_ne("Auto"),
+ ["true"] = fgettext_ne("Enabled"),
+ ["false"] = fgettext_ne("Disabled"),
+ }
end
@@ -321,11 +342,14 @@ local function check_requirements(name, requires)
local video_driver = core.get_active_driver()
local shaders_support = video_driver == "opengl" or video_driver == "opengl3" or video_driver == "ogles2"
+ local touch_controls = core.settings:get("touch_controls")
local special = {
android = PLATFORM == "Android",
desktop = PLATFORM ~= "Android",
- touchscreen_gui = core.settings:get_bool("enable_touch"),
- keyboard_mouse = not core.settings:get_bool("enable_touch"),
+ -- When touch_controls is "auto", we don't which input method will be used,
+ -- so we show settings for both.
+ touchscreen = touch_controls == "auto" or core.is_yes(touch_controls),
+ keyboard_mouse = touch_controls == "auto" or not core.is_yes(touch_controls),
shaders_support = shaders_support,
shaders = core.settings:get_bool("enable_shaders") and shaders_support,
opengl = video_driver == "opengl",
@@ -435,19 +459,6 @@ local function build_page_components(page)
end
---- Creates a scrollbaroptions for a scroll_container
---
--- @param visible_l the length of the scroll_container and scrollbar
--- @param total_l length of the scrollable area
--- @param scroll_factor as passed to scroll_container
-local function make_scrollbaroptions_for_scroll_container(visible_l, total_l, scroll_factor)
- assert(total_l >= visible_l)
- local max = total_l - visible_l
- local thumb_size = (visible_l / total_l) * max
- return ("scrollbaroptions[min=0;max=%f;thumbsize=%f]"):format(max / scroll_factor, thumb_size / scroll_factor)
-end
-
-
local formspec_show_hack = false
@@ -457,13 +468,13 @@ local function get_formspec(dialogdata)
local extra_h = 1 -- not included in tabsize.height
local tabsize = {
- width = core.settings:get_bool("enable_touch") and 16.5 or 15.5,
- height = core.settings:get_bool("enable_touch") and (10 - extra_h) or 12,
+ width = core.settings:get_bool("touch_gui") and 16.5 or 15.5,
+ height = core.settings:get_bool("touch_gui") and (10 - extra_h) or 12,
}
- local scrollbar_w = core.settings:get_bool("enable_touch") and 0.6 or 0.4
+ local scrollbar_w = core.settings:get_bool("touch_gui") and 0.6 or 0.4
- local left_pane_width = core.settings:get_bool("enable_touch") and 4.5 or 4.25
+ local left_pane_width = core.settings:get_bool("touch_gui") and 4.5 or 4.25
local left_pane_padding = 0.25
local search_width = left_pane_width + scrollbar_w - (0.75 * 2)
@@ -477,7 +488,7 @@ local function get_formspec(dialogdata)
local fs = {
"formspec_version[6]",
"size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]",
- core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "",
+ core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "",
"bgcolor[#0000]",
-- HACK: this is needed to allow resubmitting the same formspec
@@ -509,8 +520,8 @@ local function get_formspec(dialogdata)
"tooltip[search;", fgettext("Search"), "]",
"tooltip[search_clear;", fgettext("Clear"), "]",
"container_end[]",
- "scroll_container[0.25,1.25;", tostring(left_pane_width), ",",
- tostring(tabsize.height - 1.5), ";leftscroll;vertical;0.1]",
+ ("scroll_container[0.25,1.25;%f,%f;leftscroll;vertical;0.1;0]"):format(
+ left_pane_width, tabsize.height - 1.5),
"style_type[button;border=false;bgcolor=#3333]",
"style_type[button:hover;border=false;bgcolor=#6663]",
}
@@ -540,7 +551,6 @@ local function get_formspec(dialogdata)
fs[#fs + 1] = "scroll_container_end[]"
if y >= tabsize.height - 1.25 then
- fs[#fs + 1] = make_scrollbaroptions_for_scroll_container(tabsize.height - 1.5, y, 0.1)
fs[#fs + 1] = ("scrollbar[%f,1.25;%f,%f;vertical;leftscroll;%f]"):format(
left_pane_width + 0.25, scrollbar_w, tabsize.height - 1.5, dialogdata.leftscroll or 0)
end
@@ -552,7 +562,7 @@ local function get_formspec(dialogdata)
end
local right_pane_width = tabsize.width - left_pane_width - 0.375 - 2*scrollbar_w - 0.25
- fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1]"):format(
+ fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1;0.25]"):format(
tabsize.width - right_pane_width - scrollbar_w, right_pane_width, tabsize.height)
y = 0.25
@@ -608,7 +618,6 @@ local function get_formspec(dialogdata)
fs[#fs + 1] = "scroll_container_end[]"
if y >= tabsize.height then
- fs[#fs + 1] = make_scrollbaroptions_for_scroll_container(tabsize.height, y + 0.375, 0.1)
fs[#fs + 1] = ("scrollbar[%f,0;%f,%f;vertical;rightscroll;%f]"):format(
tabsize.width - scrollbar_w, scrollbar_w, tabsize.height, dialogdata.rightscroll or 0)
end
@@ -626,6 +635,18 @@ function write_settings_early()
end
end
+local function regenerate_page_list(dialogdata)
+ local suggested_page_id = update_filtered_pages(dialogdata.query)
+
+ dialogdata.components = nil
+
+ if not filtered_page_by_id[dialogdata.page_id] then
+ dialogdata.leftscroll = 0
+ dialogdata.rightscroll = 0
+
+ dialogdata.page_id = suggested_page_id
+ end
+end
local function buttonhandler(this, fields)
local dialogdata = this.data
@@ -650,27 +671,7 @@ local function buttonhandler(this, fields)
local value = core.is_yes(fields.show_advanced)
core.settings:set_bool("show_advanced", value)
write_settings_early()
- end
-
- -- enable_touch is a checkbox in a setting component. We handle this
- -- setting differently so we can hide/show pages using the next if-statement
- if fields.enable_touch ~= nil then
- local value = core.is_yes(fields.enable_touch)
- core.settings:set_bool("enable_touch", value)
- write_settings_early()
- end
-
- if fields.show_advanced ~= nil or fields.enable_touch ~= nil then
- local suggested_page_id = update_filtered_pages(dialogdata.query)
-
- dialogdata.components = nil
-
- if not filtered_page_by_id[dialogdata.page_id] then
- dialogdata.leftscroll = 0
- dialogdata.rightscroll = 0
-
- dialogdata.page_id = suggested_page_id
- end
+ regenerate_page_list(dialogdata)
return true
end
@@ -703,20 +704,26 @@ local function buttonhandler(this, fields)
end
end
- for i, comp in ipairs(dialogdata.components) do
- if comp.on_submit and comp:on_submit(fields, this) then
- write_settings_early()
-
+ local function after_setting_change(comp)
+ write_settings_early()
+ if comp.setting.name == "touch_controls" then
+ -- Changing the "touch_controls" setting may result in a different
+ -- page list.
+ regenerate_page_list(dialogdata)
+ else
-- Clear components so they regenerate
dialogdata.components = nil
+ end
+ end
+
+ for i, comp in ipairs(dialogdata.components) do
+ if comp.on_submit and comp:on_submit(fields, this) then
+ after_setting_change(comp)
return true
end
if comp.setting and fields["reset_" .. i] then
core.settings:remove(comp.setting.name)
- write_settings_early()
-
- -- Clear components so they regenerate
- dialogdata.components = nil
+ after_setting_change(comp)
return true
end
end
diff --git a/builtin/mainmenu/tab_about.lua b/builtin/mainmenu/tab_about.lua
index d798b5b09..ab3edbddc 100644
--- a/builtin/mainmenu/tab_about.lua
+++ b/builtin/mainmenu/tab_about.lua
@@ -15,111 +15,23 @@
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
--- https://github.com/orgs/minetest/teams/engine/members
-
-local core_developers = {
- "Perttu Ahola (celeron55) [Project founder]",
- "sfan5 ",
- "ShadowNinja ",
- "Nathanaëlle Courant (Nore/Ekdohibs) ",
- "Loic Blot (nerzhul/nrz) ",
- "Andrew Ward (rubenwardy) ",
- "Krock/SmallJoker ",
- "Lars Hofhansl ",
- "v-rob ",
- "Desour/DS",
- "srifqi",
- "Gregor Parzefall (grorp)",
- "Lars Müller (luatic)",
-}
-
--- currently only https://github.com/orgs/minetest/teams/triagers/members
-
-local core_team = {
- "Zughy [Issue triager]",
- "wsor [Issue triager]",
- "Hugo Locurcio (Calinou) [Issue triager]",
-}
-
--- For updating active/previous contributors, see the script in ./util/gather_git_credits.py
-
-local active_contributors = {
- "cx384",
- "numzero",
- "AFCMS",
- "sfence",
- "Wuzzy",
- "ROllerozxa",
- "JosiahWI",
- "OgelGames",
- "David Heidelberg",
- "1F616EMO",
- "HybridDog",
- "Bradley Pierce (Thresher)",
- "savilli",
- "Stvk imension",
- "y5nw",
- "chmodsayshello",
- "jordan4ibanez",
- "superfloh247",
-}
-
-local previous_core_developers = {
- "BlockMen",
- "Maciej Kasatkin (RealBadAngel) [RIP]",
- "Lisa Milne (darkrose) ",
- "proller",
- "Ilya Zhuravlev (xyz) ",
- "PilzAdam ",
- "est31 ",
- "kahrl ",
- "Ryan Kwolek (kwolekr) ",
- "sapier",
- "Zeno",
- "Auke Kok (sofar) ",
- "Aaron Suen ",
- "paramat",
- "Pierre-Yves Rollo ",
- "hecks",
- "Jude Melton-Houghton (TurkeyMcMac) [RIP]",
- "Hugues Ross ",
- "Dmitry Kostenko (x2048) ",
-}
-
-local previous_contributors = {
- "Nils Dagsson Moskopp (erlehmann) [Minetest logo]",
- "red-001 ",
- "Giuseppe Bilotta",
- "HybridDog",
- "ClobberXD",
- "Dániel Juhász (juhdanad) ",
- "MirceaKitsune ",
- "Jean-Patrick Guerrero (kilbith)",
- "MoNTE48",
- "Constantin Wenger (SpeedProg)",
- "Ciaran Gultnieks (CiaranG)",
- "Paul Ouellette (pauloue)",
- "stujones11",
- "Rogier ",
- "Gregory Currie (gregorycu)",
- "JacobF",
- "Jeija ",
-}
local function prepare_credits(dest, source)
local string = table.concat(source, "\n") .. "\n"
- local hypertext_escapes = {
- ["\\"] = "\\\\",
- ["<"] = "\\<",
- [">"] = "\\>",
- }
- string = string:gsub("[\\<>]", hypertext_escapes)
+ string = core.hypertext_escape(string)
string = string:gsub("%[.-%]", "%1")
table.insert(dest, string)
end
+local function get_credits()
+ local f = assert(io.open(core.get_mainmenu_path() .. "/credits.json"))
+ local json = core.parse_json(f:read("*all"))
+ f:close()
+ return json
+end
+
return {
name = "about",
caption = fgettext("About"),
@@ -133,30 +45,32 @@ return {
"",
}
+ local credits = get_credits()
+
table.insert_all(hypertext, {
"", fgettext_ne("Core Developers"), "\n",
})
- prepare_credits(hypertext, core_developers)
+ prepare_credits(hypertext, credits.core_developers)
table.insert_all(hypertext, {
"\n",
"", fgettext_ne("Core Team"), "\n",
})
- prepare_credits(hypertext, core_team)
+ prepare_credits(hypertext, credits.core_team)
table.insert_all(hypertext, {
"\n",
"", fgettext_ne("Active Contributors"), "\n",
})
- prepare_credits(hypertext, active_contributors)
+ prepare_credits(hypertext, credits.contributors)
table.insert_all(hypertext, {
"\n",
"", fgettext_ne("Previous Core Developers"), "\n",
})
- prepare_credits(hypertext, previous_core_developers)
+ prepare_credits(hypertext, credits.previous_core_developers)
table.insert_all(hypertext, {
"\n",
"", fgettext_ne("Previous Contributors"), "\n",
})
- prepare_credits(hypertext, previous_contributors)
+ prepare_credits(hypertext, credits.previous_contributors)
hypertext = table.concat(hypertext):sub(1, -2)
diff --git a/builtin/mainmenu/tab_content.lua b/builtin/mainmenu/tab_content.lua
index b38f12884..9cfb96d54 100644
--- a/builtin/mainmenu/tab_content.lua
+++ b/builtin/mainmenu/tab_content.lua
@@ -118,7 +118,7 @@ local function get_formspec(tabview, name, tabdata)
local title_and_name
if selected_pkg.type == "game" then
- title_and_name = selected_pkg.name
+ title_and_name = selected_pkg.title or selected_pkg.name
else
title_and_name = (selected_pkg.title or selected_pkg.name) .. "\n" ..
core.colorize("#BFBFBF", selected_pkg.name)
diff --git a/builtin/mainmenu/tab_local.lua b/builtin/mainmenu/tab_local.lua
index 1ed08d825..8d807cc79 100644
--- a/builtin/mainmenu/tab_local.lua
+++ b/builtin/mainmenu/tab_local.lua
@@ -92,10 +92,16 @@ function singleplayer_refresh_gamebar()
end
end
+ local TOUCH_GUI = core.settings:get_bool("touch_gui")
+
+ local gamebar_pos_y = MAIN_TAB_H
+ + TABHEADER_H -- tabheader included in formspec size
+ + (TOUCH_GUI and GAMEBAR_OFFSET_TOUCH or GAMEBAR_OFFSET_DESKTOP)
+
local btnbar = buttonbar_create(
"game_button_bar",
- core.settings:get_bool("enable_touch") and {x = 0, y = 7.25} or {x = 0, y = 7.475},
- {x = 15.5, y = 1.25},
+ {x = 0, y = gamebar_pos_y},
+ {x = MAIN_TAB_W, y = GAMEBAR_H},
"#000000",
game_buttonbar_button_handler)
diff --git a/builtin/profiler/instrumentation.lua b/builtin/profiler/instrumentation.lua
index 0ffd9e6a9..c4feda7b4 100644
--- a/builtin/profiler/instrumentation.lua
+++ b/builtin/profiler/instrumentation.lua
@@ -217,8 +217,9 @@ local function init()
-- Wrap register_lbm() to automatically instrument lbms.
local orig_register_lbm = core.register_lbm
core.register_lbm = function(spec)
- spec.action = instrument {
- func = spec.action,
+ local k = spec.bulk_action ~= nil and "bulk_action" or "action"
+ spec[k] = instrument {
+ func = spec[k],
class = "LBM",
label = spec.label or spec.name,
}
diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt
index 8828fc1df..f7225ef62 100644
--- a/builtin/settingtypes.txt
+++ b/builtin/settingtypes.txt
@@ -61,7 +61,7 @@
#
# # This is a comment
# #
-# # Requires: shaders, enable_dynamic_shadows, !touchscreen_gui
+# # Requires: shaders, enable_dynamic_shadows, !enable_waving_leaves
# name (Readable name) type type_args
#
# A requirement can be the name of a boolean setting or an engine-defined value.
@@ -72,7 +72,7 @@
# * shaders_support (a video driver that supports shaders, may not be enabled)
# * shaders (both enable_shaders and shaders_support)
# * desktop / android
-# * touchscreen_gui / keyboard_mouse
+# * touchscreen / keyboard_mouse
# * opengl / gles
# * You can negate any requirement by prepending with !
#
@@ -114,7 +114,7 @@ always_fly_fast (Always fly fast) bool true
# the place button.
#
# Requires: keyboard_mouse
-repeat_place_time (Place repetition interval) float 0.25 0.16 2.0
+repeat_place_time (Place repetition interval) float 0.25 0.15 2.0
# The minimum time in seconds it takes between digging nodes when holding
# the dig button.
@@ -152,42 +152,42 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false
[*Touchscreen]
-# Enables touchscreen mode, allowing you to play the game with a touchscreen.
-#
-# Requires: !android
-enable_touch (Enable touchscreen) bool true
+# Enables the touchscreen controls, allowing you to play the game with a touchscreen.
+# "auto" means that the touchscreen controls will be enabled and disabled
+# automatically depending on the last used input method.
+touch_controls (Touchscreen controls) enum auto auto,true,false
# Touchscreen sensitivity multiplier.
#
-# Requires: touchscreen_gui
+# Requires: touchscreen
touchscreen_sensitivity (Touchscreen sensitivity) float 0.2 0.001 10.0
# The length in pixels after which a touch interaction is considered movement.
#
-# Requires: touchscreen_gui
+# Requires: touchscreen
touchscreen_threshold (Movement threshold) int 20 0 100
# The delay in milliseconds after which a touch interaction is considered a long tap.
#
-# Requires: touchscreen_gui
+# Requires: touchscreen
touch_long_tap_delay (Threshold for long taps) int 400 100 1000
# Use crosshair to select object instead of whole screen.
# If enabled, a crosshair will be shown and will be used for selecting object.
#
-# Requires: touchscreen_gui
+# Requires: touchscreen
touch_use_crosshair (Use crosshair for touch screen) bool false
# Fixes the position of virtual joystick.
# If disabled, virtual joystick will center to first-touch's position.
#
-# Requires: touchscreen_gui
+# Requires: touchscreen
fixed_virtual_joystick (Fixed virtual joystick) bool false
# Use virtual joystick to trigger "Aux1" button.
# If enabled, virtual joystick will also tap "Aux1" button when out of main circle.
#
-# Requires: touchscreen_gui
+# Requires: touchscreen
virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false
# The gesture for punching players/entities.
@@ -200,7 +200,7 @@ virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool fals
# Known from the classic Minetest mobile controls.
# Combat is more or less impossible.
#
-# Requires: touchscreen_gui
+# Requires: touchscreen
touch_punch_gesture (Punch gesture) enum short_tap short_tap,long_tap
@@ -265,31 +265,6 @@ viewing_range (Viewing range) int 190 20 4000
# Higher values result in a less detailed image.
undersampling (Undersampling) int 1 1 8
-[**Graphics Effects]
-
-# Allows liquids to be translucent.
-translucent_liquids (Translucent liquids) bool true
-
-# Leaves style:
-# - Fancy: all faces visible
-# - Simple: only outer faces, if defined special_tiles are used
-# - Opaque: disable transparency
-leaves_style (Leaves style) enum fancy fancy,simple,opaque
-
-# Connects glass if supported by node.
-connected_glass (Connect glass) bool false
-
-# Enable smooth lighting with simple ambient occlusion.
-# Disable for speed or for different looks.
-smooth_lighting (Smooth lighting) bool true
-
-# Enables tradeoffs that reduce CPU load or increase rendering performance
-# at the expense of minor visual glitches that do not impact game playability.
-performance_tradeoffs (Tradeoffs for performance) bool false
-
-# Adds particles when digging a node.
-enable_particles (Digging particles) bool true
-
[**3D]
# 3D support.
@@ -406,6 +381,11 @@ enable_clouds (Clouds) bool true
# Requires: enable_clouds
enable_3d_clouds (3D clouds) bool true
+# Use smooth cloud shading.
+#
+# Requires: enable_3d_clouds, enable_clouds
+soft_clouds (Soft clouds) bool false
+
[**Filtering and Antialiasing]
# Use mipmaps when scaling textures. May slightly increase performance,
@@ -464,13 +444,29 @@ enable_raytraced_culling (Enable Raytraced Culling) bool true
-[*Shaders]
+[*Effects]
-# Shaders allow advanced visual effects and may increase performance on some video
-# cards.
-#
-# Requires: shaders_support
-enable_shaders (Shaders) bool true
+# Allows liquids to be translucent.
+translucent_liquids (Translucent liquids) bool true
+
+# Leaves style:
+# - Fancy: all faces visible
+# - Simple: only outer faces
+# - Opaque: disable transparency
+leaves_style (Leaves style) enum fancy fancy,simple,opaque
+
+# Connects glass if supported by node.
+connected_glass (Connect glass) bool false
+
+# Enable smooth lighting with simple ambient occlusion.
+smooth_lighting (Smooth lighting) bool true
+
+# Enables tradeoffs that reduce CPU load or increase rendering performance
+# at the expense of minor visual glitches that do not impact game playability.
+performance_tradeoffs (Tradeoffs for performance) bool false
+
+# Adds particles when digging a node.
+enable_particles (Digging particles) bool true
[**Waving Nodes]
@@ -623,47 +619,34 @@ exposure_compensation (Exposure compensation) float 0.0 -1.0 1.0
# Requires: shaders, enable_post_processing
debanding (Enable Debanding) bool true
-[**Bloom]
-
# Set to true to enable bloom effect.
# Bright colors will bleed over the neighboring objects.
#
# Requires: shaders, enable_post_processing
enable_bloom (Enable Bloom) bool false
-# Set to true to render debugging breakdown of the bloom effect.
-# In debug mode, the screen is split into 4 quadrants:
-# top-left - processed base image, top-right - final image
-# bottom-left - raw base image, bottom-right - bloom texture.
-#
-# Requires: shaders, enable_post_processing, enable_bloom
-enable_bloom_debug (Enable Bloom Debug) bool false
-
-# Defines how much bloom is applied to the rendered image
-# Smaller values make bloom more subtle
-# Range: from 0.01 to 1.0, default: 0.05
-#
-# Requires: shaders, enable_post_processing, enable_bloom
-bloom_intensity (Bloom Intensity) float 0.05 0.01 1.0
-
-# Defines the magnitude of bloom overexposure.
-# Range: from 0.1 to 10.0, default: 1.0
-#
-# Requires: shaders, enable_post_processing, enable_bloom
-bloom_strength_factor (Bloom Strength Factor) float 1.0 0.1 10.0
-
-# Logical value that controls how far the bloom effect spreads
-# from the bright objects.
-# Range: from 0.1 to 8, default: 1
-#
-# Requires: shaders, enable_post_processing, enable_bloom
-bloom_radius (Bloom Radius) float 1 0.1 8
-
# Set to true to enable volumetric lighting effect (a.k.a. "Godrays").
#
# Requires: shaders, enable_post_processing, enable_bloom
enable_volumetric_lighting (Volumetric lighting) bool false
+[**Other Effects]
+
+# Simulate translucency when looking at foliage in the sunlight.
+#
+# Requires: shaders, enable_dynamic_shadows
+enable_translucent_foliage (Translucent foliage) bool false
+
+# Apply specular shading to nodes.
+#
+# Requires: shaders, enable_dynamic_shadows
+enable_node_specular (Node specular) bool false
+
+# When enabled, liquid reflections are simulated.
+#
+# Requires: shaders, enable_waving_water, enable_dynamic_shadows
+enable_water_reflections (Liquid reflections) bool false
+
[*Audio]
# Volume of all sounds.
@@ -687,6 +670,10 @@ language (Language) enum ,be,bg,ca,cs,da,de,el,en,eo,es,et,eu,fi,fr,gd,gl,hu,i
[**GUI]
+# When enabled, the GUI is optimized to be more usable on touchscreens.
+# Whether this is enabled by default depends on your hardware form-factor.
+touch_gui (Optimize GUI for touchscreens) bool false
+
# Scale GUI by a user specified value.
# Use a nearest-neighbor-anti-alias filter to scale the GUI.
# This will smooth over some of the rough edges, and blend
@@ -735,6 +722,12 @@ hud_scaling (HUD scaling) float 1.0 0.5 20
# Mods may still set a background.
show_nametag_backgrounds (Show name tag backgrounds by default) bool true
+# Whether to show the client debug info (has the same effect as hitting F5).
+show_debug (Show debug info) bool false
+
+# Radius to use when the block bounds HUD feature is set to near blocks.
+show_block_bounds_radius_near (Block bounds HUD radius) int 4 0 1000
+
[**Chat]
# Maximum number of recent chat messages to show
@@ -891,8 +884,13 @@ default_privs (Default privileges) string interact, shout
# Privileges that players with basic_privs can grant
basic_privs (Basic privileges) string interact, shout
-# If enabled, disable cheat prevention in multiplayer.
-disable_anticheat (Disable anticheat) bool false
+# Server anticheat configuration.
+# Flags are positive. Uncheck the flag to disable corresponding anticheat module.
+anticheat_flags (Anticheat flags) flags digging,interaction,movement digging,interaction,movement
+
+# Tolerance of movement cheat detector.
+# Increase the value if players experience stuttery movement.
+anticheat_movement_tolerance (Anticheat movement tolerance) float 1.0 1.0
# If enabled, actions are recorded for rollback.
# This option is only read when server starts.
@@ -1020,7 +1018,7 @@ mapgen_limit (Map generation limit) int 31007 0 31007
# Global map generation attributes.
# In Mapgen v6 the 'decorations' flag controls all decorations except trees
# and jungle grass, in all other mapgens this flag controls all decorations.
-mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores,nocaves,nodungeons,nolight,nodecorations,nobiomes,noores
+mg_flags (Mapgen flags) flags caves,dungeons,light,decorations,biomes,ores caves,dungeons,light,decorations,biomes,ores
[*Biome API]
@@ -1039,7 +1037,7 @@ mg_biome_np_humidity_blend (Humidity blend noise) noise_params_2d 0, 1.5, (8, 8,
[*Mapgen V5]
# Map generation attributes specific to Mapgen v5.
-mgv5_spflags (Mapgen V5 specific flags) flags caverns caverns,nocaverns
+mgv5_spflags (Mapgen V5 specific flags) flags caverns caverns
# Controls width of tunnels, a smaller value creates wider tunnels.
# Value >= 10.0 completely disables generation of tunnels and avoids the
@@ -1113,7 +1111,7 @@ mgv5_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2
# When the 'snowbiomes' flag is enabled jungles are automatically enabled and
# the 'jungles' flag is ignored.
# The 'temples' flag disables generation of desert temples. Normal dungeons will appear instead.
-mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees,temples jungles,biomeblend,mudflow,snowbiomes,flat,trees,temples,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees,notemples
+mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees,temples jungles,biomeblend,mudflow,snowbiomes,flat,trees,temples
# Deserts occur when np_biome exceeds this value.
# When the 'snowbiomes' flag is enabled, this is ignored.
@@ -1169,7 +1167,7 @@ mgv6_np_apple_trees (Apple trees noise) noise_params_2d 0, 1, (100, 100, 100), 3
# 'ridges': Rivers.
# 'floatlands': Floating land masses in the atmosphere.
# 'caverns': Giant caves deep underground.
-mgv7_spflags (Mapgen V7 specific flags) flags mountains,ridges,nofloatlands,caverns mountains,ridges,floatlands,caverns,nomountains,noridges,nofloatlands,nocaverns
+mgv7_spflags (Mapgen V7 specific flags) flags mountains,ridges,nofloatlands,caverns mountains,ridges,floatlands,caverns
# Y of mountain density gradient zero level. Used to shift mountains vertically.
mgv7_mount_zero_level (Mountain zero level) int 0 -31000 31000
@@ -1303,7 +1301,7 @@ mgv7_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2
[*Mapgen Carpathian]
# Map generation attributes specific to Mapgen Carpathian.
-mgcarpathian_spflags (Mapgen Carpathian specific flags) flags caverns,norivers caverns,rivers,nocaverns,norivers
+mgcarpathian_spflags (Mapgen Carpathian specific flags) flags caverns,norivers caverns,rivers
# Defines the base ground level.
mgcarpathian_base_level (Base ground level) float 12.0
@@ -1412,7 +1410,7 @@ mgcarpathian_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 50
# Map generation attributes specific to Mapgen Flat.
# Occasional lakes and hills can be added to the flat world.
-mgflat_spflags (Mapgen Flat specific flags) flags nolakes,nohills,nocaverns lakes,hills,caverns,nolakes,nohills,nocaverns
+mgflat_spflags (Mapgen Flat specific flags) flags nolakes,nohills,nocaverns lakes,hills,caverns
# Y of flat ground.
mgflat_ground_level (Ground level) int 8 -31000 31000
@@ -1496,7 +1494,7 @@ mgflat_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0,
# Map generation attributes specific to Mapgen Fractal.
# 'terrain' enables the generation of non-fractal terrain:
# ocean, islands and underground.
-mgfractal_spflags (Mapgen Fractal specific flags) flags terrain terrain,noterrain
+mgfractal_spflags (Mapgen Fractal specific flags) flags terrain terrain
# Controls width of tunnels, a smaller value creates wider tunnels.
# Value >= 10.0 completely disables generation of tunnels and avoids the
@@ -1630,7 +1628,7 @@ mgfractal_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500),
# 'vary_river_depth': If enabled, low humidity and high heat causes rivers
# to become shallower and occasionally dry.
# 'altitude_dry': Reduces humidity with altitude.
-mgvalleys_spflags (Mapgen Valleys specific flags) flags altitude_chill,humid_rivers,vary_river_depth,altitude_dry altitude_chill,humid_rivers,vary_river_depth,altitude_dry,noaltitude_chill,nohumid_rivers,novary_river_depth,noaltitude_dry
+mgvalleys_spflags (Mapgen Valleys specific flags) flags altitude_chill,humid_rivers,vary_river_depth,altitude_dry altitude_chill,humid_rivers,vary_river_depth,altitude_dry
# The vertical distance over which heat drops by 20 if 'altitude_chill' is
# enabled. Also, the vertical distance over which humidity drops by 10 if
@@ -1837,6 +1835,11 @@ ignore_world_load_errors (Ignore world errors) bool false
[**Graphics]
+# Shaders are a fundamental part of rendering and enable advanced visual effects.
+#
+# Requires: shaders_support
+enable_shaders (Shaders) bool true
+
# Path to shader directory. If no path is defined, default location will be used.
#
# Requires: shaders
@@ -1845,11 +1848,11 @@ shader_path (Shader path) path
# The rendering back-end.
# Note: A restart is required after changing this!
# OpenGL is the default for desktop, and OGLES2 for Android.
-# Shaders are supported by everything but OGLES1.
-video_driver (Video driver) enum ,opengl,opengl3,ogles1,ogles2
+video_driver (Video driver) enum ,opengl,opengl3,ogles2
-# Distance in nodes at which transparency depth sorting is enabled
-# Use this to limit the performance impact of transparency depth sorting
+# Distance in nodes at which transparency depth sorting is enabled.
+# Use this to limit the performance impact of transparency depth sorting.
+# Set to 0 to disable it entirely.
transparency_sorting_distance (Transparency Sorting Distance) int 16 0 128
# Radius of cloud area stated in number of 64 node cloud squares.
@@ -1860,6 +1863,7 @@ cloud_radius (Cloud radius) int 12 1 62
desynchronize_mapblock_texture_animation (Desynchronize block animation) bool false
# Enables caching of facedir rotated meshes.
+# This is only effective with shaders disabled.
enable_mesh_cache (Mesh cache) bool false
# Delay between mesh updates on the client in ms. Increasing this will slow
@@ -1911,6 +1915,14 @@ client_mesh_chunk (Client Mesh Chunksize) int 1 1 16
# Enables debug and error-checking in the OpenGL driver.
opengl_debug (OpenGL debug) bool false
+# Set to true to render debugging breakdown of the bloom effect.
+# In debug mode, the screen is split into 4 quadrants:
+# top-left - processed base image, top-right - final image
+# bottom-left - raw base image, bottom-right - bloom texture.
+#
+# Requires: shaders, enable_post_processing, enable_bloom
+enable_bloom_debug (Enable Bloom Debug) bool false
+
[**Sound]
# Comma-separated list of AL and ALC extensions that should not be used.
# Useful for testing. See al_extensions.[h,cpp] for details.
@@ -2009,9 +2021,6 @@ client_unload_unused_data_timeout (Mapblock unload timeout) float 600.0 0.0
# Set to -1 for unlimited amount.
client_mapblock_limit (Mapblock limit) int 7500 -1 2147483647
-# Whether to show the client debug info (has the same effect as hitting F5).
-show_debug (Show debug info) bool false
-
# Maximum number of blocks that are simultaneously sent per client.
# The maximum total count is calculated dynamically:
# max_total = ceil((#clients + max_users) * per_client / 4)
@@ -2021,9 +2030,8 @@ max_simultaneous_block_sends_per_client (Maximum simultaneous block sends per cl
# This determines how long they are slowed down after placing or removing a node.
full_block_send_enable_min_time_from_building (Delay in sending blocks after building) float 2.0 0.0
-# Maximum number of packets sent per send step, if you have a slow connection
-# try reducing it, but don't reduce it to a number below double of targeted
-# client number.
+# Maximum number of packets sent per send step in the low-level networking code.
+# You generally don't need to change this, however busy servers may benefit from a higher number.
max_packets_per_iteration (Max. packets per iteration) int 1024 1 65535
# Compression level to use when sending mapblocks to the client.
diff --git a/client/shaders/Irrlicht b/client/shaders/Irrlicht
deleted file mode 120000
index 9349d3073..000000000
--- a/client/shaders/Irrlicht
+++ /dev/null
@@ -1 +0,0 @@
-../../irr/media/Shaders
\ No newline at end of file
diff --git a/irr/media/Shaders/OneTextureBlend.fsh b/client/shaders/Irrlicht/OneTextureBlend.fsh
similarity index 100%
rename from irr/media/Shaders/OneTextureBlend.fsh
rename to client/shaders/Irrlicht/OneTextureBlend.fsh
diff --git a/irr/media/Shaders/Renderer2D.fsh b/client/shaders/Irrlicht/Renderer2D.fsh
similarity index 100%
rename from irr/media/Shaders/Renderer2D.fsh
rename to client/shaders/Irrlicht/Renderer2D.fsh
diff --git a/irr/media/Shaders/Renderer2D.vsh b/client/shaders/Irrlicht/Renderer2D.vsh
similarity index 100%
rename from irr/media/Shaders/Renderer2D.vsh
rename to client/shaders/Irrlicht/Renderer2D.vsh
diff --git a/irr/media/Shaders/Renderer2D_noTex.fsh b/client/shaders/Irrlicht/Renderer2D_noTex.fsh
similarity index 100%
rename from irr/media/Shaders/Renderer2D_noTex.fsh
rename to client/shaders/Irrlicht/Renderer2D_noTex.fsh
diff --git a/irr/media/Shaders/Solid.fsh b/client/shaders/Irrlicht/Solid.fsh
similarity index 100%
rename from irr/media/Shaders/Solid.fsh
rename to client/shaders/Irrlicht/Solid.fsh
diff --git a/irr/media/Shaders/Solid.vsh b/client/shaders/Irrlicht/Solid.vsh
similarity index 96%
rename from irr/media/Shaders/Solid.vsh
rename to client/shaders/Irrlicht/Solid.vsh
index 7379e5bb4..fd7467f5c 100644
--- a/irr/media/Shaders/Solid.vsh
+++ b/client/shaders/Irrlicht/Solid.vsh
@@ -11,7 +11,6 @@ attribute vec2 inTexCoord0;
uniform mat4 uWVPMatrix;
uniform mat4 uWVMatrix;
-uniform mat4 uNMatrix;
uniform mat4 uTMatrix0;
uniform float uThickness;
diff --git a/irr/media/Shaders/TransparentAlphaChannel.fsh b/client/shaders/Irrlicht/TransparentAlphaChannel.fsh
similarity index 100%
rename from irr/media/Shaders/TransparentAlphaChannel.fsh
rename to client/shaders/Irrlicht/TransparentAlphaChannel.fsh
diff --git a/irr/media/Shaders/TransparentAlphaChannelRef.fsh b/client/shaders/Irrlicht/TransparentAlphaChannelRef.fsh
similarity index 100%
rename from irr/media/Shaders/TransparentAlphaChannelRef.fsh
rename to client/shaders/Irrlicht/TransparentAlphaChannelRef.fsh
diff --git a/irr/media/Shaders/TransparentVertexAlpha.fsh b/client/shaders/Irrlicht/TransparentVertexAlpha.fsh
similarity index 100%
rename from irr/media/Shaders/TransparentVertexAlpha.fsh
rename to client/shaders/Irrlicht/TransparentVertexAlpha.fsh
diff --git a/client/shaders/cloud_shader/opengl_vertex.glsl b/client/shaders/cloud_shader/opengl_vertex.glsl
index 3f2e7d9b3..92f5de64b 100644
--- a/client/shaders/cloud_shader/opengl_vertex.glsl
+++ b/client/shaders/cloud_shader/opengl_vertex.glsl
@@ -1,4 +1,4 @@
-uniform lowp vec4 emissiveColor;
+uniform lowp vec4 materialColor;
varying lowp vec4 varColor;
@@ -8,13 +8,9 @@ void main(void)
{
gl_Position = mWorldViewProj * inVertexPosition;
-#ifdef GL_ES
- vec4 color = inVertexColor.bgra;
-#else
vec4 color = inVertexColor;
-#endif
- color *= emissiveColor;
+ color *= materialColor;
varColor = color;
eyeVec = -(mWorldView * inVertexPosition).xyz;
diff --git a/client/shaders/default_shader/opengl_vertex.glsl b/client/shaders/default_shader/opengl_vertex.glsl
index a908ac953..d95a3c2d3 100644
--- a/client/shaders/default_shader/opengl_vertex.glsl
+++ b/client/shaders/default_shader/opengl_vertex.glsl
@@ -3,9 +3,5 @@ varying lowp vec4 varColor;
void main(void)
{
gl_Position = mWorldViewProj * inVertexPosition;
-#ifdef GL_ES
- varColor = inVertexColor.bgra;
-#else
varColor = inVertexColor;
-#endif
}
diff --git a/client/shaders/fxaa/opengl_fragment.glsl b/client/shaders/fxaa/opengl_fragment.glsl
index 130e689ea..f70064b6d 100644
--- a/client/shaders/fxaa/opengl_fragment.glsl
+++ b/client/shaders/fxaa/opengl_fragment.glsl
@@ -58,11 +58,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define FXAA_SPAN_MAX 8.0
#endif
-//optimized version for mobile, where dependent
+//optimized version for mobile, where dependent
//texture reads can be a bottleneck
vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 inverseVP,
- vec2 v_rgbNW, vec2 v_rgbNE,
- vec2 v_rgbSW, vec2 v_rgbSE,
+ vec2 v_rgbNW, vec2 v_rgbNE,
+ vec2 v_rgbSW, vec2 v_rgbSE,
vec2 v_rgbM) {
vec4 color;
vec3 rgbNW = texture2D(tex, v_rgbNW).xyz;
@@ -111,6 +111,6 @@ void main(void)
{
vec2 uv = varTexCoord.st;
- gl_FragColor = fxaa(rendered, uv, texelSize0,
+ gl_FragColor = fxaa(rendered, uv, texelSize0,
sampleNW, sampleNE, sampleSW, sampleSE, uv);
}
diff --git a/client/shaders/fxaa/opengl_vertex.glsl b/client/shaders/fxaa/opengl_vertex.glsl
index 26913c28e..68bb36d1c 100644
--- a/client/shaders/fxaa/opengl_vertex.glsl
+++ b/client/shaders/fxaa/opengl_vertex.glsl
@@ -12,7 +12,7 @@ varying vec2 sampleSW;
varying vec2 sampleSE;
/*
-Based on
+Based on
https://github.com/mattdesl/glsl-fxaa/
Portions Copyright (c) 2011 by Armin Ronacher.
*/
diff --git a/client/shaders/minimap_shader/opengl_vertex.glsl b/client/shaders/minimap_shader/opengl_vertex.glsl
index b23d27181..1a9491805 100644
--- a/client/shaders/minimap_shader/opengl_vertex.glsl
+++ b/client/shaders/minimap_shader/opengl_vertex.glsl
@@ -7,9 +7,5 @@ void main(void)
{
varTexCoord = inTexCoord0.st;
gl_Position = mWorldViewProj * inVertexPosition;
-#ifdef GL_ES
- varColor = inVertexColor.bgra;
-#else
varColor = inVertexColor;
-#endif
}
diff --git a/client/shaders/nodes_shader/opengl_fragment.glsl b/client/shaders/nodes_shader/opengl_fragment.glsl
index 46977b147..c560a8505 100644
--- a/client/shaders/nodes_shader/opengl_fragment.glsl
+++ b/client/shaders/nodes_shader/opengl_fragment.glsl
@@ -1,3 +1,7 @@
+#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_OPAQUE || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_BASIC || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT)
+#define MATERIAL_WAVING_LIQUID 1
+#endif
+
uniform sampler2D baseTexture;
uniform vec3 dayLight;
@@ -7,6 +11,7 @@ uniform float fogShadingParameter;
// The cameraOffset is the current center of the visible world.
uniform highp vec3 cameraOffset;
+uniform vec3 cameraPosition;
uniform float animationTimer;
#ifdef ENABLE_DYNAMIC_SHADOWS
// shadow texture
@@ -20,6 +25,7 @@ uniform float animationTimer;
uniform vec4 CameraPos;
uniform float xyPerspectiveBias0;
uniform float xyPerspectiveBias1;
+ uniform vec3 shadow_tint;
varying float adj_shadow_strength;
varying float cosLight;
@@ -47,6 +53,49 @@ varying highp vec3 eyeVec;
varying float nightRatio;
#ifdef ENABLE_DYNAMIC_SHADOWS
+#if (defined(MATERIAL_WAVING_LIQUID) && defined(ENABLE_WATER_REFLECTIONS) && ENABLE_WAVING_WATER)
+vec4 perm(vec4 x)
+{
+ return mod(((x * 34.0) + 1.0) * x, 289.0);
+}
+
+// Corresponding gradient of snoise
+vec3 gnoise(vec3 p){
+ vec3 a = floor(p);
+ vec3 d = p - a;
+ vec3 dd = 6.0 * d * (1.0 - d);
+ d = d * d * (3.0 - 2.0 * d);
+
+ vec4 b = a.xxyy + vec4(0.0, 1.0, 0.0, 1.0);
+ vec4 k1 = perm(b.xyxy);
+ vec4 k2 = perm(k1.xyxy + b.zzww);
+
+ vec4 c = k2 + a.zzzz;
+ vec4 k3 = perm(c);
+ vec4 k4 = perm(c + 1.0);
+
+ vec4 o1 = fract(k3 * (1.0 / 41.0));
+ vec4 o2 = fract(k4 * (1.0 / 41.0));
+
+ vec4 o3 = o2 * d.z + o1 * (1.0 - d.z);
+ vec2 o4 = o3.yw * d.x + o3.xz * (1.0 - d.x);
+
+ vec4 dz1 = (o2 - o1) * dd.z;
+ vec2 dz2 = dz1.yw * d.x + dz1.xz * (1.0 - d.x);
+
+ vec2 dx = (o3.yw - o3.xz) * dd.x;
+
+ return vec3(
+ dx.y * d.y + dx.x * (1. - d.y),
+ (o4.y - o4.x) * dd.y,
+ dz2.y * d.y + dz2.x * (1. - d.y)
+ );
+}
+
+vec2 wave_noise(vec3 p, float off) {
+ return (gnoise(p + vec3(0.0, 0.0, off)) * 0.4 + gnoise(2.0 * p + vec3(0.0, off, off)) * 0.2 + gnoise(3.0 * p + vec3(0.0, off, off)) * 0.225 + gnoise(4.0 * p + vec3(-off, off, 0.0)) * 0.2).xz;
+}
+#endif
// assuming near is always 1.0
float getLinearDepth()
@@ -66,6 +115,14 @@ float mtsmoothstep(in float edge0, in float edge1, in float x)
return t * t * (3.0 - 2.0 * t);
}
+float shadowCutoff(float x) {
+ #if defined(ENABLE_TRANSLUCENT_FOLIAGE) && MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES
+ return mtsmoothstep(0.0, 0.002, x);
+ #else
+ return step(0.0, x);
+ #endif
+}
+
#ifdef COLORED_SHADOWS
// c_precision of 128 fits within 7 base-10 digits
@@ -92,10 +149,10 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist
{
vec4 texDepth = texture2D(shadowsampler, smTexCoord.xy).rgba;
- float visibility = step(0.0, realDistance - texDepth.r);
+ float visibility = shadowCutoff(realDistance - texDepth.r);
vec4 result = vec4(visibility, vec3(0.0,0.0,0.0));//unpackColor(texDepth.g));
if (visibility < 0.1) {
- visibility = step(0.0, realDistance - texDepth.b);
+ visibility = shadowCutoff(realDistance - texDepth.b);
result = vec4(visibility, unpackColor(texDepth.a));
}
return result;
@@ -106,7 +163,7 @@ vec4 getHardShadowColor(sampler2D shadowsampler, vec2 smTexCoord, float realDist
float getHardShadow(sampler2D shadowsampler, vec2 smTexCoord, float realDistance)
{
float texDepth = texture2D(shadowsampler, smTexCoord.xy).r;
- float visibility = step(0.0, realDistance - texDepth);
+ float visibility = shadowCutoff(realDistance - texDepth);
return visibility;
}
@@ -378,6 +435,9 @@ void main(void)
vec4 col = vec4(color.rgb * varColor.rgb, 1.0);
#ifdef ENABLE_DYNAMIC_SHADOWS
+ // Fragment normal, can differ from vNormal which is derived from vertex normals.
+ vec3 fNormal = vNormal;
+
if (f_shadow_strength > 0.0) {
float shadow_int = 0.0;
vec3 shadow_color = vec3(0.0, 0.0, 0.0);
@@ -414,12 +474,19 @@ void main(void)
// Power ratio was measured on torches in MTG (brightness = 14).
float adjusted_night_ratio = pow(max(0.0, nightRatio), 0.6);
+ float shadow_uncorrected = shadow_int;
+
// Apply self-shadowing when light falls at a narrow angle to the surface
// Cosine of the cut-off angle.
const float self_shadow_cutoff_cosine = 0.035;
if (f_normal_length != 0 && cosLight < self_shadow_cutoff_cosine) {
shadow_int = max(shadow_int, 1 - clamp(cosLight, 0.0, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
shadow_color = mix(vec3(0.0), shadow_color, min(cosLight, self_shadow_cutoff_cosine)/self_shadow_cutoff_cosine);
+
+#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES || MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS)
+ // Prevents foliage from becoming insanely bright outside the shadow map.
+ shadow_uncorrected = mix(shadow_int, shadow_uncorrected, clamp(distance_rate * 4.0 - 3.0, 0.0, 1.0));
+#endif
}
shadow_int *= f_adj_shadow_strength;
@@ -428,8 +495,60 @@ void main(void)
col.rgb =
adjusted_night_ratio * col.rgb + // artificial light
(1.0 - adjusted_night_ratio) * ( // natural light
- col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color
+ col.rgb * (1.0 - shadow_int * (1.0 - shadow_color) * (1.0 - shadow_tint)) + // filtered texture color
dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight
+
+
+ vec3 reflect_ray = -normalize(v_LightDirection - fNormal * dot(v_LightDirection, fNormal) * 2.0);
+
+ vec3 viewVec = normalize(worldPosition + cameraOffset - cameraPosition);
+
+ // Water reflections
+#if (defined(MATERIAL_WAVING_LIQUID) && defined(ENABLE_WATER_REFLECTIONS) && ENABLE_WAVING_WATER)
+ vec3 wavePos = worldPosition * vec3(2.0, 0.0, 2.0);
+ float off = animationTimer * WATER_WAVE_SPEED * 10.0;
+ wavePos.x /= WATER_WAVE_LENGTH * 3.0;
+ wavePos.z /= WATER_WAVE_LENGTH * 2.0;
+
+ // This is an analogous method to the bumpmap, except we get the gradient information directly from gnoise.
+ vec2 gradient = wave_noise(wavePos, off);
+ fNormal = normalize(normalize(fNormal) + vec3(gradient.x, 0., gradient.y) * WATER_WAVE_HEIGHT * abs(fNormal.y) * 0.25);
+ reflect_ray = -normalize(v_LightDirection - fNormal * dot(v_LightDirection, fNormal) * 2.0);
+ float fresnel_factor = dot(fNormal, viewVec);
+
+ float brightness_factor = 1.0 - adjusted_night_ratio;
+
+ // A little trig hack. We go from the dot product of viewVec and normal to the dot product of viewVec and tangent to apply a fresnel effect.
+ fresnel_factor = clamp(pow(1.0 - fresnel_factor * fresnel_factor, 8.0), 0.0, 1.0) * 0.8 + 0.2;
+ col.rgb *= 0.5;
+ vec3 reflection_color = mix(vec3(max(fogColor.r, max(fogColor.g, fogColor.b))), fogColor.rgb, f_shadow_strength);
+
+ // Sky reflection
+ col.rgb += reflection_color * pow(fresnel_factor, 2.0) * 0.5 * brightness_factor;
+ vec3 water_reflect_color = 12.0 * dayLight * fresnel_factor * mtsmoothstep(0.85, 0.9, pow(clamp(dot(reflect_ray, viewVec), 0.0, 1.0), 32.0)) * max(1.0 - shadow_uncorrected, 0.0);
+
+ // This line exists to prevent ridiculously bright reflection colors.
+ water_reflect_color /= clamp(max(water_reflect_color.r, max(water_reflect_color.g, water_reflect_color.b)) * 0.375, 1.0, 400.0);
+ col.rgb += water_reflect_color * f_adj_shadow_strength * brightness_factor;
+#endif
+
+#if (defined(ENABLE_NODE_SPECULAR) && !defined(MATERIAL_WAVING_LIQUID))
+ // Apply specular to blocks.
+ if (dot(v_LightDirection, vNormal) < 0.0) {
+ float intensity = 2.0 * (1.0 - (base.r * varColor.r));
+ const float specular_exponent = 5.0;
+ const float fresnel_exponent = 4.0;
+
+ col.rgb +=
+ intensity * dayLight * (1.0 - nightRatio) * (1.0 - shadow_uncorrected) * f_adj_shadow_strength *
+ pow(max(dot(reflect_ray, viewVec), 0.0), fresnel_exponent) * pow(1.0 - abs(dot(viewVec, fNormal)), specular_exponent);
+ }
+#endif
+
+#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_PLANTS || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LEAVES) && defined(ENABLE_TRANSLUCENT_FOLIAGE)
+ // Simulate translucent foliage.
+ col.rgb += 4.0 * dayLight * base.rgb * normalize(base.rgb * varColor.rgb * varColor.rgb) * f_adj_shadow_strength * pow(max(-dot(v_LightDirection, viewVec), 0.0), 4.0) * max(1.0 - shadow_uncorrected, 0.0);
+#endif
}
#endif
@@ -444,7 +563,13 @@ void main(void)
// Note: clarity = (1 - fogginess)
float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
- col = mix(fogColor, col, clarity);
+ float fogColorMax = max(max(fogColor.r, fogColor.g), fogColor.b);
+ // Prevent zero division.
+ if (fogColorMax < 0.0000001) fogColorMax = 1.0;
+ // For high clarity (light fog) we tint the fog color.
+ // For this to not make the fog color artificially dark we need to normalize using the
+ // fog color's brightest value. We then blend our base color with this to make the fog.
+ col = mix(fogColor * pow(fogColor / fogColorMax, vec4(2.0 * clarity)), col, clarity);
col = vec4(col.rgb, base.a);
gl_FragData[0] = col;
diff --git a/client/shaders/nodes_shader/opengl_vertex.glsl b/client/shaders/nodes_shader/opengl_vertex.glsl
index d96164d76..d5d6dd59e 100644
--- a/client/shaders/nodes_shader/opengl_vertex.glsl
+++ b/client/shaders/nodes_shader/opengl_vertex.glsl
@@ -199,15 +199,11 @@ void main(void)
vNormal = inVertexNormal;
// Calculate color.
+ vec4 color = inVertexColor;
// Red, green and blue components are pre-multiplied with
// the brightness, so now we have to multiply these
// colors with the color of the incoming light.
// The pre-baked colors are halved to prevent overflow.
-#ifdef GL_ES
- vec4 color = inVertexColor.bgra;
-#else
- vec4 color = inVertexColor;
-#endif
// The alpha gives the ratio of sunlight in the incoming light.
nightRatio = 1.0 - color.a;
color.rgb = color.rgb * (color.a * dayLight.rgb +
@@ -256,7 +252,9 @@ void main(void)
z_bias *= pFactor * pFactor / f_textureresolution / f_shadowfar;
shadow_position = applyPerspectiveDistortion(m_ShadowViewProj * mWorld * (shadow_pos + vec4(normalOffsetScale * nNormal, 0.0))).xyz;
+#if !defined(ENABLE_TRANSLUCENT_FOLIAGE) || MATERIAL_TYPE != TILE_MATERIAL_WAVING_LEAVES
shadow_position.z -= z_bias;
+#endif
perspective_factor = pFactor;
if (f_timeofday < 0.2) {
diff --git a/client/shaders/object_shader/opengl_fragment.glsl b/client/shaders/object_shader/opengl_fragment.glsl
index 2b8af3fa9..db72d7f7d 100644
--- a/client/shaders/object_shader/opengl_fragment.glsl
+++ b/client/shaders/object_shader/opengl_fragment.glsl
@@ -20,6 +20,7 @@ uniform float animationTimer;
uniform vec4 CameraPos;
uniform float xyPerspectiveBias0;
uniform float xyPerspectiveBias1;
+ uniform vec3 shadow_tint;
varying float adj_shadow_strength;
varying float cosLight;
@@ -432,7 +433,7 @@ void main(void)
col.rgb =
adjusted_night_ratio * col.rgb + // artificial light
(1.0 - adjusted_night_ratio) * ( // natural light
- col.rgb * (1.0 - shadow_int * (1.0 - shadow_color)) + // filtered texture color
+ col.rgb * (1.0 - shadow_int * (1.0 - shadow_color) * (1.0 - shadow_tint)) + // filtered texture color
dayLight * shadow_color * shadow_int); // reflected filtered sunlight/moonlight
}
#endif
@@ -448,7 +449,13 @@ void main(void)
// Note: clarity = (1 - fogginess)
float clarity = clamp(fogShadingParameter
- fogShadingParameter * length(eyeVec) / fogDistance, 0.0, 1.0);
- col = mix(fogColor, col, clarity);
+ float fogColorMax = max(max(fogColor.r, fogColor.g), fogColor.b);
+ // Prevent zero division.
+ if (fogColorMax < 0.0000001) fogColorMax = 1.0;
+ // For high clarity (light fog) we tint the fog color.
+ // For this to not make the fog color artificially dark we need to normalize using the
+ // fog color's brightest value. We then blend our base color with this to make the fog.
+ col = mix(fogColor * pow(fogColor / fogColorMax, vec4(2.0 * clarity)), col, clarity);
col = vec4(col.rgb, base.a);
gl_FragData[0] = col;
diff --git a/client/shaders/object_shader/opengl_vertex.glsl b/client/shaders/object_shader/opengl_vertex.glsl
index d5a434da5..4bb109f68 100644
--- a/client/shaders/object_shader/opengl_vertex.glsl
+++ b/client/shaders/object_shader/opengl_vertex.glsl
@@ -1,7 +1,7 @@
uniform mat4 mWorld;
uniform vec3 dayLight;
uniform float animationTimer;
-uniform lowp vec4 emissiveColor;
+uniform lowp vec4 materialColor;
varying vec3 vNormal;
varying vec3 vPosition;
@@ -91,7 +91,7 @@ float directional_ambient(vec3 normal)
void main(void)
{
- varTexCoord = (mTexture * inTexCoord0).st;
+ varTexCoord = (mTexture * vec4(inTexCoord0.xy, 1.0, 1.0)).st;
gl_Position = mWorldViewProj * inVertexPosition;
vPosition = gl_Position.xyz;
@@ -109,13 +109,9 @@ void main(void)
: directional_ambient(normalize(inVertexNormal));
#endif
-#ifdef GL_ES
- vec4 color = inVertexColor.bgra;
-#else
vec4 color = inVertexColor;
-#endif
- color *= emissiveColor;
+ color *= materialColor;
// The alpha gives the ratio of sunlight in the incoming light.
nightRatio = 1.0 - color.a;
diff --git a/client/shaders/selection_shader/opengl_vertex.glsl b/client/shaders/selection_shader/opengl_vertex.glsl
index 39dde3056..9ca87a9cf 100644
--- a/client/shaders/selection_shader/opengl_vertex.glsl
+++ b/client/shaders/selection_shader/opengl_vertex.glsl
@@ -6,9 +6,5 @@ void main(void)
varTexCoord = inTexCoord0.st;
gl_Position = mWorldViewProj * inVertexPosition;
-#ifdef GL_ES
- varColor = inVertexColor.bgra;
-#else
varColor = inVertexColor;
-#endif
}
diff --git a/client/shaders/stars_shader/opengl_fragment.glsl b/client/shaders/stars_shader/opengl_fragment.glsl
index 224032fa3..e991e4f94 100644
--- a/client/shaders/stars_shader/opengl_fragment.glsl
+++ b/client/shaders/stars_shader/opengl_fragment.glsl
@@ -1,6 +1,6 @@
-uniform lowp vec4 emissiveColor;
+uniform lowp vec4 materialColor;
void main(void)
{
- gl_FragColor = emissiveColor;
+ gl_FragColor = materialColor;
}
diff --git a/client/shaders/volumetric_light/opengl_fragment.glsl b/client/shaders/volumetric_light/opengl_fragment.glsl
index 9ed5fa9ba..001f59465 100644
--- a/client/shaders/volumetric_light/opengl_fragment.glsl
+++ b/client/shaders/volumetric_light/opengl_fragment.glsl
@@ -46,7 +46,9 @@ float sampleVolumetricLight(vec2 uv, vec3 lightVec, float rawDepth)
if (min(samplepos.x, samplepos.y) > 0. && max(samplepos.x, samplepos.y) < 1.)
result += texture2D(depthmap, samplepos).r < 1. ? 0.0 : 1.0;
}
- return result / samples;
+ // We use the depth map to approximate the effect of depth on the light intensity.
+ // The exponent was chosen based on aesthetic preference.
+ return result / samples * pow(texture2D(depthmap, uv).r, 128.0);
}
vec3 getDirectLightScatteringAtGround(vec3 v_LightDirection)
diff --git a/cmake/Modules/FindLua.cmake b/cmake/Modules/FindLua.cmake
index be5d92d8c..a239046ac 100644
--- a/cmake/Modules/FindLua.cmake
+++ b/cmake/Modules/FindLua.cmake
@@ -11,7 +11,7 @@ if(ENABLE_LUAJIT)
find_package(LuaJIT)
if(LUAJIT_FOUND)
set(USE_LUAJIT TRUE)
- message (STATUS "Using LuaJIT provided by system.")
+ message (STATUS "Using LuaJIT")
elseif(REQUIRE_LUAJIT)
message(FATAL_ERROR "LuaJIT not found whereas REQUIRE_LUAJIT=\"TRUE\" is used.\n"
"To continue, either install LuaJIT or do not use REQUIRE_LUAJIT=\"TRUE\".")
diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md
index fac3f7b93..cd651f1b3 100644
--- a/doc/client_lua_api.md
+++ b/doc/client_lua_api.md
@@ -315,7 +315,7 @@ Call these functions only at load time!
* `minetest.register_globalstep(function(dtime))`
* Called every client environment step
- * `dtime` is the time since last execution in seconds.
+ * `dtime` is the time since last execution in seconds.
* `minetest.register_on_mods_loaded(function())`
* Called just after mods have finished loading.
* `minetest.register_on_shutdown(function())`
@@ -338,8 +338,6 @@ Call these functions only at load time!
is checked to see if the command exists, but after the input is parsed.
* Return `true` to mark the command as handled, which means that the default
handlers will be prevented.
-* `minetest.register_on_death(function())`
- * Called when the local player dies
* `minetest.register_on_hp_modification(function(hp))`
* Called when server modified player's HP
* `minetest.register_on_damage_taken(function(hp))`
@@ -487,8 +485,6 @@ Call these functions only at load time!
* Returns `false` if the client is already disconnecting otherwise returns `true`.
* `minetest.get_server_info()`
* Returns [server info](#server-info).
-* `minetest.send_respawn()`
- * Sends a respawn request to the server.
### Storage API
* `minetest.get_mod_storage()`:
@@ -586,9 +582,9 @@ Call these functions only at load time!
* `minetest.camera`
* Reference to the camera object. See [`Camera`](#camera) class reference for methods.
* `minetest.show_formspec(formname, formspec)` : returns true on success
- * Shows a formspec to the player
+ * Shows a formspec to the player
* `minetest.display_chat_message(message)` returns true on success
- * Shows a chat message to the current player.
+ * Shows a chat message to the current player.
Setting-related
---------------
@@ -866,9 +862,9 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or
-----------------
### Definitions
* `minetest.get_node_def(nodename)`
- * Returns [node definition](#node-definition) table of `nodename`
+ * Returns [node definition](#node-definition) table of `nodename`
* `minetest.get_item_def(itemstring)`
- * Returns item definition table of `itemstring`
+ * Returns item definition table of `itemstring`
#### Node Definition
@@ -971,10 +967,10 @@ It can be created via `Raycast(pos1, pos2, objects, liquids)` or
```lua
{
- address = "minetest.example.org", -- The domain name/IP address of a remote server or "" for a local server.
- ip = "203.0.113.156", -- The IP address of the server.
- port = 30000, -- The port the client is connected to.
- protocol_version = 30 -- Will not be accurate at start up as the client might not be connected to the server yet, in that case it will be 0.
+ address = "minetest.example.org", -- The domain name/IP address of a remote server or "" for a local server.
+ ip = "203.0.113.156", -- The IP address of the server.
+ port = 30000, -- The port the client is connected to.
+ protocol_version = 30 -- Will not be accurate at start up as the client might not be connected to the server yet, in that case it will be 0.
}
```
diff --git a/doc/compiling/README.md b/doc/compiling/README.md
index a1ab1ebbd..bfe91950f 100644
--- a/doc/compiling/README.md
+++ b/doc/compiling/README.md
@@ -22,6 +22,7 @@ General options and their default values:
MinSizeRel - Release build with -Os passed to compiler to make executable as small as possible
PRECOMPILE_HEADERS=FALSE - Precompile some headers (experimental; requires CMake 3.16 or later)
PRECOMPILED_HEADERS_PATH= - Path to a file listing all headers to precompile (default points to src/precompiled_headers.txt)
+ USE_SDL2=TRUE - Build with SDL2; Enables IrrlichtMt device SDL2
ENABLE_CURL=ON - Build with cURL; Enables use of online mod repo, public serverlist and remote media fetching via http
ENABLE_CURSES=ON - Build with (n)curses; Enables a server side terminal (command line option: --terminal)
ENABLE_GETTEXT=ON - Build with Gettext; Allows using translations
@@ -39,10 +40,15 @@ General options and their default values:
ENABLE_UPDATE_CHECKER=TRUE - Whether to enable update checks by default
INSTALL_DEVTEST=FALSE - Whether the Development Test game should be installed alongside Minetest
USE_GPROF=FALSE - Enable profiling using GProf
+ BUILD_WITH_TRACY=FALSE - Fetch and build with the Tracy profiler client
+ FETCH_TRACY_GIT_TAG=master - Git tag for fetching Tracy client. Match with your server (gui) version
VERSION_EXTRA= - Text to append to version (e.g. VERSION_EXTRA=foobar -> Minetest 0.4.9-foobar)
Library specific options:
+ SDL2_DLL - Only if building with SDL2 on Windows; path to libSDL2.dll
+ SDL2_INCLUDE_DIRS - Only if building with SDL2; directory where SDL.h is located
+ SDL2_LIBRARIES - Only if building with SDL2; path to libSDL2.a/libSDL2.so/libSDL2.lib
CURL_DLL - Only if building with cURL on Windows; path to libcurl.dll
CURL_INCLUDE_DIR - Only if building with cURL; directory where curl.h is located
CURL_LIBRARY - Only if building with cURL; path to libcurl.a/libcurl.so/libcurl.lib
diff --git a/doc/compiling/windows.md b/doc/compiling/windows.md
index c63a7b319..eeaf2e4fd 100644
--- a/doc/compiling/windows.md
+++ b/doc/compiling/windows.md
@@ -14,7 +14,7 @@ It is highly recommended to use vcpkg as package manager.
After you successfully built vcpkg you can easily install the required libraries:
```powershell
-vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp gettext sdl2 --triplet x64-windows
+vcpkg install zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp gettext[tools] sdl2 --triplet x64-windows
```
- `curl` is optional, but required to read the serverlist, `curl[winssl]` is required to use the content store.
@@ -52,7 +52,7 @@ Use `--triplet` to specify the target triplet, e.g. `x64-windows` or `x86-window
Run the following script in PowerShell:
```powershell
-cmake . -G"Visual Studio 15 2017 Win64" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_GETTEXT=OFF -DENABLE_CURSES=OFF
+cmake . -G"Visual Studio 16 2019" -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_CURSES=OFF
cmake --build . --config Release
```
Make sure that the right compiler is selected and the path to the vcpkg toolchain is correct.
diff --git a/doc/developing/misc.md b/doc/developing/misc.md
index 1d3d8c941..2ac843caf 100644
--- a/doc/developing/misc.md
+++ b/doc/developing/misc.md
@@ -1,6 +1,6 @@
# Miscellaneous
-## Profiling Minetest on Linux
+## Profiling Minetest on Linux with perf
We will be using a tool called "perf", which you can get by installing `perf` or `linux-perf` or `linux-tools-common`.
@@ -36,3 +36,54 @@ Give both files to the developer and also provide:
* commit the source was built from and/or modified source code (if applicable)
Hotspot will resolve symbols correctly when pointing the sysroot option at the collected libs.
+
+
+## Profiling with Tracy
+
+[Tracy](https://github.com/wolfpld/tracy) is
+> A real time, nanosecond resolution, remote telemetry, hybrid frame and sampling
+> profiler for games and other applications.
+
+It allows one to annotate important functions and generate traces, where one can
+see when each individual function call happened, and how long it took.
+
+Tracy can also record when frames, e.g. server step, start and end, and inspect
+frames that took longer than usual. Minetest already contains annotations for
+its frames.
+
+See also [Tracy's official documentation](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf).
+
+### Installing
+
+Tracy consists of a client (Minetest) and a server (the gui).
+
+Install the server, e.g. using your package manager.
+
+### Building
+
+Build Minetest with `-DDBUILD_WITH_TRACY=1`, this will fetch Tracy for building
+the Tracy client. And use `FETCH_TRACY_GIT_TAG` to get a version matching your
+Tracy server, e.g. `-DFETCH_TRACY_GIT_TAG=v0.11.0` if it's `0.11.0`.
+
+To actually use Tracy, you also have to enable it with Tracy's build options:
+```
+-DTRACY_ENABLE=1 -DTRACY_ONLY_LOCALHOST=1
+```
+
+See Tracy's documentation for more build options.
+
+### Using in C++
+
+Start the Tracy server and Minetest. You should see Minetest in the menu.
+
+To actually get useful traces, you have to annotate functions with `ZoneScoped`
+macros and recompile. Please refer to Tracy's official documentation.
+
+### Using in Lua
+
+Tracy also supports Lua.
+If built with Tracy, Minetest loads its API in the global `tracy` table.
+See Tracy's official documentation for more information.
+
+Note: The whole Tracy Lua API is accessible to all mods. And we don't check if it
+is or becomes insecure. Run untrusted mods at your own risk.
diff --git a/doc/lua_api.md b/doc/lua_api.md
index 389ea73f2..2c827d7ad 100644
--- a/doc/lua_api.md
+++ b/doc/lua_api.md
@@ -274,7 +274,7 @@ Accepted formats are:
images: .png, .jpg, .tga, (deprecated:) .bmp
sounds: .ogg vorbis
- models: .x, .b3d, .obj
+ models: .x, .b3d, .obj, (since version 5.10:) .gltf, .glb
Other formats won't be sent to the client (e.g. you can store .blend files
in a folder for convenience, without the risk that such files are transferred)
@@ -291,6 +291,49 @@ in one of its parents, the parent's file is used.
Although it is discouraged, a mod can overwrite a media file of any mod that it
depends on by supplying a file with an equal name.
+Only a subset of model file format features is supported:
+
+Simple textured meshes (with multiple textures), optionally with normals.
+The .x, .b3d and .gltf formats additionally support (a single) animation.
+
+#### glTF
+
+The glTF model file format for now only serves as a
+more modern alternative to the other static model file formats;
+it unlocks no special rendering features.
+
+Binary glTF (`.glb`) files are supported and recommended over `.gltf` files
+due to their space savings.
+
+This means that many glTF features are not supported *yet*, including:
+
+* Animations
+ * Only a single animation is supported,
+ use frame ranges within this animation.
+ * Only integer frames are supported.
+* Cameras
+* Materials
+ * Only base color textures are supported
+ * Backface culling is overridden
+ * Double-sided materials don't work
+* Alternative means of supplying data
+ * Embedded images
+ * References to files via URIs
+
+Textures are supplied solely via the same means as for the other model file formats:
+The `textures` object property, the `tiles` node definition field and
+the list of textures used in the `model[]` formspec element.
+
+The order in which textures are to be supplied
+is that in which they appear in the `textures` array in the glTF file.
+
+Do not rely on glTF features not being supported; they may be supported in the future.
+The backwards compatibility guarantee does not extend to ignoring unsupported features.
+
+For example, if your model used an emissive material,
+you should expect that a future version of Minetest may respect this,
+and thus cause your model to render differently there.
+
Naming conventions
------------------
@@ -453,6 +496,11 @@ to let the client generate textures on-the-fly.
The modifiers are applied directly in sRGB colorspace,
i.e. without gamma-correction.
+### Notes
+
+ * `TEXMOD_UPSCALE`: The texture with the lower resolution will be automatically
+ upscaled to the higher resolution texture.
+
### Texture overlaying
Textures can be overlaid by putting a `^` between them.
@@ -466,8 +514,9 @@ Example:
default_dirt.png^default_grass_side.png
`default_grass_side.png` is overlaid over `default_dirt.png`.
-The texture with the lower resolution will be automatically upscaled to
-the higher resolution texture.
+
+*See notes: `TEXMOD_UPSCALE`*
+
### Texture grouping
@@ -664,6 +713,8 @@ Apply a mask to the base image.
The mask is applied using binary AND.
+*See notes: `TEXMOD_UPSCALE`*
+
#### `[sheet:x:,`
Retrieves a tile at position x, y (in tiles, 0-indexed)
@@ -761,6 +812,8 @@ in GIMP. Overlay is the same as Hard light but with the role of the two
textures swapped, see the `[hardlight` modifier description for more detail
about these blend modes.
+*See notes: `TEXMOD_UPSCALE`*
+
#### `[hardlight:`
Applies a Hard light blend with the two textures, like the Hard light layer
@@ -776,6 +829,8 @@ increase contrast without clipping.
Hard light is the same as Overlay but with the roles of the two textures
swapped, i.e. `A.png^[hardlight:B.png` is the same as `B.png^[overlay:A.png`
+*See notes: `TEXMOD_UPSCALE`*
+
#### `[png:`
Embed a base64 encoded PNG image in the texture string.
@@ -794,6 +849,8 @@ In particular consider `minetest.dynamic_add_media` and test whether
using other texture modifiers could result in a shorter string than
embedding a whole image, this may vary by use case.
+*See notes: `TEXMOD_UPSCALE`*
+
Hardware coloring
-----------------
@@ -1357,16 +1414,19 @@ The function of `param2` is determined by `paramtype2` in node definition.
The palette should have 256 pixels.
* `paramtype2 = "colorfacedir"`
* Same as `facedir`, but with colors.
- * The first three bits of `param2` tells which color is picked from the
+ * The three most significant bits of `param2` tells which color is picked from the
palette. The palette should have 8 pixels.
+ * The five least significant bits contain the `facedir` value.
* `paramtype2 = "color4dir"`
- * Same as `facedir`, but with colors.
- * The first six bits of `param2` tells which color is picked from the
+ * Same as `4dir`, but with colors.
+ * The six most significant bits of `param2` tells which color is picked from the
palette. The palette should have 64 pixels.
+ * The two least significant bits contain the `4dir` rotation.
* `paramtype2 = "colorwallmounted"`
* Same as `wallmounted`, but with colors.
- * The first five bits of `param2` tells which color is picked from the
+ * The five most significant bits of `param2` tells which color is picked from the
palette. The palette should have 32 pixels.
+ * The three least significant bits contain the `wallmounted` value.
* `paramtype2 = "glasslikeliquidlevel"`
* Only valid for "glasslike_framed" or "glasslike_framed_optional"
drawtypes. "glasslike_framed_optional" nodes are only affected if the
@@ -1380,9 +1440,9 @@ The function of `param2` is determined by `paramtype2` in node definition.
* Liquid texture is defined using `special_tiles = {"modname_tilename.png"}`
* `paramtype2 = "colordegrotate"`
* Same as `degrotate`, but with colors.
- * The first (most-significant) three bits of `param2` tells which color
- is picked from the palette. The palette should have 8 pixels.
- * Remaining 5 bits store rotation in range 0–23 (i.e. in 15° steps)
+ * The three most significant bits of `param2` tells which color is picked
+ from the palette. The palette should have 8 pixels.
+ * The five least significant bits store rotation in range 0–23 (i.e. in 15° steps)
* `paramtype2 = "none"`
* `param2` will not be used by the engine and can be used to store
an arbitrary value
@@ -1433,7 +1493,8 @@ Look for examples in `games/devtest` or `games/minetest_game`.
'Connected Glass'.
* `allfaces`
* Often used for partially-transparent nodes.
- * External and internal sides of textures are visible.
+ * External sides of textures, and unlike other drawtypes, the external sides
+ of other blocks, are visible from the inside.
* `allfaces_optional`
* Often used for leaves nodes.
* This switches between `normal`, `glasslike` and `allfaces` according to
@@ -1744,6 +1805,13 @@ Displays a horizontal bar made up of half-images with an optional background.
* `item`: Position of item that is selected.
* `direction`: Direction the list will be displayed in
* `offset`: offset in pixels from position.
+* `alignment`: The alignment of the inventory. Aligned at the top left corner if not specified.
+
+### `hotbar`
+
+* `direction`: Direction the list will be displayed in
+* `offset`: offset in pixels from position.
+* `alignment`: The alignment of the inventory.
### `waypoint`
@@ -1802,6 +1870,11 @@ Displays a minimap on the HUD.
* `size`: Size of the minimap to display. Minimap should be a square to avoid
distortion.
+ * Negative values represent percentages of the screen. If either `x` or `y`
+ is specified as a percentage, the resulting pixel size will be used for
+ both `x` and `y`. Example: On a 1920x1080 screen, `{x = 0, y = -25}` will
+ result in a 270x270 minimap.
+ * Negative values are supported starting with protocol version 45.
* `alignment`: The alignment of the minimap.
* `offset`: offset in pixels from position.
@@ -2625,6 +2698,9 @@ background elements are drawn before all other elements.
**WARNING**: do _not_ use an element name starting with `key_`; those names are
reserved to pass key press events to formspec!
+**WARNING**: names and values of elements cannot contain binary data such as ASCII
+control characters. For values, escape sequences used by the engine are an exception to this.
+
**WARNING**: Minetest allows you to add elements to every single formspec instance
using `player:set_formspec_prepend()`, which may be the reason backgrounds are
appearing when you don't expect them to, or why things are styled differently
@@ -2677,6 +2753,8 @@ Version History
* Formspec version 7 (5.8.0):
* style[]: Add focused state for buttons
* Add field_enter_after_edit[] (experimental)
+* Formspec version 8 (5.10.0)
+ * scroll_container[]: content padding parameter
Elements
--------
@@ -2760,7 +2838,7 @@ Elements
* End of a container, following elements are no longer relative to this
container.
-### `scroll_container[,;,;;;]`
+### `scroll_container[,;,;;;;]`
* Start of a scroll_container block. All contained elements will ...
* take the scroll_container coordinate as position origin,
@@ -2769,6 +2847,12 @@ Elements
* be clipped to the rectangle defined by `X`, `Y`, `W` and `H`.
* `orientation`: possible values are `vertical` and `horizontal`.
* `scroll factor`: optional, defaults to `0.1`.
+* `content padding`: (optional), in formspec coordinate units
+ * If specified, the scrollbar properties `max` and `thumbsize` are calculated automatically
+ based on the content size plus `content padding` at the end of the container. `min` is set to 0.
+ * Negative `scroll factor` is not supported.
+ * When active, `scrollbaroptions[]` has no effect on the affected properties.
+ * Defaults to empty value (= disabled).
* Nesting is possible.
* Some elements might work a little different if they are in a scroll_container.
* Note: If you want the scroll_container to actually work, you also need to add a
@@ -2861,14 +2945,14 @@ Elements
* Requires formspec version >= 6.
* See `background9[]` documentation for more information.
-### `model[,;,;;;;;;;;]`
+### `model[,;,;;;;;;;;]`
* Show a mesh model.
* `name`: Element name that can be used for styling
* `mesh`: The mesh model to use.
* `textures`: The mesh textures to use according to the mesh materials.
Texture names must be separated by commas.
-* `rotation {X,Y}` (Optional): Initial rotation of the camera.
+* `rotation` (Optional): Initial rotation of the camera, format `x,y`.
The axes are euler angles in degrees.
* `continuous` (Optional): Whether the rotation is continuous. Default `false`.
* `mouse control` (Optional): Whether the model can be controlled with the mouse. Default `true`.
@@ -3658,7 +3742,7 @@ Player Inventory lists
* `hand`: list containing an override for the empty hand
* Is not created automatically, use `InvRef:set_size`
* Is only used to enhance the empty hand's tool capabilities
-
+
Custom lists can be added and deleted with `InvRef:set_size(name, size)` like
any other inventory.
@@ -3847,15 +3931,23 @@ vectors are written like this: `(x, y, z)`:
* If `v` has zero length, returns `(0, 0, 0)`.
* `vector.floor(v)`:
* Returns a vector, each dimension rounded down.
+* `vector.ceil(v)`:
+ * Returns a vector, each dimension rounded up.
* `vector.round(v)`:
* Returns a vector, each dimension rounded to nearest integer.
* At a multiple of 0.5, rounds away from zero.
-* `vector.apply(v, func)`:
+* `vector.sign(v, tolerance)`:
+ * Returns a vector where `math.sign` was called for each component.
+ * See [Helper functions] for details.
+* `vector.abs(v)`:
+ * Returns a vector with absolute values for each component.
+* `vector.apply(v, func, ...)`:
* Returns a vector where the function `func` has been applied to each
component.
+ * `...` are optional arguments passed to `func`.
* `vector.combine(v, w, func)`:
- * Returns a vector where the function `func` has combined both components of `v` and `w`
- for each component
+ * Returns a vector where the function `func` has combined both components of `v` and `w`
+ for each component
* `vector.equals(v1, v2)`:
* Returns a boolean, `true` if the vectors are identical.
* `vector.sort(v1, v2)`:
@@ -3873,10 +3965,14 @@ vectors are written like this: `(x, y, z)`:
by a `vector.*` function.
* Returns `false` for anything else, including tables like `{x=3,y=1,z=4}`.
* `vector.in_area(pos, min, max)`:
- * Returns a boolean value indicating if `pos` is inside area formed by `min` and `max`.
- * `min` and `max` are inclusive.
- * If `min` is bigger than `max` on some axis, function always returns false.
- * You can use `vector.sort` if you have two vectors and don't know which are the minimum and the maximum.
+ * Returns a boolean value indicating if `pos` is inside area formed by `min` and `max`.
+ * `min` and `max` are inclusive.
+ * If `min` is bigger than `max` on some axis, function always returns false.
+ * You can use `vector.sort` if you have two vectors and don't know which are the minimum and the maximum.
+* `vector.random_in_area(min, max)`:
+ * Returns a random integer position in area formed by `min` and `max`
+ * `min` and `max` are inclusive.
+ * You can use `vector.sort` if you have two vectors and don't know which are the minimum and the maximum.
For the following functions `x` can be either a vector or a number:
@@ -4082,10 +4178,6 @@ Translations
Texts can be translated client-side with the help of `minetest.translate` and
translation files.
-Consider using the script `mod_translation_updater.py` in the Minetest
-[modtools](https://github.com/minetest/modtools) repository to generate and
-update translation files automatically from the Lua sources.
-
Translating a string
--------------------
@@ -4093,13 +4185,15 @@ Two functions are provided to translate strings: `minetest.translate` and
`minetest.get_translator`.
* `minetest.get_translator(textdomain)` is a simple wrapper around
- `minetest.translate`, and `minetest.get_translator(textdomain)(str, ...)` is
- equivalent to `minetest.translate(textdomain, str, ...)`.
+ `minetest.translate` and `minetest.translate_n`.
+ After `local S, NS = minetest.get_translator(textdomain)`, we have
+ `S(str, ...)` equivalent to `minetest.translate(textdomain, str, ...)`, and
+ `NS(str, str_plural, n, ...)` to `minetest.translate_n(textdomain, str, str_plural, n, ...)`.
It is intended to be used in the following way, so that it avoids verbose
repetitions of `minetest.translate`:
```lua
- local S = minetest.get_translator(textdomain)
+ local S, NS = minetest.get_translator(textdomain)
S(str, ...)
```
@@ -4116,29 +4210,102 @@ Two functions are provided to translate strings: `minetest.translate` and
arguments the translated string expects.
Arguments are literal strings -- they will not be translated.
-For instance, suppose we want to greet players when they join. We can do the
+* `minetest.translate_n(textdomain, str, str_plural, n, ...)` translates the
+ string `str` with the given `textdomain` for disambiguaion. The value of
+ `n`, which must be a nonnegative integer, is used to decide whether to use
+ the singular or the plural version of the string. Depending on the locale of
+ the client, the choice between singular and plural might be more complicated,
+ but the choice will be done automatically using the value of `n`.
+
+ You can read https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html
+ for more details on the differences of plurals between languages.
+
+ Also note that plurals are only handled in .po or .mo files, and not in .tr files.
+
+For instance, suppose we want to greet players when they join and provide a
+command that shows the amount of time since the player joined. We can do the
following:
```lua
-local S = minetest.get_translator("hello")
+local S, NS = minetest.get_translator("hello")
minetest.register_on_joinplayer(function(player)
local name = player:get_player_name()
minetest.chat_send_player(name, S("Hello @1, how are you today?", name))
end)
+minetest.register_chatcommand("playtime", {
+ func = function(name)
+ local last_login = core.get_auth_handler().get_auth(name).last_login
+ local playtime = math.floor((last_login-os.time())/60)
+ return true, NS(
+ "You have been playing for @1 minute.",
+ "You have been playing for @1 minutes.",
+ minutes, tostring(minutes))
+ end,
+})
```
When someone called "CoolGuy" joins the game with an old client or a client
that does not have localization enabled, they will see `Hello CoolGuy, how are
-you today?`
+you today?`. If they use the `/playtime` command, they will see `You have been
+playing for 1 minute` or (for example) `You have been playing for 4 minutes.`
-However, if we have for instance a translation file named `hello.de.tr`
+However, if we have for instance a translation file named `hello.de.po`
containing the following:
- # textdomain: hello
- Hello @1, how are you today?=Hallo @1, wie geht es dir heute?
+```po
+msgid ""
+msgstr ""
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgid "Hello @1, how are you today?"
+msgstr "Hallo @1, wie geht es dir heute?"
+
+msgid "You have been playing for @1 minute."
+msgid_plural "You have been playing for @1 minutes."
+msgstr[0] "Du spielst seit @1 Minute."
+msgstr[1] "Du spielst seit @1 Minuten."
+```
and CoolGuy has set a German locale, they will see `Hallo CoolGuy, wie geht es
-dir heute?`
+dir heute?` when they join, and the `/playtime` command will show them `Du
+spielst seit 1 Minute.` or (for example) `Du spielst seit 4 Minuten.`
+
+Creating and updating translation files
+---------------------------------------
+
+As an alternative to writing translation files by hand (as shown in the above
+example), it is also possible to generate translation files based on the source
+code.
+
+It is recommended to first generate a translation template. The translation
+template includes translatable strings that translators can directly work on.
+After creating the `locale` directory, a translation template for the above
+example using the following command:
+
+```sh
+xgettext -L lua -kS -kNS:1,2 -kminetest.translate:1c,2 -kminetest.translate_n:1c,2,3 \
+ -d hello -o locale/hello.pot *.lua
+```
+
+The above command can also be used to update the translation template when new
+translatable strings are added.
+
+The German translator can then create the translation file with
+
+```sh
+msginit -l de -i locale/hello.pot -o locale/hello.de.po
+```
+
+and provide the translations by editing `locale/hello.de.po`.
+
+The translation file can be updated using
+
+```sh
+msgmerge -U locale/hello.de.po locale/hello.pot
+```
+
+Refer to the [Gettext manual](https://www.gnu.org/software/gettext/manual/) for
+further information on creating and updating translation files.
Operations on translated strings
--------------------------------
@@ -4152,8 +4319,8 @@ expected manner. However, string concatenation will still work as expected
sentences by breaking them into parts; arguments should be used instead), and
operations such as `minetest.colorize` which are also concatenation.
-Translation file format
------------------------
+Old translation file format
+---------------------------
A translation file has the suffix `.[lang].tr`, where `[lang]` is the language
it corresponds to. It must be put into the `locale` subdirectory of the mod.
@@ -4168,6 +4335,34 @@ The file should be a text file, with the following format:
There must be no extraneous whitespace around the `=` or at the beginning or
the end of the line.
+Using the earlier example of greeting the player, the translation file would be
+
+```
+# textdomain: hello
+Hello @1, how are you today?=Hallo @1, wie geht es dir heute?
+```
+
+For old translation files, consider using the script `mod_translation_updater.py`
+in the Minetest [modtools](https://github.com/minetest/modtools) repository to
+generate and update translation files automatically from the Lua sources.
+
+Gettext translation file format
+-------------------------------
+
+Gettext files can also be used as translations. A translation file has the suffix
+`.[lang].po` or `.[lang].mo`, depending on whether it is compiled or not, and must
+also be placed in the `locale` subdirectory of the mod. The value of `textdomain`
+is `msgctxt` in the gettext files. If `msgctxt` is not provided, the name of the
+translation file is used instead.
+
+A typical entry in a `.po` file would look like:
+
+```po
+msgctxt "textdomain"
+msgid "Hello world!"
+msgstr "Bonjour le monde!"
+```
+
Escapes
-------
@@ -4914,8 +5109,7 @@ Methods
the `VoxelManip`.
* `calc_lighting([p1, p2], [propagate_shadow])`: Calculate lighting within the
`VoxelManip`.
- * To be used only by a `VoxelManip` object from
- `minetest.get_mapgen_object`.
+ * To be used only with a `VoxelManip` object from `minetest.get_mapgen_object`.
* (`p1`, `p2`) is the area in which lighting is set, defaults to the whole
area if left out or nil. For almost all uses these should be left out
or nil to use the default.
@@ -4923,9 +5117,11 @@ Methods
generated mapchunk above are propagated down into the mapchunk, defaults
to `true` if left out.
* `update_liquids()`: Update liquid flow
-* `was_modified()`: Returns `true` or `false` if the data in the voxel
- manipulator had been modified since the last read from map, due to a call to
- `minetest.set_data()` on the loaded area elsewhere.
+* `was_modified()`: Returns `true` if the data in the voxel manipulator has been modified
+ since it was last read from the map. This means you have to call `get_data` again.
+ This only applies to a `VoxelManip` object from `minetest.get_mapgen_object`,
+ where the engine will keep the map and the VM in sync automatically.
+ * Note: this doesn't do what you think it does and is subject to removal. Don't use it!
* `get_emerged_area()`: Returns actual emerged minimum and maximum positions.
`VoxelArea`
@@ -5076,12 +5272,12 @@ Callbacks:
used for updating the entity state.
* `on_deactivate(self, removal)`
* Called when the object is about to get removed or unloaded.
- * `removal`: boolean indicating whether the object is about to get removed.
- Calling `object:remove()` on an active object will call this with `removal=true`.
- The mapblock the entity resides in being unloaded will call this with `removal=false`.
- * Note that this won't be called if the object hasn't been activated in the first place.
- In particular, `minetest.clear_objects({mode = "full"})` won't call this,
- whereas `minetest.clear_objects({mode = "quick"})` might call this.
+ * `removal`: boolean indicating whether the object is about to get removed.
+ Calling `object:remove()` on an active object will call this with `removal=true`.
+ The mapblock the entity resides in being unloaded will call this with `removal=false`.
+ * Note that this won't be called if the object hasn't been activated in the first place.
+ In particular, `minetest.clear_objects({mode = "full"})` won't call this,
+ whereas `minetest.clear_objects({mode = "quick"})` might call this.
* `on_step(self, dtime, moveresult)`
* Called on every server tick, after movement and collision processing.
* `dtime`: elapsed time since last call
@@ -5456,6 +5652,12 @@ Utilities
moveresult_new_pos = true,
-- Allow removing definition fields in `minetest.override_item` (5.9.0)
override_item_remove_fields = true,
+ -- The predefined hotbar is a Lua HUD element of type `hotbar` (5.10.0)
+ hotbar_hud_element = true,
+ -- Bulk LBM support (5.10.0)
+ bulk_lbms = true,
+ -- ABM supports field without_neighbors (5.10.0)
+ abm_without_neighbors = true,
}
```
@@ -5514,8 +5716,8 @@ Utilities
},
-- Estimated maximum formspec size before Minetest will start shrinking the
- -- formspec to fit. For a fullscreen formspec, use a size 10-20% larger than
- -- this and `padding[-0.01,-0.01]`.
+ -- formspec to fit. For a fullscreen formspec, use this formspec size and
+ -- `padding[0,0]`. `bgcolor[;true]` is also recommended.
max_formspec_size = {
x = 20,
y = 11.25
@@ -5589,6 +5791,13 @@ Utilities
* `minetest.colorspec_to_bytes(colorspec)`: Converts a ColorSpec to a raw
string of four bytes in an RGBA layout, returned as a string.
* `colorspec`: The ColorSpec to convert
+* `minetest.colorspec_to_table(colorspec)`: Converts a ColorSpec into RGBA table
+ form. If the ColorSpec is invalid, returns `nil`. You can use this to parse
+ ColorStrings.
+ * `colorspec`: The ColorSpec to convert
+* `minetest.time_to_day_night_ratio(time_of_day)`: Returns a "day-night ratio" value
+ (as accepted by `ObjectRef:override_day_night_ratio`) that is equivalent to
+ the given "time of day" value (as returned by `minetest.get_timeofday`).
* `minetest.encode_png(width, height, data, [compression])`: Encode a PNG
image and return it in string form.
* `width`: Width of the image
@@ -5723,7 +5932,7 @@ Call these functions only at load time!
* `minetest.register_globalstep(function(dtime))`
* Called every server step, usually interval of 0.1s.
- * `dtime` is the time since last execution in seconds.
+ * `dtime` is the time since last execution in seconds.
* `minetest.register_on_mods_loaded(function())`
* Called after mods have finished loading and before the media is cached or the
aliases handled.
@@ -5772,8 +5981,13 @@ Call these functions only at load time!
* `clicker`: ObjectRef - Object that acted upon `player`, may or may not be a player
* `minetest.register_on_player_hpchange(function(player, hp_change, reason), modifier)`
* Called when the player gets damaged or healed
+ * When `hp == 0`, damage doesn't trigger this callback.
+ * When `hp == hp_max`, healing does still trigger this callback.
* `player`: ObjectRef of the player
* `hp_change`: the amount of change. Negative when it is damage.
+ * Historically, the new HP value was clamped to [0, 65535] before
+ calculating the HP change. This clamping has been removed as of
+ Minetest 5.10.0
* `reason`: a PlayerHPChangeReason table.
* The `type` field will have one of the following values:
* `set_hp`: A mod or the engine called `set_hp` without
@@ -5794,6 +6008,7 @@ Call these functions only at load time!
* `minetest.register_on_dieplayer(function(ObjectRef, reason))`
* Called when a player dies
* `reason`: a PlayerHPChangeReason table, see register_on_player_hpchange
+ * For customizing the death screen, see `minetest.show_death_screen`.
* `minetest.register_on_respawnplayer(function(ObjectRef))`
* Called when player is to be respawned
* Called _before_ repositioning of player occurs
@@ -6070,6 +6285,8 @@ Environment access
* `minetest.swap_node(pos, node)`
* Swap node at position with another.
* This keeps the metadata intact and will not run con-/destructor callbacks.
+* `minetest.bulk_swap_node({pos1, pos2, pos3, ...}, node)`
+ * Equivalent to `minetest.swap_node` but in bulk.
* `minetest.remove_node(pos)`: Remove a node
* Equivalent to `minetest.set_node(pos, {name="air"})`, but a bit faster.
* `minetest.get_node(pos)`
@@ -6151,7 +6368,7 @@ Environment access
* **Warning**: The same warning as for `minetest.get_objects_inside_radius` applies.
Use `minetest.objects_in_area` instead to iterate only valid objects.
* `minetest.objects_in_area(min_pos, max_pos)`
- * returns an iterator of valid objects
+ * returns an iterator of valid objects
* `minetest.set_timeofday(val)`: set time of day
* `val` is between `0` and `1`; `0` for midnight, `0.5` for midday
* `minetest.get_timeofday()`: get time of day
@@ -6463,7 +6680,8 @@ Formspec
* `playername`: name of player to show formspec
* `formname`: name passed to `on_player_receive_fields` callbacks.
It should follow the `"modname:"` naming convention.
- `formname` must not be empty.
+ * `formname` must not be empty, unless you want to reshow
+ the inventory formspec without updating it for future opens.
* `formspec`: formspec to display
* `minetest.close_formspec(playername, formname)`
* `playername`: name of player to close formspec
@@ -6477,6 +6695,9 @@ Formspec
* `minetest.formspec_escape(string)`: returns a string
* escapes the characters "[", "]", "\", "," and ";", which cannot be used
in formspecs.
+* `minetest.hypertext_escape(string)`: returns a string
+ * escapes the characters "\", "<", and ">" to show text in a hypertext element.
+ * not safe for use with tag attributes.
* `minetest.explode_table_event(string)`: returns a table
* returns e.g. `{type="CHG", row=1, column=2}`
* `type` is one of:
@@ -6495,6 +6716,13 @@ Formspec
* `"INV"`: something failed
* `"CHG"`: has been changed
* `"VAL"`: not changed
+* `minetest.show_death_screen(player, reason)`
+ * Called when the death screen should be shown.
+ * `player` is an ObjectRef, `reason` is a PlayerHPChangeReason table or nil.
+ * By default, this shows a simple formspec with the option to respawn.
+ Respawning is done via `ObjectRef:respawn`.
+ * You can override this to show a custom death screen.
+ * For general death handling, use `minetest.register_on_dieplayer` instead.
Item handling
-------------
@@ -6727,17 +6955,6 @@ This allows you easy interoperability for delegating work to jobs.
* Register a path to a Lua file to be imported when an async environment
is initialized. You can use this to preload code which you can then call
later using `minetest.handle_async()`.
-* `minetest.register_portable_metatable(name, mt)`:
- * Register a metatable that should be preserved when data is transferred
- between the main thread and the async environment.
- * `name` is a string that identifies the metatable. It is recommended to
- follow the `modname:name` convention for this identifier.
- * `mt` is the metatable to register.
- * Note that it is allowed to register the same metatable under multiple
- names, but it is not allowed to register multiple metatables under the
- same name.
- * You must register the metatable in both the main environment
- and the async environment for this mechanism to work.
### List of APIs available in an async environment
@@ -6767,7 +6984,8 @@ Functions:
* Standalone helpers such as logging, filesystem, encoding,
hashing or compression APIs
-* `minetest.register_portable_metatable` (see above)
+* `minetest.register_portable_metatable`
+* IPC
Variables:
@@ -6845,6 +7063,7 @@ Functions:
* `minetest.get_node`, `set_node`, `find_node_near`, `find_nodes_in_area`,
`spawn_tree` and similar
* these only operate on the current chunk (if inside a callback)
+* IPC
Variables:
@@ -6922,6 +7141,52 @@ Server
this can make transfer of bigger files painless (if set up). Nevertheless
it is advised not to use dynamic media for big media files.
+IPC
+---
+
+The engine provides a generalized mechanism to enable sharing data between the
+different Lua environments (main, mapgen and async).
+It is essentially a shared in-memory key-value store.
+
+* `minetest.ipc_get(key)`:
+ * Read a value from the shared data area.
+ * `key`: string, should use the `"modname:thing"` convention to avoid conflicts.
+ * returns an arbitrary Lua value, or `nil` if this key does not exist
+* `minetest.ipc_set(key, value)`:
+ * Write a value to the shared data area.
+ * `key`: as above
+ * `value`: an arbitrary Lua value, cannot be or contain userdata.
+
+Interacting with the shared data will perform an operation comparable to
+(de)serialization on each access.
+For that reason modifying references will not have any effect, as in this example:
+```lua
+minetest.ipc_set("test:foo", {})
+minetest.ipc_get("test:foo").subkey = "value" -- WRONG!
+minetest.ipc_get("test:foo") -- returns an empty table
+```
+
+**Advanced**:
+
+* `minetest.ipc_cas(key, old_value, new_value)`:
+ * Write a value to the shared data area, but only if the previous value
+ equals what was given.
+ This operation is called Compare-and-Swap and can be used to implement
+ synchronization between threads.
+ * `key`: as above
+ * `old_value`: value compared to using `==` (`nil` compares equal for non-existing keys)
+ * `new_value`: value that will be set
+ * returns: true on success, false otherwise
+* `minetest.ipc_poll(key, timeout)`:
+ * Do a blocking wait until a value (other than `nil`) is present at the key.
+ * **IMPORTANT**: You usually don't need this function. Use this as a last resort
+ if nothing else can satisfy your use case! None of the Lua environments the
+ engine has are safe to block for extended periods, especially on the main
+ thread any delays directly translate to lag felt by players.
+ * `key`: as above
+ * `timeout`: maximum wait time, in milliseconds (positive values only)
+ * returns: true on success, false on timeout
+
Bans
----
@@ -6932,10 +7197,11 @@ Bans
* Returns boolean indicating success
* `minetest.unban_player_or_ip(ip_or_name)`: remove ban record matching
IP address or name
-* `minetest.kick_player(name, [reason])`: disconnect a player with an optional
+* `minetest.kick_player(name[, reason[, reconnect]])`: disconnect a player with an optional
reason.
* Returns boolean indicating success (false if player nonexistent)
-* `minetest.disconnect_player(name, [reason])`: disconnect a player with an
+ * If `reconnect` is true, allow the user to reconnect.
+* `minetest.disconnect_player(name[, reason[, reconnect]])`: disconnect a player with an
optional reason, this will not prefix with 'Kicked: ' like kick_player.
If no reason is given, it will default to 'Disconnected.'
* Returns boolean indicating success (false if player nonexistent)
@@ -7101,7 +7367,7 @@ Misc.
could be used as a player name (regardless of whether said player exists).
* `minetest.hud_replace_builtin(name, hud_definition)`
* Replaces definition of a builtin hud element
- * `name`: `"breath"`, `"health"` or `"minimap"`
+ * `name`: `"breath"`, `"health"`, `"minimap"` or `"hotbar"`
* `hud_definition`: definition to replace builtin definition
* `minetest.parse_relative_number(arg, relative_to)`: returns number or nil
* Helper function for chat commands.
@@ -7320,6 +7586,17 @@ Misc.
* `minetest.global_exists(name)`
* Checks if a global variable has been set, without triggering a warning.
+* `minetest.register_portable_metatable(name, mt)`:
+ * Register a metatable that should be preserved when Lua data is transferred
+ between environments (via IPC or `handle_async`).
+ * `name` is a string that identifies the metatable. It is recommended to
+ follow the `modname:name` convention for this identifier.
+ * `mt` is the metatable to register.
+ * Note that the same metatable can be registered under multiple names,
+ but multiple metatables must not be registered under the same name.
+ * You must register the metatable in both the main environment
+ and the async environment for this mechanism to work.
+
Global objects
--------------
@@ -7930,8 +8207,7 @@ child will follow movement and rotation of that bone.
* Animation interpolates towards the end frame but stops when it is reached
* If looped, there is no interpolation back to the start frame
* If looped, the model should look identical at start and end
- * Only integer numbers are supported
- * default: `{x=1, y=1}`
+ * default: `{x=1.0, y=1.0}`
* `frame_speed`: How fast the animation plays, in frames per second (number)
* default: `15.0`
* `frame_blend`: number, default: `0.0`
@@ -7961,13 +8237,13 @@ child will follow movement and rotation of that bone.
object.
* `set_detach()`: Detaches object. No-op if object was not attached.
* `set_bone_position([bone, position, rotation])`
- * Shorthand for `set_bone_override(bone, {position = position, rotation = rotation:apply(math.rad)})` using absolute values.
- * **Note:** Rotation is in degrees, not radians.
- * **Deprecated:** Use `set_bone_override` instead.
+ * Shorthand for `set_bone_override(bone, {position = position, rotation = rotation:apply(math.rad)})` using absolute values.
+ * **Note:** Rotation is in degrees, not radians.
+ * **Deprecated:** Use `set_bone_override` instead.
* `get_bone_position(bone)`: returns the previously set position and rotation of the bone
- * Shorthand for `get_bone_override(bone).position.vec, get_bone_override(bone).rotation.vec:apply(math.deg)`.
- * **Note:** Returned rotation is in degrees, not radians.
- * **Deprecated:** Use `get_bone_override` instead.
+ * Shorthand for `get_bone_override(bone).position.vec, get_bone_override(bone).rotation.vec:apply(math.deg)`.
+ * **Note:** Returned rotation is in degrees, not radians.
+ * **Deprecated:** Use `get_bone_override` instead.
* `set_bone_override(bone, override)`
* `bone`: string
* `override`: `{ position = property, rotation = property, scale = property }` or `nil`
@@ -7984,7 +8260,7 @@ child will follow movement and rotation of that bone.
* Compatibility note: Clients prior to 5.9.0 only support absolute position and rotation.
All values are treated as absolute and are set immediately (no interpolation).
* `get_bone_override(bone)`: returns `override` in the above format
- * **Note:** Unlike `get_bone_position`, the returned rotation is in radians, not degrees.
+ * **Note:** Unlike `get_bone_position`, the returned rotation is in radians, not degrees.
* `get_bone_overrides()`: returns all bone overrides as table `{[bonename] = override, ...}`
* `set_properties(object property table)`
* `get_properties()`: returns a table of all object properties
@@ -8081,8 +8357,8 @@ child will follow movement and rotation of that bone.
* Fifth column: subject viewed from above
* Sixth column: subject viewed from below
* `get_luaentity()`:
- * Returns the object's associated luaentity table, if there is one
- * Otherwise returns `nil` (e.g. for players)
+ * Returns the object's associated luaentity table, if there is one
+ * Otherwise returns `nil` (e.g. for players)
* `get_entity_name()`:
* **Deprecated**: Will be removed in a future version,
use `:get_luaentity().name` instead.
@@ -8154,12 +8430,18 @@ child will follow movement and rotation of that bone.
bgcolor[], any non-style elements (eg: label) may result in weird behavior.
* Only affects formspecs shown after this is called.
* `get_formspec_prepend()`: returns a formspec string.
-* `get_player_control()`: returns table with player pressed keys
- * The table consists of fields with the following boolean values
- representing the pressed keys: `up`, `down`, `left`, `right`, `jump`,
- `aux1`, `sneak`, `dig`, `place`, `LMB`, `RMB`, and `zoom`.
+* `get_player_control()`: returns table with player input
+ * The table contains the following boolean fields representing the pressed
+ keys: `up`, `down`, `left`, `right`, `jump`, `aux1`, `sneak`, `dig`,
+ `place`, `LMB`, `RMB` and `zoom`.
* The fields `LMB` and `RMB` are equal to `dig` and `place` respectively,
and exist only to preserve backwards compatibility.
+ * The table also contains the fields `movement_x` and `movement_y`.
+ * They represent the movement of the player. Values are numbers in the
+ range [-1.0,+1.0].
+ * They take both keyboard and joystick input into account.
+ * You should prefer them over `up`, `down`, `left` and `right` to
+ support different input methods correctly.
* Returns an empty table `{}` if the object is not a player.
* `get_player_control_bits()`: returns integer with bit packed player pressed
keys.
@@ -8441,12 +8723,15 @@ child will follow movement and rotation of that bone.
if set to zero the clouds are rendered flat.
* `speed`: 2D cloud speed + direction in nodes per second
(default `{x=0, z=-2}`).
+ * `shadow`: shadow color, applied to the base of the cloud
+ (default `#cccccc`).
* `get_clouds()`: returns a table with the current cloud parameters as in
`set_clouds`.
* `override_day_night_ratio(ratio or nil)`
* `0`...`1`: Overrides day-night ratio, controlling sunlight to a specific
amount.
* Passing no arguments disables override, defaulting to sunlight based on day-night cycle
+ * See also `minetest.time_to_day_night_ratio`,
* `get_day_night_ratio()`: returns the ratio or nil if it isn't overridden
* `set_local_animation(idle, walk, dig, walk_while_dig, frame_speed)`:
set animation for player model in third person view.
@@ -8473,27 +8758,67 @@ child will follow movement and rotation of that bone.
* Passing no arguments resets lighting to its default values.
* `light_definition` is a table with the following optional fields:
* `saturation` sets the saturation (vividness; default: `1.0`).
- * values > 1 increase the saturation
- * values in [0,1] decrease the saturation
+ * It is applied according to the function `result = b*(1-s) + c*s`, where:
+ * `c` is the original color
+ * `b` is the greyscale version of the color with the same luma
+ * `s` is the saturation set here
+ * The resulting color always has the same luma (perceived brightness) as the original.
+ * This means that:
+ * values > 1 oversaturate
+ * values < 1 down to 0 desaturate, 0 being entirely greyscale
+ * values < 0 cause an effect similar to inversion,
+ but keeping original luma and being symmetrical in terms of saturation
+ (eg. -1 and 1 is the same saturation and luma, but different hues)
+ * This value has no effect on clients who have shaders or post-processing disabled.
* `shadows` is a table that controls ambient shadows
+ * This has no effect on clients who have the "Dynamic Shadows" effect disabled.
* `intensity` sets the intensity of the shadows from 0 (no shadows, default) to 1 (blackness)
- * This value has no effect on clients who have the "Dynamic Shadows" shader disabled.
+ * `tint` tints the shadows with the provided color, with RGB values ranging from 0 to 255.
+ (default `{r=0, g=0, b=0}`)
* `exposure` is a table that controls automatic exposure.
The basic exposure factor equation is `e = 2^exposure_correction / clamp(luminance, 2^luminance_min, 2^luminance_max)`
+ * This has no effect on clients who have the "Automatic Exposure" effect disabled.
* `luminance_min` set the lower luminance boundary to use in the calculation (default: `-3.0`)
* `luminance_max` set the upper luminance boundary to use in the calculation (default: `-3.0`)
* `exposure_correction` correct observed exposure by the given EV value (default: `0.0`)
* `speed_dark_bright` set the speed of adapting to bright light (default: `1000.0`)
* `speed_bright_dark` set the speed of adapting to dark scene (default: `1000.0`)
* `center_weight_power` set the power factor for center-weighted luminance measurement (default: `1.0`)
+ * `bloom` is a table that controls bloom.
+ * This has no effect on clients with protocol version < 46 or clients who
+ have the "Bloom" effect disabled.
+ * `intensity` defines much bloom is applied to the rendered image.
+ * Recommended range: from 0.0 to 1.0, default: 0.05
+ * If set to 0, bloom is disabled.
+ * The default value is to be changed from 0.05 to 0 in the future.
+ If you wish to keep the current default value, you should set it
+ explicitly.
+ * `strength_factor` defines the magnitude of bloom overexposure.
+ * Recommended range: from 0.1 to 10.0, default: 1.0
+ * `radius` is a logical value that controls how far the bloom effect
+ spreads from the bright objects.
+ * Recommended range: from 0.1 to 8.0, default: 1.0
+ * The behavior of values outside the recommended range is unspecified.
* `volumetric_light`: is a table that controls volumetric light (a.k.a. "godrays")
- * `strength`: sets the strength of the volumetric light effect from 0 (off, default) to 1 (strongest)
- * This value has no effect on clients who have the "Volumetric Lighting" or "Bloom" shaders disabled.
+ * This has no effect on clients who have the "Volumetric Lighting" or "Bloom" effects disabled.
+ * `strength`: sets the strength of the volumetric light effect from 0 (off, default) to 1 (strongest).
+ * `0.2` is a reasonable standard value.
+ * Currently, bloom `intensity` and `strength_factor` affect volumetric
+ lighting `strength` and vice versa. This behavior is to be changed
+ in the future, do not rely on it.
* `get_lighting()`: returns the current state of lighting for the player.
* Result is a table with the same fields as `light_definition` in `set_lighting`.
* `respawn()`: Respawns the player using the same mechanism as the death screen,
including calling `on_respawnplayer` callbacks.
+* `get_flags()`: returns a table of player flags (the following boolean fields):
+ * `breathing`: Whether breathing (regaining air) is enabled, default `true`.
+ * `drowning`: Whether drowning (losing air) is enabled, default `true`.
+ * `node_damage`: Whether the player takes damage from nodes, default `true`.
+* `set_flags(flags)`: sets flags
+ * takes a table in the same format as returned by `get_flags`
+ * absent fields are left unchanged
+
`PcgRandom`
-----------
@@ -8669,7 +8994,7 @@ In multiplayer mode, the error may be arbitrarily large.
Interface for the operating system's crypto-secure PRNG.
-It can be created via `SecureRandom()`. The constructor returns nil if a
+It can be created via `SecureRandom()`. The constructor throws an error if a
secure random device cannot be found on the system.
### Methods
@@ -8940,7 +9265,7 @@ Player properties need to be saved manually.
Entity definition
-----------------
-Used by `minetest.register_entity`.
+Used by `minetest.register_entity`.
The entity definition table becomes a metatable of a newly created per-entity
luaentity table, meaning its fields (e.g. `initial_properties`) will be shared
between all instances of an entity.
@@ -8996,6 +9321,11 @@ Used by `minetest.register_abm`.
-- If left out or empty, any neighbor will do.
-- `group:groupname` can also be used here.
+ without_neighbors = {"default:lava_source", "default:lava_flowing"},
+ -- Only apply `action` to nodes that have no one of these neighbors.
+ -- If left out or empty, it has no effect.
+ -- `group:groupname` can also be used here.
+
interval = 10.0,
-- Operation interval in seconds
@@ -9031,7 +9361,12 @@ Used by `minetest.register_lbm`.
A loading block modifier (LBM) is used to define a function that is called for
specific nodes (defined by `nodenames`) when a mapblock which contains such nodes
-gets activated (not loaded!)
+gets activated (not loaded!).
+
+Note: LBMs operate on a "snapshot" of node positions taken once before they are triggered.
+That means if an LBM callback adds a node, it won't be taken into account.
+However the engine guarantees that when the callback is called that all given position(s)
+contain a matching node.
```lua
{
@@ -9055,7 +9390,13 @@ gets activated (not loaded!)
action = function(pos, node, dtime_s) end,
-- Function triggered for each qualifying node.
-- `dtime_s` is the in-game time (in seconds) elapsed since the block
- -- was last active
+ -- was last active.
+
+ bulk_action = function(pos_list, dtime_s) end,
+ -- Function triggered with a list of all applicable node positions at once.
+ -- This can be provided as an alternative to `action` (not both).
+ -- Available since `minetest.features.bulk_lbms` (5.10.0)
+ -- `dtime_s`: as above
}
```
@@ -9262,9 +9603,17 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- If specified as a table, the field to be used is selected according to
-- the current `pointed_thing`.
-- There are three possible TouchInteractionMode values:
- -- * "user" (meaning depends on client-side settings)
-- * "long_dig_short_place" (long tap = dig, short tap = place)
-- * "short_dig_long_place" (short tap = dig, long tap = place)
+ -- * "user":
+ -- * For `pointed_object`: Equivalent to "short_dig_long_place" if the
+ -- client-side setting "touch_punch_gesture" is "short_tap" (the
+ -- default value) and the item is able to punch (i.e. has no on_use
+ -- callback defined).
+ -- Equivalent to "long_dig_short_place" otherwise.
+ -- * For `pointed_node` and `pointed_nothing`:
+ -- Equivalent to "long_dig_short_place".
+ -- * The behavior of "user" may change in the future.
-- The default value is "user".
sound = {
@@ -9386,12 +9735,18 @@ Used by `minetest.register_node`.
use_texture_alpha = ...,
-- Specifies how the texture's alpha channel will be used for rendering.
- -- possible values:
- -- * "opaque": Node is rendered opaque regardless of alpha channel
- -- * "clip": A given pixel is either fully see-through or opaque
- -- depending on the alpha channel being below/above 50% in value
- -- * "blend": The alpha channel specifies how transparent a given pixel
- -- of the rendered node is
+ -- Possible values:
+ -- * "opaque":
+ -- Node is rendered opaque regardless of alpha channel.
+ -- * "clip":
+ -- A given pixel is either fully see-through or opaque
+ -- depending on the alpha channel being below/above 50% in value.
+ -- Use this for nodes with fully transparent and fully opaque areas.
+ -- * "blend":
+ -- The alpha channel specifies how transparent a given pixel
+ -- of the rendered node is. This comes at a performance cost.
+ -- Only use this when correct rendering
+ -- among semitransparent nodes is necessary.
-- The default is "opaque" for drawtypes normal, liquid and flowingliquid,
-- mesh and nodebox or "clip" otherwise.
-- If set to a boolean value (deprecated): true either sets it to blend
@@ -10640,8 +10995,9 @@ Used by `ObjectRef:hud_add`. Returned by `ObjectRef:hud_get`.
```lua
{
type = "image",
- -- Type of element, can be "image", "text", "statbar", "inventory",
- -- "waypoint", "image_waypoint", "compass" or "minimap"
+ -- Type of element, can be "compass", "hotbar" (46 ¹), "image", "image_waypoint",
+ -- "inventory", "minimap" (44 ¹), "statbar", "text" or "waypoint"
+ -- ¹: minimal protocol version for client-side support
-- If undefined "text" will be used.
hud_elem_type = "image",
@@ -10987,7 +11343,7 @@ Types used are defined in the previous section.
* vec3 range `acc`: the direction and speed with which the particle
accelerates
-* vec3 range `size`: scales the visual size of the particle texture.
+* float range `size`: scales the visual size of the particle texture.
if `node` is set, this can be set to 0 to spawn randomly-sized particles
(just like actual node dig particles).
@@ -11292,6 +11648,16 @@ Functions: bit.tobit, bit.tohex, bit.bnot, bit.band, bit.bor, bit.bxor, bit.lshi
See http://bitop.luajit.org/ for advanced information.
+Tracy Profiler
+--------------
+
+Minetest can be built with support for the Tracy profiler, which can also be
+useful for profiling mods and is exposed to Lua as the global `tracy`.
+
+See doc/developing/misc.md for details.
+
+Note: This is a development feature and not covered by compatibility promises.
+
Error Handling
--------------
diff --git a/doc/menu_lua_api.md b/doc/menu_lua_api.md
index 9f0e23a11..c03c0501e 100644
--- a/doc/menu_lua_api.md
+++ b/doc/menu_lua_api.md
@@ -8,6 +8,15 @@ The main menu is defined as a formspec by Lua in `builtin/mainmenu/`
Description of formspec language to show your menu is in `lua_api.md`
+Images and 3D models
+------
+
+Directory delimiters change according to the OS (e.g. on Unix-like systems
+is `/`, on Windows is `\`). When putting an image or a 3D model inside a formspec,
+be sure to sanitize it first with `core.formspec_escape(img)`; otherwise,
+any resource located in a subpath won't be displayed on OSs using `\` as delimiter.
+
+
Callbacks
---------
@@ -48,7 +57,10 @@ Functions
* returns the maximum supported network protocol version
* `core.open_url(url)`
* opens the URL in a web browser, returns false on failure.
- * Must begin with http:// or https://
+ * `url` must begin with http:// or https://
+* `core.open_url_dialog(url)`
+ * shows a dialog to allow the user to choose whether to open a URL.
+ * `url` must begin with http:// or https://
* `core.open_dir(path)`
* opens the path in the system file browser/explorer, returns false on failure.
* Must be an existing directory.
@@ -56,12 +68,20 @@ Functions
* Android only. Shares file using the share popup
* `core.get_version()` (possible in async calls)
* returns current core version
+* `core.get_formspec_version()`
+ * returns maximum supported formspec version
Filesystem
----------
+To access specific subpaths, use `DIR_DELIM` as a directory delimiter instead
+of manually putting one, as different OSs use different delimiters. E.g.
+```lua
+"my" .. DIR_DELIM .. "custom" .. DIR_DELIM .. "path" -- and not my/custom/path
+```
+
* `core.get_builtin_path()`
* returns path to builtin root
* `core.create_dir(absolute_path)` (possible in async calls)
@@ -238,8 +258,8 @@ GUI
},
-- Estimated maximum formspec size before Minetest will start shrinking the
- -- formspec to fit. For a fullscreen formspec, use a size 10-20% larger than
- -- this and `padding[-0.01,-0.01]`.
+ -- formspec to fit. For a fullscreen formspec, use this formspec size and
+ -- `padding[0,0]`. `bgcolor[;true]` is also recommended.
max_formspec_size = {
x = 20,
y = 11.25
@@ -282,7 +302,7 @@ Package - content which is downloadable from the content db, may or may not be i
```lua
{
mods = "/home/user/.minetest/mods",
- share = "/usr/share/minetest/mods",
+ share = "/usr/share/minetest/mods", -- only provided when RUN_IN_PLACE=0
-- Custom dirs can be specified by the MINETEST_MOD_DIR env variable
["/path/to/custom/dir"] = "/path/to/custom/dir",
diff --git a/doc/world_format.md b/doc/world_format.md
index b5a2a3cfa..93920f391 100644
--- a/doc/world_format.md
+++ b/doc/world_format.md
@@ -394,7 +394,7 @@ Timestamp and node ID mappings were introduced in map format version 29.
* `u8` `name_id_mapping_version`
* Should be zero for map format version 29.
-
+
* `u16` `num_name_id_mappings`
* foreach `num_name_id_mappings`:
* `u16` `id`
diff --git a/games/devtest/mods/benchmarks/init.lua b/games/devtest/mods/benchmarks/init.lua
index 1f5001c69..e3a4409a5 100644
--- a/games/devtest/mods/benchmarks/init.lua
+++ b/games/devtest/mods/benchmarks/init.lua
@@ -154,3 +154,36 @@ minetest.register_chatcommand("bench_bulk_get_node", {
return true, msg
end,
})
+
+minetest.register_chatcommand("bench_bulk_swap_node", {
+ params = "",
+ description = "Benchmark: Bulk-swap 99×99×99 stone nodes",
+ func = function(name, param)
+ local player = minetest.get_player_by_name(name)
+ if not player then
+ return false, "No player."
+ end
+ local pos_list = get_positions_cube(player:get_pos())
+
+ minetest.chat_send_player(name, "Benchmarking minetest.bulk_swap_node. Warming up ...")
+
+ -- warm up because first execution otherwise becomes
+ -- significantly slower
+ minetest.bulk_swap_node(pos_list, {name = "mapgen_stone"})
+
+ minetest.chat_send_player(name, "Warming up finished, now benchmarking ...")
+
+ local start_time = minetest.get_us_time()
+ for i=1,#pos_list do
+ minetest.swap_node(pos_list[i], {name = "mapgen_stone"})
+ end
+ local middle_time = minetest.get_us_time()
+ minetest.bulk_swap_node(pos_list, {name = "mapgen_stone"})
+ local end_time = minetest.get_us_time()
+ local msg = string.format("Benchmark results: minetest.swap_node loop: %.2f ms; minetest.bulk_swap_node: %.2f ms",
+ ((middle_time - start_time)) / 1000,
+ ((end_time - middle_time)) / 1000
+ )
+ return true, msg
+ end,
+})
diff --git a/games/devtest/mods/gltf/LICENSE.md b/games/devtest/mods/gltf/LICENSE.md
new file mode 100644
index 000000000..6c3828a4a
--- /dev/null
+++ b/games/devtest/mods/gltf/LICENSE.md
@@ -0,0 +1,14 @@
+The glTF test models (and corresponding textures) in this mod are all licensed freely:
+
+* Spider (`gltf_spider.gltf`, `gltf_spider.png`):
+ * By [archfan7411](https://github.com/archfan7411)
+ * Licensed under CC0, public domain "wherever public domain carries fewer rights or legal protections"
+* Frog (`gltf_frog.gltf`, `gltf_frog.png`):
+ * By [Susybaka1234](https://sketchfab.com/3d-models/african-clawed-frog-v2-c81152c93948480c931c280d18957358)
+ * Licensed under CC-BY 4.0
+* Snow Man (`gltf_snow_man.gltf`, `gltf_snow_man.png`):
+ * By [jordan4ibanez](https://github.com/jordan4ibanez)
+ * Licensed under CC0
+* Minimal triangle, triangle without indices (`gltf_minimal_triangle.gltf`, `gltf_triangle_without_indices.gltf`)
+ * From [the glTF sample model collection](https://github.com/KhronosGroup/glTF-Sample-Models)
+ * Licensed under CC0 / public domain
diff --git a/games/devtest/mods/gltf/init.lua b/games/devtest/mods/gltf/init.lua
new file mode 100644
index 000000000..252fd017d
--- /dev/null
+++ b/games/devtest/mods/gltf/init.lua
@@ -0,0 +1,95 @@
+local function register_entity(name, textures, backface_culling)
+ minetest.register_entity("gltf:" .. name, {
+ initial_properties = {
+ visual = "mesh",
+ mesh = "gltf_" .. name .. ".gltf",
+ textures = textures,
+ backface_culling = backface_culling,
+ },
+ })
+end
+
+-- These do not have texture coordinates; they simple render as black surfaces.
+register_entity("minimal_triangle", {}, false)
+register_entity("triangle_with_vertex_stride", {}, false)
+register_entity("triangle_without_indices", {}, false)
+do
+ local cube_textures = {"gltf_cube.png"}
+ register_entity("blender_cube", cube_textures)
+ register_entity("blender_cube_scaled", cube_textures)
+ register_entity("blender_cube_matrix_transform", cube_textures)
+ minetest.register_entity("gltf:blender_cube_glb", {
+ initial_properties = {
+ visual = "mesh",
+ mesh = "gltf_blender_cube.glb",
+ textures = cube_textures,
+ backface_culling = true,
+ },
+ })
+end
+
+register_entity("snow_man", {"gltf_snow_man.png"})
+register_entity("spider", {"gltf_spider.png"})
+
+minetest.register_entity("gltf:spider_animated", {
+ initial_properties = {
+ visual = "mesh",
+ mesh = "gltf_spider_animated.gltf",
+ textures = {"gltf_spider.png"},
+ },
+ on_activate = function(self)
+ self.object:set_animation({x = 0, y = 140}, 1)
+ end
+})
+
+minetest.register_entity("gltf:simple_skin", {
+ initial_properties = {
+ visual = "mesh",
+ visual_size = vector.new(5, 5, 5),
+ mesh = "gltf_simple_skin.gltf",
+ textures = {},
+ backface_culling = false
+ },
+ on_activate = function(self)
+ self.object:set_animation({x = 0, y = 5.5}, 1)
+ end
+})
+
+-- The claws rendering incorrectly from one side is expected behavior:
+-- They use an unsupported double-sided material.
+minetest.register_entity("gltf:frog", {
+ initial_properties = {
+ visual = "mesh",
+ mesh = "gltf_frog.gltf",
+ textures = {"gltf_frog.png"},
+ backface_culling = false
+ },
+ on_activate = function(self)
+ self.object:set_animation({x = 0, y = 0.75}, 1)
+ end
+})
+
+
+minetest.register_node("gltf:frog", {
+ description = "glTF frog, but it's a node",
+ tiles = {{name = "gltf_frog.png", backface_culling = false}},
+ drawtype = "mesh",
+ mesh = "gltf_frog.gltf",
+})
+
+minetest.register_chatcommand("show_model", {
+ params = " [textures]",
+ description = "Show a model (defaults to gltf models, for example '/show_model frog').",
+ func = function(name, param)
+ local model, textures = param:match"^(.-)%s+(.+)$"
+ if not model then
+ model = "gltf_" .. param .. ".gltf"
+ textures = "gltf_" .. param .. ".png"
+ end
+ minetest.show_formspec(name, "gltf:model", table.concat{
+ "formspec_version[7]",
+ "size[10,10]",
+ "model[0,0;10,10;model;", model, ";", textures, ";0,0;true;true;0,0;0]",
+ })
+ end,
+})
diff --git a/games/devtest/mods/gltf/invalid/empty.gltf b/games/devtest/mods/gltf/invalid/empty.gltf
new file mode 100644
index 000000000..e69de29bb
diff --git a/games/devtest/mods/gltf/invalid/invalid_bufferview_bounds.gltf b/games/devtest/mods/gltf/invalid/invalid_bufferview_bounds.gltf
new file mode 100644
index 000000000..2182861c6
--- /dev/null
+++ b/games/devtest/mods/gltf/invalid/invalid_bufferview_bounds.gltf
@@ -0,0 +1 @@
+{"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"buffers":[{"uri":"data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA","byteLength":36}],"bufferViews":[{"buffer":0,"byteOffset":1,"byteLength":36,"target":34962}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5126,"count":3,"type":"VEC3","max":[1,1,0],"min":[0,0,0]}],"asset":{"version":"2.0"}}
diff --git a/games/devtest/mods/gltf/invalid/json_missing_brace.gltf b/games/devtest/mods/gltf/invalid/json_missing_brace.gltf
new file mode 100644
index 000000000..98232c64f
--- /dev/null
+++ b/games/devtest/mods/gltf/invalid/json_missing_brace.gltf
@@ -0,0 +1 @@
+{
diff --git a/games/devtest/mods/gltf/mod.conf b/games/devtest/mods/gltf/mod.conf
new file mode 100644
index 000000000..3ec50d2ef
--- /dev/null
+++ b/games/devtest/mods/gltf/mod.conf
@@ -0,0 +1,2 @@
+name = gltf
+description = Hosts gltf test models, both for the C++ unit tests and for in-game viewing
diff --git a/games/devtest/mods/gltf/models/gltf_blender_cube.glb b/games/devtest/mods/gltf/models/gltf_blender_cube.glb
new file mode 100644
index 000000000..b1894fc4f
Binary files /dev/null and b/games/devtest/mods/gltf/models/gltf_blender_cube.glb differ
diff --git a/games/devtest/mods/gltf/models/gltf_blender_cube.gltf b/games/devtest/mods/gltf/models/gltf_blender_cube.gltf
new file mode 100644
index 000000000..041b4a1fc
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_blender_cube.gltf
@@ -0,0 +1 @@
+{"asset":{"generator":"Khronos glTF Blender I/O v1.7.33","version":"2.0"},"scene":0,"scenes":[{"name":"Scene","nodes":[0]}],"nodes":[{"mesh":0,"name":"Cube","scale":[10,10,10]}],"meshes":[{"name":"Cube.004","primitives":[{"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3}]}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"type":"SCALAR"}],"bufferViews":[{"buffer":0,"byteLength":288,"byteOffset":0},{"buffer":0,"byteLength":288,"byteOffset":288},{"buffer":0,"byteLength":192,"byteOffset":576},{"buffer":0,"byteLength":72,"byteOffset":768}],"buffers":[{"byteLength":840,"uri":"data:application/octet-stream;base64,AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAADAPgAAgD8AAAA+AACAPgAAwD4AAAAAAAAgPwAAgD8AACA/AAAAAAAAYD8AAIA+AADAPgAAQD8AAAA+AAAAPwAAwD4AAEA/AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AAADAAkAAAAJAAYACAAKABUACAAVABMAFAAXABEAFAARAA4ADQAPAAQADQAEAAIABwASAAwABwAMAAEAFgALAAUAFgAFABAA"}]}
diff --git a/games/devtest/mods/gltf/models/gltf_blender_cube_matrix_transform.gltf b/games/devtest/mods/gltf/models/gltf_blender_cube_matrix_transform.gltf
new file mode 100644
index 000000000..50235ceae
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_blender_cube_matrix_transform.gltf
@@ -0,0 +1 @@
+{"asset":{"generator":"Khronos glTF Blender I/O v1.7.33","version":"2.0"},"scene":0,"scenes":[{"name":"Scene","nodes":[0]}],"nodes":[{"mesh":0,"name":"Cube","matrix":[1,0,0,0,0,2,0,0,0,0,3,0,4,5,6,1]}],"meshes":[{"name":"Cube.004","primitives":[{"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3}]}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"type":"SCALAR"}],"bufferViews":[{"buffer":0,"byteLength":288,"byteOffset":0},{"buffer":0,"byteLength":288,"byteOffset":288},{"buffer":0,"byteLength":192,"byteOffset":576},{"buffer":0,"byteLength":72,"byteOffset":768}],"buffers":[{"byteLength":840,"uri":"data:application/octet-stream;base64,AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAADAPgAAgD8AAAA+AACAPgAAwD4AAAAAAAAgPwAAgD8AACA/AAAAAAAAYD8AAIA+AADAPgAAQD8AAAA+AAAAPwAAwD4AAEA/AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AAADAAkAAAAJAAYACAAKABUACAAVABMAFAAXABEAFAARAA4ADQAPAAQADQAEAAIABwASAAwABwAMAAEAFgALAAUAFgAFABAA"}]}
diff --git a/games/devtest/mods/gltf/models/gltf_blender_cube_scaled.gltf b/games/devtest/mods/gltf/models/gltf_blender_cube_scaled.gltf
new file mode 100644
index 000000000..3b626b37e
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_blender_cube_scaled.gltf
@@ -0,0 +1 @@
+{"asset":{"generator":"Khronos glTF Blender I/O v1.7.33","version":"2.0"},"scene":0,"scenes":[{"name":"Scene","nodes":[0]}],"nodes":[{"mesh":0,"name":"Cube","scale":[150,1,21.5]}],"meshes":[{"name":"Cube.004","primitives":[{"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3}]}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"type":"SCALAR"}],"bufferViews":[{"buffer":0,"byteLength":288,"byteOffset":0},{"buffer":0,"byteLength":288,"byteOffset":288},{"buffer":0,"byteLength":192,"byteOffset":576},{"buffer":0,"byteLength":72,"byteOffset":768}],"buffers":[{"byteLength":840,"uri":"data:application/octet-stream;base64,AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAvwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAADAPgAAgD8AAAA+AACAPgAAwD4AAAAAAAAgPwAAgD8AACA/AAAAAAAAYD8AAIA+AADAPgAAQD8AAAA+AAAAPwAAwD4AAEA/AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AAADAAkAAAAJAAYACAAKABUACAAVABMAFAAXABEAFAARAA4ADQAPAAQADQAEAAIABwASAAwABwAMAAEAFgALAAUAFgAFABAA"}]}
diff --git a/games/devtest/mods/gltf/models/gltf_frog.gltf b/games/devtest/mods/gltf/models/gltf_frog.gltf
new file mode 100644
index 000000000..201604fd3
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_frog.gltf
@@ -0,0 +1 @@
+{"asset":{"version":"2.0","generator":"Blockbench 4.9.4 glTF exporter"},"scenes":[{"nodes":[20],"name":"blockbench_export"}],"scene":0,"nodes":[{"name":"cube","mesh":0},{"name":"cube","mesh":1},{"name":"cube","mesh":2},{"name":"body","children":[0,1,2]},{"translation":[0,0,-0.0625],"name":"cube","mesh":3},{"translation":[0.03125,0,-0.3125],"name":"cube","mesh":4},{"rotation":[0,-0.19509032201612825,0,0.9807852804032304],"translation":[0.01812248876854733,-0.0625,-0.25194388507103505],"name":"cube","mesh":5},{"translation":[0.0625,0,0.3125],"name":"leftleg","children":[4,5,6]},{"translation":[0.0625,0,-0.3125],"name":"cube","mesh":6},{"translation":[-0.03125,0,-0.3125],"name":"cube","mesh":7},{"rotation":[0,0.19509032201612825,0,0.9807852804032304],"translation":[-0.01812248876854733,-0.0625,-0.25194388507103505],"name":"cube","mesh":8},{"translation":[-0.0625,0,0.3125],"name":"rightleg","children":[8,9,10]},{"translation":[-0.125,-0.0625,0.125],"name":"cube","mesh":9},{"rotation":[0,0.5372996083468239,0,0.8433914458128857],"translation":[0.10431178959951112,-0.0625,0.2349474087973531],"name":"cube","mesh":10},{"rotation":[0,0.5372996083468239,0,0.8433914458128857],"translation":[0.10431178959951112,-0.0625,0.2349474087973531],"name":"cube","mesh":11},{"translation":[0.125,0.0625,-0.125],"name":"leftarm","children":[12,13,14]},{"translation":[0.125,-0.0625,0.125],"name":"cube","mesh":12},{"rotation":[0,-0.5372996083468239,0,0.8433914458128857],"translation":[-0.10431178959951112,-0.0625,0.2349474087973531],"name":"cube","mesh":13},{"rotation":[0,-0.5372996083468239,0,0.8433914458128857],"translation":[-0.10431178959951112,-0.0625,0.2349474087973531],"name":"cube","mesh":14},{"translation":[-0.125,0.0625,-0.125],"name":"rightarm","children":[16,17,18]},{"children":[3,7,11,15,19]}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":288,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":576,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":768,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":840,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1128,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1416,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":1608,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":1680,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1968,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":2256,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":2448,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":2520,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":2808,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":3096,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":3288,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":3360,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":3648,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":3936,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":4128,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":4200,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":4488,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":4776,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":4968,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":5040,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":5328,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":5616,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":5808,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":5880,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":6168,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":6456,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":6648,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":6720,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":7008,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":7296,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":7488,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":7560,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":7848,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":8136,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":8328,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":8400,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":8688,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":8976,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":9168,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":9240,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":9528,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":9816,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":10008,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":10080,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":10368,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":10656,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":10848,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":10920,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":11208,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":11496,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":11688,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":11760,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":12048,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":12336,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":12528,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":12600,"byteLength":12},{"buffer":0,"byteOffset":12612,"byteLength":48},{"buffer":0,"byteOffset":12660,"byteLength":12},{"buffer":0,"byteOffset":12672,"byteLength":48},{"buffer":0,"byteOffset":12720,"byteLength":12},{"buffer":0,"byteOffset":12732,"byteLength":48},{"buffer":0,"byteOffset":12780,"byteLength":12},{"buffer":0,"byteOffset":12792,"byteLength":48},{"buffer":0,"byteOffset":12840,"byteLength":12},{"buffer":0,"byteOffset":12852,"byteLength":48},{"buffer":0,"byteOffset":12900,"byteLength":12},{"buffer":0,"byteOffset":12912,"byteLength":48},{"buffer":0,"byteOffset":12960,"byteLength":12},{"buffer":0,"byteOffset":12972,"byteLength":48},{"buffer":0,"byteOffset":13020,"byteLength":12},{"buffer":0,"byteOffset":13032,"byteLength":48},{"buffer":0,"byteOffset":13080,"byteLength":12},{"buffer":0,"byteOffset":13092,"byteLength":48},{"buffer":0,"byteOffset":13140,"byteLength":4},{"buffer":0,"byteOffset":13144,"byteLength":16},{"buffer":0,"byteOffset":13160,"byteLength":4},{"buffer":0,"byteOffset":13164,"byteLength":16}],"buffers":[{"byteLength":13180,"uri":"data:application/octet-stream;base64,AAAgPgAAAD4AAIA+AAAgPgAAAD4AAIC9AAAgPgAAAAAAAIA+AAAgPgAAAAAAAIC9AAAgvgAAAD4AAIC9AAAgvgAAAD4AAIA+AAAgvgAAAAAAAIC9AAAgvgAAAAAAAIA+AAAgvgAAAD4AAIC9AAAgPgAAAD4AAIC9AAAgvgAAAD4AAIA+AAAgPgAAAD4AAIA+AAAgvgAAAAAAAIA+AAAgPgAAAAAAAIA+AAAgvgAAAAAAAIC9AAAgPgAAAAAAAIC9AAAgvgAAAD4AAIA+AAAgPgAAAD4AAIA+AAAgvgAAAAAAAIA+AAAgPgAAAAAAAIA+AAAgPgAAAD4AAIC9AAAgvgAAAD4AAIC9AAAgPgAAAAAAAIC9AAAgvgAAAAAAAIC9AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgCAID4AgB8+AIAgPgAAADoAgF8+AIAfPgCAXz4AQKA+AIAgPgDA7z4AgCA+AECgPgCAXz4AwO8+AIBfPgDAnz4AgB8+AIAgPgCAHz4AwJ8+AAAAOgCAID4AAAA6AMDvPgAAADoAQKA+AAAAOgDA7z4AgB8+AECgPgCAHz4AQPA+AIAgPgDgHz8AgCA+AEDwPgCAXz4A4B8/AIBfPgCAID4AgCA+AMCfPgCAID4AgCA+AIBfPgDAnz4AgF8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAAAAPgAAAD4AAIC9AAAAPgAAAD4AAKC+AAAAPgAAAAAAAIC9AAAAPgAAAAAAAKC+AAAAvgAAAD4AAKC+AAAAvgAAAD4AAIC9AAAAvgAAAAAAAKC+AAAAvgAAAAAAAIC9AAAAvgAAAD4AAKC+AAAAPgAAAD4AAKC+AAAAvgAAAD4AAIC9AAAAPgAAAD4AAIC9AAAAvgAAAAAAAIC9AAAAPgAAAAAAAIC9AAAAvgAAAAAAAKC+AAAAPgAAAAAAAKC+AAAAvgAAAD4AAIC9AAAAPgAAAD4AAIC9AAAAvgAAAAAAAIC9AAAAPgAAAAAAAIC9AAAAPgAAAD4AAKC+AAAAvgAAAD4AAKC+AAAAPgAAAAAAAKC+AAAAvgAAAAAAAKC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgBAsD4AAP89AECwPgAAADoAwM8+AAD/PQDAzz4AQIA+AECwPgDAvz4AQLA+AECAPgDAzz4AwL8+AMDPPgCAfz4AwK8+AIAAPgDArz4AgH8+AIBgPgCAAD4AgGA+AMC/PgCAYD4AQIA+AIBgPgDAvz4AwK8+AECAPgDArz4AQMA+AECwPgDA/z4AQLA+AEDAPgDAzz4AwP8+AMDPPgCAAD4AQLA+AIB/PgBAsD4AgAA+AMDPPgCAfz4AwM8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAAAAPQAAAD4AAKA+AAAAPQAAAD4AAIA+AAAAPQAAAAAAAKA+AAAAPQAAAAAAAIA+AAAAvQAAAD4AAIA+AAAAvQAAAD4AAKA+AAAAvQAAAAAAAIA+AAAAvQAAAAAAAKA+AAAAvQAAAD4AAIA+AAAAPQAAAD4AAIA+AAAAvQAAAD4AAKA+AAAAPQAAAD4AAKA+AAAAvQAAAAAAAKA+AAAAPQAAAAAAAKA+AAAAvQAAAAAAAIA+AAAAPQAAAAAAAIA+AAAAvQAAAD4AAKA+AAAAPQAAAD4AAKA+AAAAvQAAAAAAAKA+AAAAPQAAAAAAAKA+AAAAPQAAAD4AAIA+AAAAvQAAAD4AAIA+AAAAPQAAAAAAAIA+AAAAvQAAAAAAAIA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AEDwPgAAAj0AwP8+AAACPQBA8D4AAL89AMD/PgAAvz0AIAg/AAACPQDgDz8AAAI9ACAIPwAAvz0A4A8/AAC/PQDgBz8AAPw8ACAAPwAA/DwA4Ac/AAAAOgAgAD8AAAA6AOAPPwAAADoAIAg/AAAAOgDgDz8AAPw8ACAIPwAA/DwAIBA/AAACPQDgFz8AAAI9ACAQPwAAvz0A4Bc/AAC/PQAgAD8AAAI9AOAHPwAAAj0AIAA/AAC/PQDgBz8AAL89AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAABgPgAAAD4AAEA+AABgPgAAAD4AAAAAAABgPgAAAAAAAEA+AABgPgAAAAAAAAAAAAAAvQAAAD4AAAAAAAAAvQAAAD4AAEA+AAAAvQAAAAAAAAAAAAAAvQAAAAAAAEA+AAAAvQAAAD4AAAAAAABgPgAAAD4AAAAAAAAAvQAAAD4AAEA+AABgPgAAAD4AAEA+AAAAvQAAAAAAAEA+AABgPgAAAAAAAEA+AAAAvQAAAAAAAAAAAABgPgAAAAAAAAAAAAAAvQAAAD4AAEA+AABgPgAAAD4AAEA+AAAAvQAAAAAAAEA+AABgPgAAAAAAAEA+AABgPgAAAD4AAAAAAAAAvQAAAD4AAAAAAABgPgAAAAAAAAAAAAAAvQAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AEDQPgBA0D4AwP8+AEDQPgBA0D4AwO8+AMD/PgDA7z4AICA/AEDQPgDgNz8AQNA+ACAgPwDA7z4A4Dc/AMDvPgDgHz8AwM8+ACAAPwDAzz4A4B8/AECgPgAgAD8AQKA+AOA/PwBAoD4AICA/AECgPgDgPz8AwM8+ACAgPwDAzz4AIDg/AEDQPgDgVz8AQNA+ACA4PwDA7z4A4Fc/AMDvPgAgAD8AQNA+AOAfPwBA0D4AIAA/AMDvPgDgHz8AwO8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAABgPgAAwD0AABA/AABgPgAAwD0AAKA+AABgPgAAAD0AABA/AABgPgAAAD0AAKA+AADAPQAAwD0AAKA+AADAPQAAwD0AABA/AADAPQAAAD0AAKA+AADAPQAAAD0AABA/AADAPQAAwD0AAKA+AABgPgAAwD0AAKA+AADAPQAAwD0AABA/AABgPgAAwD0AABA/AADAPQAAAD0AABA/AABgPgAAAD0AABA/AADAPQAAAD0AAKA+AABgPgAAAD0AAKA+AADAPQAAwD0AABA/AABgPgAAwD0AABA/AADAPQAAAD0AABA/AABgPgAAAD0AABA/AABgPgAAwD0AAKA+AADAPQAAwD0AAKA+AABgPgAAAD0AAKA+AADAPQAAAD0AAKA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AECgPgAgGD8AwN8+ACAYPwBAoD4A4B8/AMDfPgDgHz8AIAA/ACAYPwDgHz8AIBg/ACAAPwDgHz8A4B8/AOAfPwDA/z4A4Bc/AEDgPgDgFz8AwP8+AEDwPgBA4D4AQPA+AOAPPwBA8D4AIAA/AEDwPgDgDz8A4Bc/ACAAPwDgFz8AICA/ACAYPwDgLz8AIBg/ACAgPwDgHz8A4C8/AOAfPwBA4D4AIBg/AMD/PgAgGD8AQOA+AOAfPwDA/z4A4B8/AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAMQjQPgAAQD4AAMA+MQjQPgAAQD4AAIA+MQjQPgAAgD0AAMA+MQjQPgAAgD0AAIA+AADQPgAAQD4AAIA+AADQPgAAQD4AAMA+AADQPgAAgD0AAIA+AADQPgAAgD0AAMA+AADQPgAAQD4AAIA+MQjQPgAAQD4AAIA+AADQPgAAQD4AAMA+MQjQPgAAQD4AAMA+AADQPgAAgD0AAMA+MQjQPgAAgD0AAMA+AADQPgAAgD0AAIA+MQjQPgAAgD0AAIA+AADQPgAAQD4AAMA+MQjQPgAAQD4AAMA+AADQPgAAgD0AAMA+MQjQPgAAgD0AAMA+MQjQPgAAQD4AAIA+AADQPgAAQD4AAIA+MQjQPgAAgD0AAIA+AADQPgAAgD0AAIA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgCAYD4AAH49AIBgPgAAADoAwI8+AAB+PQDAjz4AAIE9AIBgPgAA/z0AgGA+AACBPQDAjz4AAP89AMCPPgAAgT0AgF8+AAB+PQCAXz4AAIE9AIAgPgAAfj0AgCA+AACBPQCAID4AAH49AIAgPgAAgT0AgF8+AAB+PQCAXz4AgAA+AIBgPgAA/z0AgGA+AIAAPgDAjz4AAP89AMCPPgAAgT0AgGA+AAB+PQCAYD4AAIE9AMCPPgAAfj0AwI8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAAAAvQAAAD4AAOA+AAAAvQAAAD4AAIA+AAAAvQAAAAAAAOA+AAAAvQAAAAAAAIA+AACQvgAAAD4AAIA+AACQvgAAAD4AAOA+AACQvgAAAAAAAIA+AACQvgAAAAAAAOA+AACQvgAAAD4AAIA+AAAAvQAAAD4AAIA+AACQvgAAAD4AAOA+AAAAvQAAAD4AAOA+AACQvgAAAAAAAOA+AAAAvQAAAAAAAOA+AACQvgAAAAAAAIA+AAAAvQAAAAAAAIA+AACQvgAAAD4AAOA+AAAAvQAAAD4AAOA+AACQvgAAAAAAAOA+AAAAvQAAAAAAAOA+AAAAvQAAAD4AAIA+AACQvgAAAD4AAIA+AAAAvQAAAAAAAIA+AACQvgAAAAAAAIA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgAgAD8AAL89ACAAPwAAADoA4A8/AAC/PQDgDz8AgGA+ACAAPwDAnz4AIAA/AIBgPgDgDz8AwJ8+AOAPPwCAXz4AwP8+AADBPQDA/z4AgF8+AEDQPgAAwT0AQNA+AMCvPgBA0D4AgGA+AEDQPgDArz4AwP8+AIBgPgDA/z4AQKA+ACAAPwDA3z4AIAA/AECgPgDgDz8AwN8+AOAPPwAAwT0AIAA/AIBfPgAgAD8AAME9AOAPPwCAXz4A4A8/AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAADAvQAAwD0AABA/AADAvQAAwD0AAKA+AADAvQAAAD0AABA/AADAvQAAAD0AAKA+AABgvgAAwD0AAKA+AABgvgAAwD0AABA/AABgvgAAAD0AAKA+AABgvgAAAD0AABA/AABgvgAAwD0AAKA+AADAvQAAwD0AAKA+AABgvgAAwD0AABA/AADAvQAAwD0AABA/AABgvgAAAD0AABA/AADAvQAAAD0AABA/AABgvgAAAD0AAKA+AADAvQAAAD0AAKA+AABgvgAAwD0AABA/AADAvQAAwD0AABA/AABgvgAAAD0AABA/AADAvQAAAD0AABA/AADAvQAAwD0AAKA+AABgvgAAwD0AAKA+AADAvQAAAD0AAKA+AABgvgAAAD0AAKA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AEDwPgCAAD4A4Bc/AIAAPgBA8D4AgB8+AOAXPwCAHz4AICg/AIAAPgDgRz8AgAA+ACAoPwCAHz4A4Ec/AIAfPgDgJz8AAP89ACAYPwAA/z0A4Cc/AAAAOgAgGD8AAAA6AOA3PwAAADoAICg/AAAAOgDgNz8AAP89ACAoPwAA/z0AIEg/AIAAPgDgVz8AgAA+ACBIPwCAHz4A4Fc/AIAfPgAgGD8AgAA+AOAnPwCAAD4AIBg/AIAfPgDgJz8AgB8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAz/fPvgAAQD4AAMA+z/fPvgAAQD4AAIA+z/fPvgAAgD0AAMA+z/fPvgAAgD0AAIA+AADQvgAAQD4AAIA+AADQvgAAQD4AAMA+AADQvgAAgD0AAIA+AADQvgAAgD0AAMA+AADQvgAAQD4AAIA+z/fPvgAAQD4AAIA+AADQvgAAQD4AAMA+z/fPvgAAQD4AAMA+AADQvgAAgD0AAMA+z/fPvgAAgD0AAMA+AADQvgAAgD0AAIA+z/fPvgAAgD0AAIA+AADQvgAAQD4AAMA+z/fPvgAAQD4AAMA+AADQvgAAgD0AAMA+z/fPvgAAgD0AAMA+z/fPvgAAQD4AAIA+AADQvgAAQD4AAIA+z/fPvgAAgD0AAIA+AADQvgAAgD0AAIA+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgAAgT0AAH49AACBPQAAADoAAP89AAB+PQAA/z0AAIE9AACBPQAA/z0AAIE9AACBPQAA/z0AAP89AAD/PQAAgT0AAH49AAB+PQAAfj0AAIE9AAAAOgAAfj0AAAA6AACBPQAAADoAAH49AAAAOgAAgT0AAH49AAB+PQAAfj0AgAA+AACBPQAA/z0AAIE9AIAAPgAA/z0AAP89AAD/PQAAgT0AAIE9AAB+PQAAgT0AAIE9AAD/PQAAfj0AAP89AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAACAPgAAwD0AAMC9AACAPgAAwD0AACC+AACAPgAAAD0AAMC9AACAPgAAAD0AACC+AAAAPgAAwD0AACC+AAAAPgAAwD0AAMC9AAAAPgAAAD0AACC+AAAAPgAAAD0AAMC9AAAAPgAAwD0AACC+AACAPgAAwD0AACC+AAAAPgAAwD0AAMC9AACAPgAAwD0AAMC9AAAAPgAAAD0AAMC9AACAPgAAAD0AAMC9AAAAPgAAAD0AACC+AACAPgAAAD0AACC+AAAAPgAAwD0AAMC9AACAPgAAwD0AAMC9AAAAPgAAAD0AAMC9AACAPgAAAD0AAMC9AACAPgAAwD0AACC+AAAAPgAAwD0AACC+AACAPgAAAD0AACC+AAAAPgAAAD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/ACAIPwBAkD4A4A8/AECQPgAgCD8AwJ8+AOAPPwDAnz4AICA/AECQPgDgJz8AQJA+ACAgPwDAnz4A4Cc/AMCfPgDgHz8AwI8+ACAQPwDAjz4A4B8/AECAPgAgED8AQIA+AOAvPwBAgD4AICA/AECAPgDgLz8AwI8+ACAgPwDAjz4AICg/AECQPgDgNz8AQJA+ACAoPwDAnz4A4Dc/AMCfPgAgED8AQJA+AOAfPwBAkD4AIBA/AMCfPgDgHz8AwJ8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAACQPgAAwD0AAMC9AACQPgAAwD0AACC+AACQPgAAAD0AAMC9AACQPgAAAD0AACC+AABgPgAAwD0AACC+AABgPgAAwD0AAMC9AABgPgAAAD0AACC+AABgPgAAAD0AAMC9AABgPgAAwD0AACC+AACQPgAAwD0AACC+AABgPgAAwD0AAMC9AACQPgAAwD0AAMC9AABgPgAAAD0AAMC9AACQPgAAAD0AAMC9AABgPgAAAD0AACC+AACQPgAAAD0AACC+AABgPgAAwD0AAMC9AACQPgAAwD0AAMC9AABgPgAAAD0AAMC9AACQPgAAAD0AAMC9AACQPgAAwD0AACC+AABgPgAAwD0AACC+AACQPgAAAD0AACC+AABgPgAAAD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgBAoD4AAPw8AECgPgAAADoAwK8+AAD8PADArz4AAIE9AECgPgAAvz0AQKA+AACBPQDArz4AAL89AMCvPgAAfj0AwJ8+AAACPQDAnz4AAH49AECQPgAAAj0AQJA+AAC/PQBAkD4AAIE9AECQPgAAvz0AwJ8+AACBPQDAnz4AAME9AECgPgAA/z0AQKA+AADBPQDArz4AAP89AMCvPgAAAj0AQKA+AAB+PQBAoD4AAAI9AMCvPgAAfj0AwK8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAACwPsUggD0AAMC9AACwPsUggD0AACC+AACwPgAAgD0AAMC9AACwPgAAgD0AACC+AACQPsUggD0AACC+AACQPsUggD0AAMC9AACQPgAAgD0AACC+AACQPgAAgD0AAMC9AACQPsUggD0AACC+AACwPsUggD0AACC+AACQPsUggD0AAMC9AACwPsUggD0AAMC9AACQPgAAgD0AAMC9AACwPgAAgD0AAMC9AACQPgAAgD0AACC+AACwPgAAgD0AACC+AACQPsUggD0AAMC9AACwPsUggD0AAMC9AACQPgAAgD0AAMC9AACwPgAAgD0AAMC9AACwPsUggD0AACC+AACQPsUggD0AACC+AACwPgAAgD0AACC+AACQPgAAgD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgCAID4AAPw8AIAgPgAAADoAgB8+AAD8PACAHz4AAIE9AIAgPgAAvz0AgCA+AACBPQCAHz4AAL89AIAfPgAAfj0AgB8+AAACPQCAHz4AAH49AIAAPgAAAj0AgAA+AAC/PQCAAD4AAIE9AIAAPgAAvz0AgB8+AACBPQCAHz4AAME9AIAgPgAA/z0AgCA+AADBPQCAHz4AAP89AIAfPgAAAj0AgCA+AAB+PQCAID4AAAI9AIAfPgAAfj0AgB8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAAAAvgAAwD0AAMC9AAAAvgAAwD0AACC+AAAAvgAAAD0AAMC9AAAAvgAAAD0AACC+AACAvgAAwD0AACC+AACAvgAAwD0AAMC9AACAvgAAAD0AACC+AACAvgAAAD0AAMC9AACAvgAAwD0AACC+AAAAvgAAwD0AACC+AACAvgAAwD0AAMC9AAAAvgAAwD0AAMC9AACAvgAAAD0AAMC9AAAAvgAAAD0AAMC9AACAvgAAAD0AACC+AAAAvgAAAD0AACC+AACAvgAAwD0AAMC9AAAAvgAAwD0AAMC9AACAvgAAAD0AAMC9AAAAvgAAAD0AAMC9AAAAvgAAwD0AACC+AACAvgAAwD0AACC+AAAAvgAAAD0AACC+AACAvgAAAD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AEDAPgBAgD4AwM8+AECAPgBAwD4AwI8+AMDPPgDAjz4AQPA+AECAPgDA/z4AQIA+AEDwPgDAjz4AwP8+AMCPPgDA7z4AgH8+AEDQPgCAfz4AwO8+AIBgPgBA0D4AgGA+AOAHPwCAYD4AQPA+AIBgPgDgBz8AgH8+AEDwPgCAfz4AIAA/AECAPgDgDz8AQIA+ACAAPwDAjz4A4A8/AMCPPgBA0D4AQIA+AMDvPgBAgD4AQNA+AMCPPgDA7z4AwI8+AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAABgvgAAwD0AAMC9AABgvgAAwD0AACC+AABgvgAAAD0AAMC9AABgvgAAAD0AACC+AACQvgAAwD0AACC+AACQvgAAwD0AAMC9AACQvgAAAD0AACC+AACQvgAAAD0AAMC9AACQvgAAwD0AACC+AABgvgAAwD0AACC+AACQvgAAwD0AAMC9AABgvgAAwD0AAMC9AACQvgAAAD0AAMC9AABgvgAAAD0AAMC9AACQvgAAAD0AACC+AABgvgAAAD0AACC+AACQvgAAwD0AAMC9AABgvgAAwD0AAMC9AACQvgAAAD0AAMC9AABgvgAAAD0AAMC9AABgvgAAwD0AACC+AACQvgAAwD0AACC+AABgvgAAAD0AACC+AACQvgAAAD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAOgAAAj0AAPw8AAACPQAAADoAAH49AAD8PAAAfj0AAIE9AAACPQAAvz0AAAI9AACBPQAAfj0AAL89AAB+PQAAfj0AAPw8AAACPQAA/DwAAH49AAAAOgAAAj0AAAA6AAC/PQAAADoAAIE9AAAAOgAAvz0AAPw8AACBPQAA/DwAAME9AAACPQAA/z0AAAI9AADBPQAAfj0AAP89AAB+PQAAAj0AAAI9AAB+PQAAAj0AAAI9AAB+PQAAfj0AAH49AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAACQvsUggD0AAMC9AACQvsUggD0AACC+AACQvgAAgD0AAMC9AACQvgAAgD0AACC+AACwvsUggD0AACC+AACwvsUggD0AAMC9AACwvgAAgD0AACC+AACwvgAAgD0AAMC9AACwvsUggD0AACC+AACQvsUggD0AACC+AACwvsUggD0AAMC9AACQvsUggD0AAMC9AACwvgAAgD0AAMC9AACQvgAAgD0AAMC9AACwvgAAgD0AACC+AACQvgAAgD0AACC+AACwvsUggD0AAMC9AACQvsUggD0AAMC9AACwvgAAgD0AAMC9AACQvgAAgD0AAMC9AACQvsUggD0AACC+AACwvsUggD0AACC+AACQvgAAgD0AACC+AACwvgAAgD0AACC+AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACBPQAAAj0AAL89AAACPQAAgT0AAPw8AAC/PQAA/DwAgAA+AAACPQCAHz4AAAI9AIAAPgAA/DwAgB8+AAD8PAAA/z0AAPw8AADBPQAA/DwAAP89AAAAOgAAwT0AAAA6AIAfPgAAADoAgAA+AAAAOgCAHz4AAPw8AIAAPgAA/DwAgCA+AAACPQCAPz4AAAI9AIAgPgAA/DwAgD8+AAD8PAAAwT0AAAI9AAD/PQAAAj0AAME9AAD8PAAA/z0AAPw8AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUAAAAAAAAAwD4AAEA/AAAAAAAAAAAAAAAAAACAPxPyhT0AAAAAAAAAAK9zfz8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAwD4AAEA/AAAAAAAAAAAAAAAAAACAPwAAAADug4Q+AAAAAOpGdz8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAwD4AAEA/AAAAAAAAAAAAAAAAAACAPwAAAADug4S+AAAAAOpGdz8AAAAAIbWyvAAAAABn8H8/AAAAAAAAwD4AAEA/AAAAAAAAAAAAAAAAAACAPwAAAACoqAU+AAAAAFXPfT8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAwD4AAEA/AAAAAAAAAAAAAAAAAACAPwAAAACoqAW+AAAAAFXPfT8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAD+rqko/AAAAAAAAAAAAAAAAAACAPwAAAAAhtbI8AAAAAGfwfz8AAAAAx71QPAAAAACu+n8/AAAAAAAAAD+rqko/AAAAAAAAAAAAAAAAAACAPwAAAAAhtbK8AAAAAGfwfz8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAQD8AAMA/AAAAAAAAAAAAAAAAAACAPwAAAAC2frK9AAAAAJ4Gfz8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAQD8AAMA/AAAAAAAAAAAAAAAAAACAPwAAAAC2frI9AAAAAJ4Gfz8AAAAAAAAAAAAAAAAAAIA/AAAAAKioBb4AAAAAAAAAAFXPfT8AAIA+qKgFvgAAAAAAAAAAVc99Pw=="}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[0.15625,0.125,0.25],"min":[-0.15625,0,-0.0625],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"max":[0.62451171875,0.21826171875],"min":[0.00048828125,0.00048828125],"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":4,"componentType":5126,"count":24,"max":[0.125,0.125,-0.0625],"min":[-0.125,0,-0.3125],"type":"VEC3"},{"bufferView":5,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":6,"componentType":5126,"count":24,"max":[0.49951171875,0.40576171875],"min":[0.00048828125,0.21923828125],"type":"VEC2"},{"bufferView":7,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":8,"componentType":5126,"count":24,"max":[0.03125,0.125,0.3125],"min":[-0.03125,0,0.25],"type":"VEC3"},{"bufferView":9,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":10,"componentType":5126,"count":24,"max":[0.59326171875,0.09326171875],"min":[0.46923828125,0.00048828125],"type":"VEC2"},{"bufferView":11,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":12,"componentType":5126,"count":24,"max":[0.21875,0.125,0.1875],"min":[-0.03125,0,0],"type":"VEC3"},{"bufferView":13,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":14,"componentType":5126,"count":24,"max":[0.84326171875,0.46826171875],"min":[0.40673828125,0.31298828125],"type":"VEC2"},{"bufferView":15,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":16,"componentType":5126,"count":24,"max":[0.21875,0.09375,0.5625],"min":[0.09375,0.03125,0.3125],"type":"VEC3"},{"bufferView":17,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":18,"componentType":5126,"count":24,"max":[0.68701171875,0.62451171875],"min":[0.31298828125,0.46923828125],"type":"VEC2"},{"bufferView":19,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":20,"componentType":5126,"count":24,"max":[0.406312495470047,0.1875,0.375],"min":[0.40625,0.0625,0.25],"type":"VEC3"},{"bufferView":21,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":22,"componentType":5126,"count":24,"max":[0.12548828125,0.28076171875],"min":[0.00048828125,0.15673828125],"type":"VEC2"},{"bufferView":23,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":24,"componentType":5126,"count":24,"max":[-0.03125,0.125,0.4375],"min":[-0.28125,0,0.25],"type":"VEC3"},{"bufferView":25,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":26,"componentType":5126,"count":24,"max":[0.43701171875,0.56201171875],"min":[0.00048828125,0.40673828125],"type":"VEC2"},{"bufferView":27,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":28,"componentType":5126,"count":24,"max":[-0.09375,0.09375,0.5625],"min":[-0.21875,0.03125,0.3125],"type":"VEC3"},{"bufferView":29,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":30,"componentType":5126,"count":24,"max":[0.84326171875,0.15576171875],"min":[0.46923828125,0.00048828125],"type":"VEC2"},{"bufferView":31,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":32,"componentType":5126,"count":24,"max":[-0.406187504529953,0.1875,0.375],"min":[-0.40625,0.0625,0.25],"type":"VEC3"},{"bufferView":33,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":34,"componentType":5126,"count":24,"max":[0.12548828125,0.12451171875],"min":[0.00048828125,0.00048828125],"type":"VEC2"},{"bufferView":35,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":36,"componentType":5126,"count":24,"max":[0.25,0.09375,-0.09375],"min":[0.125,0.03125,-0.15625],"type":"VEC3"},{"bufferView":37,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":38,"componentType":5126,"count":24,"max":[0.71826171875,0.31201171875],"min":[0.53173828125,0.25048828125],"type":"VEC2"},{"bufferView":39,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":40,"componentType":5126,"count":24,"max":[0.28125,0.09375,-0.09375],"min":[0.21875,0.03125,-0.15625],"type":"VEC3"},{"bufferView":41,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":42,"componentType":5126,"count":24,"max":[0.12451171875,0.34326171875],"min":[0.00048828125,0.28173828125],"type":"VEC2"},{"bufferView":43,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":44,"componentType":5126,"count":24,"max":[0.34375,0.0625625029206276,-0.09375],"min":[0.28125,0.0625,-0.15625],"type":"VEC3"},{"bufferView":45,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":46,"componentType":5126,"count":24,"max":[0.12451171875,0.15673828125],"min":[0.00048828125,0.12548828125],"type":"VEC2"},{"bufferView":47,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":48,"componentType":5126,"count":24,"max":[-0.125,0.09375,-0.09375],"min":[-0.25,0.03125,-0.15625],"type":"VEC3"},{"bufferView":49,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":50,"componentType":5126,"count":24,"max":[0.56201171875,0.28076171875],"min":[0.37548828125,0.21923828125],"type":"VEC2"},{"bufferView":51,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":52,"componentType":5126,"count":24,"max":[-0.21875,0.09375,-0.09375],"min":[-0.28125,0.03125,-0.15625],"type":"VEC3"},{"bufferView":53,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":54,"componentType":5126,"count":24,"max":[0.12451171875,0.06201171875],"min":[0.00048828125,0.00048828125],"type":"VEC2"},{"bufferView":55,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":56,"componentType":5126,"count":24,"max":[-0.28125,0.0625625029206276,-0.09375],"min":[-0.34375,0.0625,-0.15625],"type":"VEC3"},{"bufferView":57,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":58,"componentType":5126,"count":24,"max":[0.18701171875,0.03173828125],"min":[0.06298828125,0.00048828125],"type":"VEC2"},{"bufferView":59,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":60,"componentType":5126,"count":3,"max":[0.75],"min":[0],"type":"SCALAR"},{"bufferView":61,"componentType":5126,"count":3,"max":[0.06540312618017197,0,0,1],"min":[0,0,0,0.9978589415550232],"type":"VEC4"},{"bufferView":62,"componentType":5126,"count":3,"max":[0.75],"min":[0],"type":"SCALAR"},{"bufferView":63,"componentType":5126,"count":3,"max":[0,0.258819043636322,0,1],"min":[0,0,0,0.9659258127212524],"type":"VEC4"},{"bufferView":64,"componentType":5126,"count":3,"max":[0.75],"min":[0],"type":"SCALAR"},{"bufferView":65,"componentType":5126,"count":3,"max":[0,0,0,1],"min":[0,-0.258819043636322,0,0.9659258127212524],"type":"VEC4"},{"bufferView":66,"componentType":5126,"count":3,"max":[0.75],"min":[0],"type":"SCALAR"},{"bufferView":67,"componentType":5126,"count":3,"max":[0,0.13052618503570557,0,1],"min":[0,0,0,0.9914448857307434],"type":"VEC4"},{"bufferView":68,"componentType":5126,"count":3,"max":[0.75],"min":[0],"type":"SCALAR"},{"bufferView":69,"componentType":5126,"count":3,"max":[0,0,0,1],"min":[0,-0.13052618503570557,0,0.9914448857307434],"type":"VEC4"},{"bufferView":70,"componentType":5126,"count":3,"max":[0.7916666865348816],"min":[0],"type":"SCALAR"},{"bufferView":71,"componentType":5126,"count":3,"max":[0,0.02181488461792469,0,1],"min":[0,0,0,0.9997619986534119],"type":"VEC4"},{"bufferView":72,"componentType":5126,"count":3,"max":[0.7916666865348816],"min":[0],"type":"SCALAR"},{"bufferView":73,"componentType":5126,"count":3,"max":[0,0,0,1],"min":[0,-0.02181488461792469,0,0.9997619986534119],"type":"VEC4"},{"bufferView":74,"componentType":5126,"count":3,"max":[1.5],"min":[0],"type":"SCALAR"},{"bufferView":75,"componentType":5126,"count":3,"max":[0,0,0,1],"min":[0,-0.08715574443340302,0,0.9961947202682495],"type":"VEC4"},{"bufferView":76,"componentType":5126,"count":3,"max":[1.5],"min":[0],"type":"SCALAR"},{"bufferView":77,"componentType":5126,"count":3,"max":[0,0.08715574443340302,0,1],"min":[0,0,0,0.9961947202682495],"type":"VEC4"},{"bufferView":78,"componentType":5126,"count":1,"max":[0],"min":[0],"type":"SCALAR"},{"bufferView":79,"componentType":5126,"count":1,"max":[-0.13052618503570557,0,0,0.9914448857307434],"min":[-0.13052618503570557,0,0,0.9914448857307434],"type":"VEC4"},{"bufferView":80,"componentType":5126,"count":1,"max":[0.25],"min":[0.25],"type":"SCALAR"},{"bufferView":81,"componentType":5126,"count":1,"max":[-0.13052618503570557,0,0,0.9914448857307434],"min":[-0.13052618503570557,0,0,0.9914448857307434],"type":"VEC4"}],"materials":[{"pbrMetallicRoughness":{"metallicFactor":0,"roughnessFactor":1,"baseColorTexture":{"index":0}},"alphaMode":"MASK","alphaCutoff":0.05,"doubleSided":true}],"textures":[{"sampler":0}],"samplers":[{"magFilter":9728,"minFilter":9728,"wrapS":33071,"wrapT":33071}],"meshes":[{"primitives":[{"mode":4,"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":4,"NORMAL":5,"TEXCOORD_0":6},"indices":7,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":8,"NORMAL":9,"TEXCOORD_0":10},"indices":11,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":12,"NORMAL":13,"TEXCOORD_0":14},"indices":15,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":16,"NORMAL":17,"TEXCOORD_0":18},"indices":19,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":20,"NORMAL":21,"TEXCOORD_0":22},"indices":23,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":24,"NORMAL":25,"TEXCOORD_0":26},"indices":27,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":28,"NORMAL":29,"TEXCOORD_0":30},"indices":31,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":32,"NORMAL":33,"TEXCOORD_0":34},"indices":35,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":36,"NORMAL":37,"TEXCOORD_0":38},"indices":39,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":40,"NORMAL":41,"TEXCOORD_0":42},"indices":43,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":44,"NORMAL":45,"TEXCOORD_0":46},"indices":47,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":48,"NORMAL":49,"TEXCOORD_0":50},"indices":51,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":52,"NORMAL":53,"TEXCOORD_0":54},"indices":55,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":56,"NORMAL":57,"TEXCOORD_0":58},"indices":59,"material":0}]}],"animations":[{"name":"animation.model.walk","samplers":[{"input":60,"output":61,"interpolation":"LINEAR"},{"input":62,"output":63,"interpolation":"LINEAR"},{"input":64,"output":65,"interpolation":"LINEAR"},{"input":66,"output":67,"interpolation":"LINEAR"},{"input":68,"output":69,"interpolation":"LINEAR"}],"channels":[{"sampler":0,"target":{"node":3,"path":"rotation"}},{"sampler":1,"target":{"node":7,"path":"rotation"}},{"sampler":2,"target":{"node":11,"path":"rotation"}},{"sampler":3,"target":{"node":15,"path":"rotation"}},{"sampler":4,"target":{"node":19,"path":"rotation"}}]},{"name":"animation.model.idle","samplers":[{"input":70,"output":71,"interpolation":"LINEAR"},{"input":72,"output":73,"interpolation":"LINEAR"},{"input":74,"output":75,"interpolation":"LINEAR"},{"input":76,"output":77,"interpolation":"LINEAR"}],"channels":[{"sampler":0,"target":{"node":7,"path":"rotation"}},{"sampler":1,"target":{"node":11,"path":"rotation"}},{"sampler":2,"target":{"node":15,"path":"rotation"}},{"sampler":3,"target":{"node":19,"path":"rotation"}}]},{"name":"animation.model.back","samplers":[{"input":78,"output":79,"interpolation":"LINEAR"},{"input":80,"output":81,"interpolation":"LINEAR"}],"channels":[{"sampler":0,"target":{"node":15,"path":"rotation"}},{"sampler":1,"target":{"node":19,"path":"rotation"}}]}]}
diff --git a/games/devtest/mods/gltf/models/gltf_minimal_triangle.gltf b/games/devtest/mods/gltf/models/gltf_minimal_triangle.gltf
new file mode 100644
index 000000000..9a624f085
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_minimal_triangle.gltf
@@ -0,0 +1 @@
+{"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"attributes":{"POSITION":1},"indices":0}]}],"buffers":[{"uri":"data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=","byteLength":44}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":6,"target":34963},{"buffer":0,"byteOffset":8,"byteLength":36,"target":34962}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5123,"count":3,"type":"SCALAR","max":[2],"min":[0]},{"bufferView":1,"byteOffset":0,"componentType":5126,"count":3,"type":"VEC3","max":[1,1,0],"min":[0,0,0]}],"asset":{"version":"2.0"}}
diff --git a/games/devtest/mods/gltf/models/gltf_simple_skin.gltf b/games/devtest/mods/gltf/models/gltf_simple_skin.gltf
new file mode 100644
index 000000000..3d6c24a6c
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_simple_skin.gltf
@@ -0,0 +1 @@
+{"scene":0,"scenes":[{"nodes":[0,1]}],"nodes":[{"skin":0,"mesh":0},{"children":[2]},{"translation":[0.0,1.0,0.0],"rotation":[0.0,0.0,0.0,1.0]}],"meshes":[{"primitives":[{"attributes":{"POSITION":1,"JOINTS_0":2,"WEIGHTS_0":3},"indices":0}]}],"skins":[{"inverseBindMatrices":4,"joints":[1,2]}],"animations":[{"channels":[{"sampler":0,"target":{"node":2,"path":"rotation"}}],"samplers":[{"input":5,"interpolation":"LINEAR","output":6}]}],"buffers":[{"uri":"data:application/gltf-buffer;base64,AAABAAMAAAADAAIAAgADAAUAAgAFAAQABAAFAAcABAAHAAYABgAHAAkABgAJAAgAAAAAvwAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAvwAAAD8AAAAAAAAAPwAAAD8AAAAAAAAAvwAAgD8AAAAAAAAAPwAAgD8AAAAAAAAAvwAAwD8AAAAAAAAAPwAAwD8AAAAAAAAAvwAAAEAAAAAAAAAAPwAAAEAAAAAA","byteLength":168},{"uri":"data:application/gltf-buffer;base64,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAQD8AAIA+AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAAAAgD4AAEA/AAAAAAAAAAAAAIA+AABAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAA=","byteLength":320},{"uri":"data:application/gltf-buffer;base64,AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8=","byteLength":128},{"uri":"data:application/gltf-buffer;base64,AAAAAAAAAD8AAIA/AADAPwAAAEAAACBAAABAQAAAYEAAAIBAAACQQAAAoEAAALBAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAPT9ND/0/TQ/AAAAAAAAAAD0/TQ/9P00PwAAAAAAAAAAkxjEPkSLbD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAPT9NL/0/TQ/AAAAAAAAAAD0/TS/9P00PwAAAAAAAAAAkxjEvkSLbD8AAAAAAAAAAAAAAAAAAIA/","byteLength":240}],"bufferViews":[{"buffer":0,"byteLength":48,"target":34963},{"buffer":0,"byteOffset":48,"byteLength":120,"target":34962},{"buffer":1,"byteLength":320,"byteStride":16},{"buffer":2,"byteLength":128},{"buffer":3,"byteLength":240}],"accessors":[{"bufferView":0,"componentType":5123,"count":24,"type":"SCALAR"},{"bufferView":1,"componentType":5126,"count":10,"type":"VEC3","max":[0.5,2.0,0.0],"min":[-0.5,0.0,0.0]},{"bufferView":2,"componentType":5123,"count":10,"type":"VEC4"},{"bufferView":2,"byteOffset":160,"componentType":5126,"count":10,"type":"VEC4"},{"bufferView":3,"componentType":5126,"count":2,"type":"MAT4"},{"bufferView":4,"componentType":5126,"count":12,"type":"SCALAR","max":[5.5],"min":[0.0]},{"bufferView":4,"byteOffset":48,"componentType":5126,"count":12,"type":"VEC4","max":[0.0,0.0,0.707,1.0],"min":[0.0,0.0,-0.707,0.707]}],"asset":{"version":"2.0"}}
diff --git a/games/devtest/mods/gltf/models/gltf_simple_sparse_accessor.gltf b/games/devtest/mods/gltf/models/gltf_simple_sparse_accessor.gltf
new file mode 100644
index 000000000..979896825
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_simple_sparse_accessor.gltf
@@ -0,0 +1 @@
+{"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"attributes":{"POSITION":1},"indices":0}]}],"buffers":[{"uri":"data:application/gltf-buffer;base64,AAAIAAcAAAABAAgAAQAJAAgAAQACAAkAAgAKAAkAAgADAAoAAwALAAoAAwAEAAsABAAMAAsABAAFAAwABQANAAwABQAGAA0AAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAQAAAAAAAAAAAAABAQAAAAAAAAAAAAACAQAAAAAAAAAAAAACgQAAAAAAAAAAAAADAQAAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAQAAAgD8AAAAAAABAQAAAgD8AAAAAAACAQAAAgD8AAAAAAACgQAAAgD8AAAAAAADAQAAAgD8AAAAACAAKAAwAAAAAAIA/AAAAQAAAAAAAAEBAAABAQAAAAAAAAKBAAACAQAAAAAA=","byteLength":284}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":72,"byteLength":168},{"buffer":0,"byteOffset":240,"byteLength":6},{"buffer":0,"byteOffset":248,"byteLength":36}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5123,"count":36,"type":"SCALAR","max":[13],"min":[0]},{"bufferView":1,"byteOffset":0,"componentType":5126,"count":14,"type":"VEC3","max":[6,4,0],"min":[0,0,0],"sparse":{"count":3,"indices":{"bufferView":2,"byteOffset":0,"componentType":5123},"values":{"bufferView":3,"byteOffset":0}}}],"asset":{"version":"2.0"}}
diff --git a/games/devtest/mods/gltf/models/gltf_snow_man.gltf b/games/devtest/mods/gltf/models/gltf_snow_man.gltf
new file mode 100644
index 000000000..cd8c347d2
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_snow_man.gltf
@@ -0,0 +1 @@
+{"asset":{"version":"2.0","generator":"Blockbench 4.6.0 glTF exporter"},"scenes":[{"nodes":[3],"name":"blockbench_export"}],"scene":0,"nodes":[{"name":"cube","mesh":0},{"name":"cube","mesh":1},{"name":"cube","mesh":2},{"children":[0,1,2]}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":288,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":576,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":768,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":840,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1128,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1416,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":1608,"byteLength":72,"target":34963},{"buffer":0,"byteOffset":1680,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":1968,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":2256,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":2448,"byteLength":72,"target":34963}],"buffers":[{"byteLength":2520,"uri":"data:application/octet-stream;base64,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"}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[3,24,3],"min":[-3,18,-3],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"max":[0.8333333134651184,1],"min":[0,0.25],"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":4,"componentType":5126,"count":24,"max":[5,10,5],"min":[-5,0,-5],"type":"VEC3"},{"bufferView":5,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":6,"componentType":5126,"count":24,"max":[0.625,1],"min":[0,0.375],"type":"VEC2"},{"bufferView":7,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"},{"bufferView":8,"componentType":5126,"count":24,"max":[4,18,4],"min":[-4,10,-4],"type":"VEC3"},{"bufferView":9,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":10,"componentType":5126,"count":24,"max":[0.7083333134651184,0.7916666865348816],"min":[0.2083333283662796,0.25],"type":"VEC2"},{"bufferView":11,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"}],"materials":[{"pbrMetallicRoughness":{"metallicFactor":0,"roughnessFactor":1,"baseColorTexture":{"index":0,"texCoord":0}},"alphaMode":"MASK","alphaCutoff":0.05,"doubleSided":true}],"textures":[{}],"meshes":[{"primitives":[{"mode":4,"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":4,"NORMAL":5,"TEXCOORD_0":6},"indices":7,"material":0}]},{"primitives":[{"mode":4,"attributes":{"POSITION":8,"NORMAL":9,"TEXCOORD_0":10},"indices":11,"material":0}]}]}
diff --git a/games/devtest/mods/gltf/models/gltf_spider.gltf b/games/devtest/mods/gltf/models/gltf_spider.gltf
new file mode 100644
index 000000000..6698b6bb4
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_spider.gltf
@@ -0,0 +1 @@
+{"asset":{"generator":"Khronos glTF Blender I/O v1.7.33","version":"2.0"},"scene":0,"scenes":[{"name":"Scene","nodes":[0]}],"nodes":[{"mesh":0,"name":"Spider"}],"materials":[{"doubleSided":true,"name":"Material.001","pbrMetallicRoughness":{}}],"meshes":[{"name":"Cube","primitives":[{"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3,"material":0}]}],"accessors":[{"bufferView":0,"componentType":5126,"count":1000,"max":[2.742279291152954,1.4045029878616333,2.0192716121673584],"min":[-2.742279291152954,-0.6434623599052429,-3.534085512161255],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":1000,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1000,"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":1500,"type":"SCALAR"}],"bufferViews":[{"buffer":0,"byteLength":12000,"byteOffset":0},{"buffer":0,"byteLength":12000,"byteOffset":12000},{"buffer":0,"byteLength":8000,"byteOffset":24000},{"buffer":0,"byteLength":3000,"byteOffset":32000}],"buffers":[{"byteLength":35000,"uri":"data:application/octet-stream;base64,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dQCGAHUAgACIAIsAkQCIAJEAjgCQAJIAnQCQAJ0AmwCcAJ8AmQCcAJkAlgCVAJcAjACVAIwAigCPAJoAlACPAJQAiQCeAJMAjQCeAI0AmACiAKgAqwCiAKsApQCnALQAtwCnALcAqgCyAKwArwCyAK8AtQCtAKAAowCtAKMAsACmAKEArgCmAK4AswC2ALEApAC2AKQAqQC6AMAAwwC6AMMAvQC/AMwAzgC/AM4AwQDKAMQAxwDKAMcAzQDGALkAuwDGALsAyAC+ALgAxQC+AMUAywDPAMkAvADPALwAwgDSANgA2wDSANsA1QDXAOQA5gDXAOYA2QDiANwA3wDiAN8A5QDeANEA0wDeANMA4ADWANAA3QDWAN0A4wDnAOEA1ADnANQA2gDpAOsA8QDpAPEA7wDwAPIA/QDwAP0A+wD8AP4A+AD8APgA9gD1APcA7AD1AOwA6gDuAPoA9ADuAPQA6AD/APMA7QD/AO0A+QAAAQMBCQEAAQkBBgEHAQoBFQEHARUBEgEUARcBEQEUAREBDgENARABBQENAQUBAgEIARMBDAEIAQwBAQEWAQsBBAEWAQQBDwEpAR0BIwEpASMBLwEoASYBGQEoARkBGwEtASsBJQEtASUBJwEhAR8BLAEhASwBLgEYASQBKgEYASoBHgEcARoBIAEcASABIgExATMBOgExAToBOAE3ATkBRQE3AUUBQwFEAUYBPwFEAT8BPQE+AUABNAE+ATQBMgE2AUIBPAE2ATwBMAFHATsBNQFHATUBQQFJAUsBUQFJAVEBTwFQAVIBXQFQAV0BWwFcAV4BWAFcAVgBVgFVAVcBTAFVAUwBSgFOAVoBVAFOAVQBSAFfAVMBTQFfAU0BWQFgAWMBaQFgAWkBZgFoAWoBdQFoAXUBcwF0AXcBcQF0AXEBbgFtAW8BZAFtAWQBYgFnAXIBbAFnAWwBYQF2AWsBZQF2AWUBcAGJAX0BgwGJAYMBjwGIAYYBeQGIAXkBewGNAYsBhQGNAYUBhwGBAX8BjAGBAYwBjgF4AYQBigF4AYoBfgF8AXoBgAF8AYABggGRAZMBmQGRAZkBlwGYAZoBpQGYAaUBowGkAaYBoAGkAaABngGdAZ8BlAGdAZQBkgGWAaIBnAGWAZwBkAGnAZsBlQGnAZUBoQGpAasBsQGpAbEBrwGwAbIBvQGwAb0BuwG8Ab4BuAG8AbgBtgG1AbcBrAG1AawBqgGuAboBtAGuAbQBqAG/AbMBrQG/Aa0BuQHAAcMByQHAAckBxgHIAcoB1QHIAdUB0wHUAdcB0QHUAdEBzgHNAc8BxAHNAcQBwgHHAdIBzAHHAcwBwQHWAcsBxQHWAcUB0AHpAd0B4wHpAeMB7wHoAeYB2QHoAdkB2wHtAesB5QHtAeUB5wHhAd8B7AHhAewB7gHYAeQB6gHYAeoB3gHcAdoB4AHcAeAB4gHxAfMB+QHxAfkB9wH4AfoBBQL4AQUCAwIEAgYCAAIEAgAC/gH9Af8B9AH9AfQB8gH2AQIC/AH2AfwB8AEHAvsB9QEHAvUBAQIJAgsCEQIJAhECDwIQAhICHQIQAh0CGwIcAh4CGAIcAhgCFgIVAhcCDAIVAgwCCgIOAhoCFAIOAhQCCAIfAhMCDQIfAg0CGQIgAiMCKQIgAikCJgIoAisCNgIoAjYCMwI0AjcCMQI0AjECLgIsAi8CJAIsAiQCIQInAjICLQInAi0CIgI1AioCJQI1AiUCMAJJAj0CQwJJAkMCTwJIAkYCOQJIAjkCOwJNAksCRQJNAkUCRwJBAj8CTAJBAkwCTgI4AkQCSgI4AkoCPgI8AjoCQAI8AkACQgJSAlQCWQJSAlkCVwJYAloCZgJYAmYCZAJjAmUCYAJjAmACXgJdAl8CUwJdAlMCUQJWAmICXAJWAlwCUAJnAlsCVQJnAlUCYQJpAm8CcwJpAnMCbQJuAnsCfwJuAn8CcgJ6AnQCeAJ6AngCfgJ1AmgCbAJ1AmwCeQJwAmoCdgJwAnYCfAJ9AncCawJ9AmsCcQKCAogCiwKCAosChQKHApQClwKHApcCigKSAowCjwKSAo8ClQKNAoACgwKNAoMCkAKGAoECjgKGAo4CkwKWApEChAKWAoQCiQKnAq0CoQKnAqECmwKoAp0CmQKoApkCpAKvAqkCpQKvAqUCqwKjAq4CqgKjAqoCnwKaAqACrAKaAqwCpgKcAqICngKcAp4CmAKxArYCugKxAroCtQK3AsMCxwK3AscCuwLCAr0CwQLCAsECxgK8ArACtAK8ArQCwAK4ArICvgK4Ar4CxALFAr8CswLFArMCuQLJAs8C0wLJAtMCzQLOAtsC3wLOAt8C0gLaAtQC2ALaAtgC3gLVAsgCzALVAswC2QLQAsoC1gLQAtYC3ALdAtcCywLdAssC0QLiAugC6wLiAusC5QLmAvMC9wLmAvcC6gLyAuwC7wLyAu8C9QLtAuAC5ALtAuQC8QLnAuEC7gLnAu4C9AL2AvAC4wL2AuMC6QIHAw0DAQMHAwED+wIIA/0C+QIIA/kCBAMPAwkDBQMPAwUDCwMDAw4DCgMDAwoD/wL6AgADDAP6AgwDBgP8AgID/gL8Av4C+AIRAxcDGwMRAxsDFQMWAyMDJwMWAycDGgMiAxwDIAMiAyADJgMdAxADFAMdAxQDIQMYAxIDHgMYAx4DJAMlAx8DEwMlAxMDGQMpAy8DMwMpAzMDLQMuAzsDPwMuAz8DMgM6AzQDOAM6AzgDPgM1AygDLAM1AywDOQMwAyoDNgMwAzYDPAM9AzcDKwM9AysDMQNCA0gDSwNCA0sDRQNGA1MDVwNGA1cDSgNSA0wDTwNSA08DVQNNA0ADRANNA0QDUQNHA0EDTgNHA04DVANWA1ADQwNWA0MDSQNnA20DYQNnA2EDWwNoA10DWQNoA1kDZANvA2kDZQNvA2UDawNjA24DagNjA2oDXwNaA2ADbANaA2wDZgNcA2IDXgNcA14DWANxA3cDewNxA3sDdQN2A4MDhwN2A4cDegOCA3wDgAOCA4ADhgN9A3ADdAN9A3QDgQN4A3IDfgN4A34DhAOFA38DcwOFA3MDeQOJA48DkwOJA5MDjQOOA5sDnwOOA58DkgOaA5QDmAOaA5gDngOVA4gDjAOVA4wDmQOQA4oDlgOQA5YDnAOdA5cDiwOdA4sDkQOiA6gDqwOiA6sDpQOmA7MDtgOmA7YDqQOyA6wDrwOyA68DtQOuA6EDpAOuA6QDsQOnA6ADrQOnA60DtAO3A7ADowO3A6MDqgPHA80DwQPHA8EDuwPIA70DuQPIA7kDxAPPA8kDxQPPA8UDywPDA84DygPDA8oDvwO6A8ADzAO6A8wDxgO8A8IDvgO8A74DuAPQA9cD2wPQA9sD1APWA+ID5gPWA+YD2gPjA9wD4APjA+AD5wPdA9ED1QPdA9UD4QPYA9ID3gPYA94D5APlA98D0wPlA9MD2QM="}]}
diff --git a/games/devtest/mods/gltf/models/gltf_spider_animated.gltf b/games/devtest/mods/gltf/models/gltf_spider_animated.gltf
new file mode 100644
index 000000000..79221b0c7
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_spider_animated.gltf
@@ -0,0 +1 @@
+{"asset":{"generator":"Khronos glTF Blender I/O v1.7.33","version":"2.0"},"scene":0,"scenes":[{"name":"Scene","nodes":[58]}],"nodes":[{"name":"Pincer.L","rotation":[0.03853772580623627,0.09671717882156372,0.5138389468193054,0.8515457510948181],"translation":[-2.2351741790771484e-08,0.2836739718914032,-2.2351741790771484e-08]},{"children":[0],"name":"JawBase.L","rotation":[-0.23922589421272278,-9.208349638356594e-08,-0.38811206817626953,0.8900224566459656],"scale":[1,1,0.9999999403953552],"translation":[8.097286041675034e-08,0.7702280879020691,-1.169656727029178e-07]},{"name":"Pincer.R","rotation":[0.038537755608558655,-0.09671713411808014,-0.5138388872146606,0.8515458106994629],"scale":[0.9999997615814209,0.9999999403953552,1],"translation":[2.9802322387695312e-08,0.2836737036705017,-2.9802322387695312e-08]},{"children":[2],"name":"JawBase.R","rotation":[-0.2392251342535019,1.9714243535418063e-06,0.3881126046180725,0.8900225758552551],"translation":[1.3833086898173974e-09,0.7702280282974243,-6.620245329713725e-08]},{"children":[1,3],"name":"Head","rotation":[0.4052415192127228,-3.4197712478652165e-13,-8.695541282577324e-07,0.9142096638679504],"translation":[-2.0781445141115906e-16,0.6883190274238586,-1.4901161193847656e-08]},{"children":[4],"name":"NeckBase","rotation":[-0.778048574924469,7.488795716881214e-08,1.7622618315726868e-06,0.6282041072845459],"translation":[-3.399441372928941e-14,0.3915250301361084,-2.3283064365386963e-09]},{"name":"Body.002","rotation":[0.17414046823978424,0,-4.151832513343834e-07,0.9847208261489868],"translation":[5.897654597759178e-14,1.0079095363616943,-1.2134763416327132e-08]},{"children":[6],"name":"Body.001","rotation":[0.6673352122306824,-7.632472941426077e-14,-1.5910509318928234e-06,0.7447575330734253],"scale":[1,0.9999999403953552,0.9999999403953552],"translation":[-3.962037268363458e-15,0.3915250301361084,-3.1428597502269895e-09]},{"name":"Leg4Fore.L","rotation":[-0.021953541785478592,0.030033688992261887,-0.4378480017185211,0.8982790112495422],"scale":[1,0.9999998211860657,1.0000001192092896],"translation":[1.9202238377147296e-07,0.8228543996810913,-1.749940707895803e-07]},{"children":[8],"name":"Leg4Lower.L","rotation":[-0.11090508848428726,0.11991499364376068,-0.48737218976020813,0.8577813506126404],"scale":[0.9999995231628418,0.9999997615814209,1.000000238418579],"translation":[1.9631791303709178e-07,0.8208085298538208,-4.769351491518137e-08]},{"children":[9],"name":"Leg4Mid.L","rotation":[-0.21032677590847015,0.09273893386125565,-0.42330121994018555,0.8763437271118164],"scale":[1,0.9999996423721313,0.999999463558197],"translation":[-1.720833893159579e-07,1.5146127939224243,1.4611718768264836e-07]},{"children":[10],"name":"Leg4Upper.L","rotation":[0.581340491771698,-0.03387186676263809,0.4926694631576538,0.6466628909111023],"scale":[1.0000003576278687,1.000000238418579,1.0000003576278687],"translation":[-2.6137989550534257e-09,0.8996680974960327,-2.8558396536482178e-08]},{"children":[11],"name":"Leg4Base.L","rotation":[0.4988132119178772,0.67340087890625,0.0026363276410847902,0.5456278324127197],"scale":[0.9999998807907104,0.9999998807907104,0.9999999403953552],"translation":[6.932457941033476e-10,0.3915250301361084,-7.783789612858527e-09]},{"name":"Leg3Fore.L","rotation":[-0.040254246443510056,0.0051941643469035625,-0.3734953701496124,0.9267436861991882],"scale":[1.0000001192092896,1,1.0000003576278687],"translation":[-7.186849302343035e-07,0.761729896068573,1.4940267689667053e-08]},{"children":[13],"name":"Leg3Lower.L","rotation":[-0.02293548174202442,0.03108014352619648,-0.5376279950141907,0.8422969579696655],"scale":[1,0.9999998807907104,1],"translation":[2.6879865799855907e-07,0.890315592288971,-2.3254589365251377e-08]},{"children":[14],"name":"Leg3Mid.L","rotation":[-0.10393687337636948,0.026799378916621208,-0.47722530364990234,0.8722012042999268],"scale":[1,1.0000001192092896,1],"translation":[-4.5783519908582093e-07,1.5058820247650146,6.428529530921878e-08]},{"children":[15],"name":"Leg3Upper.L","rotation":[0.22388437390327454,0.00046301534166559577,0.7424523234367371,0.6313795447349548],"scale":[0.9999996423721313,0.9999999403953552,0.9999995231628418],"translation":[8.173065424443848e-08,0.8363674879074097,-3.891337030381692e-09]},{"children":[16],"name":"Leg3Base.L","rotation":[0.48101410269737244,0.8565962910652161,-0.006458853371441364,0.18661867082118988],"scale":[0.9999999403953552,0.9999998807907104,0.9999999403953552],"translation":[1.1383551878907383e-08,0.3915250301361084,2.5693926986036786e-09]},{"name":"Leg2Fore.L","rotation":[0.04987334460020065,-0.01207074522972107,-0.4028705060482025,0.9138174653053284],"scale":[0.999999463558197,1.0000004768371582,1.0000001192092896],"translation":[2.9161077463868423e-07,0.7777483463287354,1.7455030842938868e-07]},{"children":[18],"name":"Leg2Lower.L","rotation":[0.08352424204349518,-0.04269447177648544,-0.518085777759552,0.8501694202423096],"scale":[1.0000001192092896,1.0000004768371582,1.0000004768371582],"translation":[-1.5067358560827415e-07,0.9397417306900024,-4.163759115272114e-08]},{"children":[19],"name":"Leg2Mid.L","rotation":[0.14706559479236603,-0.028868243098258972,-0.47296836972236633,0.868239164352417],"scale":[1.0000004768371582,0.9999999403953552,0.9999999403953552],"translation":[-2.2217867012841452e-07,1.5058820247650146,-6.989571943449846e-08]},{"children":[20],"name":"Leg2Upper.L","rotation":[-0.42424774169921875,-0.0005238422891125083,0.6472405791282654,0.6333192586898804],"scale":[1.000000238418579,1.0000003576278687,1.0000005960464478],"translation":[9.311328597050306e-08,0.881853461265564,-1.8038990745594674e-08]},{"children":[21],"name":"Leg2Base.L","rotation":[-0.4972459375858307,-0.7882749438285828,0.006057440303266048,0.362398236989975],"scale":[0.9999997615814209,0.9999998807907104,0.9999999403953552],"translation":[7.375939858889069e-10,0.3915250301361084,4.028271050060539e-09]},{"name":"Leg1Fore.L","rotation":[-0.01934647001326084,-0.04218549281358719,-0.4403696358203888,0.8966162800788879],"scale":[1,1,0.9999997615814209],"translation":[2.0805721590022586e-07,0.815664529800415,4.0515438115562574e-08]},{"children":[23],"name":"Leg1Lower.L","rotation":[0.15678077936172485,-0.1661715805530548,-0.47995010018348694,0.8470270037651062],"scale":[0.999999463558197,1,0.9999999403953552],"translation":[3.670676562705921e-08,0.8788074851036072,8.29251618483795e-08]},{"children":[24],"name":"Leg1Mid.L","rotation":[0.26206591725349426,-0.11672191321849823,-0.4046621024608612,0.8683006763458252],"scale":[1.0000001192092896,0.9999999403953552,0.9999995231628418],"translation":[3.601947184961318e-08,1.5125981569290161,-1.6144279868512967e-07]},{"children":[25],"name":"Leg1Upper.L","rotation":[-0.62815922498703,0.04343283176422119,0.39305803179740906,0.6701006889343262],"translation":[-1.0171092412747385e-07,1.043814778327942,1.114601104745816e-07]},{"children":[26],"name":"Leg1Base.L","rotation":[-0.536352813243866,-0.596045732498169,-0.006935927551239729,0.5975006818771362],"scale":[0.9999998211860657,0.9999998211860657,1],"translation":[7.451212979958655e-09,0.3915250301361084,-5.977072614626877e-09]},{"name":"Leg4Fore.R","rotation":[-0.0219536405056715,-0.030033595860004425,0.43784812092781067,0.8982789516448975],"scale":[1.000000238418579,0.9999998807907104,1.0000001192092896],"translation":[4.575199454848189e-07,0.82285475730896,1.3987688873839943e-07]},{"children":[28],"name":"Leg4Lower.R","rotation":[-0.11090517044067383,-0.11991491913795471,0.48737218976020813,0.8577813506126404],"scale":[1.0000001192092896,0.9999999403953552,1.0000001192092896],"translation":[5.0247152216797986e-08,0.8208085894584656,1.2523592829438712e-07]},{"children":[29],"name":"Leg4Mid.R","rotation":[-0.21032673120498657,-0.09273889660835266,0.42330119013786316,0.876343846321106],"scale":[0.9999998211860657,0.9999995231628418,1.0000001192092896],"translation":[-1.2884336797469587e-07,1.514613151550293,6.563716681284859e-08]},{"children":[30],"name":"Leg4Upper.R","rotation":[0.5813404321670532,0.03387187048792839,-0.4926694333553314,0.6466629505157471],"scale":[1,1.000000238418579,0.9999997019767761],"translation":[-3.940737158814045e-08,0.8996680974960327,1.9567494291550247e-09]},{"children":[31],"name":"Leg4Base.R","rotation":[0.4988132119178772,-0.6733996272087097,-0.0026374668814241886,0.5456294417381287],"scale":[1,1.0000001192092896,1],"translation":[-1.1682686817948706e-08,0.3915250301361084,-1.3812247345867945e-08]},{"name":"Leg3Fore.R","rotation":[-0.04025428742170334,-0.005194155499339104,0.3734953999519348,0.9267436861991882],"scale":[0.9999998211860657,1.0000001192092896,1.0000001192092896],"translation":[-7.285660217348777e-07,0.7617300748825073,-4.0205627271916455e-08]},{"children":[33],"name":"Leg3Lower.R","rotation":[-0.02293553575873375,-0.03108006715774536,0.5376282930374146,0.8422967791557312],"scale":[1.0000001192092896,0.9999996423721313,0.9999999403953552],"translation":[7.143101754536474e-08,0.8903149366378784,6.888667769544554e-08]},{"children":[34],"name":"Leg3Mid.R","rotation":[-0.10393673926591873,-0.026799339801073074,0.47722548246383667,0.872201144695282],"scale":[1.0000003576278687,0.9999998807907104,0.9999998807907104],"translation":[1.4287303429227904e-07,1.5058823823928833,9.578651827268914e-08]},{"children":[35],"name":"Leg3Upper.R","rotation":[0.2238844484090805,-0.00046323961578309536,-0.7424524426460266,0.6313793659210205],"scale":[1.0000001192092896,1.0000005960464478,0.9999997615814209],"translation":[-2.9145089897042453e-08,0.8363675475120544,-1.3412945421009681e-08]},{"children":[36],"name":"Leg3Base.R","rotation":[0.48101410269737244,-0.8565958738327026,0.006457682233303785,0.18662074208259583],"scale":[0.9999999403953552,1.0000001192092896,1],"translation":[1.187698939197901e-09,0.3915250301361084,1.396204218906405e-08]},{"name":"Leg2Fore.R","rotation":[0.04987342655658722,0.012070796452462673,0.40287071466445923,0.9138173460960388],"scale":[0.9999997615814209,0.9999998807907104,0.9999997019767761],"translation":[4.900767294202524e-07,0.7777489423751831,1.3496240569565998e-07]},{"children":[38],"name":"Leg2Lower.R","rotation":[0.08352430164813995,0.04269447922706604,0.518085777759552,0.8501694202423096],"scale":[1.000000238418579,1.0000003576278687,0.9999999403953552],"translation":[1.2208448652017978e-07,0.9397414326667786,-3.409446946989192e-08]},{"children":[39],"name":"Leg2Mid.R","rotation":[0.1470656394958496,0.028868237510323524,0.4729681611061096,0.8682392835617065],"scale":[1.0000001192092896,1.0000003576278687,1.0000001192092896],"translation":[4.8437236443987786e-08,1.5058820247650146,-2.5024842642551448e-08]},{"children":[40],"name":"Leg2Upper.R","rotation":[-0.4242475926876068,0.0005238187150098383,-0.6472404599189758,0.6333194971084595],"scale":[0.9999997019767761,1,0.9999998211860657],"translation":[3.550610472302651e-09,0.8818532824516296,4.425183419698442e-08]},{"children":[41],"name":"Leg2Base.R","rotation":[-0.4972459375858307,0.7882757782936096,-0.006056289654225111,0.36239632964134216],"scale":[0.9999998211860657,1,0.9999999403953552],"translation":[-7.2600920830723226e-09,0.3915250301361084,-5.773719280455225e-09]},{"name":"Leg1Fore.R","rotation":[-0.015208502300083637,0.04422945901751518,0.4362727701663971,0.8985980749130249],"scale":[1.000000238418579,0.9999995827674866,0.9999997615814209],"translation":[-6.20622927272052e-07,0.8156638741493225,-1.6136721114889951e-07]},{"children":[43],"name":"Leg1Lower.R","rotation":[0.15885458886623383,0.17276015877723694,0.4745163321495056,0.848382830619812],"scale":[1.000000238418579,1.0000001192092896,1.0000004768371582],"translation":[-2.3015780925561558e-07,0.8788077235221863,2.258973452740065e-08]},{"children":[44],"name":"Leg1Mid.R","rotation":[0.2600231170654297,0.12465617805719376,0.4028773903846741,0.8686419725418091],"scale":[1,0.9999997019767761,0.9999999403953552],"translation":[-2.3629894485566183e-08,1.512597680091858,-5.442473494099431e-08]},{"children":[45],"name":"Leg1Upper.R","rotation":[-0.6237055063247681,-0.03962605446577072,-0.3963613212108612,0.6725466251373291],"scale":[1,1,0.9999995827674866],"translation":[4.151442212219081e-08,1.0438144207000732,6.221015524943141e-08]},{"children":[46],"name":"Leg1Base.R","rotation":[-0.5363527536392212,0.5960471630096436,0.0069372160360217094,0.5974993705749512],"scale":[1,1.0000001192092896,1.0000001192092896],"translation":[7.877114072130098e-09,0.3915250301361084,-5.523408841412447e-09]},{"children":[5,7,12,17,22,27,32,37,42,47],"name":"Body","rotation":[-0.9999927282333374,-4.546671483751652e-09,1.1920842553081457e-06,0.003814017167314887],"translation":[-2.1589291564903364e-17,0.5146726369857788,0.22900062799453735]},{"name":"Leg4IK.L","rotation":[-2.6692541510442425e-08,-2.6692541510442425e-08,-0.7071068286895752,0.7071068286895752],"translation":[2.2291481494903564,-0.5599625110626221,-0.7613579630851746]},{"name":"Leg3IK.L","rotation":[-2.6692541510442425e-08,-2.6692541510442425e-08,-0.7071068286895752,0.7071068286895752],"translation":[2.3687760829925537,-0.5599625110626221,-0.033313095569610596]},{"name":"Leg2IK.L","rotation":[-2.6692541510442425e-08,-2.6692541510442425e-08,-0.7071068286895752,0.7071068286895752],"translation":[2.3687760829925537,-0.5599625110626221,0.6964529752731323]},{"name":"Leg1IK.L","rotation":[-2.6692541510442425e-08,-2.6692541510442425e-08,-0.7071068286895752,0.7071068286895752],"translation":[2.2556710243225098,-0.5599625110626221,1.4977319240570068]},{"name":"Leg4IK.R","rotation":[-2.6692541510442425e-08,2.6692541510442425e-08,0.7071068286895752,0.7071068286895752],"translation":[-2.2291481494903564,-0.5599625110626221,-0.7613579630851746]},{"name":"Leg3IK.R","rotation":[-2.6692541510442425e-08,2.6692541510442425e-08,0.7071068286895752,0.7071068286895752],"translation":[-2.3687760829925537,-0.5599625110626221,-0.033313095569610596]},{"name":"Leg2IK.R","rotation":[-2.6692541510442425e-08,2.6692541510442425e-08,0.7071068286895752,0.7071068286895752],"translation":[-2.3687760829925537,-0.5599625110626221,0.6964529752731323]},{"name":"Leg1IK.R","rotation":[-2.6692541510442425e-08,2.6692541510442425e-08,0.7071068286895752,0.7071068286895752],"translation":[-2.2556710243225098,-0.5599625110626221,1.5977319478988647]},{"mesh":0,"name":"Spider","skin":0},{"children":[57,48,49,50,51,52,53,54,55,56],"name":"Armature"}],"animations":[{"channels":[{"sampler":0,"target":{"node":48,"path":"translation"}},{"sampler":1,"target":{"node":48,"path":"rotation"}},{"sampler":2,"target":{"node":48,"path":"scale"}},{"sampler":3,"target":{"node":4,"path":"translation"}},{"sampler":4,"target":{"node":4,"path":"rotation"}},{"sampler":5,"target":{"node":4,"path":"scale"}},{"sampler":6,"target":{"node":0,"path":"translation"}},{"sampler":7,"target":{"node":0,"path":"rotation"}},{"sampler":8,"target":{"node":0,"path":"scale"}},{"sampler":9,"target":{"node":2,"path":"translation"}},{"sampler":10,"target":{"node":2,"path":"rotation"}},{"sampler":11,"target":{"node":2,"path":"scale"}},{"sampler":12,"target":{"node":6,"path":"translation"}},{"sampler":13,"target":{"node":6,"path":"rotation"}},{"sampler":14,"target":{"node":6,"path":"scale"}},{"sampler":15,"target":{"node":11,"path":"rotation"}},{"sampler":16,"target":{"node":10,"path":"rotation"}},{"sampler":17,"target":{"node":9,"path":"rotation"}},{"sampler":18,"target":{"node":8,"path":"rotation"}},{"sampler":19,"target":{"node":16,"path":"rotation"}},{"sampler":20,"target":{"node":15,"path":"rotation"}},{"sampler":21,"target":{"node":14,"path":"rotation"}},{"sampler":22,"target":{"node":13,"path":"rotation"}},{"sampler":23,"target":{"node":21,"path":"rotation"}},{"sampler":24,"target":{"node":20,"path":"rotation"}},{"sampler":25,"target":{"node":19,"path":"rotation"}},{"sampler":26,"target":{"node":18,"path":"rotation"}},{"sampler":27,"target":{"node":26,"path":"rotation"}},{"sampler":28,"target":{"node":25,"path":"rotation"}},{"sampler":29,"target":{"node":24,"path":"rotation"}},{"sampler":30,"target":{"node":23,"path":"translation"}},{"sampler":31,"target":{"node":23,"path":"rotation"}},{"sampler":32,"target":{"node":23,"path":"scale"}},{"sampler":33,"target":{"node":31,"path":"rotation"}},{"sampler":34,"target":{"node":30,"path":"rotation"}},{"sampler":35,"target":{"node":29,"path":"rotation"}},{"sampler":36,"target":{"node":28,"path":"rotation"}},{"sampler":37,"target":{"node":36,"path":"rotation"}},{"sampler":38,"target":{"node":35,"path":"rotation"}},{"sampler":39,"target":{"node":34,"path":"rotation"}},{"sampler":40,"target":{"node":33,"path":"rotation"}},{"sampler":41,"target":{"node":41,"path":"rotation"}},{"sampler":42,"target":{"node":40,"path":"rotation"}},{"sampler":43,"target":{"node":39,"path":"rotation"}},{"sampler":44,"target":{"node":38,"path":"rotation"}},{"sampler":45,"target":{"node":46,"path":"rotation"}},{"sampler":46,"target":{"node":45,"path":"rotation"}},{"sampler":47,"target":{"node":44,"path":"rotation"}},{"sampler":48,"target":{"node":43,"path":"rotation"}},{"sampler":49,"target":{"node":49,"path":"translation"}},{"sampler":50,"target":{"node":49,"path":"rotation"}},{"sampler":51,"target":{"node":49,"path":"scale"}},{"sampler":52,"target":{"node":50,"path":"translation"}},{"sampler":53,"target":{"node":50,"path":"rotation"}},{"sampler":54,"target":{"node":50,"path":"scale"}},{"sampler":55,"target":{"node":51,"path":"translation"}},{"sampler":56,"target":{"node":51,"path":"rotation"}},{"sampler":57,"target":{"node":51,"path":"scale"}},{"sampler":58,"target":{"node":52,"path":"translation"}},{"sampler":59,"target":{"node":52,"path":"rotation"}},{"sampler":60,"target":{"node":52,"path":"scale"}},{"sampler":61,"target":{"node":53,"path":"translation"}},{"sampler":62,"target":{"node":53,"path":"rotation"}},{"sampler":63,"target":{"node":53,"path":"scale"}},{"sampler":64,"target":{"node":54,"path":"translation"}},{"sampler":65,"target":{"node":54,"path":"rotation"}},{"sampler":66,"target":{"node":54,"path":"scale"}},{"sampler":67,"target":{"node":55,"path":"translation"}},{"sampler":68,"target":{"node":55,"path":"rotation"}},{"sampler":69,"target":{"node":55,"path":"scale"}},{"sampler":70,"target":{"node":56,"path":"translation"}},{"sampler":71,"target":{"node":56,"path":"rotation"}},{"sampler":72,"target":{"node":56,"path":"scale"}}],"name":"ArmatureAction","samplers":[{"input":7,"interpolation":"LINEAR","output":8},{"input":7,"interpolation":"LINEAR","output":9},{"input":10,"interpolation":"LINEAR","output":11},{"input":10,"interpolation":"LINEAR","output":12},{"input":7,"interpolation":"LINEAR","output":13},{"input":10,"interpolation":"LINEAR","output":14},{"input":10,"interpolation":"LINEAR","output":15},{"input":7,"interpolation":"LINEAR","output":16},{"input":10,"interpolation":"LINEAR","output":17},{"input":10,"interpolation":"LINEAR","output":18},{"input":7,"interpolation":"LINEAR","output":19},{"input":10,"interpolation":"LINEAR","output":20},{"input":10,"interpolation":"LINEAR","output":21},{"input":7,"interpolation":"LINEAR","output":22},{"input":10,"interpolation":"LINEAR","output":23},{"input":7,"interpolation":"LINEAR","output":24},{"input":7,"interpolation":"LINEAR","output":25},{"input":7,"interpolation":"LINEAR","output":26},{"input":7,"interpolation":"LINEAR","output":27},{"input":7,"interpolation":"LINEAR","output":28},{"input":7,"interpolation":"LINEAR","output":29},{"input":7,"interpolation":"LINEAR","output":30},{"input":7,"interpolation":"LINEAR","output":31},{"input":7,"interpolation":"LINEAR","output":32},{"input":7,"interpolation":"LINEAR","output":33},{"input":7,"interpolation":"LINEAR","output":34},{"input":7,"interpolation":"LINEAR","output":35},{"input":7,"interpolation":"LINEAR","output":36},{"input":7,"interpolation":"LINEAR","output":37},{"input":7,"interpolation":"LINEAR","output":38},{"input":10,"interpolation":"LINEAR","output":39},{"input":7,"interpolation":"LINEAR","output":40},{"input":10,"interpolation":"LINEAR","output":41},{"input":7,"interpolation":"LINEAR","output":42},{"input":7,"interpolation":"LINEAR","output":43},{"input":7,"interpolation":"LINEAR","output":44},{"input":7,"interpolation":"LINEAR","output":45},{"input":7,"interpolation":"LINEAR","output":46},{"input":7,"interpolation":"LINEAR","output":47},{"input":7,"interpolation":"LINEAR","output":48},{"input":7,"interpolation":"LINEAR","output":49},{"input":7,"interpolation":"LINEAR","output":50},{"input":7,"interpolation":"LINEAR","output":51},{"input":7,"interpolation":"LINEAR","output":52},{"input":7,"interpolation":"LINEAR","output":53},{"input":7,"interpolation":"LINEAR","output":54},{"input":7,"interpolation":"LINEAR","output":55},{"input":7,"interpolation":"LINEAR","output":56},{"input":7,"interpolation":"LINEAR","output":57},{"input":7,"interpolation":"LINEAR","output":58},{"input":10,"interpolation":"LINEAR","output":59},{"input":7,"interpolation":"LINEAR","output":60},{"input":7,"interpolation":"LINEAR","output":61},{"input":10,"interpolation":"LINEAR","output":62},{"input":7,"interpolation":"LINEAR","output":63},{"input":7,"interpolation":"LINEAR","output":64},{"input":10,"interpolation":"LINEAR","output":65},{"input":7,"interpolation":"LINEAR","output":66},{"input":7,"interpolation":"LINEAR","output":67},{"input":10,"interpolation":"LINEAR","output":68},{"input":7,"interpolation":"LINEAR","output":69},{"input":7,"interpolation":"LINEAR","output":70},{"input":10,"interpolation":"LINEAR","output":71},{"input":7,"interpolation":"LINEAR","output":72},{"input":7,"interpolation":"LINEAR","output":73},{"input":10,"interpolation":"LINEAR","output":74},{"input":7,"interpolation":"LINEAR","output":75},{"input":7,"interpolation":"LINEAR","output":76},{"input":10,"interpolation":"LINEAR","output":77},{"input":7,"interpolation":"LINEAR","output":78},{"input":7,"interpolation":"LINEAR","output":79},{"input":10,"interpolation":"LINEAR","output":80},{"input":7,"interpolation":"LINEAR","output":81}]}],"materials":[{"doubleSided":true,"name":"Material.001","pbrMetallicRoughness":{}}],"meshes":[{"name":"Cube","primitives":[{"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2,"JOINTS_0":3,"WEIGHTS_0":4},"indices":5,"material":0}]}],"skins":[{"inverseBindMatrices":6,"joints":[48,5,4,1,0,3,2,7,6,12,11,10,9,8,17,16,15,14,13,22,21,20,19,18,27,26,25,24,23,32,31,30,29,28,37,36,35,34,33,42,41,40,39,38,47,46,45,44,43,49,50,51,52,53,54,55,56],"name":"Armature"}],"accessors":[{"bufferView":0,"componentType":5126,"count":1000,"max":[2.742279291152954,1.4045029878616333,2.0192716121673584],"min":[-2.742279291152954,-0.6434623599052429,-3.534085512161255],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":1000,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1000,"type":"VEC2"},{"bufferView":3,"componentType":5121,"count":1000,"type":"VEC4"},{"bufferView":4,"componentType":5126,"count":1000,"type":"VEC4"},{"bufferView":5,"componentType":5123,"count":1500,"type":"SCALAR"},{"bufferView":6,"componentType":5126,"count":57,"type":"MAT4"},{"bufferView":7,"componentType":5126,"count":120,"max":[5],"min":[0.041666666666666664],"type":"SCALAR"},{"bufferView":8,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":9,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":10,"componentType":5126,"count":2,"max":[5],"min":[0.041666666666666664],"type":"SCALAR"},{"bufferView":11,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":12,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":13,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":14,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":15,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":16,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":17,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":18,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":19,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":20,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":21,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":22,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":23,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":24,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":25,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":26,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":27,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":28,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":29,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":30,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":31,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":32,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":33,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":34,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":35,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":36,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":37,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":38,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":39,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":40,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":41,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":42,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":43,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":44,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":45,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":46,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":47,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":48,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":49,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":50,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":51,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":52,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":53,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":54,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":55,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":56,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":57,"componentType":5126,"count":120,"type":"VEC4"},{"bufferView":58,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":59,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":60,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":61,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":62,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":63,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":64,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":65,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":66,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":67,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":68,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":69,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":70,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":71,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":72,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":73,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":74,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":75,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":76,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":77,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":78,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":79,"componentType":5126,"count":120,"type":"VEC3"},{"bufferView":80,"componentType":5126,"count":2,"type":"VEC4"},{"bufferView":81,"componentType":5126,"count":120,"type":"VEC3"}],"bufferViews":[{"buffer":0,"byteLength":12000,"byteOffset":0},{"buffer":0,"byteLength":12000,"byteOffset":12000},{"buffer":0,"byteLength":8000,"byteOffset":24000},{"buffer":0,"byteLength":4000,"byteOffset":32000},{"buffer":0,"byteLength":16000,"byteOffset":36000},{"buffer":0,"byteLength":3000,"byteOffset":52000},{"buffer":0,"byteLength":3648,"byteOffset":55000},{"buffer":0,"byteLength":480,"byteOffset":58648},{"buffer":0,"byteLength":1440,"byteOffset":59128},{"buffer":0,"byteLength":1920,"byteOffset":60568},{"buffer":0,"byteLength":8,"byteOffset":62488},{"buffer":0,"byteLength":24,"byteOffset":62496},{"buffer":0,"byteLength":24,"byteOffset":62520},{"buffer":0,"byteLength":1920,"byteOffset":62544},{"buffer":0,"byteLength":24,"byteOffset":64464},{"buffer":0,"byteLength":24,"byteOffset":64488},{"buffer":0,"byteLength":1920,"byteOffset":64512},{"buffer":0,"byteLength":24,"byteOffset":66432},{"buffer":0,"byteLength":24,"byteOffset":66456},{"buffer":0,"byteLength":1920,"byteOffset":66480},{"buffer":0,"byteLength":24,"byteOffset":68400},{"buffer":0,"byteLength":24,"byteOffset":68424},{"buffer":0,"byteLength":1920,"byteOffset":68448},{"buffer":0,"byteLength":24,"byteOffset":70368},{"buffer":0,"byteLength":1920,"byteOffset":70392},{"buffer":0,"byteLength":1920,"byteOffset":72312},{"buffer":0,"byteLength":1920,"byteOffset":74232},{"buffer":0,"byteLength":1920,"byteOffset":76152},{"buffer":0,"byteLength":1920,"byteOffset":78072},{"buffer":0,"byteLength":1920,"byteOffset":79992},{"buffer":0,"byteLength":1920,"byteOffset":81912},{"buffer":0,"byteLength":1920,"byteOffset":83832},{"buffer":0,"byteLength":1920,"byteOffset":85752},{"buffer":0,"byteLength":1920,"byteOffset":87672},{"buffer":0,"byteLength":1920,"byteOffset":89592},{"buffer":0,"byteLength":1920,"byteOffset":91512},{"buffer":0,"byteLength":1920,"byteOffset":93432},{"buffer":0,"byteLength":1920,"byteOffset":95352},{"buffer":0,"byteLength":1920,"byteOffset":97272},{"buffer":0,"byteLength":24,"byteOffset":99192},{"buffer":0,"byteLength":1920,"byteOffset":99216},{"buffer":0,"byteLength":24,"byteOffset":101136},{"buffer":0,"byteLength":1920,"byteOffset":101160},{"buffer":0,"byteLength":1920,"byteOffset":103080},{"buffer":0,"byteLength":1920,"byteOffset":105000},{"buffer":0,"byteLength":1920,"byteOffset":106920},{"buffer":0,"byteLength":1920,"byteOffset":108840},{"buffer":0,"byteLength":1920,"byteOffset":110760},{"buffer":0,"byteLength":1920,"byteOffset":112680},{"buffer":0,"byteLength":1920,"byteOffset":114600},{"buffer":0,"byteLength":1920,"byteOffset":116520},{"buffer":0,"byteLength":1920,"byteOffset":118440},{"buffer":0,"byteLength":1920,"byteOffset":120360},{"buffer":0,"byteLength":1920,"byteOffset":122280},{"buffer":0,"byteLength":1920,"byteOffset":124200},{"buffer":0,"byteLength":1920,"byteOffset":126120},{"buffer":0,"byteLength":1920,"byteOffset":128040},{"buffer":0,"byteLength":1920,"byteOffset":129960},{"buffer":0,"byteLength":1440,"byteOffset":131880},{"buffer":0,"byteLength":32,"byteOffset":133320},{"buffer":0,"byteLength":1440,"byteOffset":133352},{"buffer":0,"byteLength":1440,"byteOffset":134792},{"buffer":0,"byteLength":32,"byteOffset":136232},{"buffer":0,"byteLength":1440,"byteOffset":136264},{"buffer":0,"byteLength":1440,"byteOffset":137704},{"buffer":0,"byteLength":32,"byteOffset":139144},{"buffer":0,"byteLength":1440,"byteOffset":139176},{"buffer":0,"byteLength":1440,"byteOffset":140616},{"buffer":0,"byteLength":32,"byteOffset":142056},{"buffer":0,"byteLength":1440,"byteOffset":142088},{"buffer":0,"byteLength":1440,"byteOffset":143528},{"buffer":0,"byteLength":32,"byteOffset":144968},{"buffer":0,"byteLength":1440,"byteOffset":145000},{"buffer":0,"byteLength":1440,"byteOffset":146440},{"buffer":0,"byteLength":32,"byteOffset":147880},{"buffer":0,"byteLength":1440,"byteOffset":147912},{"buffer":0,"byteLength":1440,"byteOffset":149352},{"buffer":0,"byteLength":32,"byteOffset":150792},{"buffer":0,"byteLength":1440,"byteOffset":150824},{"buffer":0,"byteLength":1440,"byteOffset":152264},{"buffer":0,"byteLength":32,"byteOffset":153704},{"buffer":0,"byteLength":1440,"byteOffset":153736}],"buffers":[{"byteLength":155176,"uri":"data:application/octet-stream;base64,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BgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALQAAAC0AAAAtAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALgAAAC4AAAAuAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAALwAAAC8AAAAvAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKQAAACkAAAApAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKgAAACoAAAAqAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAIwAAACMAAAAjAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJQAAACUAAAAlAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAJgAAACYAAAAmAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHgAAAB4AAAAeAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAIQAAACEAAAAhAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGQAAABkAAAAZAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAABoAAAAaAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAGwAAABsAAAAbAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFQAAABUAAAAVAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFgAAABYAAAAWAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAAFwAAABcAAAAXAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEQAAABEAAAARAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAAEgAAABIAAAASAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACgAAAAoAAAAKAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAACwAAAAsAAAALAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADAAAAAwAAAAMAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAADQAAAA0AAAANAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAEADgAUAAEAFAAHAAoABgATAAoAEwAXABUAEgAMABUADAAPABAAAwAJABAACQAWAAUAAgAIAAUACAALABEADQAAABEAAAAEAEwAUgAsAEwALAAfACIAHgArACIAKwAvAEgAUAAkAEgAJAAnAEsAVQAhAEsAIQAuAFYATgAgAFYAIAAjACkAJQAYACkAGAAcADcAQgA8ADcAPAAxAD0APwA0AD0ANAAyAEYAOwA1AEYANQBAADAAMwA5ADAAOQA2AEQARwBBAEQAQQA+ADgAOgBFADgARQBDAB0AGgBPAB0ATwBXACgAGwBUACgAVABKAC0AKgBRAC0AUQBJABkAJgBTABkAUwBNAFgAWwBhAFgAYQBeAF8AYgBtAF8AbQBqAGwAbwBpAGwAaQBmAGUAaABdAGUAXQBaAGAAawBkAGAAZABZAG4AYwBcAG4AXABnAHAAcwB5AHAAeQB2AHgAegCFAHgAhQCDAIQAhwCBAIQAgQB+AH0AfwB0AH0AdAByAHcAggB8AHcAfABxAIYAewB1AIYAdQCAAIgAiwCRAIgAkQCOAJAAkgCdAJAAnQCbAJwAnwCZAJwAmQCWAJUAlwCMAJUAjACKAI8AmgCUAI8AlACJAJ4AkwCNAJ4AjQCYAKIAqACrAKIAqwClAKcAtAC3AKcAtwCqALIArACvALIArwC1AK0AoACjAK0AowCwAKYAoQCuAKYArgCzALYAsQCkALYApACpALoAwADDALoAwwC9AL8AzADOAL8AzgDBAMoAxADHAMoAxwDNAMYAuQC7AMYAuwDIAL4AuADFAL4AxQDLAM8AyQC8AM8AvADCANIA2ADbANIA2wDVANcA5ADmANcA5gDZAOIA3ADfAOIA3wDlAN4A0QDTAN4A0wDgANYA0ADdANYA3QDjAOcA4QDUAOcA1ADaAOkA6wDxAOkA8QDvAPAA8gD9APAA/QD7APwA/gD4APwA+AD2APUA9wDsAPUA7ADqAO4A+gD0AO4A9ADoAP8A8wDtAP8A7QD5AAABAwEJAQABCQEGAQcBCgEVAQcBFQESARQBFwERARQBEQEOAQ0BEAEFAQ0BBQECAQgBEwEMAQgBDAEBARYBCwEEARYBBAEPASkBHQEjASkBIwEvASgBJgEZASgBGQEbAS0BKwElAS0BJQEnASEBHwEsASEBLAEuARgBJAEqARgBKgEeARwBGgEgARwBIAEiATEBMwE6ATEBOgE4ATcBOQFFATcBRQFDAUQBRgE/AUQBPwE9AT4BQAE0AT4BNAEyATYBQgE8ATYBPAEwAUcBOwE1AUcBNQFBAUkBSwFRAUkBUQFPAVABUgFdAVABXQFbAVwBXgFYAVwBWAFWAVUBVwFMAVUBTAFKAU4BWgFUAU4BVAFIAV8BUwFNAV8BTQFZAWABYwFpAWABaQFmAWgBagF1AWgBdQFzAXQBdwFxAXQBcQFuAW0BbwFkAW0BZAFiAWcBcgFsAWcBbAFhAXYBawFlAXYBZQFwAYkBfQGDAYkBgwGPAYgBhgF5AYgBeQF7AY0BiwGFAY0BhQGHAYEBfwGMAYEBjAGOAXgBhAGKAXgBigF+AXwBegGAAXwBgAGCAZEBkwGZAZEBmQGXAZgBmgGlAZgBpQGjAaQBpgGgAaQBoAGeAZ0BnwGUAZ0BlAGSAZYBogGcAZYBnAGQAacBmwGVAacBlQGhAakBqwGxAakBsQGvAbABsgG9AbABvQG7AbwBvgG4AbwBuAG2AbUBtwGsAbUBrAGqAa4BugG0Aa4BtAGoAb8BswGtAb8BrQG5AcABwwHJAcAByQHGAcgBygHVAcgB1QHTAdQB1wHRAdQB0QHOAc0BzwHEAc0BxAHCAccB0gHMAccBzAHBAdYBywHFAdYBxQHQAekB3QHjAekB4wHvAegB5gHZAegB2QHbAe0B6wHlAe0B5QHnAeEB3wHsAeEB7AHuAdgB5AHqAdgB6gHeAdwB2gHgAdwB4AHiAfEB8wH5AfEB+QH3AfgB+gEFAvgBBQIDAgQCBgIAAgQCAAL+Af0B/wH0Af0B9AHyAfYBAgL8AfYB/AHwAQcC+wH1AQcC9QEBAgkCCwIRAgkCEQIPAhACEgIdAhACHQIbAhwCHgIYAhwCGAIWAhUCFwIMAhUCDAIKAg4CGgIUAg4CFAIIAh8CEwINAh8CDQIZAiACIwIpAiACKQImAigCKwI2AigCNgIzAjQCNwIxAjQCMQIuAiwCLwIkAiwCJAIhAicCMgItAicCLQIiAjUCKgIlAjUCJQIwAkkCPQJDAkkCQwJPAkgCRgI5AkgCOQI7Ak0CSwJFAk0CRQJHAkECPwJMAkECTAJOAjgCRAJKAjgCSgI+AjwCOgJAAjwCQAJCAlICVAJZAlICWQJXAlgCWgJmAlgCZgJkAmMCZQJgAmMCYAJeAl0CXwJTAl0CUwJRAlYCYgJcAlYCXAJQAmcCWwJVAmcCVQJhAmkCbwJzAmkCcwJtAm4CewJ/Am4CfwJyAnoCdAJ4AnoCeAJ+AnUCaAJsAnUCbAJ5AnACagJ2AnACdgJ8An0CdwJrAn0CawJxAoICiAKLAoICiwKFAocClAKXAocClwKKApICjAKPApICjwKVAo0CgAKDAo0CgwKQAoYCgQKOAoYCjgKTApYCkQKEApYChAKJAqcCrQKhAqcCoQKbAqgCnQKZAqgCmQKkAq8CqQKlAq8CpQKrAqMCrgKqAqMCqgKfApoCoAKsApoCrAKmApwCogKeApwCngKYArECtgK6ArECugK1ArcCwwLHArcCxwK7AsICvQLBAsICwQLGArwCsAK0ArwCtALAArgCsgK+ArgCvgLEAsUCvwKzAsUCswK5AskCzwLTAskC0wLNAs4C2wLfAs4C3wLSAtoC1ALYAtoC2ALeAtUCyALMAtUCzALZAtACygLWAtAC1gLcAt0C1wLLAt0CywLRAuIC6ALrAuIC6wLlAuYC8wL3AuYC9wLqAvIC7ALvAvIC7wL1Au0C4ALkAu0C5ALxAucC4QLuAucC7gL0AvYC8ALjAvYC4wLpAgcDDQMBAwcDAQP7AggD/QL5AggD+QIEAw8DCQMFAw8DBQMLAwMDDgMKAwMDCgP/AvoCAAMMA/oCDAMGA/wCAgP+AvwC/gL4AhEDFwMbAxEDGwMVAxYDIwMnAxYDJwMaAyIDHAMgAyIDIAMmAx0DEAMUAx0DFAMhAxgDEgMeAxgDHgMkAyUDHwMTAyUDEwMZAykDLwMzAykDMwMtAy4DOwM/Ay4DPwMyAzoDNAM4AzoDOAM+AzUDKAMsAzUDLAM5AzADKgM2AzADNgM8Az0DNwMrAz0DKwMxA0IDSANLA0IDSwNFA0YDUwNXA0YDVwNKA1IDTANPA1IDTwNVA00DQANEA00DRANRA0cDQQNOA0cDTgNUA1YDUANDA1YDQwNJA2cDbQNhA2cDYQNbA2gDXQNZA2gDWQNkA28DaQNlA28DZQNrA2MDbgNqA2MDagNfA1oDYANsA1oDbANmA1wDYgNeA1wDXgNYA3EDdwN7A3EDewN1A3YDgwOHA3YDhwN6A4IDfAOAA4IDgAOGA30DcAN0A30DdAOBA3gDcgN+A3gDfgOEA4UDfwNzA4UDcwN5A4kDjwOTA4kDkwONA44DmwOfA44DnwOSA5oDlAOYA5oDmAOeA5UDiAOMA5UDjAOZA5ADigOWA5ADlgOcA50DlwOLA50DiwORA6IDqAOrA6IDqwOlA6YDswO2A6YDtgOpA7IDrAOvA7IDrwO1A64DoQOkA64DpAOxA6cDoAOtA6cDrQO0A7cDsAOjA7cDowOqA8cDzQPBA8cDwQO7A8gDvQO5A8gDuQPEA88DyQPFA88DxQPLA8MDzgPKA8MD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TmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+cw9Rrv9jabWlCWo/0XvPPt5waq/JcWm1pQlqP9F7zz54BNKqTmtptaUJaj/Re88+jT7qr0Z4abWlCWo/0XvPPhnLKDCae2m1pwlqP9F7zz7t5U+vDU5ptaUJaj/Se88+goncLxOCabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4MKI+vempptaUJaj/Re88+WtPpL1ZeabWlCWo/0XvPPgDKKDCYe2m1pQlqP9F7zz5ssc+vzDBptaUJaj/Re88+NwGPsPxnabWlCWo/0XvPPuaqzi7ueWm1pQlqP9F7zz5TyIEve2hptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1pQlqP9F7zz4ZhMCqTmtptaUJaj/Re88+GYTAqk5rabWlCWo/0XvPPhmEwKpOa2m1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4G1iP+fAELztE0K9GXDnvvwEZD/Ubm27I9lMvcau3r5aKWY/BMwvuVYXVL21b9a+XhZoPyUpjjptxVa98bnSvvXsaD+J6Z652tVTvVoT1L5EoWg/nweCu5kqTL01Zde+DeRnP8KSFrxBaEG9XIfbvjjyZj9jLXm8ASo1vSVf376GCmY/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/eHyevLDKLL0feOG+pYhlP3h8nrywyiy9H3jhvqWIZT94fJ68sMosvR944b6liGU/fRmbvAekLb0AjOC+bMJlP2ROkryp1y+9SxjevoVaZj/IVIa8Gdcyvf2L2r7UMmc/zpJyvNAdNr0XVta+0i1oP13HWbzWQjm9ePDRvm0taT9Ir0S8M/07vd3jzb4ME2o/FLM0vFQhPr37xMq+N8BqP2DQK7zTXz+9SjDJvpwWaz+PoyK8iLNAvd5Oyr5r2Go/nfUQvDs+Q72B+M2+7gpqP/l78Lvg/ka9nPTRvpklaT8LXL27jU5LvQiK077sxmg/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4gzaP7VZD7+1Com/8SHGP7VZD7+1Com/SEyzP7VZD7+1Com/wkSiP7VZD7+1Com/OsSTP7VZD7+1Com/joOIP7VZD7+1Com/mDuBP7VZD7+1Com/Ykp9P7VZD7+1Com/Ykp9P7VZD7+1Com/Ykp9P7VZD7+1Com/hUnlsoVJ5bL0BDW/9AQ1P4VJ5bKFSeWy9AQ1v/QENT8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/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AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP5IMgT+SDIE/AACAP5QDhD+UA4Q/AACAP/SeiD/0nog/AACAP6OYjj+jmI4/AACAP5KqlT+SqpU/AACAP7GOnT+xjp0/AACAP/D+pT/w/qU/AACAPz61rj8+ta4/AACAP4xrtz+Ma7c/AACAP8zbvz/M278/AACAP+q/xz/qv8c/AACAP9rRzj/a0c4/AACAP4rL1D+Ky9Q/AACAP+pm2T/qZtk/AACAP+ld3D/pXdw/AACAP3xq3T98at0/AACAP8BZ2z98at0/AACAP+iJ1T98at0/AACAP4SOzD98at0/AACAPyH7wD98at0/AACAP05jsz98at0/AACAP5hapD98at0/AACAP4x0lD98at0/AACAP7pEhD98at0/AACAP1+9aD98at0/AACAP/KrSj98at0/AACAP018Lz98at0/AACAP4ZVGD98at0/AACAP7xeBj98at0/AACAPyB+9T58at0/AACAPy077T58at0/AACAPy077T58at0/AACAPy077T58at0/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utbk/6lwQQLRZD797gqw/6lwQQLRZD79IT58/6lwQQLRZD79IT5k/6lwQQGaz/r6utZ0/6lwQQABNuL5IT6k/6lwQQDjNY76utbk/6lwQQDXNI757gsw/6lwQQDfNY75IT98/6lwQQAFNuL6tte8/6lwQQGez/r5HT/s/6lwQQLRZD7+utf8/6lwQQLNZD79HT/s/6lwQQLRZD7+ste8/6lwQQLRZD79GT98/6lwQQLRZD796gsw/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/6lwQQLRZD7+utb8/x1UQQLRZD78EhME/0yMQQLRZD7/oRsY/PJwPQLRZD78pAs0/MpQOQLRZD7+ZudQ/4+AMQLRZD78Jcdw/fVcKQLRZD79KLOM/MM0GQLRZD78t7+c/KhcCQLRZD7+Dvek/uef0P7RZD790D+c/YGTeP7RZD7+//t8/DoDDP7RZD7+cAdY/ghanP7RZD79Djso/ggOMP7RZD7/qGr8/mEVqP7VZD7/IHbU/TKBKP7VZD78SDa4/pM4+P7RZD78DX6s/lpVQP7RZD78DX6s/8AZ9P7RZD78DX6s/wGabP7RZD78DX6s/CEq4P7RZD78DX6s/toLOP7RZD78DX6s/LmbXP7RZD78DX6s/VszRP7RZD78DX6s/osnCP7RZD78DX6s/UQ6tP7RZD78DX6s/okqTP7RZD78EX6s/qF1wP7RZD78EX6s/StY8P7RZD78EX6s/qF8RP7RZD78EX6s/hLTmPrRZD78EX6s/IE3QPrRZD78EX6s/IE3QPrRZD78EX6s/IE3QPrRZD78EX6s/hUnlsoVJ5bL0BDW/9AQ1P4VJ5bKFSeWy9AQ1v/QENT8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/ViaAP1YmgD8AAIA/V5eAP1eXgD8AAIA/AlCBPwJQgT8AAIA/VU2CP1VNgj8AAIA/T4yDP0+Mgz8AAIA/7wmFP+8JhT8AAIA/NMOGPzTDhj8AAIA/G7WIPxu1iD8AAIA/pdyKP6Xcij8AAIA/zzaNP882jT8AAIA/mcCPP5nAjz8AAIA/AHeSPwB3kj8AAIA/A1eVPwNXlT8AAIA/o12YP6NdmD8AAIA/3IebP9yHmz8AAIA/rtKeP67Snj8AAIA/FjuiPxY7oj8AAIA/Fr6lPxa+pT8AAIA/qlipP6pYqT8AAIA/0QetP9EHrT8AAIA/i8iwP4vIsD8AAIA/1Ze0P9WXtD8AAIA/rnK4P65yuD8AAIA/F1a8PxdWvD8AAIA/Cz/APws/wD8AAIA/iyrEP4sqxD8AAIA/lhXIP5YVyD8AAIA/Kv3LPyr9yz8AAIA/Rd7PP0Xezz8AAIA/57XTP+e10z8AAIA/DYHXPw2B1z8AAIA/tzzbP7c82z8AAIA/4+XeP+Pl3j8AAIA/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tFkPvxnErL4gwuK9tFkPvxnErL6ACWC9tFkPvxnErL5AFw69tFkPvxnErL5AFw69tFkPvxnErL5AFw69tFkPvxnErL6FSeWyhUnlMvQENT/0BDU/hUnlsoVJ5TL0BDU/9AQ1PwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD9vNoA/bzaAPwAAgD851oA/OdaAPwAAgD8Z2oE/GdqBPwAAgD/KPIM/yjyDPwAAgD8I+YQ/CPmEPwAAgD+QCYc/kAmHPwAAgD8caYk/HGmJPwAAgD9nEow/ZxKMPwAAgD8tAI8/LQCPPwAAgD8pLZI/KS2SPwAAgD8XlJU/F5SVPwAAgD+zL5k/sy+ZPwAAgD+3+pw/t/qcPwAAgD/g76A/4O+gPwAAgD/oCaU/6AmlPwAAgD+MQ6k/jEOpPwAAgD+Gl60/hpetPwAAgD+TALI/kwCyPwAAgD9tebY/bXm2PwAAgD/R/Lo/0fy6PwAAgD95hb8/eYW/PwAAgD8hDsQ/IQ7EPwAAgD+Ekcg/hJHIPwAAgD9eCs0/XgrNPwAAgD9qc9E/anPRPwAAgD9mx9U/ZsfVPwAAgD8KAdo/CgHaPwAAgD8QG94/EBvePwAAgD86EOI/OhDiPwAAgD8/2+U/P9vlPwAAgD/Zduk/2XbpPwAAgD/I3ew/yN3sPwAAgD/FCvA/xQrwPwAAgD+L+PI/i/jyPwAAgD/VofU/1aH1PwAAgD9hAfg/YQH4PwAAgD/pEfo/6RH6PwAAgD8mzvs/Js77PwAAgD/YMP0/2DD9PwAAgD+4NP4/uDT+PwAAgD+D1P4/g9T+PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD/xCv8/8Qr/PwAAgD+Wsfo/8Qr/PwAAgD/YxO4/8Qr/PwAAgD+y89w/8Qr/PwAAgD8c7cY/8Qr/PwAAgD8OYK4/8Qr/PwAAgD+F+5Q/8Qr/PwAAgD/v3Hg/8Qr/PwAAgD/Az0w/8Qr/PwAAgD9wLSk/8Qr/PwAAgD/2UxE/8Qr/PwAAgD9CoQg/8Qr/PwAAgD9CoQg/8Qr/PwAAgD9CoQg/8Qr/PweaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8A2zSO+UHMIvQeaF8A4zWO+FTNePAeaF8ACTbi+kvnuPQeaF8Bns/6+XhZhPgeaF8C0WQ+/MIuIPgeaF8C0WQ+/kkl6PgeaF8C0WQ+/+K86PgeaF8C0WQ+/jPmyPQeaF8C0WQ+/UHMIvQeaF8C0WQ+/brYtvgeaF8C0WQ+/aA6ZvgeaF8CzWQ+/znTKvgeaF8C0WQ+/N9vdvgeaF8Bms/6+Ntu9vgeaF8AATbi+oeluvgeaF8A2zWO+qznEvQeaF8A2zSO+UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvQeaF8C0WQ+/UHMIvSVLF8C0WQ+/gVX5vDFiFsC0WQ+/Xbu2vLDkFMC0WQ+/HHwevCvYEsC0WQ+/MPLbOypCEMC0WQ+/mM7VPDMoDcC0WQ+/HolDPc+PCcC0WQ+/ZiiRPYZ+BcC0WQ+/tBXCPd35AMC0WQ+/AAPzPb0O+L+0WQ+/bDMRPiBZ7b+0WQ+/4lsnPvTd4b+0WQ+/JDY7Pkmo1b+0WQ+/dv1LPizDyL+0WQ+/Hu1YPq85u7+0WQ+/ZEBhPt8Wrb+0WQ+/iDJkPpRsnr+0WQ+/iDJkPs9nj7+0WQ+/iDJkPlk8gL+0WQ+/iDJkPvI7Yr+0WQ+/iDJkPvCARL+0WQ+/iDJkPkCvJ7+0WQ+/iDJkPnYuDL+0WQ+/iDJkPjjM5L60WQ+/iDJkPpB7tb60WQ+/iDJkPig6i760WQ+/iDJkPvCtTb60WQ+/iDJkPpBCEr60WQ+/iDJkPuCgy720WQ+/iDJkPmDrk720WQ+/iDJkPqChgL20WQ+/iDJkPqChgL20WQ+/iDJkPqChgL20WQ+/iDJkPoVJ5bKFSeUy9AQ1P/QENT+FSeWyhUnlMvQENT/0BDU/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP5IMgT+SDIE/AACAP5QDhD+UA4Q/AACAP/SeiD/0nog/AACAP6OYjj+jmI4/AACAP5KqlT+SqpU/AACAP7GOnT+xjp0/AACAP/D+pT/w/qU/AACAPz61rj8+ta4/AACAP4xrtz+Ma7c/AACAP8zbvz/M278/AACAP+q/xz/qv8c/AACAP9rRzj/a0c4/AACAP4rL1D+Ky9Q/AACAP+pm2T/qZtk/AACAP+ld3D/pXdw/AACAP3xq3T98at0/AACAP8BZ2z98at0/AACAP+iJ1T98at0/AACAP4SOzD98at0/AACAPyH7wD98at0/AACAP05jsz98at0/AACAP5hapD98at0/AACAP4x0lD98at0/AACAP7pEhD98at0/AACAP1+9aD98at0/AACAP/KrSj98at0/AACAP018Lz98at0/AACAP4ZVGD98at0/AACAP7xeBj98at0/AACAPyB+9T58at0/AACAPy077T58at0/AACAPy077T58at0/AACAPy077T58at0/B5oXwLRZD7++SjI/B5oXwLRZD7++SjI/B5oXwLRZD7++SjI/B5oXwLRZD7++SjI/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AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/ViaAP1YmgD8AAIA/V5eAP1eXgD8AAIA/AlCBPwJQgT8AAIA/VU2CP1VNgj8AAIA/T4yDP0+Mgz8AAIA/7wmFP+8JhT8AAIA/NMOGPzTDhj8AAIA/G7WIPxu1iD8AAIA/pdyKP6Xcij8AAIA/zzaNP882jT8AAIA/mcCPP5nAjz8AAIA/AHeSPwB3kj8AAIA/A1eVPwNXlT8AAIA/o12YP6NdmD8AAIA/3IebP9yHmz8AAIA/rtKeP67Snj8AAIA/FjuiPxY7oj8AAIA/Fr6lPxa+pT8AAIA/qlipP6pYqT8AAIA/0QetP9EHrT8AAIA/i8iwP4vIsD8AAIA/1Ze0P9WXtD8AAIA/rnK4P65yuD8AAIA/F1a8PxdWvD8AAIA/Cz/APws/wD8AAIA/iyrEP4sqxD8AAIA/lhXIP5YVyD8AAIA/Kv3LPyr9yz8AAIA/Rd7PP0Xezz8AAIA/57XTP+e10z8AAIA/DYHXPw2B1z8AAIA/tzzbP7c82z8AAIA/4+XeP+Pl3j8AAIA/kXniP5F54j8AAIA/vvTlP7705T8AAIA/aFTpP2hU6T8AAIA/lJXsP5SV7D8AAIA/OLXvPzi17z8AAIA/VrDyP1aw8j8AAIA/7oP1P+6D9T8AAIA//yz4P/8s+D8AAIA/hqj6P4ao+j8AAIA/gvP8P4Lz/D8AAIA/8Qr/P/EK/z8AAIA/+nUAQPp1AEAAAIA/E0oBQBNKAUAAAIA/pAACQKQAAkAAAIA/jJgCQIyYAkAAAIA/qBADQKgQA0AAAIA/2GcDQNhnA0AAAIA//ZwDQP2cA0AAAIA/9a4DQPWuA0AAAIA/9a4DQPWuA0AAAIA/9a4DQPWuA0AAAIA/9a4DQPWuA0AAAIA/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AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD+SDIE/kgyBPwAAgD+UA4Q/lAOEPwAAgD/0nog/9J6IPwAAgD+jmI4/o5iOPwAAgD+SqpU/kqqVPwAAgD+xjp0/sY6dPwAAgD/w/qU/8P6lPwAAgD8+ta4/PrWuPwAAgD+Ma7c/jGu3PwAAgD/M278/zNu/PwAAgD/qv8c/6r/HPwAAgD/a0c4/2tHOPwAAgD+Ky9Q/isvUPwAAgD/qZtk/6mbZPwAAgD/pXdw/6V3cPwAAgD98at0/fGrdPwAAgD/AWds/fGrdPwAAgD/oidU/fGrdPwAAgD+Ejsw/fGrdPwAAgD8h+8A/fGrdPwAAgD9OY7M/fGrdPwAAgD+YWqQ/fGrdPwAAgD+MdJQ/fGrdPwAAgD+6RIQ/fGrdPwAAgD9fvWg/fGrdPwAAgD/yq0o/fGrdPwAAgD9NfC8/fGrdPwAAgD+GVRg/fGrdPwAAgD+8XgY/fGrdPwAAgD8gfvU+fGrdPwAAgD8tO+0+fGrdPwAAgD8tO+0+fGrdPwAAgD8tO+0+fGrdPw=="}]}
diff --git a/games/devtest/mods/gltf/models/gltf_triangle_with_vertex_stride.gltf b/games/devtest/mods/gltf/models/gltf_triangle_with_vertex_stride.gltf
new file mode 100644
index 000000000..feddfbb02
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_triangle_with_vertex_stride.gltf
@@ -0,0 +1 @@
+{"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"attributes":{"POSITION":1},"indices":0}]}],"buffers":[{"uri":"data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAA=","byteLength":80}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":6,"target":34963},{"buffer":0,"byteOffset":8,"byteLength":72,"byteStride":24,"target":34962}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5123,"count":3,"type":"SCALAR","max":[2],"min":[0]},{"bufferView":1,"byteOffset":0,"componentType":5126,"count":3,"type":"VEC3","max":[1,1,0],"min":[0,0,0]}],"asset":{"version":"2.0"}}
diff --git a/games/devtest/mods/gltf/models/gltf_triangle_without_indices.gltf b/games/devtest/mods/gltf/models/gltf_triangle_without_indices.gltf
new file mode 100644
index 000000000..e91cc0e5a
--- /dev/null
+++ b/games/devtest/mods/gltf/models/gltf_triangle_without_indices.gltf
@@ -0,0 +1 @@
+{"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"buffers":[{"uri":"data:application/octet-stream;base64,AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA","byteLength":36}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":36,"target":34962}],"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5126,"count":3,"type":"VEC3","max":[1,1,0],"min":[0,0,0]}],"asset":{"version":"2.0"}}
diff --git a/games/devtest/mods/gltf/textures/gltf_cube.png b/games/devtest/mods/gltf/textures/gltf_cube.png
new file mode 100644
index 000000000..1d0191085
Binary files /dev/null and b/games/devtest/mods/gltf/textures/gltf_cube.png differ
diff --git a/games/devtest/mods/gltf/textures/gltf_frog.png b/games/devtest/mods/gltf/textures/gltf_frog.png
new file mode 100644
index 000000000..552ae3649
Binary files /dev/null and b/games/devtest/mods/gltf/textures/gltf_frog.png differ
diff --git a/games/devtest/mods/gltf/textures/gltf_snow_man.png b/games/devtest/mods/gltf/textures/gltf_snow_man.png
new file mode 100644
index 000000000..7f2784358
Binary files /dev/null and b/games/devtest/mods/gltf/textures/gltf_snow_man.png differ
diff --git a/games/devtest/mods/gltf/textures/gltf_spider.png b/games/devtest/mods/gltf/textures/gltf_spider.png
new file mode 100644
index 000000000..1e3d3ae8c
Binary files /dev/null and b/games/devtest/mods/gltf/textures/gltf_spider.png differ
diff --git a/games/devtest/mods/lighting/init.lua b/games/devtest/mods/lighting/init.lua
index 7b4392fb8..20448d925 100644
--- a/games/devtest/mods/lighting/init.lua
+++ b/games/devtest/mods/lighting/init.lua
@@ -14,7 +14,21 @@ local lighting_sections = {
{n = "speed_bright_dark", d = "Dark scene adaptation speed", min = -10, max = 10, type="log2"},
{n = "center_weight_power", d = "Power factor for center-weighting", min = 0.1, max = 10},
}
- }
+ },
+ {
+ n = "bloom", d = "Bloom",
+ entries = {
+ {n = "intensity", d = "Intensity", min = 0, max = 1},
+ {n = "strength_factor", d = "Strength Factor", min = 0.1, max = 10},
+ {n = "radius", d = "Radius", min = 0.1, max = 8},
+ },
+ },
+ {
+ n = "volumetric_light", d = "Volumetric Lighting",
+ entries = {
+ {n = "strength", d = "Strength", min = 0, max = 1},
+ },
+ },
}
local function dump_lighting(lighting)
@@ -59,38 +73,40 @@ minetest.register_chatcommand("set_lighting", {
local lighting = player:get_lighting()
local exposure = lighting.exposure or {}
- local form = {
- "formspec_version[2]",
- "size[15,30]",
- "position[0.99,0.15]",
- "anchor[1,0]",
- "padding[0.05,0.1]",
- "no_prepend[]"
- };
-
+ local content = {}
local line = 1
for _,section in ipairs(lighting_sections) do
local parameters = section.entries or {}
local state = lighting[section.n] or {}
- table.insert(form, "label[1,"..line..";"..section.d.."]")
+ table.insert(content, "label[1,"..line..";"..section.d.."]")
line = line + 1
for _,v in ipairs(parameters) do
- table.insert(form, "label[2,"..line..";"..v.d.."]")
- table.insert(form, "scrollbaroptions[min=0;max=1000;smallstep=10;largestep=100;thumbsize=10]")
+ table.insert(content, "label[2,"..line..";"..v.d.."]")
+ table.insert(content, "scrollbaroptions[min=0;max=1000;smallstep=10;largestep=100;thumbsize=10]")
local value = state[v.n]
if v.type == "log2" then
value = math.log(value or 1) / math.log(2)
end
local sb_scale = math.floor(1000 * (math.max(v.min, value or 0) - v.min) / (v.max - v.min))
- table.insert(form, "scrollbar[2,"..(line+0.7)..";12,1;horizontal;"..section.n.."."..v.n..";"..sb_scale.."]")
+ table.insert(content, "scrollbar[2,"..(line+0.7)..";12,1;horizontal;"..section.n.."."..v.n..";"..sb_scale.."]")
line = line + 2.7
end
line = line + 1
end
+ local form = {
+ "formspec_version[2]",
+ "size[15,", line, "]",
+ "position[0.99,0.15]",
+ "anchor[1,0]",
+ "padding[0.05,0.1]",
+ "no_prepend[]",
+ }
+ table.insert_all(form, content)
+
minetest.show_formspec(player_name, "lighting", table.concat(form))
local debug_value = dump_lighting(lighting)
local debug_ui = player:hud_add({type="text", position={x=0.1, y=0.3}, scale={x=1,y=1}, alignment = {x=1, y=1}, text=debug_value, number=0xFFFFFF})
diff --git a/games/devtest/mods/testabms/README.md b/games/devtest/mods/testabms/README.md
new file mode 100644
index 000000000..60fa6d656
--- /dev/null
+++ b/games/devtest/mods/testabms/README.md
@@ -0,0 +1,6 @@
+# Test ABMs
+
+This mod contains a nodes and related ABM actions.
+By placing these nodes, you can test basic ABM behaviours.
+
+There are separate tests for ABM `chance`, `interval`, `min_y`, `max_y`, `neighbor` and `without_neighbor` fields.
diff --git a/games/devtest/mods/testabms/after_node.lua b/games/devtest/mods/testabms/after_node.lua
new file mode 100644
index 000000000..64cdfb484
--- /dev/null
+++ b/games/devtest/mods/testabms/after_node.lua
@@ -0,0 +1,12 @@
+
+local S = minetest.get_translator("testnodes")
+
+-- After ABM node
+minetest.register_node("testabms:after_abm", {
+ description = S("After ABM processed node."),
+ drawtype = "normal",
+ tiles = { "testabms_after_node.png" },
+
+ groups = { dig_immediate = 3 },
+})
+
diff --git a/games/devtest/mods/testabms/chances.lua b/games/devtest/mods/testabms/chances.lua
new file mode 100644
index 000000000..95f416b45
--- /dev/null
+++ b/games/devtest/mods/testabms/chances.lua
@@ -0,0 +1,56 @@
+-- test ABMs with different chances
+
+local S = minetest.get_translator("testnodes")
+
+-- ABM chance 5 node
+minetest.register_node("testabms:chance_5", {
+ description = S("Node for test ABM chance_5"),
+ drawtype = "normal",
+ tiles = { "testabms_wait_node.png" },
+
+ groups = { dig_immediate = 3 },
+
+ on_construct = function (pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "Waiting for ABM testabms:chance_5")
+ end,
+})
+
+minetest.register_abm({
+ label = "testabms:chance_5",
+ nodenames = "testabms:chance_5",
+ interval = 10,
+ chance = 5,
+ action = function (pos)
+ minetest.swap_node(pos, {name="testabms:after_abm"})
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "ABM testabsm:chance_5 changed this node.")
+ end
+})
+
+-- ABM chance 20 node
+minetest.register_node("testabms:chance_20", {
+ description = S("Node for test ABM chance_20"),
+ drawtype = "normal",
+ tiles = { "testabms_wait_node.png" },
+
+ groups = { dig_immediate = 3 },
+
+ on_construct = function (pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "Waiting for ABM testabms:chance_20")
+ end,
+})
+
+minetest.register_abm({
+ label = "testabms:chance_20",
+ nodenames = "testabms:chance_20",
+ interval = 10,
+ chance = 20,
+ action = function (pos)
+ minetest.swap_node(pos, {name="testabms:after_abm"})
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "ABM testabsm:chance_20 changed this node.")
+ end
+})
+
diff --git a/games/devtest/mods/testabms/init.lua b/games/devtest/mods/testabms/init.lua
new file mode 100644
index 000000000..7830d8436
--- /dev/null
+++ b/games/devtest/mods/testabms/init.lua
@@ -0,0 +1,7 @@
+local path = minetest.get_modpath(minetest.get_current_modname())
+
+dofile(path.."/after_node.lua")
+dofile(path.."/chances.lua")
+dofile(path.."/intervals.lua")
+dofile(path.."/min_max.lua")
+dofile(path.."/neighbors.lua")
diff --git a/games/devtest/mods/testabms/intervals.lua b/games/devtest/mods/testabms/intervals.lua
new file mode 100644
index 000000000..57b58faa5
--- /dev/null
+++ b/games/devtest/mods/testabms/intervals.lua
@@ -0,0 +1,56 @@
+-- test ABMs with different interval
+
+local S = minetest.get_translator("testnodes")
+
+-- ABM inteval 1 node
+minetest.register_node("testabms:interval_1", {
+ description = S("Node for test ABM interval_1"),
+ drawtype = "normal",
+ tiles = { "testabms_wait_node.png" },
+
+ groups = { dig_immediate = 3 },
+
+ on_construct = function (pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "Waiting for ABM testabms:interval_1")
+ end,
+})
+
+minetest.register_abm({
+ label = "testabms:interval_1",
+ nodenames = "testabms:interval_1",
+ interval = 1,
+ chance = 1,
+ action = function (pos)
+ minetest.swap_node(pos, {name="testabms:after_abm"})
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "ABM testabsm:interval_1 changed this node.")
+ end
+})
+
+-- ABM interval 60 node
+minetest.register_node("testabms:interval_60", {
+ description = S("Node for test ABM interval_60"),
+ drawtype = "normal",
+ tiles = { "testabms_wait_node.png" },
+
+ groups = { dig_immediate = 3 },
+
+ on_construct = function (pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "Waiting for ABM testabms:interval_60")
+ end,
+})
+
+minetest.register_abm({
+ label = "testabms:interval_60",
+ nodenames = "testabms:interval_60",
+ interval = 60,
+ chance = 1,
+ action = function (pos)
+ minetest.swap_node(pos, {name="testabms:after_abm"})
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "ABM testabsm:interval_60 changed this node.")
+ end
+})
+
diff --git a/games/devtest/mods/testabms/min_max.lua b/games/devtest/mods/testabms/min_max.lua
new file mode 100644
index 000000000..62f1ccd53
--- /dev/null
+++ b/games/devtest/mods/testabms/min_max.lua
@@ -0,0 +1,58 @@
+-- test ABMs with min_y and max_y
+
+local S = minetest.get_translator("testnodes")
+
+-- ABM min_y node
+minetest.register_node("testabms:min_y", {
+ description = S("Node for test ABM min_y."),
+ drawtype = "normal",
+ tiles = { "testabms_wait_node.png" },
+
+ groups = { dig_immediate = 3 },
+
+ on_construct = function (pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "Waiting for ABM testabms:min_y at y "..pos.y.." with min_y = 0")
+ end,
+})
+
+minetest.register_abm({
+ label = "testabms:min_y",
+ nodenames = "testabms:min_y",
+ interval = 10,
+ chance = 1,
+ min_y = 0,
+ action = function (pos)
+ minetest.swap_node(pos, {name="testabms:after_abm"})
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "ABM testabsm:min_y changed this node.")
+ end
+})
+
+-- ABM max_y node
+minetest.register_node("testabms:max_y", {
+ description = S("Node for test ABM max_y."),
+ drawtype = "normal",
+ tiles = { "testabms_wait_node.png" },
+
+ groups = { dig_immediate = 3 },
+
+ on_construct = function (pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "Waiting for ABM testabms:max_y at y "..pos.y.." with max_y = 0")
+ end,
+})
+
+minetest.register_abm({
+ label = "testabms:max_y",
+ nodenames = "testabms:max_y",
+ interval = 10,
+ chance = 1,
+ max_y = 0,
+ action = function (pos)
+ minetest.swap_node(pos, {name="testabms:after_abm"})
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext", "ABM testabsm:max_y changed this node.")
+ end
+})
+
diff --git a/games/devtest/mods/testabms/mod.conf b/games/devtest/mods/testabms/mod.conf
new file mode 100644
index 000000000..ad74cd2ed
--- /dev/null
+++ b/games/devtest/mods/testabms/mod.conf
@@ -0,0 +1,2 @@
+name = testabms
+description = Contains some nodes for test ABMs.
diff --git a/games/devtest/mods/testabms/neighbors.lua b/games/devtest/mods/testabms/neighbors.lua
new file mode 100644
index 000000000..42ce47dff
--- /dev/null
+++ b/games/devtest/mods/testabms/neighbors.lua
@@ -0,0 +1,99 @@
+-- test ABMs with neighbor and without_neighbor
+
+local S = minetest.get_translator("testnodes")
+
+-- ABM required neighbor
+minetest.register_node("testabms:required_neighbor", {
+ description = S("Node for test ABM required_neighbor.") .. "\n"
+ .. S("Sensitive neighbor node is testnodes:normal."),
+ drawtype = "normal",
+ tiles = { "testabms_wait_node.png" },
+
+ groups = { dig_immediate = 3 },
+
+ on_construct = function (pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext",
+ "Waiting for ABM testabms:required_neighbor "
+ .. "(normal drawtype testnode sensitive)")
+ end,
+})
+
+minetest.register_abm({
+ label = "testabms:required_neighbor",
+ nodenames = "testabms:required_neighbor",
+ neighbors = {"testnodes:normal"},
+ interval = 1,
+ chance = 1,
+ action = function (pos)
+ minetest.swap_node(pos, {name="testabms:after_abm"})
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext",
+ "ABM testabsm:required_neighbor changed this node.")
+ end
+})
+
+-- ABM missing neighbor node
+minetest.register_node("testabms:missing_neighbor", {
+ description = S("Node for test ABM missing_neighbor.") .. "\n"
+ .. S("Sensitive neighbor node is testnodes:normal."),
+ drawtype = "normal",
+ tiles = { "testabms_wait_node.png" },
+
+ groups = { dig_immediate = 3 },
+
+ on_construct = function (pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext",
+ "Waiting for ABM testabms:missing_neighbor"
+ .. " (normal drawtype testnode sensitive)")
+ end,
+})
+
+minetest.register_abm({
+ label = "testabms:missing_neighbor",
+ nodenames = "testabms:missing_neighbor",
+ without_neighbors = {"testnodes:normal"},
+ interval = 1,
+ chance = 1,
+ action = function (pos)
+ minetest.swap_node(pos, {name="testabms:after_abm"})
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext",
+ "ABM testabsm:missing_neighbor changed this node.")
+ end
+})
+
+-- ABM required and missing neighbor node
+minetest.register_node("testabms:required_missing_neighbor", {
+ description = S("Node for test ABM required_missing_neighbor.") .. "\n"
+ .. S("Sensitive neighbor nodes are testnodes:normal and testnodes:glasslike."),
+ drawtype = "normal",
+ tiles = { "testabms_wait_node.png" },
+
+ groups = { dig_immediate = 3 },
+
+ on_construct = function (pos)
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext",
+ "Waiting for ABM testabms:required_missing_neighbor"
+ .. " (wint normal drawtype testnode and no glasslike"
+ .. " drawtype testnode sensitive)")
+ end,
+})
+
+minetest.register_abm({
+ label = "testabms:required_missing_neighbor",
+ nodenames = "testabms:required_missing_neighbor",
+ neighbors = {"testnodes:normal"},
+ without_neighbors = {"testnodes:glasslike"},
+ interval = 1,
+ chance = 1,
+ action = function (pos)
+ minetest.swap_node(pos, {name="testabms:after_abm"})
+ local meta = minetest.get_meta(pos)
+ meta:set_string("infotext",
+ "ABM testabsm:required_missing_neighbor changed this node.")
+ end
+})
+
diff --git a/games/devtest/mods/testabms/textures/testabms_after_node.png b/games/devtest/mods/testabms/textures/testabms_after_node.png
new file mode 100644
index 000000000..dab87594b
Binary files /dev/null and b/games/devtest/mods/testabms/textures/testabms_after_node.png differ
diff --git a/games/devtest/mods/testabms/textures/testabms_wait_node.png b/games/devtest/mods/testabms/textures/testabms_wait_node.png
new file mode 100644
index 000000000..a9bd9a36f
Binary files /dev/null and b/games/devtest/mods/testabms/textures/testabms_wait_node.png differ
diff --git a/games/devtest/mods/testformspec/formspec.lua b/games/devtest/mods/testformspec/formspec.lua
index 99ee691f1..f8f17798b 100644
--- a/games/devtest/mods/testformspec/formspec.lua
+++ b/games/devtest/mods/testformspec/formspec.lua
@@ -66,7 +66,7 @@ local inv_style_fs = [[
-- Some textures from textures/base/pack and Devtest, with many different sizes
-- and aspect ratios.
-local image_column = "image,0=logo.png,1=rare_controls.png,2=checkbox_16.png," ..
+local image_column = "image,0=logo.png,1=crack_anylength.png^[invert:rgb,2=checkbox_16.png," ..
"3=checkbox_32.png,4=checkbox_64.png,5=default_lava.png," ..
"6=progress_bar.png,7=progress_bar_bg.png"
local words = {
@@ -299,7 +299,18 @@ local scroll_fs =
"scrollbaroptions[max=170]".. -- lowest seen pos is: 0.1*170+6=23 (factor*max+height)
"scrollbar[7.5,0;0.3,4;vertical;scrbar;0]"..
"scrollbar[8,0;0.3,4;vertical;scrbarhmmm;0]"..
- "dropdown[0,6;2;hmdrpdwnnn;Outside,of,container;1]"
+ "dropdown[0,6;2;hmdrpdwnnn;Outside,of,container;1]"..
+ "scroll_container[0,8;10,4;scrbar420;vertical;0.1;2]"..
+ "button[0.5,0.5;10,1;;Container with padding=2]"..
+ "list[current_player;main;0,5;8,4;]"..
+ "scroll_container_end[]"..
+ "scrollbar[10.1,8;0.5,4;vertical;scrbar420;0]"..
+ -- Buttons for scale comparison
+ "button[11,8;1,1;;0]"..
+ "button[11,9;1,1;;1]"..
+ "button[11,10;1,1;;2]"..
+ "button[11,11;1,1;;3]"..
+ "button[11,12;1,1;;4]"
--style_type[label;textcolor=green]
--label[0,0;Green]
@@ -462,7 +473,7 @@ mouse control = true]
]],
-- Scroll containers
- "formspec_version[3]size[12,13]" ..
+ "formspec_version[7]size[12,13]" ..
scroll_fs,
-- Sound
diff --git a/games/devtest/mods/testfullscreenfs/init.lua b/games/devtest/mods/testfullscreenfs/init.lua
index e1af3ae33..7abc7f817 100644
--- a/games/devtest/mods/testfullscreenfs/init.lua
+++ b/games/devtest/mods/testfullscreenfs/init.lua
@@ -1,18 +1,30 @@
-local function show_fullscreen_fs(name)
- local window = minetest.get_player_window_information(name)
- if not window then
- return false, "Unable to get window info"
- end
+local function window_info_equal(a, b)
+ return
+ -- size
+ a.size.x == b.size.x and a.size.y == b.size.y and
+ -- real_gui_scaling, real_hud_scaling
+ a.real_gui_scaling == b.real_gui_scaling and
+ a.real_hud_scaling == b.real_hud_scaling and
+ -- max_formspec_size
+ a.max_formspec_size.x == b.max_formspec_size.x and
+ a.max_formspec_size.y == b.max_formspec_size.y and
+ -- touch_controls
+ a.touch_controls == b.touch_controls
+end
+local last_window_info = {}
+
+local function show_fullscreen_fs(name, window)
print(dump(window))
- local size = { x = window.max_formspec_size.x * 1.1, y = window.max_formspec_size.y * 1.1 }
+ local size = window.max_formspec_size
local touch_text = window.touch_controls and "Touch controls enabled" or
"Touch controls disabled"
local fs = {
"formspec_version[4]",
("size[%f,%f]"):format(size.x, size.y),
- "padding[-0.01,-0.01]",
+ "padding[0,0]",
+ "bgcolor[;true]",
("button[%f,%f;1,1;%s;%s]"):format(0, 0, "tl", "TL"),
("button[%f,%f;1,1;%s;%s]"):format(size.x - 1, 0, "tr", "TR"),
("button[%f,%f;1,1;%s;%s]"):format(size.x - 1, size.y - 1, "br", "BR"),
@@ -23,10 +35,37 @@ local function show_fullscreen_fs(name)
}
minetest.show_formspec(name, "testfullscreenfs:fs", table.concat(fs))
- return true, ("Calculated size of %f, %f"):format(size.x, size.y)
+ minetest.chat_send_player(name, ("Calculated size of %f, %f"):format(size.x, size.y))
+ last_window_info[name] = window
end
-
minetest.register_chatcommand("testfullscreenfs", {
- func = show_fullscreen_fs,
+ func = function(name)
+ local window = minetest.get_player_window_information(name)
+ if not window then
+ return false, "Unable to get window info"
+ end
+
+ show_fullscreen_fs(name, window)
+ return true
+ end,
})
+
+minetest.register_globalstep(function()
+ for name, last_window in pairs(last_window_info) do
+ local window = minetest.get_player_window_information(name)
+ if window and not window_info_equal(last_window, window) then
+ show_fullscreen_fs(name, window)
+ end
+ end
+end)
+
+minetest.register_on_player_receive_fields(function(player, formname, fields)
+ if formname == "testfullscreenfs:fs" and fields.quit then
+ last_window_info[player:get_player_name()] = nil
+ end
+end)
+
+minetest.register_on_leaveplayer(function(player)
+ last_window_info[player:get_player_name()] = nil
+end)
diff --git a/games/devtest/mods/testhud/init.lua b/games/devtest/mods/testhud/init.lua
index 9afed8fc7..4afece209 100644
--- a/games/devtest/mods/testhud/init.lua
+++ b/games/devtest/mods/testhud/init.lua
@@ -208,9 +208,145 @@ minetest.register_chatcommand("zoomfov", {
end,
})
+-- Hotbars
+
+local hud_hotbar_defs = {
+ {
+ type = "hotbar",
+ position = {x=0.2, y=0.5},
+ direction = 0,
+ alignment = {x=1, y=-1},
+ },
+ {
+ type = "hotbar",
+ position = {x=0.2, y=0.5},
+ direction = 2,
+ alignment = {x=1, y=1},
+ },
+ {
+ type = "hotbar",
+ position = {x=0.7, y=0.5},
+ direction = 0,
+ offset = {x=140, y=20},
+ alignment = {x=-1, y=-1},
+ },
+ {
+ type = "hotbar",
+ position = {x=0.7, y=0.5},
+ direction = 2,
+ offset = {x=140, y=20},
+ alignment = {x=-1, y=1},
+ },
+}
+
+
+local player_hud_hotbars= {}
+minetest.register_chatcommand("hudhotbars", {
+ description = "Shows some test Lua HUD elements of type hotbar. " ..
+ "(add: Adds elements (default). remove: Removes elements)",
+ params = "[ add | remove ]",
+ func = function(name, params)
+ local player = minetest.get_player_by_name(name)
+ if not player then
+ return false, "No player."
+ end
+
+ local id_table = player_hud_hotbars[name]
+ if not id_table then
+ id_table = {}
+ player_hud_hotbars[name] = id_table
+ end
+
+ if params == "remove" then
+ for _, id in ipairs(id_table) do
+ player:hud_remove(id)
+ end
+ return true, "Hotbars removed."
+ end
+
+ -- params == "add" or default
+ for _, def in ipairs(hud_hotbar_defs) do
+ table.insert(id_table, player:hud_add(def))
+ end
+ return true, #hud_hotbar_defs .." Hotbars added."
+ end
+})
+
+-- Inventories
+
+local hud_inventory_defs = {
+ {
+ type = "inventory",
+ position = {x=0.2, y=0.5},
+ direction = 0,
+ text = "main",
+ number = 4,
+ item = 2,
+ },
+ {
+ type = "inventory",
+ position = {x=0.2, y=0.5},
+ direction = 2,
+ text = "main",
+ number = 4,
+ item = 2,
+ },
+ {
+ type = "inventory",
+ position = {x=0.7, y=0.5},
+ direction = 1,
+ text = "main",
+ number = 4,
+ item = 2,
+ },
+ {
+ type = "inventory",
+ position = {x=0.7, y=0.5},
+ direction = 3,
+ text = "main",
+ number = 4,
+ item = 2,
+ },
+}
+
+local player_hud_inventories= {}
+minetest.register_chatcommand("hudinventories", {
+ description = "Shows some test Lua HUD elements of type inventory. (add: Adds elements (default). remove: Removes elements)",
+ params = "[ add | remove ]",
+ func = function(name, params)
+ local player = minetest.get_player_by_name(name)
+ if not player then
+ return false, "No player."
+ end
+
+ local id_table = player_hud_inventories[name]
+ if not id_table then
+ id_table = {}
+ player_hud_inventories[name] = id_table
+ end
+
+ if params == "remove" then
+ for _, id in ipairs(id_table) do
+ player:hud_remove(id)
+ end
+ return true, "HUD Inventories removed."
+ end
+
+ -- params == "add" or default
+ for _, def in ipairs(hud_inventory_defs) do
+ table.insert(id_table, player:hud_add(def))
+ end
+ return true, #hud_inventory_defs .." HUD Inventories added."
+ end
+})
+
+
minetest.register_on_leaveplayer(function(player)
- player_font_huds[player:get_player_name()] = nil
- player_waypoints[player:get_player_name()] = nil
+ local playername = player:get_player_name()
+ player_font_huds[playername] = nil
+ player_waypoints[playername] = nil
+ player_hud_hotbars[playername] = nil
+ player_hud_inventories[playername] = nil
end)
minetest.register_chatcommand("hudprint", {
@@ -232,3 +368,26 @@ minetest.register_chatcommand("hudprint", {
return true, s
end
})
+
+local hud_flags = {"hotbar", "healthbar", "crosshair", "wielditem", "breathbar",
+ "minimap", "minimap_radar", "basic_debug", "chat"}
+
+minetest.register_chatcommand("hudtoggleflag", {
+ description = "Toggles a hud flag.",
+ params = "[ ".. table.concat(hud_flags, " | ") .." ]",
+ func = function(name, params)
+ local player = minetest.get_player_by_name(name)
+ if not player then
+ return false, "No player."
+ end
+
+ local flags = player:hud_get_flags()
+ if flags[params] == nil then
+ return false, "Unknown hud flag."
+ end
+
+ flags[params] = not flags[params]
+ player:hud_set_flags(flags)
+ return true, "Flag \"".. params .."\" set to ".. tostring(flags[params]) .. "."
+ end
+})
diff --git a/games/devtest/mods/testnodes/drawtypes.lua b/games/devtest/mods/testnodes/drawtypes.lua
index 087d09eff..4a657b739 100644
--- a/games/devtest/mods/testnodes/drawtypes.lua
+++ b/games/devtest/mods/testnodes/drawtypes.lua
@@ -98,6 +98,23 @@ minetest.register_node("testnodes:allfaces", {
groups = { dig_immediate = 3 },
})
+minetest.register_node("testnodes:allfaces_6", {
+ description = S("\"allfaces 6 Textures\" Drawtype Test Node").."\n"..
+ S("Transparent node with visible internal backfaces"),
+ drawtype = "allfaces",
+ paramtype = "light",
+ tiles = {
+ "testnodes_allfaces.png^[colorize:red",
+ "testnodes_allfaces.png^[colorize:orange",
+ "testnodes_allfaces.png^[colorize:yellow",
+ "testnodes_allfaces.png^[colorize:green",
+ "testnodes_allfaces.png^[colorize:blue",
+ "testnodes_allfaces.png^[colorize:purple"
+ },
+
+ groups = { dig_immediate = 3 },
+})
+
local allfaces_optional_tooltip = ""..
S("Rendering depends on 'leaves_style' setting:").."\n"..
S("* 'fancy': transparent with visible internal backfaces").."\n"..
diff --git a/games/devtest/mods/testnodes/textures.lua b/games/devtest/mods/testnodes/textures.lua
index 96f291d6a..b95fbd62e 100644
--- a/games/devtest/mods/testnodes/textures.lua
+++ b/games/devtest/mods/testnodes/textures.lua
@@ -52,6 +52,12 @@ minetest.register_node("testnodes:fill_positioning_reference", {
groups = {dig_immediate = 3},
})
+minetest.register_node("testnodes:modifier_mask", {
+ description = S("[mask Modifier Test Node"),
+ tiles = {"testnodes_128x128_rgb.png^[mask:testnodes_mask_WRGBKW.png"},
+ groups = {dig_immediate = 3},
+})
+
-- Node texture transparency test
local alphas = { 64, 128, 191 }
diff --git a/games/devtest/mods/testnodes/textures/testnodes_128x128_rgb.png b/games/devtest/mods/testnodes/textures/testnodes_128x128_rgb.png
new file mode 100644
index 000000000..060d8e67a
Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_128x128_rgb.png differ
diff --git a/games/devtest/mods/testnodes/textures/testnodes_mask_WRGBKW.png b/games/devtest/mods/testnodes/textures/testnodes_mask_WRGBKW.png
new file mode 100644
index 000000000..03ab71e3f
Binary files /dev/null and b/games/devtest/mods/testnodes/textures/testnodes_mask_WRGBKW.png differ
diff --git a/games/devtest/mods/testtranslations/init.lua b/games/devtest/mods/testtranslations/init.lua
new file mode 100644
index 000000000..bb3696e7e
--- /dev/null
+++ b/games/devtest/mods/testtranslations/init.lua
@@ -0,0 +1,26 @@
+local S, NS = minetest.get_translator("testtranslations")
+
+local function send_compare(name, text)
+ core.chat_send_player(name, ("%s | %s | %s"):format(
+ core.get_translated_string("", text), text, core.get_translated_string("fr", text)))
+end
+
+minetest.register_chatcommand("testtranslations", {
+ params = "",
+ description = "Test translations",
+ privs = {},
+ func = function(name, param)
+ core.chat_send_player(name, "Please ensure your locale is set to \"fr\"")
+ core.chat_send_player(name, "Untranslated | Client-side translation | Server-side translation (fr)")
+ send_compare(name, S("Testing .tr files: untranslated"))
+ send_compare(name, S("Testing .po files: untranslated"))
+ send_compare(name, S("Testing .mo files: untranslated"))
+ send_compare(name, S("Testing fuzzy .po entry: untranslated (expected)"))
+ send_compare(name, core.translate("translation_po", "Testing .po without context: untranslated"))
+ send_compare(name, core.translate("translation_mo", "Testing .mo without context: untranslated"))
+ for i = 0,4 do
+ send_compare(name, NS("@1: .po singular", "@1: .po plural", i, tostring(i)))
+ send_compare(name, NS("@1: .mo singular", "@1: .mo plural", i, tostring(i)))
+ end
+ end
+})
diff --git a/games/devtest/mods/testtranslations/locale/testtranslations.fr.po b/games/devtest/mods/testtranslations/locale/testtranslations.fr.po
new file mode 100644
index 000000000..2bcc6c7d4
--- /dev/null
+++ b/games/devtest/mods/testtranslations/locale/testtranslations.fr.po
@@ -0,0 +1,9 @@
+# Dummy entry. This is a test to make sure that a parser error is not thrown
+# if the following line is msgctxt.
+msgctxt "testtranslations"
+msgid "Dummy entry"
+msgstr "Dummy result"
+
+# Used for translating the mod title
+msgid "Test translations"
+msgstr "Test translations (French)"
diff --git a/games/devtest/mods/testtranslations/locale/translation_mo.fr.mo b/games/devtest/mods/testtranslations/locale/translation_mo.fr.mo
new file mode 100644
index 000000000..0e7190de9
Binary files /dev/null and b/games/devtest/mods/testtranslations/locale/translation_mo.fr.mo differ
diff --git a/games/devtest/mods/testtranslations/locale/translation_po.fr.po b/games/devtest/mods/testtranslations/locale/translation_po.fr.po
new file mode 100644
index 000000000..5aefc0f41
--- /dev/null
+++ b/games/devtest/mods/testtranslations/locale/translation_po.fr.po
@@ -0,0 +1,22 @@
+# Test Plural-Forms parsing
+msgid ""
+msgstr ""
+"Plural-Forms: nplurals=2; plural= (n-1+1)<=1 ? n||1?0:1 : 1?(!!2):2;"
+
+msgctxt "testtranslations"
+msgid "Testing .po files: untranslated"
+msgstr "Testing .po files: translated"
+
+msgctxt "testtranslations"
+msgid "@1: .po singular"
+msgid_plural "@1: .po plural"
+msgstr[0] "@1: .po 0 and 1 (French singular)"
+msgstr[1] "@1: .po >1 (French plural)"
+
+#, foo bar fuzzy
+msgctxt "testtranslations"
+msgid "Testing fuzzy .po entry: untranslated (expected)"
+msgstr "Testing fuzzy .po entry: translated (wrong)"
+
+msgid "Testing .po without context: untranslated"
+msgstr "Testing .po without context: translated"
diff --git a/games/devtest/mods/testtranslations/locale/translation_tr.fr.tr b/games/devtest/mods/testtranslations/locale/translation_tr.fr.tr
new file mode 100644
index 000000000..b9ac66af5
--- /dev/null
+++ b/games/devtest/mods/testtranslations/locale/translation_tr.fr.tr
@@ -0,0 +1,2 @@
+# textdomain: testtranslations
+Testing .tr files: untranslated=Testing .tr files: translated
diff --git a/games/devtest/mods/testtranslations/mod.conf b/games/devtest/mods/testtranslations/mod.conf
new file mode 100644
index 000000000..1fc09cf6b
--- /dev/null
+++ b/games/devtest/mods/testtranslations/mod.conf
@@ -0,0 +1,3 @@
+name = testtranslations
+title = Test translations
+description = Test mod to test translations.
diff --git a/games/devtest/mods/testtranslations/test_locale/readme.txt b/games/devtest/mods/testtranslations/test_locale/readme.txt
new file mode 100644
index 000000000..7a2ed4329
--- /dev/null
+++ b/games/devtest/mods/testtranslations/test_locale/readme.txt
@@ -0,0 +1,4 @@
+The translation files in this directory intentionally include errors (which
+would be reported when someone starts the devtest game in the de locale). This
+allows the unittest to check that the translation file reader also handles
+files that include errors.
diff --git a/games/devtest/mods/testtranslations/test_locale/translation_mo.de.mo b/games/devtest/mods/testtranslations/test_locale/translation_mo.de.mo
new file mode 100644
index 000000000..ffe05cd71
Binary files /dev/null and b/games/devtest/mods/testtranslations/test_locale/translation_mo.de.mo differ
diff --git a/games/devtest/mods/testtranslations/test_locale/translation_po.de.po b/games/devtest/mods/testtranslations/test_locale/translation_po.de.po
new file mode 100644
index 000000000..9a64805a6
--- /dev/null
+++ b/games/devtest/mods/testtranslations/test_locale/translation_po.de.po
@@ -0,0 +1,42 @@
+# This file is used by the C++ unittest for testing the parser
+msgid ""
+msgstr "\n\n\n"
+"Plural-Forms: nplurals=2; plural=n!=1;"
+"\n\n\n"
+
+msgid "foo"
+ msgstr "bar"
+
+msgid "Untranslated"
+msgstr ""
+
+#, fuzzy
+msgid "Fuzzy entry"
+msgstr "Wrong"
+
+msgid "Multi\\""line\n"
+"string"
+msgstr "Multi\\\"" "li\\ne\nresult"
+
+msgctxt "Something" in "between"
+msgctxt "String does not end
+msgstr "Lost string"
+msgid "Wrong order"
+
+msgid "Singular form"
+msgid_plural "Plural form"
+msgstr[0] "Singular result"
+msgstr[1] "Plural result"
+
+msgid "Not enough value"
+msgid_plural "Not enough values"
+msgstr[0] "Result"
+
+msgid "Partial translation"
+msgid_plural "Partial translations"
+msgstr[0] "Partially translated"
+msgstr[1] ""
+
+msgctxt "context"
+msgid "With context"
+msgstr "Has context"
diff --git a/games/devtest/mods/testtranslations/translation_mo.de.po b/games/devtest/mods/testtranslations/translation_mo.de.po
new file mode 100644
index 000000000..c3f22c4ed
--- /dev/null
+++ b/games/devtest/mods/testtranslations/translation_mo.de.po
@@ -0,0 +1,26 @@
+msgid ""
+msgstr "Plural-Forms: nplurals=2; plural= n != 1;"
+
+msgctxt "context"
+msgid "With context"
+msgstr "Has context"
+
+msgctxt "context"
+msgid "Singular form"
+msgid_plural "Plural form"
+msgstr[0] "Singular result"
+msgstr[1] "Plural result"
+
+# Replace plural form delimiter in the msgstr
+msgid "Corrupt singular"
+msgid_plural "Corrupt plural"
+msgstr[0] "Corrupt singular result"
+msgstr[1] "Corrupt plural result"
+
+# Replace terminating NUL in the MO file
+msgid "Corrupt entry"
+msgstr "Corrupted result"
+
+# Change the address of this entry to something invalid
+msgid "Removed entry"
+msgstr "Removed result"
diff --git a/games/devtest/mods/testtranslations/translation_mo.fr.po b/games/devtest/mods/testtranslations/translation_mo.fr.po
new file mode 100644
index 000000000..e6cf6d6ea
--- /dev/null
+++ b/games/devtest/mods/testtranslations/translation_mo.fr.po
@@ -0,0 +1,18 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n>1;"
+
+msgctxt "testtranslations"
+msgid "Testing .mo files: untranslated"
+msgstr "Testing .mo files: translated"
+
+msgid "Testing .mo without context: untranslated"
+msgstr "Testing .mo without context: translated"
+
+msgctxt "testtranslations"
+msgid "@1: .mo singular"
+msgid_plural "@1: .mo plural"
+msgstr[0] "@1: .mo 0 and 1 (French singular)"
+msgstr[1] "@1: .mo >1 (French plural)"
diff --git a/games/devtest/mods/unittests/color.lua b/games/devtest/mods/unittests/color.lua
new file mode 100644
index 000000000..86154445c
--- /dev/null
+++ b/games/devtest/mods/unittests/color.lua
@@ -0,0 +1,17 @@
+local function assert_colors_equal(c1, c2)
+ if type(c1) == "table" and type(c2) == "table" then
+ assert(c1.r == c2.r and c1.g == c2.g and c1.b == c2.b and c1.a == c2.a)
+ else
+ assert(c1 == c2)
+ end
+end
+
+local function test_color_conversion()
+ assert_colors_equal(core.colorspec_to_table("#fff"), {r = 255, g = 255, b = 255, a = 255})
+ assert_colors_equal(core.colorspec_to_table(0xFF00FF00), {r = 0, g = 255, b = 0, a = 255})
+ assert_colors_equal(core.colorspec_to_table("#00000000"), {r = 0, g = 0, b = 0, a = 0})
+ assert_colors_equal(core.colorspec_to_table("green"), {r = 0, g = 128, b = 0, a = 255})
+ assert_colors_equal(core.colorspec_to_table("gren"), nil)
+end
+
+unittests.register("test_color_conversion", test_color_conversion)
diff --git a/games/devtest/mods/unittests/entity.lua b/games/devtest/mods/unittests/entity.lua
index 8e8bd1c48..af91a2a94 100644
--- a/games/devtest/mods/unittests/entity.lua
+++ b/games/devtest/mods/unittests/entity.lua
@@ -214,3 +214,23 @@ unittests.register("test_objects_in_area", function(_, pos)
return core.objects_in_area(pos:offset(-1, -1, -1), pos:offset(1, 1, 1))
end)
end, {map=true})
+
+-- Tests that bone rotation euler angles are preserved (see #14992)
+local function test_get_bone_rot(_, pos)
+ local obj = core.add_entity(pos, "unittests:dummy")
+ for _ = 1, 100 do
+ local function assert_similar(euler_angles)
+ local _, rot = obj:get_bone_position("bonename")
+ assert(euler_angles:distance(rot) < 1e-3)
+ local override = obj:get_bone_override("bonename")
+ assert(euler_angles:distance(override.rotation.vec:apply(math.deg)) < 1e-3)
+ end
+ local deg = 1e3 * vector.new(math.random(), math.random(), math.random())
+ obj:set_bone_position("bonename", vector.zero(), deg)
+ assert_similar(deg)
+ local rad = 3 * math.pi * vector.new(math.random(), math.random(), math.random())
+ obj:set_bone_override("bonename", {rotation = {vec = rad}})
+ assert_similar(rad:apply(math.deg))
+ end
+end
+unittests.register("test_get_bone_rot", test_get_bone_rot, {map=true})
diff --git a/games/devtest/mods/unittests/init.lua b/games/devtest/mods/unittests/init.lua
index eae003a2a..a967a986f 100644
--- a/games/devtest/mods/unittests/init.lua
+++ b/games/devtest/mods/unittests/init.lua
@@ -187,6 +187,7 @@ dofile(modpath .. "/raycast.lua")
dofile(modpath .. "/inventory.lua")
dofile(modpath .. "/load_time.lua")
dofile(modpath .. "/on_shutdown.lua")
+dofile(modpath .. "/color.lua")
--------------
diff --git a/games/devtest/mods/unittests/inside_mapgen_env.lua b/games/devtest/mods/unittests/inside_mapgen_env.lua
index a8df004de..f6f8513ce 100644
--- a/games/devtest/mods/unittests/inside_mapgen_env.lua
+++ b/games/devtest/mods/unittests/inside_mapgen_env.lua
@@ -22,9 +22,11 @@ local function do_tests()
assert(core.registered_items["unittests:description_test"].on_place == true)
end
--- there's no (usable) communcation path between mapgen and the regular env
--- so we just run the test unconditionally
-do_tests()
+-- first thread to get here runs the tests
+if core.ipc_cas("unittests:mg_once", nil, true) then
+ -- this is checked from the main env
+ core.ipc_set("unittests:mg", { pcall(do_tests) })
+end
core.register_on_generated(function(vm, pos1, pos2, blockseed)
local n = tonumber(core.get_mapgen_setting("chunksize")) * 16 - 1
diff --git a/games/devtest/mods/unittests/misc.lua b/games/devtest/mods/unittests/misc.lua
index 6ff5c7e84..a807a390f 100644
--- a/games/devtest/mods/unittests/misc.lua
+++ b/games/devtest/mods/unittests/misc.lua
@@ -153,6 +153,18 @@ local function test_urlencode()
end
unittests.register("test_urlencode", test_urlencode)
+local function test_parse_json()
+ local raw = "{\"how\\u0000weird\":\n\"yes\\u0000really\",\"n\":-1234567891011,\"z\":null}"
+ local data = core.parse_json(raw)
+ assert(data["how\000weird"] == "yes\000really")
+ assert(data.n == -1234567891011)
+ assert(data.z == nil)
+ local null = {}
+ data = core.parse_json(raw, null)
+ assert(data.z == null)
+end
+unittests.register("test_parse_json", test_parse_json)
+
local function test_game_info()
local info = minetest.get_game_info()
local game_conf = Settings(info.path .. "/game.conf")
@@ -254,3 +266,43 @@ local function test_gennotify_api()
assert(#custom == 0, "custom ids not empty")
end
unittests.register("test_gennotify_api", test_gennotify_api)
+
+-- <=> inside_mapgen_env.lua
+local function test_mapgen_env(cb)
+ -- emerge threads start delayed so this can take a second
+ local res = core.ipc_get("unittests:mg")
+ if res == nil then
+ return core.after(0, test_mapgen_env, cb)
+ end
+ -- handle error status
+ if res[1] then
+ cb()
+ else
+ cb(res[2])
+ end
+end
+unittests.register("test_mapgen_env", test_mapgen_env, {async=true})
+
+local function test_ipc_vector_preserve(cb)
+ -- the IPC also uses register_portable_metatable
+ core.ipc_set("unittests:v", vector.new(4, 0, 4))
+ local v = core.ipc_get("unittests:v")
+ assert(type(v) == "table")
+ assert(vector.check(v))
+end
+unittests.register("test_ipc_vector_preserve", test_ipc_vector_preserve)
+
+local function test_ipc_poll(cb)
+ core.ipc_set("unittests:flag", nil)
+ assert(core.ipc_poll("unittests:flag", 1) == false)
+
+ -- Note that unlike the async result callback - which has to wait for the
+ -- next server step - the IPC is instant
+ local t0 = core.get_us_time()
+ core.handle_async(function()
+ core.ipc_set("unittests:flag", true)
+ end, function() end)
+ assert(core.ipc_poll("unittests:flag", 1000) == true, "Wait failed (or slow machine?)")
+ print("delta: " .. (core.get_us_time() - t0) .. "us")
+end
+unittests.register("test_ipc_poll", test_ipc_poll)
diff --git a/games/devtest/mods/unittests/player.lua b/games/devtest/mods/unittests/player.lua
index 0dbe450b0..f8945f320 100644
--- a/games/devtest/mods/unittests/player.lua
+++ b/games/devtest/mods/unittests/player.lua
@@ -2,7 +2,7 @@
-- HP Change Reasons
--
local expect = nil
-minetest.register_on_player_hpchange(function(player, hp, reason)
+core.register_on_player_hpchange(function(player, hp_change, reason)
if expect == nil then
return
end
@@ -37,6 +37,104 @@ local function run_hpchangereason_tests(player)
end
unittests.register("test_hpchangereason", run_hpchangereason_tests, {player=true})
+--
+-- HP differences
+--
+
+local expected_diff = nil
+local hpchange_counter = 0
+local die_counter = 0
+core.register_on_player_hpchange(function(player, hp_change, reason)
+ if expected_diff then
+ assert(hp_change == expected_diff)
+ hpchange_counter = hpchange_counter + 1
+ end
+end)
+core.register_on_dieplayer(function()
+ die_counter = die_counter + 1
+end)
+
+local function hp_diference_test(player, hp_max)
+ assert(hp_max >= 22)
+
+ local old_hp = player:get_hp()
+ local old_hp_max = player:get_properties().hp_max
+
+ hpchange_counter = 0
+ die_counter = 0
+
+ expected_diff = nil
+ player:set_properties({hp_max = hp_max})
+ player:set_hp(22)
+ assert(player:get_hp() == 22)
+ assert(hpchange_counter == 0)
+ assert(die_counter == 0)
+
+ -- HP difference is not clamped
+ expected_diff = -25
+ player:set_hp(-3)
+ -- actual final HP value is clamped to >= 0
+ assert(player:get_hp() == 0)
+ assert(hpchange_counter == 1)
+ assert(die_counter == 1)
+
+ expected_diff = 22
+ player:set_hp(22)
+ assert(player:get_hp() == 22)
+ assert(hpchange_counter == 2)
+ assert(die_counter == 1)
+
+ -- Integer overflow is prevented
+ -- so result is S32_MIN, not S32_MIN - 22
+ expected_diff = -2147483648
+ player:set_hp(-2147483648)
+ -- actual final HP value is clamped to >= 0
+ assert(player:get_hp() == 0)
+ assert(hpchange_counter == 3)
+ assert(die_counter == 2)
+
+ -- Damage is ignored if player is already dead (hp == 0)
+ expected_diff = "never equal"
+ player:set_hp(-11)
+ assert(player:get_hp() == 0)
+ -- no on_player_hpchange or on_dieplayer call expected
+ assert(hpchange_counter == 3)
+ assert(die_counter == 2)
+
+ expected_diff = 11
+ player:set_hp(11)
+ assert(player:get_hp() == 11)
+ assert(hpchange_counter == 4)
+ assert(die_counter == 2)
+
+ -- HP difference is not clamped
+ expected_diff = 1000000 - 11
+ player:set_hp(1000000)
+ -- actual final HP value is clamped to <= hp_max
+ assert(player:get_hp() == hp_max)
+ assert(hpchange_counter == 5)
+ assert(die_counter == 2)
+
+ -- "Healing" is not ignored when hp == hp_max
+ expected_diff = 80000 - hp_max
+ player:set_hp(80000)
+ assert(player:get_hp() == hp_max)
+ -- on_player_hpchange_call expected
+ assert(hpchange_counter == 6)
+ assert(die_counter == 2)
+
+ expected_diff = nil
+ player:set_properties({hp_max = old_hp_max})
+ player:set_hp(old_hp)
+ core.close_formspec(player:get_player_name(), "") -- hide death screen
+end
+local function run_hp_difference_tests(player)
+ hp_diference_test(player, 22)
+ hp_diference_test(player, 30)
+ hp_diference_test(player, 65535) -- U16_MAX
+end
+unittests.register("test_hp_difference", run_hp_difference_tests, {player=true})
+
--
-- Player meta
--
diff --git a/irr/.github/workflows/build.yml b/irr/.github/workflows/build.yml
deleted file mode 100644
index f31521bd2..000000000
--- a/irr/.github/workflows/build.yml
+++ /dev/null
@@ -1,310 +0,0 @@
-name: build
-
-# build on c/cpp changes or workflow changes
-on:
- - push
- - pull_request
-
-jobs:
-
- linux-gl:
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v4
- - name: Install deps
- run: |
- sudo apt-get update
- sudo apt-get install g++ cmake libxi-dev libgl1-mesa-dev libpng-dev libjpeg-dev zlib1g-dev -qyy
-
- - name: Build
- run: |
- cmake . -DUSE_SDL2=OFF
- make VERBOSE=1 -j2
-
- - name: Test
- run: |
- ctest --output-on-failure
-
- - name: Package
- run: |
- make DESTDIR=$PWD/_install install
- tar -c -I "gzip -9" -f irrlicht-linux.tar.gz -C ./_install/usr/local .
-
- - uses: actions/upload-artifact@v4
- with:
- name: irrlicht-linux
- path: ./irrlicht-linux.tar.gz
-
- linux-gles:
- # Xvfb test is broken on 20.04 for unknown reasons (not our bug)
- runs-on: ubuntu-22.04
- steps:
- - uses: actions/checkout@v4
- - name: Install deps
- run: |
- sudo apt-get update
- sudo apt-get install g++ cmake libxi-dev libgles2-mesa-dev libpng-dev libjpeg-dev zlib1g-dev xvfb -qyy
-
- - name: Build
- run: |
- cmake . -DBUILD_EXAMPLES=1 -DUSE_SDL2=OFF -DENABLE_OPENGL=OFF -DENABLE_GLES2=ON
- make -j2
-
- - name: Test (headless)
- run: |
- cd bin/Linux
- ./AutomatedTest null
-
- - name: Test (Xvfb)
- run: |
- cd bin/Linux
- LIBGL_ALWAYS_SOFTWARE=true xvfb-run ./AutomatedTest ogles2
-
- linux-sdl:
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v4
- - name: Install deps
- run: |
- sudo apt-get update
- sudo apt-get install g++ cmake libsdl2-dev libpng-dev libjpeg-dev zlib1g-dev -qyy
-
- - name: Build
- run: |
- cmake . -DBUILD_EXAMPLES=1 -DUSE_SDL2=ON -DCMAKE_BUILD_TYPE=Debug
- make -j2
-
- - name: Test (headless)
- run: |
- cd bin/Linux
- ./AutomatedTest null
-
- linux-sdl-gl3:
- # Xvfb test is broken on 20.04 for unknown reasons (not our bug)
- runs-on: ubuntu-22.04
- steps:
- - uses: actions/checkout@v4
- - name: Install deps
- run: |
- sudo apt-get update
- sudo apt-get install g++ cmake libsdl2-dev libpng-dev libjpeg-dev zlib1g-dev xvfb -qyy
-
- - name: Build
- run: |
- cmake . -DBUILD_EXAMPLES=1 -DUSE_SDL2=ON -DENABLE_OPENGL=OFF -DENABLE_OPENGL3=ON
- make -j2
-
- - name: Test (headless)
- run: |
- cd bin/Linux
- ./AutomatedTest null
-
- - name: Test (Xvfb)
- run: |
- cd bin/Linux
- LIBGL_ALWAYS_SOFTWARE=true xvfb-run ./AutomatedTest opengl3
-
- linux-sdl-gles2:
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v4
- - name: Install deps
- run: |
- sudo apt-get update
- sudo apt-get install g++ cmake libsdl2-dev libpng-dev libjpeg-dev zlib1g-dev xvfb -qyy
-
- - name: Build
- run: |
- cmake . -DBUILD_EXAMPLES=1 -DUSE_SDL2=ON -DENABLE_OPENGL=OFF -DENABLE_GLES2=ON
- make -j2
-
- - name: Test (headless)
- run: |
- cd bin/Linux
- ./AutomatedTest null
-
- - name: Test (Xvfb)
- run: |
- cd bin/Linux
- LIBGL_ALWAYS_SOFTWARE=true xvfb-run ./AutomatedTest ogles2
-
- mingw:
- name: "MinGW ${{matrix.config.variant}}${{matrix.config.extras}}"
- runs-on: ubuntu-22.04
- strategy:
- fail-fast: false
- matrix:
- config:
- - {variant: win32, arch: i686}
- - {variant: win64, arch: x86_64}
- - {variant: win32, arch: i686, extras: "-sdl"}
- - {variant: win64, arch: x86_64, extras: "-sdl"}
- steps:
- - uses: actions/checkout@v4
- - name: Install compiler
- run: |
- sudo apt-get update && sudo apt-get install cmake -qyy
- ./scripts/ci-get-mingw.sh
-
- - name: Build
- run: |
- ./scripts/ci-build-mingw.sh package
- env:
- CC: ${{matrix.config.arch}}-w64-mingw32-clang
- CXX: ${{matrix.config.arch}}-w64-mingw32-clang++
- extras: ${{matrix.config.extras}}
-
- - uses: actions/upload-artifact@v4
- with:
- name: irrlicht-${{matrix.config.variant}}${{matrix.config.extras}}
- path: ./irrlicht-${{matrix.config.variant}}${{matrix.config.extras}}.zip
-
- macos:
- runs-on: macos-latest
- steps:
- - uses: actions/checkout@v4
- - name: Install deps
- run: |
- brew update --auto-update
- brew install cmake libpng jpeg
- env:
- HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
- HOMEBREW_NO_INSTALL_CLEANUP: 1
-
- - name: Build
- run: |
- cmake . -DCMAKE_FIND_FRAMEWORK=LAST -DBUILD_EXAMPLES=1
- make -j3
-
- - name: Test (headless)
- run: |
- ./bin/OSX/AutomatedTest null
-
- macos-sdl:
- runs-on: macos-latest
- steps:
- - uses: actions/checkout@v4
- - name: Install deps
- run: |
- brew update --auto-update
- brew install cmake libpng jpeg sdl2
- env:
- HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
- HOMEBREW_NO_INSTALL_CLEANUP: 1
-
- - name: Build
- run: |
- cmake . -DCMAKE_FIND_FRAMEWORK=LAST -DBUILD_EXAMPLES=1 -DUSE_SDL2=1
- make -j3
-
- msvc:
- name: VS 2019 ${{ matrix.config.arch }} ${{ matrix.sdl.label }}
- runs-on: windows-2019
- env:
- VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50
- # 2023.10.19
- vcpkg_packages: zlib libpng libjpeg-turbo
- strategy:
- fail-fast: false
- matrix:
- config:
- -
- arch: x86
- generator: "-G'Visual Studio 16 2019' -A Win32"
- vcpkg_triplet: x86-windows
- -
- arch: x64
- generator: "-G'Visual Studio 16 2019' -A x64"
- vcpkg_triplet: x64-windows
- sdl:
- -
- use: FALSE
- label: '(no SDL)'
- vcpkg_packages: opengl-registry
- -
- use: TRUE
- label: '(with SDL)'
- vcpkg_packages: sdl2
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Restore from cache and run vcpkg
- uses: lukka/run-vcpkg@v7
- with:
- vcpkgArguments: ${{env.vcpkg_packages}} ${{matrix.sdl.vcpkg_packages}}
- vcpkgDirectory: '${{ github.workspace }}\vcpkg'
- appendedCacheKey: ${{ matrix.config.vcpkg_triplet }}
- vcpkgGitCommitId: ${{ env.VCPKG_VERSION }}
- vcpkgTriplet: ${{ matrix.config.vcpkg_triplet }}
-
- - name: CMake
- run: |
- cmake ${{matrix.config.generator}} `
- -DUSE_SDL2=${{matrix.sdl.use}} `
- -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}\vcpkg\scripts\buildsystems\vcpkg.cmake" `
- -DCMAKE_BUILD_TYPE=Release .
-
- - name: Build
- run: cmake --build . --config Release
-
- - name: Create artifact folder
- run: |
- mkdir artifact/
- mkdir artifact/lib/
-
- - name: Move dlls into artifact folder
- run: move bin\Win32-VisualStudio\Release\* artifact\lib\
-
- - name: Move includes into artifact folder
- run: move include artifact/
-
- - name: Upload Artifact
- uses: actions/upload-artifact@v4
- with:
- name: msvc-${{ matrix.config.arch }}-${{matrix.sdl.use}}
- path: artifact/
-
- android:
- name: Android ${{ matrix.arch }}
- runs-on: ubuntu-20.04
- env:
- ndk_version: "r25c"
- ANDROID_NDK: ${{ github.workspace }}/android-ndk
- strategy:
- matrix:
- arch: [armeabi-v7a, arm64-v8a, x86, x86_64]
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Install deps
- run: |
- sudo rm /var/lib/man-db/auto-update
- sudo apt-get update
- sudo apt-get install -qyy wget unzip zip gcc-multilib make cmake
-
- - name: Cache NDK
- id: cache-ndk
- uses: actions/cache@v4
- with:
- key: android-ndk-${{ env.ndk_version }}-linux
- path: ${{ env.ANDROID_NDK }}
-
- - name: Install NDK
- run: |
- wget --progress=bar:force "http://dl.google.com/android/repository/android-ndk-${ndk_version}-linux.zip"
- unzip -q "android-ndk-${ndk_version}-linux.zip"
- rm "android-ndk-${ndk_version}-linux.zip"
- mv "android-ndk-${ndk_version}" "${ANDROID_NDK}"
- if: ${{ steps.cache-ndk.outputs.cache-hit != 'true' }}
-
- - name: Build
- run: ./scripts/ci-build-android.sh ${{ matrix.arch }}
-
- #- name: Upload Artifact
- # uses: actions/upload-artifact@v4
- # with:
- # name: irrlicht-android-${{ matrix.arch }}
- # path: ${{ runner.temp }}/pkg/${{ matrix.arch }}
diff --git a/irr/CMakeLists.txt b/irr/CMakeLists.txt
index ccc00f271..dfd6b189a 100644
--- a/irr/CMakeLists.txt
+++ b/irr/CMakeLists.txt
@@ -11,14 +11,5 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type: Debug or Release" FORCE)
endif()
-# FIXME: tests need to be moved to MT if we want to keep them
-
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
-#enable_testing()
add_subdirectory(src)
-#add_subdirectory(test)
-
-#option(BUILD_EXAMPLES "Build example applications" FALSE)
-#if(BUILD_EXAMPLES)
-# add_subdirectory(examples)
-#endif()
diff --git a/irr/README.md b/irr/README.md
index 96d3b0d97..50e7338a5 100644
--- a/irr/README.md
+++ b/irr/README.md
@@ -20,12 +20,11 @@ The following libraries are required to be installed:
Aside from standard search options (`ZLIB_INCLUDE_DIR`, `ZLIB_LIBRARY`, ...) the following options are available:
* `ENABLE_OPENGL` - Enable OpenGL driver
* `ENABLE_OPENGL3` (default: `OFF`) - Enable OpenGL 3+ driver
-* `ENABLE_GLES1` - Enable OpenGL ES driver, legacy
* `ENABLE_GLES2` - Enable OpenGL ES 2+ driver
* `USE_SDL2` (default: platform-dependent, usually `ON`) - Use SDL2 instead of older native device code
However, IrrlichtMt cannot be built or installed separately.
-
+
Platforms
---------
diff --git a/irr/examples/AutomatedTest/main.cpp b/irr/examples/AutomatedTest/main.cpp
deleted file mode 100644
index c9e5bab68..000000000
--- a/irr/examples/AutomatedTest/main.cpp
+++ /dev/null
@@ -1,154 +0,0 @@
-#include
-#include
-#include "exampleHelper.h"
-
-using namespace irr;
-
-static IrrlichtDevice *device = nullptr;
-static int test_fail = 0;
-
-void test_irr_array();
-void test_irr_string();
-
-static video::E_DRIVER_TYPE chooseDriver(core::stringc arg_)
-{
- if (arg_ == "null")
- return video::EDT_NULL;
- if (arg_ == "ogles1")
- return video::EDT_OGLES1;
- if (arg_ == "ogles2")
- return video::EDT_OGLES2;
- if (arg_ == "opengl")
- return video::EDT_OPENGL;
- if (arg_ == "opengl3")
- return video::EDT_OPENGL3;
- std::cerr << "Unknown driver type: " << arg_.c_str() << ". Trying OpenGL." << std::endl;
- return video::EDT_OPENGL;
-}
-
-static inline void check(bool ok, const char *msg)
-{
- if (!ok) {
- test_fail++;
- device->getLogger()->log((core::stringc("FAILED TEST: ") + msg).c_str(), ELL_ERROR);
- }
-}
-
-void run_unit_tests()
-{
- std::cout << "Running unit tests:" << std::endl;
- try {
- test_irr_array();
- test_irr_string();
- } catch (const std::exception &e) {
- std::cerr << e.what() << std::endl;
- test_fail++;
- }
- std::cout << std::endl;
-}
-
-int main(int argc, char *argv[])
-{
- run_unit_tests();
-
- SIrrlichtCreationParameters p;
- p.DriverType = chooseDriver(argc > 1 ? argv[1] : "");
- p.WindowSize = core::dimension2du(640, 480);
- p.Vsync = true;
- p.LoggingLevel = ELL_DEBUG;
-
- device = createDeviceEx(p);
- if (!device)
- return 1;
-
- {
- u32 total = 0;
- device->getOSOperator()->getSystemMemory(&total, nullptr);
- core::stringc message = core::stringc("Total RAM in MiB: ") + core::stringc(total >> 10);
- device->getLogger()->log(message.c_str(), ELL_INFORMATION);
- check(total > 130 * 1024, "RAM amount");
- }
-
- device->setWindowCaption(L"Hello World!");
- device->setResizable(true);
-
- video::IVideoDriver *driver = device->getVideoDriver();
- scene::ISceneManager *smgr = device->getSceneManager();
- gui::IGUIEnvironment *guienv = device->getGUIEnvironment();
-
- guienv->addStaticText(L"sample text", core::rect(10, 10, 110, 22), false);
-
- gui::IGUIButton *button = guienv->addButton(
- core::rect(10, 30, 110, 30 + 32), 0, -1, L"sample button",
- L"sample tooltip");
-
- gui::IGUIEditBox *editbox = guienv->addEditBox(L"",
- core::rect(10, 70, 60, 70 + 16));
-
- const io::path mediaPath = getExampleMediaPath();
-
- auto mesh_file = device->getFileSystem()->createAndOpenFile(mediaPath + "coolguy_opt.x");
- check(mesh_file, "mesh file loading");
- scene::IAnimatedMesh *mesh = smgr->getMesh(mesh_file);
- check(mesh, "mesh loading");
- if (mesh_file)
- mesh_file->drop();
- if (mesh) {
- video::ITexture *tex = driver->getTexture(mediaPath + "cooltexture.png");
- check(tex, "texture loading");
- scene::IAnimatedMeshSceneNode *node = smgr->addAnimatedMeshSceneNode(mesh);
- if (node) {
- node->forEachMaterial([tex](video::SMaterial &mat) {
- mat.Lighting = false;
- mat.setTexture(0, tex);
- });
- node->setFrameLoop(0, 29);
- node->setAnimationSpeed(30);
- }
- }
-
- smgr->addCameraSceneNode(0, core::vector3df(0, 4, 5), core::vector3df(0, 2, 0));
-
- s32 n = 0;
- SEvent event;
- device->getTimer()->start();
-
- while (device->run()) {
- if (device->getTimer()->getTime() >= 1000) {
- device->getTimer()->setTime(0);
- ++n;
- if (n == 1) { // Tooltip display
- bzero(&event, sizeof(SEvent));
- event.EventType = irr::EET_MOUSE_INPUT_EVENT;
- event.MouseInput.Event = irr::EMIE_MOUSE_MOVED;
- event.MouseInput.X = button->getAbsolutePosition().getCenter().X;
- event.MouseInput.Y = button->getAbsolutePosition().getCenter().Y;
- device->postEventFromUser(event);
- } else if (n == 2) // Text input focus
- guienv->setFocus(editbox);
- else if (n == 3) { // Keypress for Text input
- bzero(&event, sizeof(SEvent));
- event.EventType = irr::EET_KEY_INPUT_EVENT;
- event.KeyInput.Char = L'a';
- event.KeyInput.Key = KEY_KEY_A;
- event.KeyInput.PressedDown = true;
- device->postEventFromUser(event);
- event.KeyInput.PressedDown = false;
- device->postEventFromUser(event);
- } else
- device->closeDevice();
- }
-
- driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH,
- video::SColor(255, 100, 100, 150));
- smgr->drawAll();
- guienv->drawAll();
- driver->endScene();
- }
-
- check(core::stringw(L"a") == editbox->getText(), "EditBox text");
-
- device->getLogger()->log("Done.", ELL_INFORMATION);
- device->drop();
- return test_fail > 0 ? 1 : 0;
-}
diff --git a/irr/examples/AutomatedTest/test_array.cpp b/irr/examples/AutomatedTest/test_array.cpp
deleted file mode 100644
index 42959e913..000000000
--- a/irr/examples/AutomatedTest/test_array.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-#include
-#include "test_helper.h"
-
-using namespace irr;
-using core::array;
-
-static void test_basics()
-{
- array v;
- v.push_back(1); // 1
- v.push_front(2); // 2, 1
- v.insert(4, 0); // 4, 2, 1
- v.insert(3, 1); // 4, 3, 2, 1
- v.insert(0, 4); // 4, 3, 2, 1, 0
- UASSERTEQ(v.size(), 5);
- UASSERTEQ(v[0], 4);
- UASSERTEQ(v[1], 3);
- UASSERTEQ(v[2], 2);
- UASSERTEQ(v[3], 1);
- UASSERTEQ(v[4], 0);
- array w = v;
- UASSERTEQ(w.size(), 5);
- UASSERT(w == v);
- w.clear();
- UASSERTEQ(w.size(), 0);
- UASSERTEQ(w.allocated_size(), 0);
- UASSERT(w.empty());
- w = v;
- UASSERTEQ(w.size(), 5);
- w.set_used(3);
- UASSERTEQ(w.size(), 3);
- UASSERTEQ(w[0], 4);
- UASSERTEQ(w[1], 3);
- UASSERTEQ(w[2], 2);
- UASSERTEQ(w.getLast(), 2);
- w.set_used(20);
- UASSERTEQ(w.size(), 20);
- w = v;
- w.sort();
- UASSERTEQ(w.size(), 5);
- UASSERTEQ(w[0], 0);
- UASSERTEQ(w[1], 1);
- UASSERTEQ(w[2], 2);
- UASSERTEQ(w[3], 3);
- UASSERTEQ(w[4], 4);
- w.erase(0);
- UASSERTEQ(w.size(), 4);
- UASSERTEQ(w[0], 1);
- UASSERTEQ(w[1], 2);
- UASSERTEQ(w[2], 3);
- UASSERTEQ(w[3], 4);
- w.erase(1, 2);
- UASSERTEQ(w.size(), 2);
- UASSERTEQ(w[0], 1);
- UASSERTEQ(w[1], 4);
- w.swap(v);
- UASSERTEQ(w.size(), 5);
- UASSERTEQ(v.size(), 2);
-}
-
-static void test_linear_searches()
-{
- // Populate the array with 0, 1, 2, ..., 100, 100, 99, 98, 97, ..., 0
- array arr;
- for (int i = 0; i <= 100; i++)
- arr.push_back(i);
- for (int i = 100; i >= 0; i--)
- arr.push_back(i);
- s32 end = arr.size() - 1;
- for (int i = 0; i <= 100; i++) {
- s32 index = arr.linear_reverse_search(i);
- UASSERTEQ(index, end - i);
- }
- for (int i = 0; i <= 100; i++) {
- s32 index = arr.linear_search(i);
- UASSERTEQ(index, i);
- }
-}
-
-static void test_binary_searches()
-{
- const auto &values = {3, 5, 1, 2, 5, 10, 19, 9, 7, 1, 2, 5, 8, 15};
- array arr;
- for (int value : values) {
- arr.push_back(value);
- }
- // Test the const form first, it uses a linear search without sorting
- const array &carr = arr;
- UASSERTEQ(carr.binary_search(20), -1);
- UASSERTEQ(carr.binary_search(0), -1);
- UASSERTEQ(carr.binary_search(1), 2);
-
- // Sorted: 1, 1, 2, 2, 3, 5, 5, 5, 7, 8, 9, 10, 15, 19
- UASSERTEQ(arr.binary_search(20), -1);
- UASSERTEQ(arr.binary_search(0), -1);
-
- for (int value : values) {
- s32 i = arr.binary_search(value);
- UASSERTNE(i, -1);
- UASSERTEQ(arr[i], value);
- }
-
- s32 first, last;
- first = arr.binary_search_multi(1, last);
- UASSERTEQ(first, 0);
- UASSERTEQ(last, 1);
-
- first = arr.binary_search_multi(2, last);
- UASSERTEQ(first, 2);
- UASSERTEQ(last, 3);
-
- first = arr.binary_search_multi(3, last);
- UASSERTEQ(first, 4);
- UASSERTEQ(last, 4);
-
- first = arr.binary_search_multi(4, last);
- UASSERTEQ(first, -1);
-
- first = arr.binary_search_multi(5, last);
- UASSERTEQ(first, 5);
- UASSERTEQ(last, 7);
-
- first = arr.binary_search_multi(7, last);
- UASSERTEQ(first, 8);
- UASSERTEQ(last, 8);
-
- first = arr.binary_search_multi(19, last);
- UASSERTEQ(first, 13);
- UASSERTEQ(last, 13);
-}
-
-void test_irr_array()
-{
- test_basics();
- test_linear_searches();
- test_binary_searches();
- std::cout << " test_irr_array PASSED" << std::endl;
-}
diff --git a/irr/examples/AutomatedTest/test_helper.h b/irr/examples/AutomatedTest/test_helper.h
deleted file mode 100644
index 5229eff29..000000000
--- a/irr/examples/AutomatedTest/test_helper.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#pragma once
-
-#include
-#include
-
-class TestFailedException : public std::exception
-{
-};
-
-// Asserts the comparison specified by CMP is true, or fails the current unit test
-#define UASSERTCMP(CMP, actual, expected) \
- do { \
- const auto &a = (actual); \
- const auto &e = (expected); \
- if (!CMP(a, e)) { \
- std::cout \
- << "Test assertion failed: " << #actual << " " << #CMP << " " \
- << #expected << std::endl \
- << " at " << __FILE__ << ":" << __LINE__ << std::endl \
- << " actual: " << a << std::endl \
- << " expected: " \
- << e << std::endl; \
- throw TestFailedException(); \
- } \
- } while (0)
-
-#define CMPEQ(a, e) (a == e)
-#define CMPTRUE(a, e) (a)
-#define CMPNE(a, e) (a != e)
-
-#define UASSERTEQ(actual, expected) UASSERTCMP(CMPEQ, actual, expected)
-#define UASSERTNE(actual, nexpected) UASSERTCMP(CMPNE, actual, nexpected)
-#define UASSERT(actual) UASSERTCMP(CMPTRUE, actual, true)
diff --git a/irr/examples/AutomatedTest/test_string.cpp b/irr/examples/AutomatedTest/test_string.cpp
deleted file mode 100644
index 4d1291f18..000000000
--- a/irr/examples/AutomatedTest/test_string.cpp
+++ /dev/null
@@ -1,205 +0,0 @@
-#include
-#include
-#include
-#include
-#include "test_helper.h"
-
-using namespace irr;
-using namespace irr::core;
-
-#define CMPSTR(a, b) (!strcmp(a, b))
-#define UASSERTSTR(actual, expected) UASSERTCMP(CMPSTR, actual.c_str(), expected)
-
-static void test_basics()
-{
- // ctor
- stringc s;
- UASSERTEQ(s.c_str()[0], '\0');
- s = stringc(0.1234567);
- UASSERTSTR(s, "0.123457");
- s = stringc(0x1p+53);
- UASSERTSTR(s, "9007199254740992.000000");
- s = stringc(static_cast(-102400));
- UASSERTSTR(s, "-102400");
- s = stringc(static_cast(102400));
- UASSERTSTR(s, "102400");
- s = stringc(static_cast(-1024000));
- UASSERTSTR(s, "-1024000");
- s = stringc(static_cast(1024000));
- UASSERTSTR(s, "1024000");
- s = stringc("YESno", 3);
- UASSERTSTR(s, "YES");
- s = stringc(L"test", 4);
- UASSERTSTR(s, "test");
- s = stringc("Hello World!");
- UASSERTSTR(s, "Hello World!");
- // operator=
- s = stringw(L"abcdef");
- UASSERTSTR(s, "abcdef");
- s = L"abcdef";
- UASSERTSTR(s, "abcdef");
- s = static_cast(nullptr);
- UASSERTSTR(s, "");
- // operator+
- s = s + stringc("foo");
- UASSERTSTR(s, "foo");
- s = s + L"bar";
- UASSERTSTR(s, "foobar");
- // the rest
- s = "f";
- UASSERTEQ(s[0], 'f');
- const auto &sref = s;
- UASSERTEQ(sref[0], 'f');
- UASSERT(sref == "f");
- UASSERT(sref == stringc("f"));
- s = "a";
- UASSERT(sref < stringc("aa"));
- UASSERT(sref < stringc("b"));
- UASSERT(stringc("Z") < sref);
- UASSERT(!(sref < stringc("a")));
- UASSERT(sref.lower_ignore_case("AA"));
- UASSERT(sref.lower_ignore_case("B"));
- UASSERT(!sref.lower_ignore_case("A"));
- s = "dog";
- UASSERT(sref != "cat");
- UASSERT(sref != stringc("cat"));
-}
-
-static void test_methods()
-{
- stringc s;
- const auto &sref = s;
- s = "irrlicht";
- UASSERTEQ(sref.size(), 8);
- UASSERT(!sref.empty());
- s.clear();
- UASSERTEQ(sref.size(), 0);
- UASSERT(sref.empty());
- UASSERT(sref[0] == 0);
- s = "\tAz#`";
- s.make_lower();
- UASSERTSTR(s, "\taz#`");
- s.make_upper();
- UASSERTSTR(s, "\tAZ#`");
- UASSERT(sref.equals_ignore_case("\taz#`"));
- UASSERT(sref.equals_substring_ignore_case("Z#`", 2));
- s = "irrlicht";
- UASSERT(sref.equalsn(stringc("irr"), 3));
- UASSERT(sref.equalsn("irr", 3));
- s = "fo";
- s.append('o');
- UASSERTSTR(s, "foo");
- s.append("bar", 1);
- UASSERTSTR(s, "foob");
- s.append("ar", 999999);
- UASSERTSTR(s, "foobar");
- s = "nyan";
- s.append(stringc("cat"));
- UASSERTSTR(s, "nyancat");
- s.append(stringc("sam"), 1);
- UASSERTSTR(s, "nyancats");
- s = "fbar";
- s.insert(1, "ooXX", 2);
- UASSERTSTR(s, "foobar");
- UASSERTEQ(sref.findFirst('o'), 1);
- UASSERTEQ(sref.findFirst('X'), -1);
- UASSERTEQ(sref.findFirstChar("abff", 2), 3);
- UASSERTEQ(sref.findFirstCharNotInList("fobb", 2), 3);
- UASSERTEQ(sref.findLast('o'), 2);
- UASSERTEQ(sref.findLast('X'), -1);
- UASSERTEQ(sref.findLastChar("abrr", 2), 4);
- UASSERTEQ(sref.findLastCharNotInList("rabb", 2), 3);
- UASSERTEQ(sref.findNext('o', 2), 2);
- UASSERTEQ(sref.findLast('o', 1), 1);
- s = "ob-oob";
- UASSERTEQ(sref.find("ob", 1), 4);
- UASSERTEQ(sref.find("ob"), 0);
- UASSERTEQ(sref.find("?"), -1);
- s = "HOMEOWNER";
- stringc s2 = sref.subString(2, 4);
- UASSERTSTR(s2, "MEOW");
- s2 = sref.subString(2, 4, true);
- UASSERTSTR(s2, "meow");
- s = "land";
- s.replace('l', 's');
- UASSERTSTR(s, "sand");
- s = ">dog<";
- s.replace("dog", "cat");
- UASSERTSTR(s, ">cat<");
- s.replace("cat", "horse");
- UASSERTSTR(s, ">horse<");
- s.replace("horse", "gnu");
- UASSERTSTR(s, ">gnu<");
- s = " h e l p ";
- s.remove(' ');
- UASSERTSTR(s, "help");
- s.remove("el");
- UASSERTSTR(s, "hp");
- s = "irrlicht";
- s.removeChars("it");
- UASSERTSTR(s, "rrlch");
- s = "\r\nfoo bar ";
- s.trim();
- UASSERTSTR(s, "foo bar");
- s = "foxo";
- s.erase(2);
- UASSERTSTR(s, "foo");
- s = "a";
- s.append('\0');
- s.append('b');
- UASSERTEQ(s.size(), 3);
- s.validate();
- UASSERTEQ(s.size(), 1);
- UASSERTEQ(s.lastChar(), 'a');
- std::vector res;
- s = "a,,b,c";
- s.split(res, ",aa", 1, true, false);
- UASSERTEQ(res.size(), 3);
- UASSERTSTR(res[0], "a");
- UASSERTSTR(res[2], "c");
- res.clear();
- s.split(res, ",", 1, false, true);
- UASSERTEQ(res.size(), 7);
- UASSERTSTR(res[0], "a");
- UASSERTSTR(res[2], "");
- for (int i = 0; i < 3; i++)
- UASSERTSTR(res[2 * i + 1], ",");
-}
-
-static void test_conv()
-{
- // locale-independent
-
- stringw out;
- utf8ToWString(out, "†††");
- UASSERTEQ(out.size(), 3);
- for (int i = 0; i < 3; i++)
- UASSERTEQ(static_cast(out[i]), 0x2020);
-
- stringc out2;
- wStringToUTF8(out2, L"†††");
- UASSERTEQ(out2.size(), 9);
- for (int i = 0; i < 3; i++) {
- UASSERTEQ(static_cast(out2[3 * i]), 0xe2);
- UASSERTEQ(static_cast(out2[3 * i + 1]), 0x80);
- UASSERTEQ(static_cast(out2[3 * i + 2]), 0xa0);
- }
-
- // locale-dependent
- if (!setlocale(LC_CTYPE, "C.UTF-8"))
- setlocale(LC_CTYPE, "UTF-8"); // macOS
-
- stringw out3;
- multibyteToWString(out3, "†††");
- UASSERTEQ(out3.size(), 3);
- for (int i = 0; i < 3; i++)
- UASSERTEQ(static_cast(out3[i]), 0x2020);
-}
-
-void test_irr_string()
-{
- test_basics();
- test_methods();
- test_conv();
- std::cout << " test_irr_string PASSED" << std::endl;
-}
diff --git a/irr/examples/CMakeLists.txt b/irr/examples/CMakeLists.txt
deleted file mode 100644
index 03553048e..000000000
--- a/irr/examples/CMakeLists.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-set(IRREXAMPLES
- # removed
-)
-if(UNIX)
- list(APPEND IRREXAMPLES AutomatedTest)
-endif()
-
-foreach(exname IN ITEMS ${IRREXAMPLES})
- file(GLOB sources "${CMAKE_CURRENT_SOURCE_DIR}/${exname}/*.cpp")
- add_executable(${exname} ${sources})
-
- target_include_directories(${exname} PRIVATE
- ${CMAKE_SOURCE_DIR}/include
- ${CMAKE_CURRENT_SOURCE_DIR}/${exname}
- )
- target_link_libraries(${exname} IrrlichtMt)
-endforeach()
diff --git a/irr/include/CIndexBuffer.h b/irr/include/CIndexBuffer.h
new file mode 100644
index 000000000..904b0ab9a
--- /dev/null
+++ b/irr/include/CIndexBuffer.h
@@ -0,0 +1,103 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#pragma once
+
+#include
+#include "IIndexBuffer.h"
+
+// Define to receive warnings when violating the hw mapping hints
+//#define INDEXBUFFER_HINT_DEBUG
+
+#ifdef INDEXBUFFER_HINT_DEBUG
+#include "../src/os.h"
+#endif
+
+namespace irr
+{
+namespace scene
+{
+//! Template implementation of the IIndexBuffer interface
+template
+class CIndexBuffer final : public IIndexBuffer
+{
+public:
+ //! Default constructor for empty buffer
+ CIndexBuffer()
+ {
+#ifdef _DEBUG
+ setDebugName("CIndexBuffer");
+#endif
+ }
+
+ video::E_INDEX_TYPE getType() const override
+ {
+ static_assert(sizeof(T) == 2 || sizeof(T) == 4, "invalid index type");
+ return sizeof(T) == 2 ? video::EIT_16BIT : video::EIT_32BIT;
+ }
+
+ const void *getData() const override
+ {
+ return Data.data();
+ }
+
+ void *getData() override
+ {
+ return Data.data();
+ }
+
+ u32 getCount() const override
+ {
+ return static_cast(Data.size());
+ }
+
+ E_HARDWARE_MAPPING getHardwareMappingHint() const override
+ {
+ return MappingHint;
+ }
+
+ void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint) override
+ {
+ MappingHint = NewMappingHint;
+ }
+
+ void setDirty() override
+ {
+ ++ChangedID;
+#ifdef INDEXBUFFER_HINT_DEBUG
+ if (MappingHint == EHM_STATIC && HWBuffer) {
+ char buf[100];
+ snprintf_irr(buf, sizeof(buf), "CIndexBuffer @ %p modified, but it has a static hint", this);
+ os::Printer::log(buf, ELL_WARNING);
+ }
+#endif
+ }
+
+ u32 getChangedID() const override { return ChangedID; }
+
+ void setHWBuffer(void *ptr) const override
+ {
+ HWBuffer = ptr;
+ }
+
+ void *getHWBuffer() const override
+ {
+ return HWBuffer;
+ }
+
+ u32 ChangedID = 1;
+
+ //! hardware mapping hint
+ E_HARDWARE_MAPPING MappingHint = EHM_NEVER;
+ mutable void *HWBuffer = nullptr;
+
+ //! Indices of this buffer
+ std::vector Data;
+};
+
+//! Standard 16-bit buffer
+typedef CIndexBuffer SIndexBuffer;
+
+} // end namespace scene
+} // end namespace irr
diff --git a/irr/include/CMeshBuffer.h b/irr/include/CMeshBuffer.h
index 8f8158ff1..9a6d4426f 100644
--- a/irr/include/CMeshBuffer.h
+++ b/irr/include/CMeshBuffer.h
@@ -4,8 +4,10 @@
#pragma once
-#include "irrArray.h"
+#include
#include "IMeshBuffer.h"
+#include "CVertexBuffer.h"
+#include "CIndexBuffer.h"
namespace irr
{
@@ -13,16 +15,24 @@ namespace scene
{
//! Template implementation of the IMeshBuffer interface
template
-class CMeshBuffer : public IMeshBuffer
+class CMeshBuffer final : public IMeshBuffer
{
public:
//! Default constructor for empty meshbuffer
CMeshBuffer() :
- ChangedID_Vertex(1), ChangedID_Index(1), MappingHint_Vertex(EHM_NEVER), MappingHint_Index(EHM_NEVER), HWBuffer(NULL), PrimitiveType(EPT_TRIANGLES)
+ PrimitiveType(EPT_TRIANGLES)
{
#ifdef _DEBUG
setDebugName("CMeshBuffer");
#endif
+ Vertices = new CVertexBuffer();
+ Indices = new SIndexBuffer();
+ }
+
+ ~CMeshBuffer()
+ {
+ Vertices->drop();
+ Indices->drop();
}
//! Get material of this meshbuffer
@@ -39,53 +49,24 @@ public:
return Material;
}
- //! Get pointer to vertices
- /** \return Pointer to vertices. */
- const void *getVertices() const override
+ const scene::IVertexBuffer *getVertexBuffer() const override
{
- return Vertices.const_pointer();
+ return Vertices;
}
- //! Get pointer to vertices
- /** \return Pointer to vertices. */
- void *getVertices() override
+ scene::IVertexBuffer *getVertexBuffer() override
{
- return Vertices.pointer();
+ return Vertices;
}
- //! Get number of vertices
- /** \return Number of vertices. */
- u32 getVertexCount() const override
+ const scene::IIndexBuffer *getIndexBuffer() const override
{
- return Vertices.size();
+ return Indices;
}
- //! Get type of index data which is stored in this meshbuffer.
- /** \return Index type of this buffer. */
- video::E_INDEX_TYPE getIndexType() const override
+ scene::IIndexBuffer *getIndexBuffer() override
{
- return video::EIT_16BIT;
- }
-
- //! Get pointer to indices
- /** \return Pointer to indices. */
- const u16 *getIndices() const override
- {
- return Indices.const_pointer();
- }
-
- //! Get pointer to indices
- /** \return Pointer to indices. */
- u16 *getIndices() override
- {
- return Indices.pointer();
- }
-
- //! Get number of indices
- /** \return Number of indices. */
- u32 getIndexCount() const override
- {
- return Indices.size();
+ return Indices;
}
//! Get the axis aligned bounding box
@@ -107,102 +88,34 @@ public:
/** should be called if the mesh changed. */
void recalculateBoundingBox() override
{
- if (!Vertices.empty()) {
- BoundingBox.reset(Vertices[0].Pos);
- const irr::u32 vsize = Vertices.size();
+ if (Vertices->getCount()) {
+ BoundingBox.reset(Vertices->getPosition(0));
+ const irr::u32 vsize = Vertices->getCount();
for (u32 i = 1; i < vsize; ++i)
- BoundingBox.addInternalPoint(Vertices[i].Pos);
+ BoundingBox.addInternalPoint(Vertices->getPosition(i));
} else
BoundingBox.reset(0, 0, 0);
}
- //! Get type of vertex data stored in this buffer.
- /** \return Type of vertex data. */
- video::E_VERTEX_TYPE getVertexType() const override
- {
- return T::getType();
- }
-
- //! returns position of vertex i
- const core::vector3df &getPosition(u32 i) const override
- {
- return Vertices[i].Pos;
- }
-
- //! returns position of vertex i
- core::vector3df &getPosition(u32 i) override
- {
- return Vertices[i].Pos;
- }
-
- //! returns normal of vertex i
- const core::vector3df &getNormal(u32 i) const override
- {
- return Vertices[i].Normal;
- }
-
- //! returns normal of vertex i
- core::vector3df &getNormal(u32 i) override
- {
- return Vertices[i].Normal;
- }
-
- //! returns texture coord of vertex i
- const core::vector2df &getTCoords(u32 i) const override
- {
- return Vertices[i].TCoords;
- }
-
- //! returns texture coord of vertex i
- core::vector2df &getTCoords(u32 i) override
- {
- return Vertices[i].TCoords;
- }
-
//! Append the vertices and indices to the current buffer
- /** Only works for compatible types, i.e. either the same type
- or the main buffer is of standard type. Otherwise, behavior is
- undefined.
- */
void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) override
{
if (vertices == getVertices())
return;
const u32 vertexCount = getVertexCount();
- u32 i;
+ const u32 indexCount = getIndexCount();
- Vertices.reallocate(vertexCount + numVertices);
- for (i = 0; i < numVertices; ++i) {
- Vertices.push_back(static_cast(vertices)[i]);
- BoundingBox.addInternalPoint(static_cast(vertices)[i].Pos);
+ auto *vt = static_cast(vertices);
+ Vertices->Data.insert(Vertices->Data.end(), vt, vt + numVertices);
+ for (u32 i = vertexCount; i < getVertexCount(); i++)
+ BoundingBox.addInternalPoint(Vertices->getPosition(i));
+
+ Indices->Data.insert(Indices->Data.end(), indices, indices + numIndices);
+ if (vertexCount != 0) {
+ for (u32 i = indexCount; i < getIndexCount(); i++)
+ Indices->Data[i] += vertexCount;
}
-
- Indices.reallocate(getIndexCount() + numIndices);
- for (i = 0; i < numIndices; ++i) {
- Indices.push_back(indices[i] + vertexCount);
- }
- }
-
- //! get the current hardware mapping hint
- E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const override
- {
- return MappingHint_Vertex;
- }
-
- //! get the current hardware mapping hint
- E_HARDWARE_MAPPING getHardwareMappingHint_Index() const override
- {
- return MappingHint_Index;
- }
-
- //! set the hardware mapping hint, for driver
- void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint, E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override
- {
- if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX)
- MappingHint_Vertex = NewMappingHint;
- if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX)
- MappingHint_Index = NewMappingHint;
}
//! Describe what kind of primitive geometry is used by the meshbuffer
@@ -217,47 +130,12 @@ public:
return PrimitiveType;
}
- //! flags the mesh as changed, reloads hardware buffers
- void setDirty(E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override
- {
- if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX)
- ++ChangedID_Vertex;
- if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX)
- ++ChangedID_Index;
- }
-
- //! Get the currently used ID for identification of changes.
- /** This shouldn't be used for anything outside the VideoDriver. */
- u32 getChangedID_Vertex() const override { return ChangedID_Vertex; }
-
- //! Get the currently used ID for identification of changes.
- /** This shouldn't be used for anything outside the VideoDriver. */
- u32 getChangedID_Index() const override { return ChangedID_Index; }
-
- void setHWBuffer(void *ptr) const override
- {
- HWBuffer = ptr;
- }
-
- void *getHWBuffer() const override
- {
- return HWBuffer;
- }
-
- u32 ChangedID_Vertex;
- u32 ChangedID_Index;
-
- //! hardware mapping hint
- E_HARDWARE_MAPPING MappingHint_Vertex;
- E_HARDWARE_MAPPING MappingHint_Index;
- mutable void *HWBuffer;
-
//! Material for this meshbuffer.
video::SMaterial Material;
- //! Vertices of this buffer
- core::array Vertices;
- //! Indices into the vertices of this buffer.
- core::array Indices;
+ //! Vertex buffer
+ CVertexBuffer *Vertices;
+ //! Index buffer
+ SIndexBuffer *Indices;
//! Bounding box of this meshbuffer.
core::aabbox3d BoundingBox;
//! Primitive type used for rendering (triangles, lines, ...)
diff --git a/irr/include/CVertexBuffer.h b/irr/include/CVertexBuffer.h
new file mode 100644
index 000000000..4b3f33688
--- /dev/null
+++ b/irr/include/CVertexBuffer.h
@@ -0,0 +1,136 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#pragma once
+
+#include
+#include "IVertexBuffer.h"
+
+// Define to receive warnings when violating the hw mapping hints
+//#define VERTEXBUFFER_HINT_DEBUG
+
+#ifdef VERTEXBUFFER_HINT_DEBUG
+#include "../src/os.h"
+#endif
+
+namespace irr
+{
+namespace scene
+{
+//! Template implementation of the IVertexBuffer interface
+template
+class CVertexBuffer final : public IVertexBuffer
+{
+public:
+ //! Default constructor for empty buffer
+ CVertexBuffer()
+ {
+#ifdef _DEBUG
+ setDebugName("CVertexBuffer");
+#endif
+ }
+
+ const void *getData() const override
+ {
+ return Data.data();
+ }
+
+ void *getData() override
+ {
+ return Data.data();
+ }
+
+ u32 getCount() const override
+ {
+ return static_cast(Data.size());
+ }
+
+ video::E_VERTEX_TYPE getType() const override
+ {
+ return T::getType();
+ }
+
+ const core::vector3df &getPosition(u32 i) const override
+ {
+ return Data[i].Pos;
+ }
+
+ core::vector3df &getPosition(u32 i) override
+ {
+ return Data[i].Pos;
+ }
+
+ const core::vector3df &getNormal(u32 i) const override
+ {
+ return Data[i].Normal;
+ }
+
+ core::vector3df &getNormal(u32 i) override
+ {
+ return Data[i].Normal;
+ }
+
+ const core::vector2df &getTCoords(u32 i) const override
+ {
+ return Data[i].TCoords;
+ }
+
+ core::vector2df &getTCoords(u32 i) override
+ {
+ return Data[i].TCoords;
+ }
+
+ E_HARDWARE_MAPPING getHardwareMappingHint() const override
+ {
+ return MappingHint;
+ }
+
+ void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint) override
+ {
+ MappingHint = NewMappingHint;
+ }
+
+ void setDirty() override
+ {
+ ++ChangedID;
+#ifdef VERTEXBUFFER_HINT_DEBUG
+ if (MappingHint == EHM_STATIC && HWBuffer) {
+ char buf[100];
+ snprintf_irr(buf, sizeof(buf), "CVertexBuffer @ %p modified, but it has a static hint", this);
+ os::Printer::log(buf, ELL_WARNING);
+ }
+#endif
+ }
+
+ u32 getChangedID() const override { return ChangedID; }
+
+ void setHWBuffer(void *ptr) const override
+ {
+ HWBuffer = ptr;
+ }
+
+ void *getHWBuffer() const override
+ {
+ return HWBuffer;
+ }
+
+ u32 ChangedID = 1;
+
+ //! hardware mapping hint
+ E_HARDWARE_MAPPING MappingHint = EHM_NEVER;
+ mutable void *HWBuffer = nullptr;
+
+ //! Vertices of this buffer
+ std::vector Data;
+};
+
+//! Standard buffer
+typedef CVertexBuffer SVertexBuffer;
+//! Buffer with two texture coords per vertex, e.g. for lightmaps
+typedef CVertexBuffer SVertexBufferLightMap;
+//! Buffer with vertices having tangents stored, e.g. for normal mapping
+typedef CVertexBuffer SVertexBufferTangents;
+
+} // end namespace scene
+} // end namespace irr
diff --git a/irr/include/EDriverTypes.h b/irr/include/EDriverTypes.h
index 33f987889..f19e65ace 100644
--- a/irr/include/EDriverTypes.h
+++ b/irr/include/EDriverTypes.h
@@ -24,9 +24,6 @@ enum E_DRIVER_TYPE
primitives. */
EDT_OPENGL,
- //! OpenGL-ES 1.x driver, for embedded and mobile systems
- EDT_OGLES1,
-
//! OpenGL-ES 2.x driver, for embedded and mobile systems
/** Supports shaders etc. */
EDT_OGLES2,
diff --git a/irr/include/EMaterialProps.h b/irr/include/EMaterialProps.h
index 6f37c95b8..765084340 100644
--- a/irr/include/EMaterialProps.h
+++ b/irr/include/EMaterialProps.h
@@ -18,12 +18,6 @@ enum E_MATERIAL_PROP
//! Corresponds to SMaterial::PointCloud.
EMP_POINTCLOUD = 0x2,
- //! Corresponds to SMaterial::GouraudShading.
- EMP_GOURAUD_SHADING = 0x4,
-
- //! Corresponds to SMaterial::Lighting.
- EMP_LIGHTING = 0x8,
-
//! Corresponds to SMaterial::ZBuffer.
EMP_ZBUFFER = 0x10,
@@ -48,9 +42,6 @@ enum E_MATERIAL_PROP
//! Corresponds to SMaterial::FogEnable.
EMP_FOG_ENABLE = 0x800,
- //! Corresponds to SMaterial::NormalizeNormals.
- EMP_NORMALIZE_NORMALS = 0x1000,
-
//! Corresponds to SMaterialLayer::TextureWrapU, TextureWrapV and
//! TextureWrapW.
EMP_TEXTURE_WRAP = 0x2000,
@@ -61,9 +52,6 @@ enum E_MATERIAL_PROP
//! Corresponds to SMaterial::ColorMask.
EMP_COLOR_MASK = 0x8000,
- //! Corresponds to SMaterial::ColorMaterial.
- EMP_COLOR_MATERIAL = 0x10000,
-
//! Corresponds to SMaterial::UseMipMaps.
EMP_USE_MIP_MAPS = 0x20000,
diff --git a/irr/include/EVideoTypes.h b/irr/include/EVideoTypes.h
new file mode 100644
index 000000000..fe90f0652
--- /dev/null
+++ b/irr/include/EVideoTypes.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#pragma once
+
+#include "SMaterial.h" // MATERIAL_MAX_TEXTURES
+
+namespace irr::video
+{
+
+//! enumeration for geometry transformation states
+enum E_TRANSFORMATION_STATE
+{
+ //! View transformation
+ ETS_VIEW = 0,
+ //! World transformation
+ ETS_WORLD,
+ //! Projection transformation
+ ETS_PROJECTION,
+ //! Texture 0 transformation
+ //! Use E_TRANSFORMATION_STATE(ETS_TEXTURE_0 + texture_number) to access other texture transformations
+ ETS_TEXTURE_0,
+ //! Only used internally
+ ETS_COUNT = ETS_TEXTURE_0 + MATERIAL_MAX_TEXTURES
+};
+
+//! Special render targets, which usually map to dedicated hardware
+/** These render targets (besides 0 and 1) need not be supported by gfx cards */
+enum E_RENDER_TARGET
+{
+ //! Render target is the main color frame buffer
+ ERT_FRAME_BUFFER = 0,
+ //! Render target is a render texture
+ ERT_RENDER_TEXTURE,
+ //! Multi-Render target textures
+ ERT_MULTI_RENDER_TEXTURES,
+ //! Render target is the main color frame buffer
+ ERT_STEREO_LEFT_BUFFER,
+ //! Render target is the right color buffer (left is the main buffer)
+ ERT_STEREO_RIGHT_BUFFER,
+ //! Render to both stereo buffers at once
+ ERT_STEREO_BOTH_BUFFERS,
+ //! Auxiliary buffer 0
+ ERT_AUX_BUFFER0,
+ //! Auxiliary buffer 1
+ ERT_AUX_BUFFER1,
+ //! Auxiliary buffer 2
+ ERT_AUX_BUFFER2,
+ //! Auxiliary buffer 3
+ ERT_AUX_BUFFER3,
+ //! Auxiliary buffer 4
+ ERT_AUX_BUFFER4
+};
+
+//! Enum for the flags of clear buffer
+enum E_CLEAR_BUFFER_FLAG
+{
+ ECBF_NONE = 0,
+ ECBF_COLOR = 1,
+ ECBF_DEPTH = 2,
+ ECBF_STENCIL = 4,
+ ECBF_ALL = ECBF_COLOR | ECBF_DEPTH | ECBF_STENCIL
+};
+
+//! Enum for the types of fog distributions to choose from
+enum E_FOG_TYPE
+{
+ EFT_FOG_EXP = 0,
+ EFT_FOG_LINEAR,
+ EFT_FOG_EXP2
+};
+
+} // irr::video
+
diff --git a/irr/include/IAnimatedMesh.h b/irr/include/IAnimatedMesh.h
index 80b3bc3ca..2a1c1f4b1 100644
--- a/irr/include/IAnimatedMesh.h
+++ b/irr/include/IAnimatedMesh.h
@@ -19,11 +19,8 @@ irr::scene::SMeshBuffer etc. */
class IAnimatedMesh : public IMesh
{
public:
- //! Gets the frame count of the animated mesh.
- /** Note that the play-time is usually getFrameCount()-1 as it stops as soon as the last frame-key is reached.
- \return The amount of frames. If the amount is 1,
- it is a static, non animated mesh. */
- virtual u32 getFrameCount() const = 0;
+ //! Gets the maximum frame number, 0 if the mesh is static.
+ virtual f32 getMaxFrameNumber() const = 0;
//! Gets the animation speed of the animated mesh.
/** \return The number of frames per second to play the
@@ -39,19 +36,10 @@ public:
virtual void setAnimationSpeed(f32 fps) = 0;
//! Returns the IMesh interface for a frame.
- /** \param frame: Frame number as zero based index. The maximum
- frame number is getFrameCount() - 1;
- \param detailLevel: Level of detail. 0 is the lowest, 255 the
- highest level of detail. Most meshes will ignore the detail level.
- \param startFrameLoop: Because some animated meshes (.MD2) are
- blended between 2 static frames, and maybe animated in a loop,
- the startFrameLoop and the endFrameLoop have to be defined, to
- prevent the animation to be blended between frames which are
- outside of this loop.
- If startFrameLoop and endFrameLoop are both -1, they are ignored.
- \param endFrameLoop: see startFrameLoop.
- \return Returns the animated mesh based on a detail level. */
- virtual IMesh *getMesh(s32 frame, s32 detailLevel = 255, s32 startFrameLoop = -1, s32 endFrameLoop = -1) = 0;
+ /** \param frame: Frame number, >= 0, <= getMaxFrameNumber()
+ Linear interpolation is used if this is between two frames.
+ \return Returns the animated mesh for the given frame */
+ virtual IMesh *getMesh(f32 frame) = 0;
//! Returns the type of the animated mesh.
/** In most cases it is not necessary to use this method.
diff --git a/irr/include/IAnimatedMeshSceneNode.h b/irr/include/IAnimatedMeshSceneNode.h
index 65fdaaadf..8f9f6d661 100644
--- a/irr/include/IAnimatedMeshSceneNode.h
+++ b/irr/include/IAnimatedMeshSceneNode.h
@@ -63,7 +63,7 @@ public:
virtual void setCurrentFrame(f32 frame) = 0;
//! Sets the frame numbers between the animation is looped.
- /** The default is 0 to getFrameCount()-1 of the mesh.
+ /** The default is 0 to getMaxFrameNumber() of the mesh.
Number of played frames is end-start.
It interpolates toward the last frame but stops when it is reached.
It does not interpolate back to start even when looping.
@@ -71,7 +71,7 @@ public:
\param begin: Start frame number of the loop.
\param end: End frame number of the loop.
\return True if successful, false if not. */
- virtual bool setFrameLoop(s32 begin, s32 end) = 0;
+ virtual bool setFrameLoop(f32 begin, f32 end) = 0;
//! Sets the speed with which the animation is played.
/** \param framesPerSecond: Frames per second played. */
@@ -108,9 +108,9 @@ public:
//! Returns the currently displayed frame number.
virtual f32 getFrameNr() const = 0;
//! Returns the current start frame number.
- virtual s32 getStartFrame() const = 0;
+ virtual f32 getStartFrame() const = 0;
//! Returns the current end frame number.
- virtual s32 getEndFrame() const = 0;
+ virtual f32 getEndFrame() const = 0;
//! Sets looping mode which is on by default.
/** If set to false, animations will not be played looped. */
diff --git a/irr/include/IAttributes.h b/irr/include/IAttributes.h
index 906d334a2..4606c5710 100644
--- a/irr/include/IAttributes.h
+++ b/irr/include/IAttributes.h
@@ -23,27 +23,13 @@ namespace io
class IAttributes : public virtual IReferenceCounted
{
public:
- //! Returns amount of attributes in this collection of attributes.
- virtual u32 getAttributeCount() const = 0;
-
- //! Returns attribute name by index.
- //! \param index: Index value, must be between 0 and getAttributeCount()-1.
- virtual const c8 *getAttributeName(s32 index) const = 0;
-
//! Returns the type of an attribute
//! \param attributeName: Name for the attribute
virtual E_ATTRIBUTE_TYPE getAttributeType(const c8 *attributeName) const = 0;
- //! Returns attribute type by index.
- //! \param index: Index value, must be between 0 and getAttributeCount()-1.
- virtual E_ATTRIBUTE_TYPE getAttributeType(s32 index) const = 0;
-
//! Returns if an attribute with a name exists
virtual bool existsAttribute(const c8 *attributeName) const = 0;
- //! Returns attribute index from name, -1 if not found
- virtual s32 findAttribute(const c8 *attributeName) const = 0;
-
//! Removes all attributes
virtual void clear() = 0;
@@ -65,13 +51,6 @@ public:
//! \return Returns value of the attribute previously set by setAttribute()
virtual s32 getAttributeAsInt(const c8 *attributeName, irr::s32 defaultNotFound = 0) const = 0;
- //! Gets an attribute as integer value
- //! \param index: Index value, must be between 0 and getAttributeCount()-1.
- virtual s32 getAttributeAsInt(s32 index) const = 0;
-
- //! Sets an attribute as integer value
- virtual void setAttribute(s32 index, s32 value) = 0;
-
/*
Float Attribute
@@ -90,13 +69,6 @@ public:
//! \return Returns value of the attribute previously set by setAttribute()
virtual f32 getAttributeAsFloat(const c8 *attributeName, irr::f32 defaultNotFound = 0.f) const = 0;
- //! Gets an attribute as float value
- //! \param index: Index value, must be between 0 and getAttributeCount()-1.
- virtual f32 getAttributeAsFloat(s32 index) const = 0;
-
- //! Sets an attribute as float value
- virtual void setAttribute(s32 index, f32 value) = 0;
-
/*
Bool Attribute
*/
@@ -112,13 +84,6 @@ public:
//! \param defaultNotFound Value returned when attributeName was not found
//! \return Returns value of the attribute previously set by setAttribute()
virtual bool getAttributeAsBool(const c8 *attributeName, bool defaultNotFound = false) const = 0;
-
- //! Gets an attribute as boolean value
- //! \param index: Index value, must be between 0 and getAttributeCount()-1.
- virtual bool getAttributeAsBool(s32 index) const = 0;
-
- //! Sets an attribute as boolean value
- virtual void setAttribute(s32 index, bool value) = 0;
};
} // end namespace io
diff --git a/irr/include/IEventReceiver.h b/irr/include/IEventReceiver.h
index a484bfb84..7fb9e5f4e 100644
--- a/irr/include/IEventReceiver.h
+++ b/irr/include/IEventReceiver.h
@@ -347,6 +347,9 @@ struct SEvent
//! Type of mouse event
EMOUSE_INPUT_EVENT Event;
+
+ //! Is this a simulated mouse event generated by Minetest itself?
+ bool Simulated;
};
//! Any kind of keyboard event.
@@ -538,6 +541,11 @@ struct SEvent
struct SUserEvent UserEvent;
struct SApplicationEvent ApplicationEvent;
};
+
+ SEvent() {
+ // would be left uninitialized in many places otherwise
+ MouseInput.Simulated = false;
+ }
};
//! Interface of an object which can receive events.
diff --git a/irr/include/IFileSystem.h b/irr/include/IFileSystem.h
index 1fe9fe6f2..f144bbaee 100644
--- a/irr/include/IFileSystem.h
+++ b/irr/include/IFileSystem.h
@@ -85,113 +85,6 @@ public:
See IReferenceCounted::drop() for more information. */
virtual IWriteFile *createAndWriteFile(const path &filename, bool append = false) = 0;
- //! Adds an archive to the file system.
- /** After calling this, the Irrlicht Engine will also search and open
- files directly from this archive. This is useful for hiding data from
- the end user, speeding up file access and making it possible to access
- for example Quake3 .pk3 files, which are just renamed .zip files. By
- default Irrlicht supports ZIP, PAK, TAR, PNK, and directories as
- archives. You can provide your own archive types by implementing
- IArchiveLoader and passing an instance to addArchiveLoader.
- Irrlicht supports AES-encrypted zip files, and the advanced compression
- techniques lzma and bzip2.
- \param filename: Filename of the archive to add to the file system.
- \param ignoreCase: If set to true, files in the archive can be accessed without
- writing all letters in the right case.
- \param ignorePaths: If set to true, files in the added archive can be accessed
- without its complete path.
- \param archiveType: If no specific E_FILE_ARCHIVE_TYPE is selected then
- the type of archive will depend on the extension of the file name. If
- you use a different extension then you can use this parameter to force
- a specific type of archive.
- \param password An optional password, which is used in case of encrypted archives.
- \param retArchive A pointer that will be set to the archive that is added.
- \return True if the archive was added successfully, false if not. */
- virtual bool addFileArchive(const path &filename, bool ignoreCase = true,
- bool ignorePaths = true,
- E_FILE_ARCHIVE_TYPE archiveType = EFAT_UNKNOWN,
- const core::stringc &password = "",
- IFileArchive **retArchive = 0) = 0;
-
- //! Adds an archive to the file system.
- /** After calling this, the Irrlicht Engine will also search and open
- files directly from this archive. This is useful for hiding data from
- the end user, speeding up file access and making it possible to access
- for example Quake3 .pk3 files, which are just renamed .zip files. By
- default Irrlicht supports ZIP, PAK, TAR, PNK, and directories as
- archives. You can provide your own archive types by implementing
- IArchiveLoader and passing an instance to addArchiveLoader.
- Irrlicht supports AES-encrypted zip files, and the advanced compression
- techniques lzma and bzip2.
- If you want to add a directory as an archive, prefix its name with a
- slash in order to let Irrlicht recognize it as a folder mount (mypath/).
- Using this technique one can build up a search order, because archives
- are read first, and can be used more easily with relative filenames.
- \param file: Archive to add to the file system.
- \param ignoreCase: If set to true, files in the archive can be accessed without
- writing all letters in the right case.
- \param ignorePaths: If set to true, files in the added archive can be accessed
- without its complete path.
- \param archiveType: If no specific E_FILE_ARCHIVE_TYPE is selected then
- the type of archive will depend on the extension of the file name. If
- you use a different extension then you can use this parameter to force
- a specific type of archive.
- \param password An optional password, which is used in case of encrypted archives.
- \param retArchive A pointer that will be set to the archive that is added.
- \return True if the archive was added successfully, false if not. */
- virtual bool addFileArchive(IReadFile *file, bool ignoreCase = true,
- bool ignorePaths = true,
- E_FILE_ARCHIVE_TYPE archiveType = EFAT_UNKNOWN,
- const core::stringc &password = "",
- IFileArchive **retArchive = 0) = 0;
-
- //! Adds an archive to the file system.
- /** \param archive: The archive to add to the file system.
- \return True if the archive was added successfully, false if not. */
- virtual bool addFileArchive(IFileArchive *archive) = 0;
-
- //! Get the number of archives currently attached to the file system
- virtual u32 getFileArchiveCount() const = 0;
-
- //! Removes an archive from the file system.
- /** This will close the archive and free any file handles, but will not
- close resources which have already been loaded and are now cached, for
- example textures and meshes.
- \param index: The index of the archive to remove
- \return True on success, false on failure */
- virtual bool removeFileArchive(u32 index) = 0;
-
- //! Removes an archive from the file system.
- /** This will close the archive and free any file handles, but will not
- close resources which have already been loaded and are now cached, for
- example textures and meshes. Note that a relative filename might be
- interpreted differently on each call, depending on the current working
- directory. In case you want to remove an archive that was added using
- a relative path name, you have to change to the same working directory
- again. This means, that the filename given on creation is not an
- identifier for the archive, but just a usual filename that is used for
- locating the archive to work with.
- \param filename The archive pointed to by the name will be removed
- \return True on success, false on failure */
- virtual bool removeFileArchive(const path &filename) = 0;
-
- //! Removes an archive from the file system.
- /** This will close the archive and free any file handles, but will not
- close resources which have already been loaded and are now cached, for
- example textures and meshes.
- \param archive The archive to remove.
- \return True on success, false on failure */
- virtual bool removeFileArchive(const IFileArchive *archive) = 0;
-
- //! Changes the search order of attached archives.
- /**
- \param sourceIndex: The index of the archive to change
- \param relative: The relative change in position, archives with a lower index are searched first */
- virtual bool moveFileArchive(u32 sourceIndex, s32 relative) = 0;
-
- //! Get the archive at a given index.
- virtual IFileArchive *getFileArchive(u32 index) = 0;
-
//! Adds an external archive loader to the engine.
/** Use this function to add support for new archive types to the
engine, for example proprietary or encrypted file storage. */
diff --git a/irr/include/IGUISkin.h b/irr/include/IGUISkin.h
index 36b510606..b323983ae 100644
--- a/irr/include/IGUISkin.h
+++ b/irr/include/IGUISkin.h
@@ -437,6 +437,10 @@ public:
virtual void draw3DButtonPaneStandard(IGUIElement *element,
const core::rect &rect,
const core::rect *clip = 0) = 0;
+ virtual void drawColored3DButtonPaneStandard(IGUIElement* element,
+ const core::rect& rect,
+ const core::rect* clip=0,
+ const video::SColor* colors=0) = 0;
//! draws a pressed 3d button pane
/** Used for drawing for example buttons in pressed state.
@@ -450,6 +454,10 @@ public:
virtual void draw3DButtonPanePressed(IGUIElement *element,
const core::rect &rect,
const core::rect *clip = 0) = 0;
+ virtual void drawColored3DButtonPanePressed(IGUIElement* element,
+ const core::rect& rect,
+ const core::rect* clip=0,
+ const video::SColor* colors=0) = 0;
//! draws a sunken 3d pane
/** Used for drawing the background of edit, combo or check boxes.
diff --git a/irr/include/IIndexBuffer.h b/irr/include/IIndexBuffer.h
index 3d5b8e76a..01282f0c8 100644
--- a/irr/include/IIndexBuffer.h
+++ b/irr/include/IIndexBuffer.h
@@ -7,45 +7,39 @@
#include "IReferenceCounted.h"
#include "irrArray.h"
#include "EHardwareBufferFlags.h"
+#include "EPrimitiveTypes.h"
#include "SVertexIndex.h"
namespace irr
{
-namespace video
-{
-
-}
-
namespace scene
{
class IIndexBuffer : public virtual IReferenceCounted
{
public:
+ //! Get type of index data which is stored in this meshbuffer.
+ /** \return Index type of this buffer. */
+ virtual video::E_INDEX_TYPE getType() const = 0;
+
+ //! Get access to indices.
+ /** \return Pointer to indices array. */
+ virtual const void *getData() const = 0;
+
+ //! Get access to indices.
+ /** \return Pointer to indices array. */
virtual void *getData() = 0;
- virtual video::E_INDEX_TYPE getType() const = 0;
- virtual void setType(video::E_INDEX_TYPE IndexType) = 0;
-
- virtual u32 stride() const = 0;
-
- virtual u32 size() const = 0;
- virtual void push_back(const u32 &element) = 0;
- virtual u32 operator[](u32 index) const = 0;
- virtual u32 getLast() = 0;
- virtual void setValue(u32 index, u32 value) = 0;
- virtual void set_used(u32 usedNow) = 0;
- virtual void reallocate(u32 new_size) = 0;
- virtual u32 allocated_size() const = 0;
-
- virtual void *pointer() = 0;
+ //! Get amount of indices in this meshbuffer.
+ /** \return Number of indices in this buffer. */
+ virtual u32 getCount() const = 0;
//! get the current hardware mapping hint
virtual E_HARDWARE_MAPPING getHardwareMappingHint() const = 0;
//! set the hardware mapping hint, for driver
- virtual void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint) = 0;
+ virtual void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint) = 0;
//! flags the meshbuffer as changed, reloads hardware buffers
virtual void setDirty() = 0;
@@ -53,6 +47,35 @@ public:
//! Get the currently used ID for identification of changes.
/** This shouldn't be used for anything outside the VideoDriver. */
virtual u32 getChangedID() const = 0;
+
+ //! Used by the VideoDriver to remember the buffer link.
+ virtual void setHWBuffer(void *ptr) const = 0;
+ virtual void *getHWBuffer() const = 0;
+
+ //! Calculate how many geometric primitives would be drawn
+ u32 getPrimitiveCount(E_PRIMITIVE_TYPE primitiveType) const
+ {
+ const u32 indexCount = getCount();
+ switch (primitiveType) {
+ case scene::EPT_POINTS:
+ return indexCount;
+ case scene::EPT_LINE_STRIP:
+ return indexCount - 1;
+ case scene::EPT_LINE_LOOP:
+ return indexCount;
+ case scene::EPT_LINES:
+ return indexCount / 2;
+ case scene::EPT_TRIANGLE_STRIP:
+ return (indexCount - 2);
+ case scene::EPT_TRIANGLE_FAN:
+ return (indexCount - 2);
+ case scene::EPT_TRIANGLES:
+ return indexCount / 3;
+ case scene::EPT_POINT_SPRITES:
+ return indexCount;
+ }
+ return 0;
+ }
};
} // end namespace scene
diff --git a/irr/include/IMesh.h b/irr/include/IMesh.h
index 6d06eb762..8ee180d5d 100644
--- a/irr/include/IMesh.h
+++ b/irr/include/IMesh.h
@@ -86,6 +86,17 @@ public:
mesh buffer. */
virtual IMeshBuffer *getMeshBuffer(const video::SMaterial &material) const = 0;
+ //! Minetest binds textures (node tiles, object textures) to models.
+ // glTF allows multiple primitives (mesh buffers) to reference the same texture.
+ // This is reflected here: This function gets the texture slot for a mesh buffer.
+ /** \param meshbufNr: Zero based index of the mesh buffer. The maximum value is
+ getMeshBufferCount() - 1;
+ \return number of texture slot to bind to the given mesh buffer */
+ virtual u32 getTextureSlot(u32 meshbufNr) const
+ {
+ return meshbufNr;
+ }
+
//! Get an axis aligned bounding box of the mesh.
/** \return Bounding box of this mesh. */
virtual const core::aabbox3d &getBoundingBox() const = 0;
diff --git a/irr/include/IMeshBuffer.h b/irr/include/IMeshBuffer.h
index c69a12d1d..55c05211a 100644
--- a/irr/include/IMeshBuffer.h
+++ b/irr/include/IMeshBuffer.h
@@ -7,8 +7,8 @@
#include "IReferenceCounted.h"
#include "SMaterial.h"
#include "aabbox3d.h"
-#include "S3DVertex.h"
-#include "SVertexIndex.h"
+#include "IVertexBuffer.h"
+#include "IIndexBuffer.h"
#include "EHardwareBufferFlags.h"
#include "EPrimitiveTypes.h"
@@ -46,39 +46,17 @@ public:
/** \return Material of this buffer. */
virtual const video::SMaterial &getMaterial() const = 0;
- //! Get type of vertex data which is stored in this meshbuffer.
- /** \return Vertex type of this buffer. */
- virtual video::E_VERTEX_TYPE getVertexType() const = 0;
+ /// Get the vertex buffer
+ virtual const scene::IVertexBuffer *getVertexBuffer() const = 0;
- //! Get access to vertex data. The data is an array of vertices.
- /** Which vertex type is used can be determined by getVertexType().
- \return Pointer to array of vertices. */
- virtual const void *getVertices() const = 0;
+ /// Get the vertex buffer
+ virtual scene::IVertexBuffer *getVertexBuffer() = 0;
- //! Get access to vertex data. The data is an array of vertices.
- /** Which vertex type is used can be determined by getVertexType().
- \return Pointer to array of vertices. */
- virtual void *getVertices() = 0;
+ /// Get the index buffer
+ virtual const scene::IIndexBuffer *getIndexBuffer() const = 0;
- //! Get amount of vertices in meshbuffer.
- /** \return Number of vertices in this buffer. */
- virtual u32 getVertexCount() const = 0;
-
- //! Get type of index data which is stored in this meshbuffer.
- /** \return Index type of this buffer. */
- virtual video::E_INDEX_TYPE getIndexType() const = 0;
-
- //! Get access to indices.
- /** \return Pointer to indices array. */
- virtual const u16 *getIndices() const = 0;
-
- //! Get access to indices.
- /** \return Pointer to indices array. */
- virtual u16 *getIndices() = 0;
-
- //! Get amount of indices in this meshbuffer.
- /** \return Number of indices in this buffer. */
- virtual u32 getIndexCount() const = 0;
+ /// Get the index buffer
+ virtual scene::IIndexBuffer *getIndexBuffer() = 0;
//! Get the axis aligned bounding box of this meshbuffer.
/** \return Axis aligned bounding box of this buffer. */
@@ -92,24 +70,6 @@ public:
//! Recalculates the bounding box. Should be called if the mesh changed.
virtual void recalculateBoundingBox() = 0;
- //! returns position of vertex i
- virtual const core::vector3df &getPosition(u32 i) const = 0;
-
- //! returns position of vertex i
- virtual core::vector3df &getPosition(u32 i) = 0;
-
- //! returns normal of vertex i
- virtual const core::vector3df &getNormal(u32 i) const = 0;
-
- //! returns normal of vertex i
- virtual core::vector3df &getNormal(u32 i) = 0;
-
- //! returns texture coord of vertex i
- virtual const core::vector2df &getTCoords(u32 i) const = 0;
-
- //! returns texture coord of vertex i
- virtual core::vector2df &getTCoords(u32 i) = 0;
-
//! Append the vertices and indices to the current buffer
/** Only works for compatible vertex types.
\param vertices Pointer to a vertex array.
@@ -118,29 +78,123 @@ public:
\param numIndices Number of indices in array. */
virtual void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) = 0;
- //! get the current hardware mapping hint
- virtual E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const = 0;
+ /* Leftover functions that are now just helpers for accessing the respective buffer. */
- //! get the current hardware mapping hint
- virtual E_HARDWARE_MAPPING getHardwareMappingHint_Index() const = 0;
+ //! Get type of vertex data which is stored in this meshbuffer.
+ /** \return Vertex type of this buffer. */
+ inline video::E_VERTEX_TYPE getVertexType() const
+ {
+ return getVertexBuffer()->getType();
+ }
+
+ //! Get access to vertex data. The data is an array of vertices.
+ /** Which vertex type is used can be determined by getVertexType().
+ \return Pointer to array of vertices. */
+ inline const void *getVertices() const
+ {
+ return getVertexBuffer()->getData();
+ }
+
+ //! Get access to vertex data. The data is an array of vertices.
+ /** Which vertex type is used can be determined by getVertexType().
+ \return Pointer to array of vertices. */
+ inline void *getVertices()
+ {
+ return getVertexBuffer()->getData();
+ }
+
+ //! Get amount of vertices in meshbuffer.
+ /** \return Number of vertices in this buffer. */
+ inline u32 getVertexCount() const
+ {
+ return getVertexBuffer()->getCount();
+ }
+
+ //! Get type of index data which is stored in this meshbuffer.
+ /** \return Index type of this buffer. */
+ inline video::E_INDEX_TYPE getIndexType() const
+ {
+ return getIndexBuffer()->getType();
+ }
+
+ //! Get access to indices.
+ /** \return Pointer to indices array. */
+ inline const u16 *getIndices() const
+ {
+ _IRR_DEBUG_BREAK_IF(getIndexBuffer()->getType() != video::EIT_16BIT);
+ return static_cast(getIndexBuffer()->getData());
+ }
+
+ //! Get access to indices.
+ /** \return Pointer to indices array. */
+ inline u16 *getIndices()
+ {
+ _IRR_DEBUG_BREAK_IF(getIndexBuffer()->getType() != video::EIT_16BIT);
+ return static_cast(getIndexBuffer()->getData());
+ }
+
+ //! Get amount of indices in this meshbuffer.
+ /** \return Number of indices in this buffer. */
+ inline u32 getIndexCount() const
+ {
+ return getIndexBuffer()->getCount();
+ }
+
+ //! returns position of vertex i
+ inline const core::vector3df &getPosition(u32 i) const
+ {
+ return getVertexBuffer()->getPosition(i);
+ }
+
+ //! returns position of vertex i
+ inline core::vector3df &getPosition(u32 i)
+ {
+ return getVertexBuffer()->getPosition(i);
+ }
+
+ //! returns normal of vertex i
+ inline const core::vector3df &getNormal(u32 i) const
+ {
+ return getVertexBuffer()->getNormal(i);
+ }
+
+ //! returns normal of vertex i
+ inline core::vector3df &getNormal(u32 i)
+ {
+ return getVertexBuffer()->getNormal(i);
+ }
+
+ //! returns texture coord of vertex i
+ inline const core::vector2df &getTCoords(u32 i) const
+ {
+ return getVertexBuffer()->getTCoords(i);
+ }
+
+ //! returns texture coord of vertex i
+ inline core::vector2df &getTCoords(u32 i)
+ {
+ return getVertexBuffer()->getTCoords(i);
+ }
//! set the hardware mapping hint, for driver
- virtual void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0;
+ inline void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX)
+ {
+ if (buffer == EBT_VERTEX_AND_INDEX || buffer == EBT_VERTEX)
+ getVertexBuffer()->setHardwareMappingHint(newMappingHint);
+ if (buffer == EBT_VERTEX_AND_INDEX || buffer == EBT_INDEX)
+ getIndexBuffer()->setHardwareMappingHint(newMappingHint);
+ }
//! flags the meshbuffer as changed, reloads hardware buffers
- virtual void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0;
+ inline void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX)
+ {
+ if (buffer == EBT_VERTEX_AND_INDEX || buffer == EBT_VERTEX)
+ getVertexBuffer()->setDirty();
+ if (buffer == EBT_VERTEX_AND_INDEX || buffer == EBT_INDEX)
+ getIndexBuffer()->setDirty();
+ }
- //! Get the currently used ID for identification of changes.
- /** This shouldn't be used for anything outside the VideoDriver. */
- virtual u32 getChangedID_Vertex() const = 0;
-
- //! Get the currently used ID for identification of changes.
- /** This shouldn't be used for anything outside the VideoDriver. */
- virtual u32 getChangedID_Index() const = 0;
-
- //! Used by the VideoDriver to remember the buffer link.
- virtual void setHWBuffer(void *ptr) const = 0;
- virtual void *getHWBuffer() const = 0;
+ /* End helpers */
//! Describe what kind of primitive geometry is used by the meshbuffer
/** Note: Default is EPT_TRIANGLES. Using other types is fine for rendering.
@@ -153,32 +207,13 @@ public:
virtual E_PRIMITIVE_TYPE getPrimitiveType() const = 0;
//! Calculate how many geometric primitives are used by this meshbuffer
- virtual u32 getPrimitiveCount() const
+ u32 getPrimitiveCount() const
{
- const u32 indexCount = getIndexCount();
- switch (getPrimitiveType()) {
- case scene::EPT_POINTS:
- return indexCount;
- case scene::EPT_LINE_STRIP:
- return indexCount - 1;
- case scene::EPT_LINE_LOOP:
- return indexCount;
- case scene::EPT_LINES:
- return indexCount / 2;
- case scene::EPT_TRIANGLE_STRIP:
- return (indexCount - 2);
- case scene::EPT_TRIANGLE_FAN:
- return (indexCount - 2);
- case scene::EPT_TRIANGLES:
- return indexCount / 3;
- case scene::EPT_POINT_SPRITES:
- return indexCount;
- }
- return 0;
+ return getIndexBuffer()->getPrimitiveCount(getPrimitiveType());
}
//! Calculate size of vertices and indices in memory
- virtual size_t getSize() const
+ size_t getSize() const
{
size_t ret = 0;
switch (getVertexType()) {
diff --git a/irr/include/IReferenceCounted.h b/irr/include/IReferenceCounted.h
index 6f85bb904..68aa20fb6 100644
--- a/irr/include/IReferenceCounted.h
+++ b/irr/include/IReferenceCounted.h
@@ -42,7 +42,7 @@ class IReferenceCounted
public:
//! Constructor.
IReferenceCounted() :
- DebugName(0), ReferenceCounter(1)
+ ReferenceCounter(1)
{
}
@@ -51,6 +51,10 @@ public:
{
}
+ // Reference counted objects can be neither copied nor moved.
+ IReferenceCounted(const IReferenceCounted &) = delete;
+ IReferenceCounted &operator=(const IReferenceCounted &) = delete;
+
//! Grabs the object. Increments the reference counter by one.
/** Someone who calls grab() to an object, should later also
call drop() to it. If an object never gets as much drop() as
@@ -132,6 +136,7 @@ public:
return ReferenceCounter;
}
+#ifdef _DEBUG
//! Returns the debug name of the object.
/** The Debugname may only be set and changed by the object
itself. This method should only be used in Debug mode.
@@ -153,7 +158,10 @@ protected:
private:
//! The debug name.
- const c8 *DebugName;
+ const c8 *DebugName = nullptr;
+#endif
+
+private:
//! The reference counter. Mutable to do reference counting on const objects.
mutable s32 ReferenceCounter;
diff --git a/irr/include/ISceneNode.h b/irr/include/ISceneNode.h
index 7a03a2256..1eab3a3fd 100644
--- a/irr/include/ISceneNode.h
+++ b/irr/include/ISceneNode.h
@@ -10,6 +10,7 @@
#include "EDebugSceneTypes.h"
#include "SMaterial.h"
#include "irrString.h"
+#include "irrArray.h"
#include "aabbox3d.h"
#include "matrix4.h"
#include "IAttributes.h"
diff --git a/irr/include/ISkinnedMesh.h b/irr/include/ISkinnedMesh.h
index 9cc7469cb..869327bcd 100644
--- a/irr/include/ISkinnedMesh.h
+++ b/irr/include/ISkinnedMesh.h
@@ -159,15 +159,17 @@ public:
core::array Weights;
//! Unnecessary for loaders, will be overwritten on finalize
- core::matrix4 GlobalMatrix;
+ core::matrix4 GlobalMatrix; // loaders may still choose to set this (temporarily) to calculate absolute vertex data.
core::matrix4 GlobalAnimatedMatrix;
core::matrix4 LocalAnimatedMatrix;
+
+ //! These should be set by loaders.
core::vector3df Animatedposition;
core::vector3df Animatedscale;
core::quaternion Animatedrotation;
- core::matrix4 GlobalInversedMatrix; // the x format pre-calculates this
-
+ // The .x and .gltf formats pre-calculate this
+ std::optional GlobalInversedMatrix;
private:
//! Internal members used by CSkinnedMesh
friend class CSkinnedMesh;
@@ -199,6 +201,9 @@ public:
//! Adds a new meshbuffer to the mesh, access it as last one
virtual SSkinMeshBuffer *addMeshBuffer() = 0;
+ //! Adds a new meshbuffer to the mesh, access it as last one
+ virtual void addMeshBuffer(SSkinMeshBuffer *meshbuf) = 0;
+
//! Adds a new joint to the mesh, access it as last one
virtual SJoint *addJoint(SJoint *parent = 0) = 0;
diff --git a/irr/include/IVertexBuffer.h b/irr/include/IVertexBuffer.h
index a4ec5efd9..e5be83904 100644
--- a/irr/include/IVertexBuffer.h
+++ b/irr/include/IVertexBuffer.h
@@ -17,24 +17,47 @@ namespace scene
class IVertexBuffer : public virtual IReferenceCounted
{
public:
- virtual void *getData() = 0;
+ //! Get type of vertex data which is stored in this meshbuffer.
+ /** \return Vertex type of this buffer. */
virtual video::E_VERTEX_TYPE getType() const = 0;
- virtual void setType(video::E_VERTEX_TYPE vertexType) = 0;
- virtual u32 stride() const = 0;
- virtual u32 size() const = 0;
- virtual void push_back(const video::S3DVertex &element) = 0;
- virtual video::S3DVertex &operator[](const u32 index) const = 0;
- virtual video::S3DVertex &getLast() = 0;
- virtual void set_used(u32 usedNow) = 0;
- virtual void reallocate(u32 new_size) = 0;
- virtual u32 allocated_size() const = 0;
- virtual video::S3DVertex *pointer() = 0;
+
+ //! Get access to vertex data. The data is an array of vertices.
+ /** Which vertex type is used can be determined by getVertexType().
+ \return Pointer to array of vertices. */
+ virtual const void *getData() const = 0;
+
+ //! Get access to vertex data. The data is an array of vertices.
+ /** Which vertex type is used can be determined by getVertexType().
+ \return Pointer to array of vertices. */
+ virtual void *getData() = 0;
+
+ //! Get amount of vertices in meshbuffer.
+ /** \return Number of vertices in this buffer. */
+ virtual u32 getCount() const = 0;
+
+ //! returns position of vertex i
+ virtual const core::vector3df &getPosition(u32 i) const = 0;
+
+ //! returns position of vertex i
+ virtual core::vector3df &getPosition(u32 i) = 0;
+
+ //! returns normal of vertex i
+ virtual const core::vector3df &getNormal(u32 i) const = 0;
+
+ //! returns normal of vertex i
+ virtual core::vector3df &getNormal(u32 i) = 0;
+
+ //! returns texture coord of vertex i
+ virtual const core::vector2df &getTCoords(u32 i) const = 0;
+
+ //! returns texture coord of vertex i
+ virtual core::vector2df &getTCoords(u32 i) = 0;
//! get the current hardware mapping hint
virtual E_HARDWARE_MAPPING getHardwareMappingHint() const = 0;
//! set the hardware mapping hint, for driver
- virtual void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint) = 0;
+ virtual void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint) = 0;
//! flags the meshbuffer as changed, reloads hardware buffers
virtual void setDirty() = 0;
@@ -42,6 +65,10 @@ public:
//! Get the currently used ID for identification of changes.
/** This shouldn't be used for anything outside the VideoDriver. */
virtual u32 getChangedID() const = 0;
+
+ //! Used by the VideoDriver to remember the buffer link.
+ virtual void setHWBuffer(void *ptr) const = 0;
+ virtual void *getHWBuffer() const = 0;
};
} // end namespace scene
diff --git a/irr/include/IVideoDriver.h b/irr/include/IVideoDriver.h
index 2d651c6bf..af8d97fef 100644
--- a/irr/include/IVideoDriver.h
+++ b/irr/include/IVideoDriver.h
@@ -9,14 +9,16 @@
#include "ITexture.h"
#include "irrArray.h"
#include "matrix4.h"
-#include "plane3d.h"
#include "dimension2d.h"
#include "position2d.h"
-#include "IMeshBuffer.h"
#include "EDriverTypes.h"
#include "EDriverFeatures.h"
+#include "EPrimitiveTypes.h"
+#include "EVideoTypes.h"
#include "SExposedVideoData.h"
#include "SOverrideMaterial.h"
+#include "S3DVertex.h" // E_VERTEX_TYPE
+#include "SVertexIndex.h" // E_INDEX_TYPE
namespace irr
{
@@ -29,6 +31,8 @@ class IWriteFile;
namespace scene
{
class IMeshBuffer;
+class IVertexBuffer;
+class IIndexBuffer;
class IMesh;
class IMeshManipulator;
class ISceneNode;
@@ -36,77 +40,12 @@ class ISceneNode;
namespace video
{
-struct S3DVertex;
-struct S3DVertex2TCoords;
-struct S3DVertexTangents;
class IImageLoader;
class IImageWriter;
class IMaterialRenderer;
class IGPUProgrammingServices;
class IRenderTarget;
-//! enumeration for geometry transformation states
-enum E_TRANSFORMATION_STATE
-{
- //! View transformation
- ETS_VIEW = 0,
- //! World transformation
- ETS_WORLD,
- //! Projection transformation
- ETS_PROJECTION,
- //! Texture 0 transformation
- //! Use E_TRANSFORMATION_STATE(ETS_TEXTURE_0 + texture_number) to access other texture transformations
- ETS_TEXTURE_0,
- //! Only used internally
- ETS_COUNT = ETS_TEXTURE_0 + MATERIAL_MAX_TEXTURES
-};
-
-//! Special render targets, which usually map to dedicated hardware
-/** These render targets (besides 0 and 1) need not be supported by gfx cards */
-enum E_RENDER_TARGET
-{
- //! Render target is the main color frame buffer
- ERT_FRAME_BUFFER = 0,
- //! Render target is a render texture
- ERT_RENDER_TEXTURE,
- //! Multi-Render target textures
- ERT_MULTI_RENDER_TEXTURES,
- //! Render target is the main color frame buffer
- ERT_STEREO_LEFT_BUFFER,
- //! Render target is the right color buffer (left is the main buffer)
- ERT_STEREO_RIGHT_BUFFER,
- //! Render to both stereo buffers at once
- ERT_STEREO_BOTH_BUFFERS,
- //! Auxiliary buffer 0
- ERT_AUX_BUFFER0,
- //! Auxiliary buffer 1
- ERT_AUX_BUFFER1,
- //! Auxiliary buffer 2
- ERT_AUX_BUFFER2,
- //! Auxiliary buffer 3
- ERT_AUX_BUFFER3,
- //! Auxiliary buffer 4
- ERT_AUX_BUFFER4
-};
-
-//! Enum for the flags of clear buffer
-enum E_CLEAR_BUFFER_FLAG
-{
- ECBF_NONE = 0,
- ECBF_COLOR = 1,
- ECBF_DEPTH = 2,
- ECBF_STENCIL = 4,
- ECBF_ALL = ECBF_COLOR | ECBF_DEPTH | ECBF_STENCIL
-};
-
-//! Enum for the types of fog distributions to choose from
-enum E_FOG_TYPE
-{
- EFT_FOG_EXP = 0,
- EFT_FOG_LINEAR,
- EFT_FOG_EXP2
-};
-
const c8 *const FogTypeNames[] = {
"FogExp",
"FogLinear",
@@ -114,6 +53,17 @@ const c8 *const FogTypeNames[] = {
0,
};
+struct SFrameStats {
+ //! Number of draw calls
+ u32 Drawcalls = 0;
+ //! Count of primitives drawn
+ u32 PrimitivesDrawn = 0;
+ //! Number of hardware buffers uploaded (new or updated)
+ u32 HWBuffersUploaded = 0;
+ //! Sum of uploaded hardware buffer size
+ u32 HWBuffersUploadedSize = 0;
+};
+
//! Interface to driver which is able to perform 2d and 3d graphics functions.
/** This interface is one of the most important interfaces of
the Irrlicht Engine: All rendering and texture manipulation is done with
@@ -182,7 +132,6 @@ public:
MaxSupportedTextures (int) The maximum number of simultaneous textures supported by the fixed function pipeline of the (hw) driver. The actual supported number of textures supported by the engine can be lower.
MaxLights (int) Number of hardware lights supported in the fixed function pipeline of the driver, typically 6-8. Use light manager or deferred shading for more.
MaxAnisotropy (int) Number of anisotropy levels supported for filtering. At least 1, max is typically at 16 or 32.
- MaxUserClipPlanes (int) Number of additional clip planes, which can be set by the user via dedicated driver methods.
MaxAuxBuffers (int) Special render buffers, which are currently not really usable inside Irrlicht. Only supported by OpenGL
MaxMultipleRenderTargets (int) Number of render targets which can be bound simultaneously. Rendering to MRTs is done via shaders.
MaxIndices (int) Number of indices which can be used in one render call (i.e. one mesh buffer).
@@ -195,12 +144,6 @@ public:
*/
virtual const io::IAttributes &getDriverAttributes() const = 0;
- //! Check if the driver was recently reset.
- /** For d3d devices you will need to recreate the RTTs if the
- driver was reset. Should be queried right after beginScene().
- */
- virtual bool checkDriverReset() = 0;
-
//! Sets transformation matrices.
/** \param state Transformation type to be set, e.g. view,
world, or projection.
@@ -360,7 +303,10 @@ public:
virtual void removeAllTextures() = 0;
//! Remove hardware buffer
- virtual void removeHardwareBuffer(const scene::IMeshBuffer *mb) = 0;
+ virtual void removeHardwareBuffer(const scene::IVertexBuffer *vb) = 0;
+
+ //! Remove hardware buffer
+ virtual void removeHardwareBuffer(const scene::IIndexBuffer *ib) = 0;
//! Remove all hardware buffers
virtual void removeAllHardwareBuffers() = 0;
@@ -799,6 +745,17 @@ public:
/** \param mb Buffer to draw */
virtual void drawMeshBuffer(const scene::IMeshBuffer *mb) = 0;
+ /**
+ * Draws a mesh from individual vertex and index buffers.
+ * @param vb vertices to use
+ * @param ib indices to use
+ * @param primCount amount of primitives
+ * @param pType primitive type
+ */
+ virtual void drawBuffers(const scene::IVertexBuffer *vb,
+ const scene::IIndexBuffer *ib, u32 primCount,
+ scene::E_PRIMITIVE_TYPE pType = scene::EPT_TRIANGLES) = 0;
+
//! Draws normals of a mesh buffer
/** \param mb Buffer to draw the normals of
\param length length scale factor of the normals
@@ -856,12 +813,8 @@ public:
\return Approximate amount of frames per second drawn. */
virtual s32 getFPS() const = 0;
- //! Returns amount of primitives (mostly triangles) which were drawn in the last frame.
- /** Together with getFPS() very useful method for statistics.
- \param mode Defines if the primitives drawn are accumulated or
- counted per frame.
- \return Amount of primitives drawn in the last frame. */
- virtual u32 getPrimitiveCountDrawn(u32 mode = 0) const = 0;
+ //! Return some statistics about the last frame
+ virtual SFrameStats getFrameStats() const = 0;
//! Gets name of this video driver.
/** \return Returns the name of the video driver, e.g. in case
@@ -1109,26 +1062,6 @@ public:
\return Pointer to loaded texture, or 0 if not found. */
virtual video::ITexture *findTexture(const io::path &filename) = 0;
- //! Set or unset a clipping plane.
- /** There are at least 6 clipping planes available for the user
- to set at will.
- \param index The plane index. Must be between 0 and
- MaxUserClipPlanes.
- \param plane The plane itself.
- \param enable If true, enable the clipping plane else disable
- it.
- \return True if the clipping plane is usable. */
- virtual bool setClipPlane(u32 index, const core::plane3df &plane, bool enable = false) = 0;
-
- //! Enable or disable a clipping plane.
- /** There are at least 6 clipping planes available for the user
- to set at will.
- \param index The plane index. Must be between 0 and
- MaxUserClipPlanes.
- \param enable If true, enable the clipping plane else disable
- it. */
- virtual void enableClipPlane(u32 index, bool enable) = 0;
-
//! Set the minimum number of vertices for which a hw buffer will be created
/** \param count Number of vertices to set as minimum. */
virtual void setMinHardwareBufferVertexCount(u32 count) = 0;
diff --git a/irr/include/IrrlichtDevice.h b/irr/include/IrrlichtDevice.h
index 11619010c..777a23420 100644
--- a/irr/include/IrrlichtDevice.h
+++ b/irr/include/IrrlichtDevice.h
@@ -6,14 +6,16 @@
#include "IReferenceCounted.h"
#include "dimension2d.h"
-#include "IVideoDriver.h"
#include "EDriverTypes.h"
#include "EDeviceTypes.h"
#include "IEventReceiver.h"
#include "ICursorControl.h"
#include "ITimer.h"
#include "IOSOperator.h"
+#include "irrArray.h"
#include "IrrCompileConfig.h"
+#include "position2d.h"
+#include "SColor.h" // video::ECOLOR_FORMAT
namespace irr
{
@@ -38,6 +40,9 @@ class ISceneManager;
namespace video
{
class IContextManager;
+class IImage;
+class ITexture;
+class IVideoDriver;
extern "C" IRRLICHT_API bool IRRCALLCONV isDriverSupported(E_DRIVER_TYPE driver);
} // end namespace video
diff --git a/irr/src/KHR/khrplatform.h b/irr/include/KHR/khrplatform.h
similarity index 100%
rename from irr/src/KHR/khrplatform.h
rename to irr/include/KHR/khrplatform.h
diff --git a/irr/include/SAnimatedMesh.h b/irr/include/SAnimatedMesh.h
index 8fdaae0ee..dd7306633 100644
--- a/irr/include/SAnimatedMesh.h
+++ b/irr/include/SAnimatedMesh.h
@@ -4,10 +4,10 @@
#pragma once
+#include
#include "IAnimatedMesh.h"
#include "IMesh.h"
#include "aabbox3d.h"
-#include "irrArray.h"
namespace irr
{
@@ -15,7 +15,7 @@ namespace scene
{
//! Simple implementation of the IAnimatedMesh interface.
-struct SAnimatedMesh : public IAnimatedMesh
+struct SAnimatedMesh final : public IAnimatedMesh
{
//! constructor
SAnimatedMesh(scene::IMesh *mesh = 0, scene::E_ANIMATED_MESH_TYPE type = scene::EAMT_UNKNOWN) :
@@ -32,15 +32,13 @@ struct SAnimatedMesh : public IAnimatedMesh
virtual ~SAnimatedMesh()
{
// drop meshes
- for (u32 i = 0; i < Meshes.size(); ++i)
- Meshes[i]->drop();
+ for (auto *mesh : Meshes)
+ mesh->drop();
}
- //! Gets the frame count of the animated mesh.
- /** \return Amount of frames. If the amount is 1, it is a static, non animated mesh. */
- u32 getFrameCount() const override
+ f32 getMaxFrameNumber() const override
{
- return Meshes.size();
+ return static_cast(Meshes.size() - 1);
}
//! Gets the default animation speed of the animated mesh.
@@ -59,19 +57,14 @@ struct SAnimatedMesh : public IAnimatedMesh
}
//! Returns the IMesh interface for a frame.
- /** \param frame: Frame number as zero based index. The maximum frame number is
- getFrameCount() - 1;
- \param detailLevel: Level of detail. 0 is the lowest,
- 255 the highest level of detail. Most meshes will ignore the detail level.
- \param startFrameLoop: start frame
- \param endFrameLoop: end frame
- \return The animated mesh based on a detail level. */
- IMesh *getMesh(s32 frame, s32 detailLevel = 255, s32 startFrameLoop = -1, s32 endFrameLoop = -1) override
+ /** \param frame: Frame number as zero based index.
+ \return The animated mesh based for the given frame */
+ IMesh *getMesh(f32 frame) override
{
if (Meshes.empty())
- return 0;
+ return nullptr;
- return Meshes[frame];
+ return Meshes[static_cast(frame)];
}
//! adds a Mesh
@@ -161,7 +154,7 @@ struct SAnimatedMesh : public IAnimatedMesh
}
//! All meshes defining the animated mesh
- core::array Meshes;
+ std::vector Meshes;
//! The bounding box of this mesh
core::aabbox3d Box;
diff --git a/irr/include/SMaterial.h b/irr/include/SMaterial.h
index c803f5fde..8c0a51dfd 100644
--- a/irr/include/SMaterial.h
+++ b/irr/include/SMaterial.h
@@ -6,7 +6,6 @@
#include "SColor.h"
#include "matrix4.h"
-#include "irrArray.h"
#include "irrMath.h"
#include "EMaterialTypes.h"
#include "EMaterialProps.h"
@@ -195,29 +194,6 @@ enum E_ANTI_ALIASING_MODE
EAAM_ALPHA_TO_COVERAGE = 4
};
-//! These flags allow to define the interpretation of vertex color when lighting is enabled
-/** Without lighting being enabled the vertex color is the only value defining the fragment color.
-Once lighting is enabled, the four values for diffuse, ambient, emissive, and specular take over.
-With these flags it is possible to define which lighting factor shall be defined by the vertex color
-instead of the lighting factor which is the same for all faces of that material.
-The default is to use vertex color for the diffuse value, another pretty common value is to use
-vertex color for both diffuse and ambient factor. */
-enum E_COLOR_MATERIAL
-{
- //! Don't use vertex color for lighting
- ECM_NONE = 0,
- //! Use vertex color for diffuse light, this is default
- ECM_DIFFUSE,
- //! Use vertex color for ambient light
- ECM_AMBIENT,
- //! Use vertex color for emissive light
- ECM_EMISSIVE,
- //! Use vertex color for specular light
- ECM_SPECULAR,
- //! Use vertex color for both diffuse and ambient light
- ECM_DIFFUSE_AND_AMBIENT
-};
-
//! Names for polygon offset direction
const c8 *const PolygonOffsetDirectionNames[] = {
"Back",
@@ -263,16 +239,14 @@ class SMaterial
public:
//! Default constructor. Creates a solid, lit material with white colors
SMaterial() :
- MaterialType(EMT_SOLID), AmbientColor(255, 255, 255, 255),
- DiffuseColor(255, 255, 255, 255), EmissiveColor(0, 0, 0, 0),
- SpecularColor(255, 255, 255, 255), Shininess(0.0f),
+ MaterialType(EMT_SOLID), ColorParam(0, 0, 0, 0),
MaterialTypeParam(0.0f), Thickness(1.0f), ZBuffer(ECFN_LESSEQUAL),
- AntiAliasing(EAAM_SIMPLE), ColorMask(ECP_ALL), ColorMaterial(ECM_DIFFUSE),
+ AntiAliasing(EAAM_SIMPLE), ColorMask(ECP_ALL),
BlendOperation(EBO_NONE), BlendFactor(0.0f), PolygonOffsetDepthBias(0.f),
PolygonOffsetSlopeScale(0.f), Wireframe(false), PointCloud(false),
- GouraudShading(true), Lighting(true), ZWriteEnable(EZW_AUTO),
+ ZWriteEnable(EZW_AUTO),
BackfaceCulling(true), FrontfaceCulling(false), FogEnable(false),
- NormalizeNormals(false), UseMipMaps(true)
+ UseMipMaps(true)
{
}
@@ -282,42 +256,9 @@ public:
//! Type of the material. Specifies how everything is blended together
E_MATERIAL_TYPE MaterialType;
- //! How much ambient light (a global light) is reflected by this material.
- /** The default is full white, meaning objects are completely
- globally illuminated. Reduce this if you want to see diffuse
- or specular light effects. */
- SColor AmbientColor;
-
- //! How much diffuse light coming from a light source is reflected by this material.
- /** The default is full white. */
- SColor DiffuseColor;
-
- //! Light emitted by this material. Default is to emit no light.
- SColor EmissiveColor;
-
- //! How much specular light (highlights from a light) is reflected.
- /** The default is to reflect white specular light. See
- SMaterial::Shininess on how to enable specular lights. */
- SColor SpecularColor;
-
- //! Value affecting the size of specular highlights.
- /** A value of 20 is common. If set to 0, no specular
- highlights are being used. To activate, simply set the
- shininess of a material to a value in the range [0.5;128]:
- \code
- sceneNode->getMaterial(0).Shininess = 20.0f;
- \endcode
-
- You can change the color of the highlights using
- \code
- sceneNode->getMaterial(0).SpecularColor.set(255,255,255,255);
- \endcode
-
- The specular color of the dynamic lights
- (SLight::SpecularColor) will influence the the highlight color
- too, but they are set to a useful value by default when
- creating the light scene node.*/
- f32 Shininess;
+ //! Custom color parameter, can be used by custom shader materials.
+ // See MainShaderConstantSetter in Minetest.
+ SColor ColorParam;
//! Free parameter, dependent on the material type.
/** Mostly ignored, used for example in
@@ -345,14 +286,6 @@ public:
depth or stencil buffer, or using Red and Green for Stereo rendering. */
u8 ColorMask : 4;
- //! Defines the interpretation of vertex color in the lighting equation
- /** Values should be chosen from E_COLOR_MATERIAL.
- When lighting is enabled, vertex color can be used instead of the
- material values for light modulation. This allows to easily change e.g. the
- diffuse light behavior of each face. The default, ECM_DIFFUSE, will result in
- a very similar rendering as with lighting turned off, just with light shading. */
- u8 ColorMaterial : 3;
-
//! Store the blend operation of choice
/** Values to be chosen from E_BLEND_OPERATION. */
E_BLEND_OPERATION BlendOperation : 4;
@@ -393,12 +326,6 @@ public:
//! Draw as point cloud or filled triangles? Default: false
bool PointCloud : 1;
- //! Flat or Gouraud shading? Default: true
- bool GouraudShading : 1;
-
- //! Will this material be lighted? Default: true
- bool Lighting : 1;
-
//! Is the zbuffer writable or is it read-only. Default: EZW_AUTO.
/** If this parameter is not EZW_OFF, you probably also want to set ZBuffer
to values other than ECFN_DISABLED */
@@ -413,10 +340,6 @@ public:
//! Is fog enabled? Default: false
bool FogEnable : 1;
- //! Should normals be normalized?
- /** Always use this if the mesh lit and scaled. Default: false */
- bool NormalizeNormals : 1;
-
//! Shall mipmaps be used if available
/** Sometimes, disabling mipmap usage can be useful. Default: true */
bool UseMipMaps : 1;
@@ -487,26 +410,18 @@ public:
{
bool different =
MaterialType != b.MaterialType ||
- AmbientColor != b.AmbientColor ||
- DiffuseColor != b.DiffuseColor ||
- EmissiveColor != b.EmissiveColor ||
- SpecularColor != b.SpecularColor ||
- Shininess != b.Shininess ||
+ ColorParam != b.ColorParam ||
MaterialTypeParam != b.MaterialTypeParam ||
Thickness != b.Thickness ||
Wireframe != b.Wireframe ||
PointCloud != b.PointCloud ||
- GouraudShading != b.GouraudShading ||
- Lighting != b.Lighting ||
ZBuffer != b.ZBuffer ||
ZWriteEnable != b.ZWriteEnable ||
BackfaceCulling != b.BackfaceCulling ||
FrontfaceCulling != b.FrontfaceCulling ||
FogEnable != b.FogEnable ||
- NormalizeNormals != b.NormalizeNormals ||
AntiAliasing != b.AntiAliasing ||
ColorMask != b.ColorMask ||
- ColorMaterial != b.ColorMaterial ||
BlendOperation != b.BlendOperation ||
BlendFactor != b.BlendFactor ||
PolygonOffsetDepthBias != b.PolygonOffsetDepthBias ||
diff --git a/irr/include/SMesh.h b/irr/include/SMesh.h
index e865a5d2d..15fa65115 100644
--- a/irr/include/SMesh.h
+++ b/irr/include/SMesh.h
@@ -4,17 +4,17 @@
#pragma once
+#include
#include "IMesh.h"
#include "IMeshBuffer.h"
#include "aabbox3d.h"
-#include "irrArray.h"
namespace irr
{
namespace scene
{
//! Simple implementation of the IMesh interface.
-struct SMesh : public IMesh
+struct SMesh final : public IMesh
{
//! constructor
SMesh()
@@ -28,15 +28,15 @@ struct SMesh : public IMesh
virtual ~SMesh()
{
// drop buffers
- for (u32 i = 0; i < MeshBuffers.size(); ++i)
- MeshBuffers[i]->drop();
+ for (auto *buf : MeshBuffers)
+ buf->drop();
}
//! clean mesh
virtual void clear()
{
- for (u32 i = 0; i < MeshBuffers.size(); ++i)
- MeshBuffers[i]->drop();
+ for (auto *buf : MeshBuffers)
+ buf->drop();
MeshBuffers.clear();
BoundingBox.reset(0.f, 0.f, 0.f);
}
@@ -44,7 +44,7 @@ struct SMesh : public IMesh
//! returns amount of mesh buffers.
u32 getMeshBufferCount() const override
{
- return MeshBuffers.size();
+ return static_cast(MeshBuffers.size());
}
//! returns pointer to a mesh buffer
@@ -57,14 +57,24 @@ struct SMesh : public IMesh
/** reverse search */
IMeshBuffer *getMeshBuffer(const video::SMaterial &material) const override
{
- for (s32 i = (s32)MeshBuffers.size() - 1; i >= 0; --i) {
- if (material == MeshBuffers[i]->getMaterial())
- return MeshBuffers[i];
+ for (auto it = MeshBuffers.rbegin(); it != MeshBuffers.rend(); it++) {
+ if (material == (*it)->getMaterial())
+ return *it;
}
-
- return 0;
+ return nullptr;
}
+ u32 getTextureSlot(u32 meshbufNr) const override
+ {
+ return TextureSlots.at(meshbufNr);
+ }
+
+ void setTextureSlot(u32 meshbufNr, u32 textureSlot)
+ {
+ TextureSlots.at(meshbufNr) = textureSlot;
+ }
+
+
//! returns an axis aligned bounding box
const core::aabbox3d &getBoundingBox() const override
{
@@ -81,8 +91,8 @@ struct SMesh : public IMesh
void recalculateBoundingBox()
{
bool hasMeshBufferBBox = false;
- for (u32 i = 0; i < MeshBuffers.size(); ++i) {
- const core::aabbox3df &bb = MeshBuffers[i]->getBoundingBox();
+ for (auto *buf : MeshBuffers) {
+ const core::aabbox3df &bb = buf->getBoundingBox();
if (!bb.isEmpty()) {
if (!hasMeshBufferBBox) {
hasMeshBufferBBox = true;
@@ -104,25 +114,28 @@ struct SMesh : public IMesh
if (buf) {
buf->grab();
MeshBuffers.push_back(buf);
+ TextureSlots.push_back(getMeshBufferCount() - 1);
}
}
//! set the hardware mapping hint, for driver
void setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override
{
- for (u32 i = 0; i < MeshBuffers.size(); ++i)
- MeshBuffers[i]->setHardwareMappingHint(newMappingHint, buffer);
+ for (auto *buf : MeshBuffers)
+ buf->setHardwareMappingHint(newMappingHint, buffer);
}
//! flags the meshbuffer as changed, reloads hardware buffers
void setDirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) override
{
- for (u32 i = 0; i < MeshBuffers.size(); ++i)
- MeshBuffers[i]->setDirty(buffer);
+ for (auto *buf : MeshBuffers)
+ buf->setDirty(buffer);
}
//! The meshbuffers of this mesh
- core::array MeshBuffers;
+ std::vector MeshBuffers;
+ //! Mapping from meshbuffer number to bindable texture slot
+ std::vector TextureSlots;
//! The bounding box of this mesh
core::aabbox3d BoundingBox;
diff --git a/irr/include/SOverrideMaterial.h b/irr/include/SOverrideMaterial.h
index 6de6e6ebb..1ae324211 100644
--- a/irr/include/SOverrideMaterial.h
+++ b/irr/include/SOverrideMaterial.h
@@ -4,6 +4,7 @@
#pragma once
+#include
#include "SMaterial.h"
namespace irr
@@ -57,7 +58,7 @@ struct SOverrideMaterial
};
//! To overwrite SMaterial::MaterialType
- core::array MaterialTypes;
+ std::vector MaterialTypes;
//! Default constructor
SOverrideMaterial() :
@@ -83,9 +84,8 @@ struct SOverrideMaterial
void apply(SMaterial &material)
{
if (Enabled) {
- for (u32 i = 0; i < MaterialTypes.size(); ++i) {
- const SMaterialTypeReplacement &mtr = MaterialTypes[i];
- if (mtr.Original < 0 || (s32)mtr.Original == material.MaterialType)
+ for (const auto &mtr : MaterialTypes) {
+ if (mtr.Original < 0 || mtr.Original == (s32)material.MaterialType)
material.MaterialType = (E_MATERIAL_TYPE)mtr.Replacement;
}
for (u32 f = 0; f < 32; ++f) {
@@ -98,12 +98,6 @@ struct SOverrideMaterial
case EMP_POINTCLOUD:
material.PointCloud = Material.PointCloud;
break;
- case EMP_GOURAUD_SHADING:
- material.GouraudShading = Material.GouraudShading;
- break;
- case EMP_LIGHTING:
- material.Lighting = Material.Lighting;
- break;
case EMP_ZBUFFER:
material.ZBuffer = Material.ZBuffer;
break;
@@ -140,9 +134,6 @@ struct SOverrideMaterial
case EMP_FOG_ENABLE:
material.FogEnable = Material.FogEnable;
break;
- case EMP_NORMALIZE_NORMALS:
- material.NormalizeNormals = Material.NormalizeNormals;
- break;
case EMP_TEXTURE_WRAP:
for (u32 i = 0; i < MATERIAL_MAX_TEXTURES; ++i) {
if (EnableLayerProps[i]) {
@@ -158,9 +149,6 @@ struct SOverrideMaterial
case EMP_COLOR_MASK:
material.ColorMask = Material.ColorMask;
break;
- case EMP_COLOR_MATERIAL:
- material.ColorMaterial = Material.ColorMaterial;
- break;
case EMP_USE_MIP_MAPS:
material.UseMipMaps = Material.UseMipMaps;
break;
diff --git a/irr/include/SSkinMeshBuffer.h b/irr/include/SSkinMeshBuffer.h
index 5ced6057d..303207d93 100644
--- a/irr/include/SSkinMeshBuffer.h
+++ b/irr/include/SSkinMeshBuffer.h
@@ -5,7 +5,10 @@
#pragma once
#include "IMeshBuffer.h"
+#include "CVertexBuffer.h"
+#include "CIndexBuffer.h"
#include "S3DVertex.h"
+#include "irrArray.h"
namespace irr
{
@@ -13,19 +16,36 @@ namespace scene
{
//! A mesh buffer able to choose between S3DVertex2TCoords, S3DVertex and S3DVertexTangents at runtime
-struct SSkinMeshBuffer : public IMeshBuffer
+struct SSkinMeshBuffer final : public IMeshBuffer
{
//! Default constructor
SSkinMeshBuffer(video::E_VERTEX_TYPE vt = video::EVT_STANDARD) :
- ChangedID_Vertex(1), ChangedID_Index(1), VertexType(vt),
- PrimitiveType(EPT_TRIANGLES),
- MappingHint_Vertex(EHM_NEVER), MappingHint_Index(EHM_NEVER),
- HWBuffer(NULL),
+ VertexType(vt), PrimitiveType(EPT_TRIANGLES),
BoundingBoxNeedsRecalculated(true)
{
#ifdef _DEBUG
setDebugName("SSkinMeshBuffer");
#endif
+ Vertices_Tangents = new SVertexBufferTangents();
+ Vertices_2TCoords = new SVertexBufferLightMap();
+ Vertices_Standard = new SVertexBuffer();
+ Indices = new SIndexBuffer();
+ }
+
+ //! Constructor for standard vertices
+ SSkinMeshBuffer(std::vector &&vertices, std::vector &&indices) :
+ SSkinMeshBuffer()
+ {
+ Vertices_Standard->Data = std::move(vertices);
+ Indices->Data = std::move(indices);
+ }
+
+ ~SSkinMeshBuffer()
+ {
+ Vertices_Tangents->drop();
+ Vertices_2TCoords->drop();
+ Vertices_Standard->drop();
+ Indices->drop();
}
//! Get Material of this buffer.
@@ -40,83 +60,53 @@ struct SSkinMeshBuffer : public IMeshBuffer
return Material;
}
+ const scene::IVertexBuffer *getVertexBuffer() const override
+ {
+ switch (VertexType) {
+ case video::EVT_2TCOORDS:
+ return Vertices_2TCoords;
+ case video::EVT_TANGENTS:
+ return Vertices_Tangents;
+ default:
+ return Vertices_Standard;
+ }
+ }
+
+ scene::IVertexBuffer *getVertexBuffer() override
+ {
+ switch (VertexType) {
+ case video::EVT_2TCOORDS:
+ return Vertices_2TCoords;
+ case video::EVT_TANGENTS:
+ return Vertices_Tangents;
+ default:
+ return Vertices_Standard;
+ }
+ }
+
+ const scene::IIndexBuffer *getIndexBuffer() const override
+ {
+ return Indices;
+ }
+
+ scene::IIndexBuffer *getIndexBuffer() override
+ {
+ return Indices;
+ }
+
//! Get standard vertex at given index
virtual video::S3DVertex *getVertex(u32 index)
{
switch (VertexType) {
case video::EVT_2TCOORDS:
- return (video::S3DVertex *)&Vertices_2TCoords[index];
+ return &Vertices_2TCoords->Data[index];
case video::EVT_TANGENTS:
- return (video::S3DVertex *)&Vertices_Tangents[index];
+ return &Vertices_Tangents->Data[index];
default:
- return &Vertices_Standard[index];
+ return &Vertices_Standard->Data[index];
}
}
- //! Get pointer to vertex array
- const void *getVertices() const override
- {
- switch (VertexType) {
- case video::EVT_2TCOORDS:
- return Vertices_2TCoords.const_pointer();
- case video::EVT_TANGENTS:
- return Vertices_Tangents.const_pointer();
- default:
- return Vertices_Standard.const_pointer();
- }
- }
-
- //! Get pointer to vertex array
- void *getVertices() override
- {
- switch (VertexType) {
- case video::EVT_2TCOORDS:
- return Vertices_2TCoords.pointer();
- case video::EVT_TANGENTS:
- return Vertices_Tangents.pointer();
- default:
- return Vertices_Standard.pointer();
- }
- }
-
- //! Get vertex count
- u32 getVertexCount() const override
- {
- switch (VertexType) {
- case video::EVT_2TCOORDS:
- return Vertices_2TCoords.size();
- case video::EVT_TANGENTS:
- return Vertices_Tangents.size();
- default:
- return Vertices_Standard.size();
- }
- }
-
- //! Get type of index data which is stored in this meshbuffer.
- /** \return Index type of this buffer. */
- video::E_INDEX_TYPE getIndexType() const override
- {
- return video::EIT_16BIT;
- }
-
- //! Get pointer to index array
- const u16 *getIndices() const override
- {
- return Indices.const_pointer();
- }
-
- //! Get pointer to index array
- u16 *getIndices() override
- {
- return Indices.pointer();
- }
-
- //! Get index count
- u32 getIndexCount() const override
- {
- return Indices.size();
- }
-
//! Get bounding box
const core::aabbox3d &getBoundingBox() const override
{
@@ -129,6 +119,28 @@ struct SSkinMeshBuffer : public IMeshBuffer
BoundingBox = box;
}
+private:
+ template void recalculateBoundingBox(const CVertexBuffer *buf)
+ {
+ if (!buf->getCount()) {
+ BoundingBox.reset(0, 0, 0);
+ } else {
+ auto &vertices = buf->Data;
+ BoundingBox.reset(vertices[0].Pos);
+ for (size_t i = 1; i < vertices.size(); ++i)
+ BoundingBox.addInternalPoint(vertices[i].Pos);
+ }
+ }
+
+ template static void copyVertex(const T1 &src, T2 &dst)
+ {
+ dst.Pos = src.Pos;
+ dst.Normal = src.Normal;
+ dst.Color = src.Color;
+ dst.TCoords = src.TCoords;
+ }
+public:
+
//! Recalculate bounding box
void recalculateBoundingBox() override
{
@@ -139,57 +151,30 @@ struct SSkinMeshBuffer : public IMeshBuffer
switch (VertexType) {
case video::EVT_STANDARD: {
- if (Vertices_Standard.empty())
- BoundingBox.reset(0, 0, 0);
- else {
- BoundingBox.reset(Vertices_Standard[0].Pos);
- for (u32 i = 1; i < Vertices_Standard.size(); ++i)
- BoundingBox.addInternalPoint(Vertices_Standard[i].Pos);
- }
+ recalculateBoundingBox(Vertices_Standard);
break;
}
case video::EVT_2TCOORDS: {
- if (Vertices_2TCoords.empty())
- BoundingBox.reset(0, 0, 0);
- else {
- BoundingBox.reset(Vertices_2TCoords[0].Pos);
- for (u32 i = 1; i < Vertices_2TCoords.size(); ++i)
- BoundingBox.addInternalPoint(Vertices_2TCoords[i].Pos);
- }
+ recalculateBoundingBox(Vertices_2TCoords);
break;
}
case video::EVT_TANGENTS: {
- if (Vertices_Tangents.empty())
- BoundingBox.reset(0, 0, 0);
- else {
- BoundingBox.reset(Vertices_Tangents[0].Pos);
- for (u32 i = 1; i < Vertices_Tangents.size(); ++i)
- BoundingBox.addInternalPoint(Vertices_Tangents[i].Pos);
- }
+ recalculateBoundingBox(Vertices_Tangents);
break;
}
}
}
- //! Get vertex type
- video::E_VERTEX_TYPE getVertexType() const override
- {
- return VertexType;
- }
-
//! Convert to 2tcoords vertex type
void convertTo2TCoords()
{
if (VertexType == video::EVT_STANDARD) {
- for (u32 n = 0; n < Vertices_Standard.size(); ++n) {
- video::S3DVertex2TCoords Vertex;
- Vertex.Color = Vertices_Standard[n].Color;
- Vertex.Pos = Vertices_Standard[n].Pos;
- Vertex.Normal = Vertices_Standard[n].Normal;
- Vertex.TCoords = Vertices_Standard[n].TCoords;
- Vertices_2TCoords.push_back(Vertex);
+ video::S3DVertex2TCoords Vertex;
+ for (const auto &Vertex_Standard : Vertices_Standard->Data) {
+ copyVertex(Vertex_Standard, Vertex);
+ Vertices_2TCoords->Data.push_back(Vertex);
}
- Vertices_Standard.clear();
+ Vertices_Standard->Data.clear();
VertexType = video::EVT_2TCOORDS;
}
}
@@ -198,134 +183,28 @@ struct SSkinMeshBuffer : public IMeshBuffer
void convertToTangents()
{
if (VertexType == video::EVT_STANDARD) {
- for (u32 n = 0; n < Vertices_Standard.size(); ++n) {
- video::S3DVertexTangents Vertex;
- Vertex.Color = Vertices_Standard[n].Color;
- Vertex.Pos = Vertices_Standard[n].Pos;
- Vertex.Normal = Vertices_Standard[n].Normal;
- Vertex.TCoords = Vertices_Standard[n].TCoords;
- Vertices_Tangents.push_back(Vertex);
+ video::S3DVertexTangents Vertex;
+ for (const auto &Vertex_Standard : Vertices_Standard->Data) {
+ copyVertex(Vertex_Standard, Vertex);
+ Vertices_Tangents->Data.push_back(Vertex);
}
- Vertices_Standard.clear();
+ Vertices_Standard->Data.clear();
VertexType = video::EVT_TANGENTS;
} else if (VertexType == video::EVT_2TCOORDS) {
- for (u32 n = 0; n < Vertices_2TCoords.size(); ++n) {
- video::S3DVertexTangents Vertex;
- Vertex.Color = Vertices_2TCoords[n].Color;
- Vertex.Pos = Vertices_2TCoords[n].Pos;
- Vertex.Normal = Vertices_2TCoords[n].Normal;
- Vertex.TCoords = Vertices_2TCoords[n].TCoords;
- Vertices_Tangents.push_back(Vertex);
+ video::S3DVertexTangents Vertex;
+ for (const auto &Vertex_2TCoords : Vertices_2TCoords->Data) {
+ copyVertex(Vertex_2TCoords, Vertex);
+ Vertices_Tangents->Data.push_back(Vertex);
}
- Vertices_2TCoords.clear();
+ Vertices_2TCoords->Data.clear();
VertexType = video::EVT_TANGENTS;
}
}
- //! returns position of vertex i
- const core::vector3df &getPosition(u32 i) const override
- {
- switch (VertexType) {
- case video::EVT_2TCOORDS:
- return Vertices_2TCoords[i].Pos;
- case video::EVT_TANGENTS:
- return Vertices_Tangents[i].Pos;
- default:
- return Vertices_Standard[i].Pos;
- }
- }
-
- //! returns position of vertex i
- core::vector3df &getPosition(u32 i) override
- {
- switch (VertexType) {
- case video::EVT_2TCOORDS:
- return Vertices_2TCoords[i].Pos;
- case video::EVT_TANGENTS:
- return Vertices_Tangents[i].Pos;
- default:
- return Vertices_Standard[i].Pos;
- }
- }
-
- //! returns normal of vertex i
- const core::vector3df &getNormal(u32 i) const override
- {
- switch (VertexType) {
- case video::EVT_2TCOORDS:
- return Vertices_2TCoords[i].Normal;
- case video::EVT_TANGENTS:
- return Vertices_Tangents[i].Normal;
- default:
- return Vertices_Standard[i].Normal;
- }
- }
-
- //! returns normal of vertex i
- core::vector3df &getNormal(u32 i) override
- {
- switch (VertexType) {
- case video::EVT_2TCOORDS:
- return Vertices_2TCoords[i].Normal;
- case video::EVT_TANGENTS:
- return Vertices_Tangents[i].Normal;
- default:
- return Vertices_Standard[i].Normal;
- }
- }
-
- //! returns texture coords of vertex i
- const core::vector2df &getTCoords(u32 i) const override
- {
- switch (VertexType) {
- case video::EVT_2TCOORDS:
- return Vertices_2TCoords[i].TCoords;
- case video::EVT_TANGENTS:
- return Vertices_Tangents[i].TCoords;
- default:
- return Vertices_Standard[i].TCoords;
- }
- }
-
- //! returns texture coords of vertex i
- core::vector2df &getTCoords(u32 i) override
- {
- switch (VertexType) {
- case video::EVT_2TCOORDS:
- return Vertices_2TCoords[i].TCoords;
- case video::EVT_TANGENTS:
- return Vertices_Tangents[i].TCoords;
- default:
- return Vertices_Standard[i].TCoords;
- }
- }
-
//! append the vertices and indices to the current buffer
- void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) override {}
-
- //! get the current hardware mapping hint for vertex buffers
- E_HARDWARE_MAPPING getHardwareMappingHint_Vertex() const override
+ void append(const void *const vertices, u32 numVertices, const u16 *const indices, u32 numIndices) override
{
- return MappingHint_Vertex;
- }
-
- //! get the current hardware mapping hint for index buffers
- E_HARDWARE_MAPPING getHardwareMappingHint_Index() const override
- {
- return MappingHint_Index;
- }
-
- //! set the hardware mapping hint, for driver
- void setHardwareMappingHint(E_HARDWARE_MAPPING NewMappingHint, E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override
- {
- if (Buffer == EBT_VERTEX)
- MappingHint_Vertex = NewMappingHint;
- else if (Buffer == EBT_INDEX)
- MappingHint_Index = NewMappingHint;
- else if (Buffer == EBT_VERTEX_AND_INDEX) {
- MappingHint_Vertex = NewMappingHint;
- MappingHint_Index = NewMappingHint;
- }
+ _IRR_DEBUG_BREAK_IF(true);
}
//! Describe what kind of primitive geometry is used by the meshbuffer
@@ -340,41 +219,14 @@ struct SSkinMeshBuffer : public IMeshBuffer
return PrimitiveType;
}
- //! flags the mesh as changed, reloads hardware buffers
- void setDirty(E_BUFFER_TYPE Buffer = EBT_VERTEX_AND_INDEX) override
- {
- if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_VERTEX)
- ++ChangedID_Vertex;
- if (Buffer == EBT_VERTEX_AND_INDEX || Buffer == EBT_INDEX)
- ++ChangedID_Index;
- }
-
- u32 getChangedID_Vertex() const override { return ChangedID_Vertex; }
-
- u32 getChangedID_Index() const override { return ChangedID_Index; }
-
- void setHWBuffer(void *ptr) const override
- {
- HWBuffer = ptr;
- }
-
- void *getHWBuffer() const override
- {
- return HWBuffer;
- }
-
//! Call this after changing the positions of any vertex.
void boundingBoxNeedsRecalculated(void) { BoundingBoxNeedsRecalculated = true; }
- core::array Vertices_Tangents;
- core::array Vertices_2TCoords;
- core::array Vertices_Standard;
- core::array Indices;
+ SVertexBufferTangents *Vertices_Tangents;
+ SVertexBufferLightMap *Vertices_2TCoords;
+ SVertexBuffer *Vertices_Standard;
+ SIndexBuffer *Indices;
- u32 ChangedID_Vertex;
- u32 ChangedID_Index;
-
- // ISkinnedMesh::SJoint *AttachedJoint;
core::matrix4 Transformation;
video::SMaterial Material;
@@ -385,13 +237,7 @@ struct SSkinMeshBuffer : public IMeshBuffer
//! Primitive type used for rendering (triangles, lines, ...)
E_PRIMITIVE_TYPE PrimitiveType;
- // hardware mapping hint
- E_HARDWARE_MAPPING MappingHint_Vertex : 3;
- E_HARDWARE_MAPPING MappingHint_Index : 3;
-
- mutable void *HWBuffer;
-
- bool BoundingBoxNeedsRecalculated : 1;
+ bool BoundingBoxNeedsRecalculated;
};
} // end namespace scene
diff --git a/irr/include/SViewFrustum.h b/irr/include/SViewFrustum.h
index 06983cc5e..cd898e032 100644
--- a/irr/include/SViewFrustum.h
+++ b/irr/include/SViewFrustum.h
@@ -9,7 +9,7 @@
#include "line3d.h"
#include "aabbox3d.h"
#include "matrix4.h"
-#include "IVideoDriver.h"
+#include "EVideoTypes.h"
namespace irr
{
diff --git a/irr/include/coreutil.h b/irr/include/coreutil.h
index 60014c4a7..73d1c4b43 100644
--- a/irr/include/coreutil.h
+++ b/irr/include/coreutil.h
@@ -63,7 +63,7 @@ inline io::path &getFileNameExtension(io::path &dest, const io::path &source)
}
//! delete path from filename
-inline io::path &deletePathFromFilename(io::path &filename)
+inline io::path deletePathFromFilename(const io::path &filename)
{
// delete path from filename
const fschar_t *s = filename.c_str();
@@ -73,11 +73,10 @@ inline io::path &deletePathFromFilename(io::path &filename)
while (*p != '/' && *p != '\\' && p != s)
p--;
- if (p != s) {
+ if (p != s)
++p;
- filename = p;
- }
- return filename;
+
+ return p;
}
//! trim paths
diff --git a/irr/include/irrArray.h b/irr/include/irrArray.h
index b6f573a79..9f390e79b 100644
--- a/irr/include/irrArray.h
+++ b/irr/include/irrArray.h
@@ -45,6 +45,10 @@ public:
{
}
+ //! Move constructor
+ array(std::vector &&data) :
+ m_data(std::move(data)), is_sorted(false) {}
+
//! Reallocates the array, make it bigger or smaller.
/** \param new_size New size of array.
\param canShrink Specifies whether the array is reallocated even if
@@ -167,13 +171,6 @@ public:
return *this;
}
- array &operator=(std::vector &&other)
- {
- m_data = std::move(other);
- is_sorted = false;
- return *this;
- }
-
//! Equality operator
bool operator==(const array &other) const
{
@@ -400,16 +397,6 @@ public:
std::swap(is_sorted, other.is_sorted);
}
- //! Pull the contents of this array as a vector.
- // The array is left empty.
- std::vector steal()
- {
- std::vector ret = std::move(m_data);
- m_data.clear();
- is_sorted = true;
- return ret;
- }
-
typedef T value_type;
typedef u32 size_type;
diff --git a/irr/include/irrString.h b/irr/include/irrString.h
index a583c9e4b..76e0548d3 100644
--- a/irr/include/irrString.h
+++ b/irr/include/irrString.h
@@ -11,7 +11,6 @@
#include
#include
#include
-#include
/* HACK: import these string methods from MT's util/string.h */
extern std::wstring utf8_to_wide(std::string_view input);
@@ -65,6 +64,7 @@ static inline u32 locale_upper(u32 x)
template
class string
{
+ using stl_type = std::basic_string;
public:
typedef T char_type;
@@ -79,6 +79,10 @@ public:
*this = other;
}
+ string(const stl_type &str) : str(str) {}
+
+ string(stl_type &&str) : str(std::move(str)) {}
+
//! Constructor from other string types
template
string(const string &other)
@@ -169,13 +173,24 @@ public:
return *this;
}
- // no longer allowed!
- _IRR_DEBUG_BREAK_IF((void *)c == (void *)c_str());
+ if constexpr (sizeof(T) != sizeof(B)) {
+ _IRR_DEBUG_BREAK_IF(
+ (uintptr_t)c >= (uintptr_t)(str.data()) &&
+ (uintptr_t)c < (uintptr_t)(str.data() + str.size()));
+ }
+
+ if ((void *)c == (void *)c_str())
+ return *this;
u32 len = calclen(c);
- str.resize(len);
+ // In case `c` is a pointer to our own buffer, we may not resize first
+ // or it can become invalid.
+ if (len > str.size())
+ str.resize(len);
for (u32 l = 0; l < len; ++l)
- str[l] = (T)c[l];
+ str[l] = static_cast(c[l]);
+ if (len < str.size())
+ str.resize(len);
return *this;
}
@@ -814,13 +829,6 @@ public:
friend size_t wStringToUTF8(stringc &destination, const wchar_t *source);
private:
- typedef std::basic_string stl_type;
-
- //! Private constructor
- string(stl_type &&str) :
- str(str)
- {
- }
//! strlen wrapper
template
diff --git a/src/irr_ptr.h b/irr/include/irr_ptr.h
similarity index 87%
rename from src/irr_ptr.h
rename to irr/include/irr_ptr.h
index fc4a0f558..48717976b 100644
--- a/src/irr_ptr.h
+++ b/irr/include/irr_ptr.h
@@ -20,8 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include
#include
-#include "irrlichttypes.h"
-#include "IReferenceCounted.h"
+namespace irr { class IReferenceCounted; }
/** Shared pointer for IrrLicht objects.
*
@@ -37,15 +36,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
* from such object is a bug and may lead to a crash. Indirect construction
* is possible though; see the @c grab free function for details and use cases.
*/
-template ::value>::type>
+template
class irr_ptr
{
ReferenceCounted *value = nullptr;
public:
- irr_ptr() {}
+ irr_ptr() noexcept = default;
irr_ptr(std::nullptr_t) noexcept {}
@@ -53,15 +50,15 @@ public:
irr_ptr(irr_ptr &&b) noexcept { reset(b.release()); }
- template ::value>::type>
+ template , bool> = true>
irr_ptr(const irr_ptr &b) noexcept
{
grab(b.get());
}
- template ::value>::type>
+ template , bool> = true>
irr_ptr(irr_ptr &&b) noexcept
{
reset(b.release());
@@ -88,16 +85,16 @@ public:
return *this;
}
- template ::value>::type>
+ template , bool> = true>
irr_ptr &operator=(const irr_ptr &b) noexcept
{
grab(b.get());
return *this;
}
- template ::value>::type>
+ template , bool> = true>
irr_ptr &operator=(irr_ptr &&b) noexcept
{
reset(b.release());
@@ -128,6 +125,8 @@ public:
*/
void reset(ReferenceCounted *object = nullptr) noexcept
{
+ static_assert(std::is_base_of_v,
+ "Class is not an IReferenceCounted");
if (value)
value->drop();
value = object;
@@ -138,6 +137,8 @@ public:
*/
void grab(ReferenceCounted *object) noexcept
{
+ static_assert(std::is_base_of_v,
+ "Class is not an IReferenceCounted");
if (object)
object->grab();
reset(object);
@@ -152,6 +153,7 @@ public:
* in this function and decreased when the returned pointer is destroyed.
*/
template
+[[nodiscard]]
irr_ptr grab(ReferenceCounted *object) noexcept
{
irr_ptr ptr;
diff --git a/irr/include/matrix4.h b/irr/include/matrix4.h
index 374fc6e4a..8fce0157a 100644
--- a/irr/include/matrix4.h
+++ b/irr/include/matrix4.h
@@ -24,7 +24,12 @@ namespace core
{
//! 4x4 matrix. Mostly used as transformation matrix for 3d calculations.
-/** The matrix is a D3D style matrix, row major with translations in the 4th row. */
+/** Conventions: Matrices are considered to be in row-major order.
+ * Multiplication of a matrix A with a row vector v is the premultiplication vA.
+ * Translations are thus in the 4th row.
+ * The matrix product AB yields a matrix C such that vC = (vB)A:
+ * B is applied first, then A.
+ */
template
class CMatrix4
{
@@ -242,17 +247,11 @@ public:
//! Translate a vector by the inverse of the translation part of this matrix.
void inverseTranslateVect(vector3df &vect) const;
- //! Rotate a vector by the inverse of the rotation part of this matrix.
- void inverseRotateVect(vector3df &vect) const;
+ //! Scale a vector, then rotate by the inverse of the rotation part of this matrix.
+ [[nodiscard]] vector3d scaleThenInvRotVect(const vector3d &vect) const;
- //! Rotate a vector by the rotation part of this matrix.
- void rotateVect(vector3df &vect) const;
-
- //! An alternate transform vector method, writing into a second vector
- void rotateVect(core::vector3df &out, const core::vector3df &in) const;
-
- //! An alternate transform vector method, writing into an array of 3 floats
- void rotateVect(T *out, const core::vector3df &in) const;
+ //! Rotate and scale a vector. Applies both rotation & scale part of the matrix.
+ [[nodiscard]] vector3d rotateAndScaleVect(const vector3d &vect) const;
//! Transforms the vector by this matrix
/** This operation is performed as if the vector was 4d with the 4th component =1 */
@@ -1154,39 +1153,23 @@ inline bool CMatrix4::isIdentity_integer_base() const
}
template
-inline void CMatrix4::rotateVect(vector3df &vect) const
+inline vector3d CMatrix4::rotateAndScaleVect(const vector3d &v) const
{
- vector3d tmp(static_cast(vect.X), static_cast(vect.Y), static_cast(vect.Z));
- vect.X = static_cast(tmp.X * M[0] + tmp.Y * M[4] + tmp.Z * M[8]);
- vect.Y = static_cast(tmp.X * M[1] + tmp.Y * M[5] + tmp.Z * M[9]);
- vect.Z = static_cast(tmp.X * M[2] + tmp.Y * M[6] + tmp.Z * M[10]);
-}
-
-//! An alternate transform vector method, writing into a second vector
-template
-inline void CMatrix4::rotateVect(core::vector3df &out, const core::vector3df &in) const
-{
- out.X = in.X * M[0] + in.Y * M[4] + in.Z * M[8];
- out.Y = in.X * M[1] + in.Y * M[5] + in.Z * M[9];
- out.Z = in.X * M[2] + in.Y * M[6] + in.Z * M[10];
-}
-
-//! An alternate transform vector method, writing into an array of 3 floats
-template
-inline void CMatrix4::rotateVect(T *out, const core::vector3df &in) const
-{
- out[0] = in.X * M[0] + in.Y * M[4] + in.Z * M[8];
- out[1] = in.X * M[1] + in.Y * M[5] + in.Z * M[9];
- out[2] = in.X * M[2] + in.Y * M[6] + in.Z * M[10];
+ return {
+ v.X * M[0] + v.Y * M[4] + v.Z * M[8],
+ v.X * M[1] + v.Y * M[5] + v.Z * M[9],
+ v.X * M[2] + v.Y * M[6] + v.Z * M[10]
+ };
}
template
-inline void CMatrix4::inverseRotateVect(vector3df &vect) const
+inline vector3d CMatrix4::scaleThenInvRotVect(const vector3d &v) const
{
- vector3d tmp(static_cast(vect.X), static_cast(vect.Y), static_cast