Merge branch 'master' into doc-refactor-2

This commit is contained in:
Bradley Pierce 2024-02-27 16:57:07 -05:00
commit 027d9f4217
91 changed files with 1396 additions and 551 deletions

View File

@ -74,7 +74,7 @@ jobs:
env:
VCPKG_VERSION: 8eb57355a4ffb410a2e94c07b4dca2dffbee8e50
# 2023.10.19
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp opengl-registry sdl2
vcpkg_packages: zlib zstd curl[winssl] openal-soft libvorbis libogg libjpeg-turbo sqlite3 freetype luajit gmp jsoncpp sdl2
strategy:
fail-fast: false
matrix:

View File

@ -71,7 +71,6 @@ files["builtin/mainmenu"] = {
read_globals = {
"PLATFORM",
"TOUCHSCREEN_GUI",
},
}
@ -82,9 +81,3 @@ files["builtin/common/tests"] = {
"assert",
},
}
files["builtin/fstk"] = {
read_globals = {
"TOUCHSCREEN_GUI",
},
}

View File

@ -42,6 +42,18 @@ set(DEFAULT_ENABLE_LTO TRUE)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(DEFAULT_ENABLE_LTO FALSE)
endif()
#### LTO testing list ####
# - Linux: seems to work always
# - win32/msvc: works
# - win32/gcc: fails to link
# - win32/clang: works
# - macOS on x86: seems to be fine
# - macOS on ARM: crashes, see <https://github.com/minetest/minetest/issues/14397>
# Note: since CMake has no easy architecture detection disabling for Mac entirely
#### ####
if((WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR APPLE)
set(DEFAULT_ENABLE_LTO FALSE)
endif()
set(ENABLE_LTO ${DEFAULT_ENABLE_LTO} CACHE BOOL "Use Link Time Optimization")
set(DEFAULT_RUN_IN_PLACE FALSE)
@ -66,20 +78,6 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type: Debug or Release" FORCE)
endif()
# FIXME: Windows build fails in multiple places to link, needs to be investigated.
if (ENABLE_LTO AND NOT WIN32)
include(CheckIPOSupported)
check_ipo_supported(RESULT lto_supported OUTPUT lto_output)
if(lto_supported)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
message(STATUS "LTO/IPO is enabled")
else()
message(STATUS "LTO/IPO requested but it is not supported by the compiler: ${lto_output}")
endif()
else()
message(STATUS "LTO/IPO is not enabled")
endif()
set(ENABLE_UPDATE_CHECKER (NOT ${DEVELOPMENT_BUILD}) CACHE BOOL
"Whether to enable update checks by default")
@ -154,7 +152,7 @@ elseif(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt)
endif()
message(STATUS "Found IrrlichtMt ${IrrlichtMt_VERSION}")
set(TARGET_VER_S 1.9.0mt14)
set(TARGET_VER_S 1.9.0mt15)
string(REPLACE "mt" "." TARGET_VER ${TARGET_VER_S})
if(IrrlichtMt_VERSION VERSION_LESS ${TARGET_VER})
message(FATAL_ERROR "At least IrrlichtMt ${TARGET_VER_S} is required to build")
@ -163,6 +161,20 @@ elseif(BUILD_CLIENT AND TARGET IrrlichtMt::IrrlichtMt)
endif()
endif()
if (ENABLE_LTO OR CMAKE_INTERPROCEDURAL_OPTIMIZATION)
include(CheckIPOSupported)
check_ipo_supported(RESULT lto_supported OUTPUT lto_output)
if(lto_supported)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
message(STATUS "LTO/IPO is enabled")
else()
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION FALSE)
message(STATUS "LTO/IPO was requested but is not supported by the compiler: ${lto_output}")
endif()
else()
message(STATUS "LTO/IPO is not enabled")
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "${GCC_MINIMUM_VERSION}")
message(FATAL_ERROR "Insufficient gcc version, found ${CMAKE_CXX_COMPILER_VERSION}. "

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -148,6 +148,11 @@ invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false
[*Touchscreen]
# Enables touchscreen mode, allowing you to play the game with a touchscreen.
#
# Requires: !android
enable_touch (Enable touchscreen) bool true
# The length in pixels it takes for touchscreen interaction to start.
#
# Requires: touchscreen_gui
@ -2014,9 +2019,10 @@ ask_reconnect_on_crash (Ask to reconnect after crash) bool false
[**Server/Env Performance]
# Length of a server tick and the interval at which objects are generally updated over
# network, stated in seconds.
dedicated_server_step (Dedicated server step) float 0.09 0.0
# Length of a server tick (the interval at which everything is generally updated),
# stated in seconds.
# Does not apply to sessions hosted from the client menu.
dedicated_server_step (Dedicated server step) float 0.09 0.0 1.0
# Whether players are shown to clients without any range limit.
# Deprecated, use the setting player_transfer_distance instead.
@ -2086,8 +2092,7 @@ liquid_update (Liquid update tick) float 1.0 0.001
# At this distance the server will aggressively optimize which blocks are sent to
# clients.
# Small values potentially improve performance a lot, at the expense of visible
# rendering glitches (some blocks will not be rendered under water and in caves,
# as well as sometimes on land).
# rendering glitches (some blocks might not be rendered correctly in caves).
# Setting this to a value greater than max_block_send_distance disables this
# optimization.
# Stated in MapBlocks (16 nodes).

View File

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

View File

@ -6,7 +6,9 @@
Install dependencies with homebrew:
brew install cmake freetype gettext gmp hiredis jpeg jsoncpp leveldb libogg libpng libvorbis luajit zstd gettext
```bash
brew install cmake freetype gettext gmp hiredis jpeg-turbo jsoncpp leveldb libogg libpng libvorbis luajit zstd gettext
```
## Download
@ -43,6 +45,6 @@ codesign --force --deep -s - macos/minetest.app
## Run
```
```bash
open ./build/macos/minetest.app
```

View File

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

View File

@ -57,7 +57,8 @@ The game directory can contain the following files:
* `game.conf`, with the following keys:
* `title`: Required, a human-readable title to address the game, e.g. `title = Minetest Game`.
* `name`: (Deprecated) same as title.
* `description`: Short description to be shown in the content tab
* `description`: Short description to be shown in the content tab.
See [Translating content meta](#translating-content-meta).
* `allowed_mapgens = <comma-separated mapgens>`
e.g. `allowed_mapgens = v5,v6,flat`
Mapgens not in this list are removed from the list of mapgens for the
@ -83,10 +84,11 @@ The game directory can contain the following files:
`enable_damage`, `creative_mode`, `enable_server`.
* `map_persistent`: Specifies whether newly created worlds should use
a persistent map backend. Defaults to `true` (= "sqlite3")
* `author`: The author of the game. It only appears when downloaded from
ContentDB.
* `author`: The author's ContentDB
* `release`: Ignore this: Should only ever be set by ContentDB, as it is
an internal ID used to track versions.
* `textdomain`: Textdomain used to translate description. Defaults to game ID.
See [Translating content meta](#translating-content-meta).
* `minetest.conf`:
Used to set default settings when running this game.
* `settingtypes.txt`:
@ -148,8 +150,8 @@ The file is a key-value store of modpack details.
* `name`: The modpack name. Allows Minetest to determine the modpack name even
if the folder is wrongly named.
* `description`: Description of mod to be shown in the Mods tab of the main
menu.
* `title`: A human-readable title to address the modpack. See [Translating content meta](#translating-content-meta).
* `description`: Description of mod to be shown in the Mods tab of the main menu.
* `author`: The author of the modpack. It only appears when downloaded from
ContentDB.
* `release`: Ignore this: Should only ever be set by ContentDB, as it is an
@ -193,17 +195,19 @@ A `Settings` file that provides meta information about the mod.
* `name`: The mod name. Allows Minetest to determine the mod name even if the
folder is wrongly named.
* `title`: A human-readable title to address the mod. See
[Translating content meta](#translating-content-meta).
* `description`: Description of mod to be shown in the Mods tab of the main
menu.
menu. See [Translating content meta](#translating-content-meta).
* `depends`: A comma separated list of dependencies. These are mods that must be
loaded before this mod.
* `optional_depends`: A comma separated list of optional dependencies.
Like a dependency, but no error if the mod doesn't exist.
* `author`: The author of the mod. It only appears when downloaded from
ContentDB.
* `author`: The author's ContentDB username.
* `release`: Ignore this: Should only ever be set by ContentDB, as it is an
internal ID used to track versions.
* `title`: A human-readable title to address the mod.
* `textdomain`: Textdomain used to translate title and description. Defaults to modname.
See [Translating content meta](#translating-content-meta).
### `screenshot.png`
@ -4078,10 +4082,41 @@ markup, e.g. `minetest.get_translated_string("fr", S("Hello"))`.
The `lang_code` to use for a given player can be retrieved from
the table returned by `minetest.get_player_information(name)`.
> [!WARNING]
> This functionality should only be used for sorting, filtering or
similar purposes. You do not need to use this to get translated strings to show
up on the client.
> [!NOTE]
> This functionality should only be used for sorting, filtering
or similar purposes. You do not need to use this to get translated strings
to show up on the client.
## Translating Content Meta
You can translate content meta, such as `title` and `description`, by placing
translations in a `locale/DOMAIN.LANG.tr` file. The textdomain defaults to the
content name, but can be customised using `textdomain` in the content's .conf.
### Mods and Texture Packs
Say you have a mod called `mymod` with a short description in mod.conf:
description = This is the short description
Minetest will look for translations in the `mymod` textdomain as there's no
textdomain specified in mod.conf. For example, `mymod/locale/mymod.fr.tr`:
# textdomain:mymod
This is the short description=Voici la description succincte
### Games and Modpacks
For games and modpacks, Minetest will look for the textdomain in all mods.
Say you have a game called `mygame` with the following game.conf:
description = This is the game's short description
textdomain = mygame
Minetest will then look for the textdomain `mygame` in all mods, for example,
`mygame/mods/anymod/locale/mygame.fr.tr`. Note that it is still recommended that your
textdomain match the mod name, but this isn't required.
@ -5367,6 +5402,9 @@ Minetest includes the following settings to control behavior of privileges:
* `minetest.sha1(data, [raw])`: returns the sha1 hash of data
* `data`: string of data to hash
* `raw`: return raw bytes instead of hex digits, default: false
* `minetest.sha256(data, [raw])`: returns the sha256 hash of data
* `data`: string of data to hash
* `raw`: return raw bytes instead of hex digits, default: false
* `minetest.colorspec_to_colorstring(colorspec)`: Converts a ColorSpec to a
ColorString. If the ColorSpec is invalid, returns `nil`.
* `colorspec`: The ColorSpec to convert

View File

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

View File

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

View File

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

View File

@ -111,6 +111,13 @@ unittests.register("test_punch_node", function(_, pos)
-- currently failing: assert(on_punch_called)
end, {map=true})
local function test_hashing()
local input = "hello\000world"
assert(core.sha1(input) == "f85b420f1e43ebf88649dfcab302b898d889606c")
assert(core.sha256(input) == "b206899bc103669c8e7b36de29d73f95b46795b508aa87d612b2ce84bfb29df2")
end
unittests.register("test_hashing", test_hashing)
local function test_compress()
-- This text should be compressible, to make sure the results are... normal
local text = "The\000 icey canoe couldn't move very well on the\128 lake. The\000 ice was too stiff and the icey canoe's paddles simply wouldn't punch through."

View File

@ -1 +1 @@
1.9.0mt14
1.9.0mt15

View File

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

View File

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

View File

@ -1806,7 +1806,7 @@ struct TextureUpdateArgs {
void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progress)
{
TextureUpdateArgs* targs = (TextureUpdateArgs*) args;
u16 cur_percent = ceil(progress / (double) max_progress * 100.);
u16 cur_percent = std::ceil(progress / max_progress * 100.f);
// update the loading menu -- if necessary
bool do_draw = false;
@ -2123,3 +2123,11 @@ const std::string &Client::getFormspecPrepend() const
{
return m_env.getLocalPlayer()->formspec_prepend;
}
void Client::removeActiveObjectSounds(u16 id)
{
for (auto it : m_sounds_to_objects) {
if (it.second == id)
m_sound->stopSound(it.first);
}
}

View File

@ -471,6 +471,9 @@ private:
bool canSendChatMessage() const;
// remove sounds attached to object
void removeActiveObjectSounds(u16 id);
float m_packetcounter_timer = 0.0f;
float m_connection_reinit_timer = 0.1f;
float m_avg_rtt_timer = 0.0f;

View File

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

View File

@ -249,10 +249,10 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
m_rendering_engine->get_video_driver()->setTextureCreationFlag(
video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map"));
#ifdef HAVE_TOUCHSCREENGUI
receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver);
g_touchscreengui = receiver->m_touchscreengui;
#endif
if (g_settings->getBool("enable_touch")) {
receiver->m_touchscreengui = new TouchScreenGUI(m_rendering_engine->get_raw_device(), receiver);
g_touchscreengui = receiver->m_touchscreengui;
}
the_game(
kill,
@ -283,11 +283,11 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args)
m_rendering_engine->get_scene_manager()->clear();
#ifdef HAVE_TOUCHSCREENGUI
delete g_touchscreengui;
g_touchscreengui = NULL;
receiver->m_touchscreengui = NULL;
#endif
if (g_touchscreengui) {
delete g_touchscreengui;
g_touchscreengui = NULL;
receiver->m_touchscreengui = NULL;
}
/* Save the settings when leaving the game.
* This makes sure that setting changes made in-game are persisted even

View File

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

View File

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

View File

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

View File

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

View File

@ -664,11 +664,7 @@ public:
}
};
#ifdef HAVE_TOUCHSCREENGUI
#define SIZE_TAG "size[11,5.5]"
#else
#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
#endif
#define SIZE_TAG "size[11,5.5,true]" // Fixed size (ignored in touchscreen mode)
/****************************************************************************
****************************************************************************/
@ -1021,13 +1017,11 @@ private:
// this happens in pause menu in singleplayer
bool m_is_paused = false;
#ifdef HAVE_TOUCHSCREENGUI
bool m_cache_hold_aux1;
bool m_touch_simulate_aux1 = false;
bool m_touch_use_crosshair;
inline bool isNoCrosshairAllowed() {
inline bool isTouchCrosshairDisabled() {
return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST;
}
#endif
#ifdef __ANDROID__
bool m_android_chat_open;
#endif
@ -1075,11 +1069,6 @@ Game::Game() :
&settingChangedCallback, this);
readSettings();
#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = false; // This is initialised properly later
#endif
}
@ -1182,9 +1171,7 @@ bool Game::startup(bool *kill,
m_first_loop_after_window_activation = true;
#ifdef HAVE_TOUCHSCREENGUI
m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair");
#endif
g_client_translations->clear();
@ -1219,10 +1206,8 @@ void Game::run()
set_light_table(g_settings->getFloat("display_gamma"));
#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = g_settings->getBool("fast_move")
m_touch_simulate_aux1 = g_settings->getBool("fast_move")
&& client->checkPrivilege("fast");
#endif
const irr::core::dimension2du initial_screen_size(
g_settings->getU16("screen_w"),
@ -1308,9 +1293,8 @@ void Game::shutdown()
// Clear text when exiting.
m_game_ui->clearText();
#ifdef HAVE_TOUCHSCREENGUI
g_touchscreengui->hide();
#endif
if (g_touchscreengui)
g_touchscreengui->hide();
showOverlayMessage(N_("Shutting down..."), 0, 0, false);
@ -1520,11 +1504,10 @@ bool Game::createClient(const GameStartData &start_data)
if (client->modsLoaded())
client->getScript()->on_camera_ready(camera);
client->setCamera(camera);
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) {
g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
g_touchscreengui->setUseCrosshair(!isTouchCrosshairDisabled());
}
#endif
/* Clouds
*/
@ -1594,10 +1577,8 @@ bool Game::initGui()
gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
-1, chat_backend, client, &g_menumgr);
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->init(texture_src);
#endif
return true;
}
@ -2026,18 +2007,17 @@ void Game::processUserInput(f32 dtime)
} else {
input->clear();
}
#ifdef HAVE_TOUCHSCREENGUI
g_touchscreengui->hide();
#endif
if (g_touchscreengui)
g_touchscreengui->hide();
} else {
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) {
/* on touchscreengui step may generate own input events which ain't
* what we want in case we just did clear them */
g_touchscreengui->show();
g_touchscreengui->step(dtime);
}
#endif
m_game_focused = true;
}
@ -2229,13 +2209,11 @@ void Game::processItemSelection(u16 *new_playeritem)
}
}
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) {
std::optional<u16> selection = g_touchscreengui->getHotbarSelection();
if (selection)
*new_playeritem = *selection;
}
#endif
// Clamp selection again in case it wasn't changed but max_item was
*new_playeritem = MYMIN(*new_playeritem, max_item);
@ -2386,9 +2364,7 @@ void Game::toggleFast()
m_game_ui->showTranslatedStatusText("Fast mode disabled");
}
#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = fast_move && has_fast_privs;
#endif
m_touch_simulate_aux1 = fast_move && has_fast_privs;
}
@ -2633,10 +2609,8 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
Since Minetest has its own code to synthesize mouse events from touch events,
this results in duplicated input. To avoid that, we don't enable relative
mouse mode if we're in touchscreen mode. */
#ifndef HAVE_TOUCHSCREENGUI
if (cur_control)
cur_control->setRelativeMode(!isMenuActive());
#endif
cur_control->setRelativeMode(!g_touchscreengui && !isMenuActive());
if ((device->isWindowActive() && device->isWindowFocused()
&& !isMenuActive()) || input->isRandom()) {
@ -2674,17 +2648,15 @@ f32 Game::getSensitivityScaleFactor() const
// Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
// 16:9 aspect ratio to minimize disruption of existing sensitivity
// settings.
return tan(fov_y / 2.0f) * 1.3763818698f;
return std::tan(fov_y / 2.0f) * 1.3763819f;
}
void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
{
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui) {
cam->camera_yaw += g_touchscreengui->getYawChange();
cam->camera_pitch += g_touchscreengui->getPitchChange();
} else {
#endif
v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2);
v2s32 dist = input->getMousePos() - center;
@ -2698,9 +2670,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
if (dist.X != 0 || dist.Y != 0)
input->setMousePos(center.X, center.Y);
#ifdef HAVE_TOUCHSCREENGUI
}
#endif
if (m_cache_enable_joysticks) {
f32 sens_scale = getSensitivityScaleFactor();
@ -2741,20 +2711,18 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
client->activeObjectsReceived() && !player->isDead()) {
control.movement_speed = 1.0f;
// sideways movement only
float dx = sin(control.movement_direction);
control.movement_direction = atan2(dx, 1.0f);
float dx = std::sin(control.movement_direction);
control.movement_direction = std::atan2(dx, 1.0f);
}
#ifdef HAVE_TOUCHSCREENGUI
/* For touch, simulate holding down AUX1 (fast move) if the user has
* the fast_move setting toggled on. If there is an aux1 key defined for
* touch then its meaning is inverted (i.e. holding aux1 means walk and
* not fast)
*/
if (m_cache_hold_aux1) {
if (g_touchscreengui && m_touch_simulate_aux1) {
control.aux1 = control.aux1 ^ true;
}
#endif
client->setPlayerControl(control);
@ -2783,10 +2751,16 @@ inline void Game::step(f32 dtime)
g_settings->getFloat("fps_max_unfocused") :
g_settings->getFloat("fps_max");
fps_max = std::max(fps_max, 1.0f);
float steplen = 1.0f / fps_max;
/*
* Unless you have a barebones game, running the server at more than 60Hz
* is hardly realistic and you're at the point of diminishing returns.
* fps_max is also not necessarily anywhere near the FPS actually achieved
* (also due to vsync).
*/
fps_max = std::min(fps_max, 60.0f);
server->setStepSettings(Server::StepSettings{
steplen,
1.0f / fps_max,
m_is_paused
});
@ -3235,10 +3209,8 @@ void Game::updateCamera(f32 dtime)
camera->toggleCameraMode();
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed());
#endif
g_touchscreengui->setUseCrosshair(!isTouchCrosshairDisabled());
// Make the player visible depending on camera mode.
playercao->updateMeshCulling();
@ -3339,8 +3311,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
}
shootline.end = shootline.start + camera_direction * BS * d;
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui && isNoCrosshairAllowed()) {
if (g_touchscreengui && isTouchCrosshairDisabled()) {
shootline = g_touchscreengui->getShootline();
// Scale shootline to the acual distance the player can reach
shootline.end = shootline.start +
@ -3348,7 +3319,6 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
shootline.start += intToFloat(camera_offset, BS);
shootline.end += intToFloat(camera_offset, BS);
}
#endif
PointedThing pointed = updatePointedThing(shootline,
selected_def.liquids_pointable,
@ -3359,10 +3329,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
if (pointed != runData.pointed_old)
infostream << "Pointing at " << pointed.dump() << std::endl;
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed));
#endif
// Note that updating the selection mesh every frame is not particularly efficient,
// but the halo rendering code is already inefficient so there's no point in optimizing it here
@ -3740,11 +3708,11 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
break;
};
case NDT_SIGNLIKE: {
rotate90 = abs(pdir.X) < abs(pdir.Z);
rotate90 = std::abs(pdir.X) < std::abs(pdir.Z);
break;
}
default: {
rotate90 = abs(pdir.X) > abs(pdir.Z);
rotate90 = std::abs(pdir.X) > std::abs(pdir.Z);
break;
}
}
@ -4282,14 +4250,14 @@ void Game::updateShadows()
if (!shadow)
return;
float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f);
float in_timeofday = std::fmod(runData.time_of_day_smooth, 1.0f);
float timeoftheday = getWickedTimeOfDay(in_timeofday);
bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75;
bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible();
shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f);
timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
timeoftheday = std::fmod(timeoftheday + 0.75f, 0.5f) + 0.25f;
const float offset_constant = 10000.0f;
v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection();
@ -4345,10 +4313,10 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats)
bool draw_crosshair = (
(player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) &&
(this->camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT));
#ifdef HAVE_TOUCHSCREENGUI
if (this->isNoCrosshairAllowed())
if (g_touchscreengui && isTouchCrosshairDisabled())
draw_crosshair = false;
#endif
this->m_rendering_engine->draw_scene(sky_color, this->m_game_ui->m_flags.show_hud,
draw_wield_tool, draw_crosshair);
@ -4496,21 +4464,23 @@ void Game::showDeathFormspec()
#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
void Game::showPauseMenu()
{
#ifdef HAVE_TOUCHSCREENGUI
static const std::string control_text = strgettext("Controls:\n"
"No menu open:\n"
"- slide finger: look around\n"
"- tap: place/punch/use (default)\n"
"- long tap: dig/use (default)\n"
"Menu/inventory open:\n"
"- double tap (outside):\n"
" --> close\n"
"- touch stack, touch slot:\n"
" --> move stack\n"
"- touch&drag, tap 2nd finger\n"
" --> place single item to slot\n"
);
#endif
std::string control_text;
if (g_touchscreengui) {
control_text = strgettext("Controls:\n"
"No menu open:\n"
"- slide finger: look around\n"
"- tap: place/punch/use (default)\n"
"- long tap: dig/use (default)\n"
"Menu/inventory open:\n"
"- double tap (outside):\n"
" --> close\n"
"- touch stack, touch slot:\n"
" --> move stack\n"
"- touch&drag, tap 2nd finger\n"
" --> place single item to slot\n"
);
}
float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
std::ostringstream os;
@ -4540,9 +4510,9 @@ void Game::showPauseMenu()
<< strgettext("Exit to Menu") << "]";
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
<< strgettext("Exit to OS") << "]";
#ifdef HAVE_TOUCHSCREENGUI
if (!control_text.empty()) {
os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]";
#endif
}
os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n"
<< "\n"
<< strgettext("Game info:") << "\n";

View File

@ -39,10 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "wieldmesh.h"
#include "client/renderingengine.h"
#include "client/minimap.h"
#ifdef HAVE_TOUCHSCREENGUI
#include "gui/touchscreengui.h"
#endif
#define OBJECT_CROSSHAIR_LINE_SIZE 8
#define CROSSHAIR_LINE_SIZE 10
@ -292,10 +289,8 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
drawItem(mainlist->getItem(i), item_rect, (i + 1) == selectitem);
#ifdef HAVE_TOUCHSCREENGUI
if (is_hotbar && g_touchscreengui)
g_touchscreengui->registerHotbarRect(i, item_rect);
#endif
}
}
@ -749,10 +744,8 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
void Hud::drawHotbar(u16 playeritem)
{
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->resetHotbarRects();
#endif
InventoryList *mainlist = inventory->getList("main");
if (mainlist == NULL) {

View File

@ -71,14 +71,14 @@ static void imageCleanTransparentWithInlining(video::IImage *src, u32 threshold)
void *const src_data = src->getData();
const core::dimension2d<u32> dim = src->getDimension();
auto get_pixel = [src, src_data, dim](u32 x, u32 y) -> video::SColor {
auto get_pixel = [=](u32 x, u32 y) -> video::SColor {
if constexpr (IS_A8R8G8B8) {
return reinterpret_cast<u32 *>(src_data)[y*dim.Width + x];
} else {
return src->getPixel(x, y);
}
};
auto set_pixel = [src, src_data, dim](u32 x, u32 y, video::SColor color) {
auto set_pixel = [=](u32 x, u32 y, video::SColor color) {
if constexpr (IS_A8R8G8B8) {
u32 *dest = &reinterpret_cast<u32 *>(src_data)[y*dim.Width + x];
*dest = color.color;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -171,8 +171,8 @@ f32 ShadowRenderer::getMaxShadowFar() const
void ShadowRenderer::setShadowIntensity(float shadow_intensity)
{
m_shadow_strength = pow(shadow_intensity, 1.0f / m_shadow_strength_gamma);
if (m_shadow_strength > 1E-2)
m_shadow_strength = std::pow(shadow_intensity, 1.0f / m_shadow_strength_gamma);
if (m_shadow_strength > 1e-2f)
enable();
else
disable();

View File

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

View File

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

View File

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#ifndef SERVER
#include "settings.h"
#include "client/renderingengine.h"
#include "gui/touchscreengui.h"
#endif
@ -37,8 +38,8 @@ public:
bool equal(const ClientDynamicInfo &other) const {
return render_target_size == other.render_target_size &&
abs(real_gui_scaling - other.real_gui_scaling) < 0.001f &&
abs(real_hud_scaling - other.real_hud_scaling) < 0.001f &&
std::abs(real_gui_scaling - other.real_gui_scaling) < 0.001f &&
std::abs(real_hud_scaling - other.real_hud_scaling) < 0.001f &&
touch_controls == other.touch_controls;
}
@ -50,11 +51,7 @@ public:
f32 hud_scaling = g_settings->getFloat("hud_scaling", 0.5f, 20.0f);
f32 real_gui_scaling = gui_scaling * density;
f32 real_hud_scaling = hud_scaling * density;
#ifdef HAVE_TOUCHSCREENGUI
bool touch_controls = true;
#else
bool touch_controls = false;
#endif
bool touch_controls = g_touchscreengui;
return {
screen_size, real_gui_scaling, real_hud_scaling,
@ -67,12 +64,7 @@ public:
private:
#ifndef SERVER
static v2f32 calculateMaxFSSize(v2u32 render_target_size, f32 gui_scaling) {
f32 factor =
#ifdef HAVE_TOUCHSCREENGUI
10 / gui_scaling;
#else
15 / gui_scaling;
#endif
f32 factor = (g_settings->getBool("enable_touch") ? 10 : 15) / gui_scaling;
f32 ratio = (f32)render_target_size.X / (f32)render_target_size.Y;
if (ratio < 1)
return { factor, factor / ratio };

View File

@ -24,68 +24,59 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "filesys.h"
#include "settings.h"
enum ContentType
ContentType getContentType(const std::string &path)
{
ECT_UNKNOWN,
ECT_MOD,
ECT_MODPACK,
ECT_GAME,
ECT_TXP
};
ContentType getContentType(const ContentSpec &spec)
{
std::ifstream modpack_is((spec.path + DIR_DELIM + "modpack.txt").c_str());
std::ifstream modpack_is((path + DIR_DELIM + "modpack.txt").c_str());
if (modpack_is.good()) {
modpack_is.close();
return ECT_MODPACK;
return ContentType::MODPACK;
}
std::ifstream modpack2_is((spec.path + DIR_DELIM + "modpack.conf").c_str());
std::ifstream modpack2_is((path + DIR_DELIM + "modpack.conf").c_str());
if (modpack2_is.good()) {
modpack2_is.close();
return ECT_MODPACK;
return ContentType::MODPACK;
}
std::ifstream init_is((spec.path + DIR_DELIM + "init.lua").c_str());
std::ifstream init_is((path + DIR_DELIM + "init.lua").c_str());
if (init_is.good()) {
init_is.close();
return ECT_MOD;
return ContentType::MOD;
}
std::ifstream game_is((spec.path + DIR_DELIM + "game.conf").c_str());
std::ifstream game_is((path + DIR_DELIM + "game.conf").c_str());
if (game_is.good()) {
game_is.close();
return ECT_GAME;
return ContentType::GAME;
}
std::ifstream txp_is((spec.path + DIR_DELIM + "texture_pack.conf").c_str());
std::ifstream txp_is((path + DIR_DELIM + "texture_pack.conf").c_str());
if (txp_is.good()) {
txp_is.close();
return ECT_TXP;
return ContentType::TXP;
}
return ECT_UNKNOWN;
return ContentType::UNKNOWN;
}
void parseContentInfo(ContentSpec &spec)
{
std::string conf_path;
switch (getContentType(spec)) {
case ECT_MOD:
switch (getContentType(spec.path)) {
case ContentType::MOD:
spec.type = "mod";
conf_path = spec.path + DIR_DELIM + "mod.conf";
break;
case ECT_MODPACK:
case ContentType::MODPACK:
spec.type = "modpack";
conf_path = spec.path + DIR_DELIM + "modpack.conf";
break;
case ECT_GAME:
case ContentType::GAME:
spec.type = "game";
conf_path = spec.path + DIR_DELIM + "game.conf";
break;
case ECT_TXP:
case ContentType::TXP:
spec.type = "txp";
conf_path = spec.path + DIR_DELIM + "texture_pack.conf";
break;
@ -104,6 +95,15 @@ void parseContentInfo(ContentSpec &spec)
if (spec.type != "game" && conf.exists("name"))
spec.name = conf.get("name");
if (conf.exists("title"))
spec.title = conf.get("title");
if (spec.type == "game") {
if (spec.title.empty())
spec.title = spec.name;
spec.name = "";
}
if (conf.exists("description"))
spec.desc = conf.get("description");
@ -112,8 +112,17 @@ void parseContentInfo(ContentSpec &spec)
if (conf.exists("release"))
spec.release = conf.getS32("release");
if (conf.exists("textdomain"))
spec.textdomain = conf.get("textdomain");
}
if (spec.name.empty())
spec.name = fs::GetFilenameFromPath(spec.path.c_str());
if (spec.textdomain.empty())
spec.textdomain = spec.name;
if (spec.desc.empty()) {
std::ifstream is((spec.path + DIR_DELIM + "description.txt").c_str());
spec.desc = std::string((std::istreambuf_iterator<char>(is)),

View File

@ -22,6 +22,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "convert_json.h"
#include "irrlichttypes.h"
enum class ContentType
{
UNKNOWN,
MOD,
MODPACK,
GAME,
TXP
};
struct ContentSpec
{
std::string type;
@ -37,6 +47,9 @@ struct ContentSpec
/// Short description
std::string desc;
std::string path;
std::string textdomain;
};
ContentType getContentType(const std::string &path);
void parseContentInfo(ContentSpec &spec);

View File

@ -39,6 +39,11 @@ void set_default_settings()
// Client
settings->setDefault("address", "");
settings->setDefault("enable_sound", "true");
#if ENABLE_TOUCH
settings->setDefault("enable_touch", "true");
#else
settings->setDefault("enable_touch", "false");
#endif
settings->setDefault("sound_volume", "0.8");
settings->setDefault("sound_volume_unfocused", "0.3");
settings->setDefault("mute_sound", "false");
@ -90,7 +95,7 @@ void set_default_settings()
settings->setDefault("keymap_cmd_local", ".");
settings->setDefault("keymap_minimap", "KEY_KEY_V");
settings->setDefault("keymap_console", "KEY_F10");
#if HAVE_TOUCHSCREENGUI
#if ENABLE_TOUCH
// See https://github.com/minetest/minetest/issues/12792
settings->setDefault("keymap_rangeselect", "KEY_KEY_R");
#else
@ -192,7 +197,11 @@ void set_default_settings()
settings->setDefault("screen_h", "600");
settings->setDefault("window_maximized", "false");
settings->setDefault("autosave_screensize", "true");
#ifdef ENABLE_TOUCH
settings->setDefault("fullscreen", "true");
#else
settings->setDefault("fullscreen", "false");
#endif
settings->setDefault("vsync", "false");
settings->setDefault("fov", "72");
settings->setDefault("leaves_style", "fancy");
@ -298,7 +307,7 @@ void set_default_settings()
settings->setDefault("aux1_descends", "false");
settings->setDefault("doubletap_jump", "false");
settings->setDefault("always_fly_fast", "true");
#ifdef HAVE_TOUCHSCREENGUI
#ifdef ENABLE_TOUCH
settings->setDefault("autojump", "true");
#else
settings->setDefault("autojump", "false");
@ -477,12 +486,12 @@ void set_default_settings()
settings->setDefault("keymap_sneak", "KEY_SHIFT");
#endif
#ifdef HAVE_TOUCHSCREENGUI
settings->setDefault("touchscreen_threshold", "20");
settings->setDefault("touchscreen_sensitivity", "0.2");
settings->setDefault("touch_use_crosshair", "false");
settings->setDefault("fixed_virtual_joystick", "false");
settings->setDefault("virtual_joystick_triggers_aux1", "false");
#ifdef ENABLE_TOUCH
settings->setDefault("clickable_chat_weblinks", "false");
#else
settings->setDefault("clickable_chat_weblinks", "true");
@ -491,7 +500,6 @@ void set_default_settings()
#ifdef __ANDROID__
settings->setDefault("screen_w", "0");
settings->setDefault("screen_h", "0");
settings->setDefault("fullscreen", "true");
settings->setDefault("performance_tradeoffs", "true");
settings->setDefault("max_simultaneous_block_sends_per_client", "10");
settings->setDefault("emergequeue_limit_diskonly", "16");

View File

@ -1,8 +1,3 @@
set(extra_gui_SRCS "")
if(ENABLE_TOUCH)
set(extra_gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/touchscreengui.cpp)
endif()
set(gui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/guiAnimatedImage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiBackgroundImage.cpp
@ -29,6 +24,6 @@ set(gui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp
${CMAKE_CURRENT_SOURCE_DIR}/modalMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/profilergraph.cpp
${extra_gui_SRCS}
${CMAKE_CURRENT_SOURCE_DIR}/touchscreengui.cpp
PARENT_SCOPE
)

View File

@ -36,6 +36,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/guiscalingfilter.h"
#include "irrlicht_changes/static_text.h"
#include "client/tile.h"
#include "content/content.h"
#include "content/mods.h"
#if USE_SOUND
#include "client/sound/sound_openal.h"
@ -204,6 +206,57 @@ GUIEngine::GUIEngine(JoystickController *joystick,
m_menu.reset();
}
/******************************************************************************/
std::string findLocaleFileInMods(const std::string &path, const std::string &filename)
{
std::vector<ModSpec> mods = flattenMods(getModsInPath(path, "root", true));
for (const auto &mod : mods) {
std::string ret = mod.path + DIR_DELIM "locale" DIR_DELIM + filename;
if (fs::PathExists(ret)) {
return ret;
}
}
return "";
}
/******************************************************************************/
Translations *GUIEngine::getContentTranslations(const std::string &path,
const std::string &domain, const std::string &lang_code)
{
if (domain.empty() || lang_code.empty())
return nullptr;
std::string filename = domain + "." + lang_code + ".tr";
std::string key = path + DIR_DELIM "locale" DIR_DELIM + filename;
if (key == m_last_translations_key)
return &m_last_translations;
std::string trans_path = key;
ContentType type = getContentType(path);
if (type == ContentType::GAME)
trans_path = findLocaleFileInMods(path + DIR_DELIM "mods" DIR_DELIM, filename);
else if (type == ContentType::MODPACK)
trans_path = findLocaleFileInMods(path, filename);
// We don't need to search for locale files in a mod, as there's only one `locale` folder.
if (trans_path.empty())
return nullptr;
m_last_translations_key = key;
m_last_translations = {};
std::string data;
if (fs::ReadFile(trans_path, data)) {
m_last_translations.loadTranslation(data);
}
return &m_last_translations;
}
/******************************************************************************/
bool GUIEngine::loadMainMenuScript()
{

View File

@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/sound.h"
#include "client/tile.h"
#include "util/enriched_string.h"
#include "translation.h"
/******************************************************************************/
/* Structs and macros */
@ -165,7 +166,22 @@ public:
return m_scriptdir;
}
/**
* Get translations for content
*
* Only loads a single textdomain from the path, as specified by `domain`,
* for performance reasons.
*
* WARNING: Do not store the returned pointer for long as the contents may
* change with the next call to `getContentTranslations`.
* */
Translations *getContentTranslations(const std::string &path,
const std::string &domain, const std::string &lang_code);
private:
std::string m_last_translations_key;
/** Only the most recently used translation set is kept loaded */
Translations m_last_translations;
/** find and run the main menu script */
bool loadMainMenuScript();

View File

@ -316,13 +316,11 @@ void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element)
data->invsize.Y = MYMAX(0, stof(parts[1]));
lockSize(false);
#ifndef HAVE_TOUCHSCREENGUI
if (parts.size() == 3) {
if (!g_settings->getBool("enable_touch") && parts.size() == 3) {
if (parts[2] == "true") {
lockSize(true,v2u32(800,600));
}
}
#endif
data->explicit_size = true;
return;
}
@ -3284,14 +3282,15 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
s32 min_screen_dim = std::min(padded_screensize.X, padded_screensize.Y);
#ifdef HAVE_TOUCHSCREENGUI
// In Android, the preferred imgsize should be larger to accommodate the
// smaller screensize.
double prefer_imgsize = min_screen_dim / 10 * gui_scaling;
#else
// Desktop computers have more space, so try to fit 15 coordinates.
double prefer_imgsize = min_screen_dim / 15 * gui_scaling;
#endif
double prefer_imgsize;
if (g_settings->getBool("enable_touch")) {
// The preferred imgsize should be larger to accommodate the
// smaller screensize.
prefer_imgsize = min_screen_dim / 10 * gui_scaling;
} else {
// Desktop computers have more space, so try to fit 15 coordinates.
prefer_imgsize = min_screen_dim / 15 * gui_scaling;
}
// Try to use the preferred imgsize, but if that's bigger than the maximum
// size, use the maximum size.
use_imgsize = std::min(prefer_imgsize,

View File

@ -25,10 +25,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <IGUIStaticText.h>
#include <IGUIFont.h>
#ifdef HAVE_TOUCHSCREENGUI
#include "client/renderingengine.h"
#endif
#include "porting.h"
#include "gettext.h"
@ -66,11 +62,8 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize)
/*
Calculate new sizes and positions
*/
#ifdef HAVE_TOUCHSCREENGUI
const float s = m_gui_scale * RenderingEngine::getDisplayDensity() / 2;
#else
const float s = m_gui_scale;
#endif
DesiredRect = core::rect<s32>(
screensize.X / 2 - 580 * s / 2,
screensize.Y / 2 - 300 * s / 2,

View File

@ -27,10 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gettext.h"
#include "porting.h"
#include "settings.h"
#ifdef HAVE_TOUCHSCREENGUI
#include "touchscreengui.h"
#endif
GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent,
s32 id, IMenuManager *menumgr, bool remap_dbl_click) :
@ -44,11 +41,12 @@ GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent,
{
m_gui_scale = std::max(g_settings->getFloat("gui_scaling"), 0.5f);
const float screen_dpi_scale = RenderingEngine::getDisplayDensity();
#ifdef HAVE_TOUCHSCREENGUI
m_gui_scale *= 1.1f - 0.3f * screen_dpi_scale + 0.2f * screen_dpi_scale * screen_dpi_scale;
#else
m_gui_scale *= screen_dpi_scale;
#endif
if (g_settings->getBool("enable_touch")) {
m_gui_scale *= 1.1f - 0.3f * screen_dpi_scale + 0.2f * screen_dpi_scale * screen_dpi_scale;
} else {
m_gui_scale *= screen_dpi_scale;
}
setVisible(true);
m_menumgr->createdMenu(this);
@ -102,10 +100,8 @@ void GUIModalMenu::quitMenu()
Environment->removeFocus(this);
m_menumgr->deletingMenu(this);
this->remove();
#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->show();
#endif
}
static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent)

View File

@ -55,6 +55,7 @@ const std::string joystick_image_names[] = {
static EKEY_CODE id_to_keycode(touch_gui_button_id id)
{
EKEY_CODE code;
// ESC isn't part of the keymap.
if (id == exit_id)
return KEY_ESCAPE;
@ -110,7 +111,15 @@ static EKEY_CODE id_to_keycode(touch_gui_button_id id)
break;
}
assert(!key.empty());
return keyname_to_keycode(g_settings->get("keymap_" + key).c_str());
std::string resolved = g_settings->get("keymap_" + key);
try {
code = keyname_to_keycode(resolved.c_str());
} catch (UnknownKeycode &e) {
code = KEY_UNKNOWN;
warningstream << "TouchScreenGUI: Unknown key '" << resolved
<< "' for '" << key << "', hiding button." << std::endl;
}
return code;
}
static void load_button_texture(const button_info *btn, const std::string &path,
@ -523,13 +532,23 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc)
+ 0.5f * button_size),
AHBB_Dir_Right_Left, 3.0f);
m_settings_bar.addButton(fly_id, L"fly", "fly_btn.png");
m_settings_bar.addButton(noclip_id, L"noclip", "noclip_btn.png");
m_settings_bar.addButton(fast_id, L"fast", "fast_btn.png");
m_settings_bar.addButton(debug_id, L"debug", "debug_btn.png");
m_settings_bar.addButton(camera_id, L"camera", "camera_btn.png");
m_settings_bar.addButton(range_id, L"rangeview", "rangeview_btn.png");
m_settings_bar.addButton(minimap_id, L"minimap", "minimap_btn.png");
const static std::map<touch_gui_button_id, std::string> settings_bar_buttons {
{fly_id, "fly"},
{noclip_id, "noclip"},
{fast_id, "fast"},
{debug_id, "debug"},
{camera_id, "camera"},
{range_id, "rangeview"},
{minimap_id, "minimap"},
};
for (const auto &pair : settings_bar_buttons) {
if (id_to_keycode(pair.first) == KEY_UNKNOWN)
continue;
std::wstring wide = utf8_to_wide(pair.second);
m_settings_bar.addButton(pair.first, wide.c_str(),
pair.second + "_btn.png");
}
// Chat is shown by default, so chat_hide_btn.png is shown first.
m_settings_bar.addToggleButton(toggle_chat_id, L"togglechat",
@ -545,10 +564,20 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc)
+ 0.5f * button_size),
AHBB_Dir_Left_Right, 2.0f);
m_rare_controls_bar.addButton(chat_id, L"chat", "chat_btn.png");
m_rare_controls_bar.addButton(inventory_id, L"inv", "inventory_btn.png");
m_rare_controls_bar.addButton(drop_id, L"drop", "drop_btn.png");
m_rare_controls_bar.addButton(exit_id, L"exit", "exit_btn.png");
const static std::map<touch_gui_button_id, std::string> rare_controls_bar_buttons {
{chat_id, "chat"},
{inventory_id, "inventory"},
{drop_id, "drop"},
{exit_id, "exit"},
};
for (const auto &pair : rare_controls_bar_buttons) {
if (id_to_keycode(pair.first) == KEY_UNKNOWN)
continue;
std::wstring wide = utf8_to_wide(pair.second);
m_rare_controls_bar.addButton(pair.first, wide.c_str(),
pair.second + "_btn.png");
}
m_initialized = true;
}
@ -687,12 +716,9 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id)
<< evt_id << std::endl;
}
for (auto iter = m_known_ids.begin(); iter != m_known_ids.end(); ++iter) {
if (iter->id == evt_id) {
m_known_ids.erase(iter);
break;
}
}
// By the way: Android reuses pointer IDs, so m_pointer_pos[evt_id]
// will be overwritten soon anyway.
m_pointer_pos.erase(evt_id);
}
void TouchScreenGUI::translateEvent(const SEvent &event)
@ -719,17 +745,6 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
const v2s32 dir_fixed = touch_pos - fixed_joystick_center;
if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
/*
* Add to own copy of event list...
* android would provide this information but Irrlicht guys don't
* wanna design an efficient interface
*/
id_status to_be_added{};
to_be_added.id = event.TouchInput.ID;
to_be_added.X = event.TouchInput.X;
to_be_added.Y = event.TouchInput.Y;
m_known_ids.push_back(to_be_added);
size_t eventID = event.TouchInput.ID;
touch_gui_button_id button = getButtonID(X, Y);
@ -786,6 +801,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
m_move_id = event.TouchInput.ID;
m_move_has_really_moved = false;
m_move_downtime = porting::getTimeMs();
m_move_pos = touch_pos;
// DON'T reset m_tap_state here, otherwise many short taps
// will be ignored if you tap very fast.
}
@ -804,8 +820,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
m_pointer_pos[event.TouchInput.ID] == touch_pos)
return;
const v2s32 free_joystick_center = v2s32(m_pointer_pos[event.TouchInput.ID].X,
m_pointer_pos[event.TouchInput.ID].Y);
const v2s32 free_joystick_center = m_pointer_pos[event.TouchInput.ID];
const v2s32 dir_free = touch_pos - free_joystick_center;
const double touch_threshold_sq = m_touchscreen_threshold * m_touchscreen_threshold;
@ -814,6 +829,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
if (dir_free.getLengthSQ() > touch_threshold_sq || m_move_has_really_moved) {
m_move_has_really_moved = true;
m_move_pos = touch_pos;
m_pointer_pos[event.TouchInput.ID] = touch_pos;
if (m_tap_state == TapState::None || m_draw_crosshair) {
@ -994,7 +1010,9 @@ void TouchScreenGUI::step(float dtime)
// thus the camera position can change, it doesn't suffice to update the
// shootline when a touch event occurs.
// Note that the shootline isn't used if touch_use_crosshair is enabled.
if (!m_draw_crosshair) {
// Only updating when m_has_move_id means that the shootline will stay at
// it's last in-world position when the player doesn't need it.
if (!m_draw_crosshair && m_has_move_id) {
v2s32 pointer_pos = getPointerPos();
m_shootline = m_device
->getSceneManager()
@ -1032,8 +1050,8 @@ void TouchScreenGUI::setVisible(bool visible)
// clear all active buttons
if (!visible) {
while (!m_known_ids.empty())
handleReleaseEvent(m_known_ids.begin()->id);
while (!m_pointer_pos.empty())
handleReleaseEvent(m_pointer_pos.begin()->first);
m_settings_bar.hide();
m_rare_controls_bar.hide();
@ -1063,7 +1081,9 @@ v2s32 TouchScreenGUI::getPointerPos()
{
if (m_draw_crosshair)
return v2s32(m_screensize.X / 2, m_screensize.Y / 2);
return m_pointer_pos[m_move_id];
// We can't just use m_pointer_pos[m_move_id] because applyContextControls
// may emit release events after m_pointer_pos[m_move_id] is erased.
return m_move_pos;
}
void TouchScreenGUI::emitMouseEvent(EMOUSE_INPUT_EVENT type)

View File

@ -246,6 +246,8 @@ private:
size_t m_move_id;
bool m_move_has_really_moved = false;
u64 m_move_downtime = 0;
// m_move_pos stays valid even after m_move_id has been released.
v2s32 m_move_pos;
bool m_has_joystick_id = false;
size_t m_joystick_id;
@ -281,16 +283,6 @@ private:
const rect<s32> &button_rect, int texture_id,
bool visible = true);
struct id_status
{
size_t id;
int X;
int Y;
};
// vector to store known ids and their initial touch positions
std::vector<id_status> m_known_ids;
// handle a button event
void handleButtonEvent(touch_gui_button_id bID, size_t eventID, bool action);
@ -303,7 +295,7 @@ private:
// apply joystick status
void applyJoystickStatus();
// array for saving last known position of a pointer
// map to store the IDs and positions of currently pressed pointers
std::unordered_map<size_t, v2s32> m_pointer_pos;
// settings bar

View File

@ -50,9 +50,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gui/guiEngine.h"
#include "gui/mainmenumanager.h"
#endif
#ifdef HAVE_TOUCHSCREENGUI
#include "gui/touchscreengui.h"
#endif
// for version information only
extern "C" {

View File

@ -228,12 +228,12 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
std::vector<std::pair<v3s16, MapNode> > oldnodes;
oldnodes.emplace_back(p, oldnode);
voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks);
for (auto &modified_block : modified_blocks) {
modified_block.second->expireDayNightDiff();
}
}
if (n.getContent() != oldnode.getContent() &&
(oldnode.getContent() == CONTENT_AIR || n.getContent() == CONTENT_AIR))
block->expireIsAirCache();
// Report for rollback
if(m_gamedef->rollback())
{
@ -1462,14 +1462,14 @@ void ServerMap::finishBlockMake(BlockMakeData *data,
if (!block)
continue;
/*
Update day/night difference cache of the MapBlocks
Update is air cache of the MapBlocks
*/
block->expireDayNightDiff();
block->expireIsAirCache();
/*
Set block as modified
*/
block->raiseModified(MOD_STATE_WRITE_NEEDED,
MOD_REASON_EXPIRE_DAYNIGHTDIFF);
MOD_REASON_EXPIRE_IS_AIR);
}
/*
@ -2064,6 +2064,7 @@ void MMVManip::blitBackAll(std::map<v3s16, MapBlock*> *modified_blocks,
block->copyFrom(*this);
block->raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_VMANIP);
block->expireIsAirCache();
if(modified_blocks)
(*modified_blocks)[p] = block;

View File

@ -186,57 +186,27 @@ void MapBlock::copyFrom(VoxelManipulator &dst)
getPosRelative(), data_size);
}
void MapBlock::actuallyUpdateDayNightDiff()
void MapBlock::actuallyUpdateIsAir()
{
const NodeDefManager *nodemgr = m_gamedef->ndef();
// Running this function un-expires m_is_air
m_is_air_expired = false;
// Running this function un-expires m_day_night_differs
m_day_night_differs_expired = false;
bool differs = false;
/*
Check if any lighting value differs
*/
MapNode previous_n(CONTENT_IGNORE);
bool only_air = true;
for (u32 i = 0; i < nodecount; i++) {
MapNode n = data[i];
// If node is identical to previous node, don't verify if it differs
if (n == previous_n)
continue;
differs = !n.isLightDayNightEq(nodemgr->getLightingFlags(n));
if (differs)
MapNode &n = data[i];
if (n.getContent() != CONTENT_AIR) {
only_air = false;
break;
previous_n = n;
}
/*
If some lighting values differ, check if the whole thing is
just air. If it is just air, differs = false
*/
if (differs) {
bool only_air = true;
for (u32 i = 0; i < nodecount; i++) {
MapNode &n = data[i];
if (n.getContent() != CONTENT_AIR) {
only_air = false;
break;
}
}
if (only_air)
differs = false;
}
// Set member variable
m_day_night_differs = differs;
m_is_air = only_air;
}
void MapBlock::expireDayNightDiff()
void MapBlock::expireIsAirCache()
{
m_day_night_differs_expired = true;
m_is_air_expired = true;
}
/*
@ -369,7 +339,12 @@ void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int
u8 flags = 0;
if(is_underground)
flags |= 0x01;
if(getDayNightDiff())
// This flag used to be day-night-differs, and it is no longer used.
// We write it anyway so that old servers can still use this.
// Above ground isAir implies !day-night-differs, !isAir is good enough for old servers
// to check whether above ground blocks should be sent.
// See RemoteClient::getNextBlocks(...)
if(!isAir())
flags |= 0x02;
if (!m_generated)
flags |= 0x08;
@ -473,7 +448,7 @@ void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
TRACESTREAM(<<"MapBlock::deSerialize "<<getPos()<<std::endl);
m_day_night_differs_expired = false;
m_is_air_expired = true;
if(version <= 21)
{
@ -489,7 +464,9 @@ void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
u8 flags = readU8(is);
is_underground = (flags & 0x01) != 0;
m_day_night_differs = (flags & 0x02) != 0;
// IMPORTANT: when the version is bumped to 30 we can read m_is_air from here
// m_is_air = (flags & 0x02) == 0;
if (version < 27)
m_lighting_complete = 0xFFFF;
else
@ -599,6 +576,10 @@ void MapBlock::deSerialize(std::istream &in_compressed, u8 version, bool disk)
<<": Node timers (ver>=25)"<<std::endl);
m_node_timers.deSerialize(is, version);
}
u16 dummy;
m_is_air = nimap.size() == 1 && nimap.getId("air", dummy);
m_is_air_expired = false;
}
TRACESTREAM(<<"MapBlock::deSerialize "<<getPos()
@ -647,7 +628,7 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
{
// Initialize default flags
is_underground = false;
m_day_night_differs = false;
m_is_air = false;
m_lighting_complete = 0xFFFF;
m_generated = true;
@ -713,7 +694,6 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
u8 flags;
is.read((char*)&flags, 1);
is_underground = (flags & 0x01) != 0;
m_day_night_differs = (flags & 0x02) != 0;
if (version >= 18)
m_generated = (flags & 0x08) == 0;
@ -798,9 +778,13 @@ void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
// If supported, read node definition id mapping
if (version >= 21) {
nimap.deSerialize(is);
u16 dummy;
m_is_air = nimap.size() == 1 && nimap.getId("air", dummy);
// Else set the legacy mapping
} else {
content_mapnode_get_name_id_mapping(&nimap);
m_is_air = false;
m_is_air_expired = true;
}
correctBlockNodeIds(&nimap, data, m_gamedef);
}

View File

@ -60,7 +60,7 @@ enum ModReason : u32 {
MOD_REASON_STATIC_DATA_ADDED = 1 << 13,
MOD_REASON_STATIC_DATA_REMOVED = 1 << 14,
MOD_REASON_STATIC_DATA_CHANGED = 1 << 15,
MOD_REASON_EXPIRE_DAYNIGHTDIFF = 1 << 16,
MOD_REASON_EXPIRE_IS_AIR = 1 << 16,
MOD_REASON_VMANIP = 1 << 17,
MOD_REASON_UNKNOWN = 1 << 18,
};
@ -310,20 +310,19 @@ public:
// Copies data from VoxelManipulator getPosRelative()
void copyFrom(VoxelManipulator &dst);
// Update day-night lighting difference flag.
// Sets m_day_night_differs to appropriate value.
// These methods don't care about neighboring blocks.
void actuallyUpdateDayNightDiff();
// Update is air flag.
// Sets m_is_air to appropriate value.
void actuallyUpdateIsAir();
// Call this to schedule what the previous function does to be done
// when the value is actually needed.
void expireDayNightDiff();
void expireIsAirCache();
inline bool getDayNightDiff()
inline bool isAir()
{
if (m_day_night_differs_expired)
actuallyUpdateDayNightDiff();
return m_day_night_differs;
if (m_is_air_expired)
actuallyUpdateIsAir();
return m_is_air;
}
bool onObjectsActivation();
@ -517,8 +516,8 @@ public:
private:
// Whether day and night lighting differs
bool m_day_night_differs = false;
bool m_day_night_differs_expired = true;
bool m_is_air = false;
bool m_is_air_expired = true;
/*
- On the server, this is used for telling whether the

View File

@ -471,6 +471,7 @@ void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt)
for (u16 i = 0; i < removed_count; i++) {
*pkt >> id;
m_env.removeActiveObject(id);
removeActiveObjectSounds(id);
}
// Read added objects

View File

@ -615,7 +615,7 @@ class Peer {
u64 m_last_timeout_check;
};
class UDPPeer : public Peer
class UDPPeer final : public Peer
{
public:
@ -628,15 +628,15 @@ public:
virtual ~UDPPeer() = default;
void PutReliableSendCommand(ConnectionCommandPtr &c,
unsigned int max_packet_size);
unsigned int max_packet_size) override;
bool getAddress(MTProtocols type, Address& toset);
bool getAddress(MTProtocols type, Address& toset) override;
u16 getNextSplitSequenceNumber(u8 channel);
void setNextSplitSequenceNumber(u8 channel, u16 seqnum);
u16 getNextSplitSequenceNumber(u8 channel) override;
void setNextSplitSequenceNumber(u8 channel, u16 seqnum) override;
SharedBuffer<u8> addSplitPacket(u8 channel, BufferedPacketPtr &toadd,
bool reliable);
bool reliable) override;
bool isTimedOut(float timeout, std::string &reason) override;
@ -645,7 +645,7 @@ protected:
Calculates avg_rtt and resend_timeout.
rtt=-1 only recalculates resend_timeout
*/
void reportRTT(float rtt);
void reportRTT(float rtt) override;
void RunCommandQueues(
unsigned int max_packet_size,
@ -657,7 +657,7 @@ protected:
void setResendTimeout(float timeout)
{ MutexAutoLock lock(m_exclusive_access_mutex); resend_timeout = timeout; }
bool Ping(float dtime,SharedBuffer<u8>& data);
bool Ping(float dtime, SharedBuffer<u8>& data) override;
Channel channels[CHANNEL_COUNT];
bool m_pending_disconnect = false;

View File

@ -46,9 +46,9 @@ T RangedParameter<T>::pickWithin() const
auto p = numericAbsolute(bias) + 1;
for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); ++i) {
if (bias < 0)
values[i] = 1.0f - pow(myrand_float(), p);
values[i] = 1.0f - std::pow(myrand_float(), p);
else
values[i] = pow(myrand_float(), p);
values[i] = std::pow(myrand_float(), p);
}
return T::pick(values, min, max);
}

View File

@ -191,7 +191,7 @@ u32 PlayerControl::getKeysPressed() const
float abs_d;
// (absolute value indicates forward / backward)
abs_d = abs(movement_direction);
abs_d = std::abs(movement_direction);
if (abs_d < 3.0f / 8.0f * M_PI)
keypress_bits |= (u32)1; // Forward
if (abs_d > 5.0f / 8.0f * M_PI)
@ -201,7 +201,7 @@ u32 PlayerControl::getKeysPressed() const
abs_d = movement_direction + M_PI_2;
if (abs_d >= M_PI)
abs_d -= 2 * M_PI;
abs_d = abs(abs_d);
abs_d = std::abs(abs_d);
// (value now indicates left / right)
if (abs_d < 3.0f / 8.0f * M_PI)
keypress_bits |= (u32)1 << 2; // Left

View File

@ -630,7 +630,7 @@ void script_dump_packed(const PackedValue *val)
printf("table(%d, %d)", i.uidata1, i.uidata2);
break;
case LUA_TFUNCTION:
printf("function(%lu byte)", i.sdata.size());
printf("function(%d bytes)", (int)i.sdata.size());
break;
case LUA_TUSERDATA:
printf("userdata %s %p", i.sdata.c_str(), i.ptrdata);

View File

@ -153,13 +153,6 @@ ScriptApiBase::ScriptApiBase(ScriptingType type):
lua_pushstring(m_luastack, porting::getPlatformName());
lua_setglobal(m_luastack, "PLATFORM");
#ifdef HAVE_TOUCHSCREENGUI
lua_pushboolean(m_luastack, true);
#else
lua_pushboolean(m_luastack, false);
#endif
lua_setglobal(m_luastack, "TOUCHSCREEN_GUI");
// Make sure Lua uses the right locale
setlocale(LC_NUMERIC, "C");
}

View File

@ -363,6 +363,9 @@ int ModApiMainMenu::l_get_content_info(lua_State *L)
lua_pushstring(L, spec.name.c_str());
lua_setfield(L, -2, "name");
lua_pushstring(L, spec.title.c_str());
lua_setfield(L, -2, "title");
lua_pushstring(L, spec.type.c_str());
lua_setfield(L, -2, "type");
@ -383,6 +386,9 @@ int ModApiMainMenu::l_get_content_info(lua_State *L)
lua_pushstring(L, spec.path.c_str());
lua_setfield(L, -2, "path");
lua_pushstring(L, spec.textdomain.c_str());
lua_setfield(L, -2, "textdomain");
if (spec.type == "mod") {
ModSpec spec;
spec.path = path;
@ -432,8 +438,7 @@ int ModApiMainMenu::l_check_mod_configuration(lua_State *L)
// Ignore non-string keys
if (lua_type(L, -2) != LUA_TSTRING) {
throw LuaError(
"Unexpected non-string key in table passed to "
"core.check_mod_configuration");
"Unexpected non-string key in table passed to core.check_mod_configuration");
}
std::string modpath = luaL_checkstring(L, -1);
@ -472,7 +477,6 @@ int ModApiMainMenu::l_check_mod_configuration(lua_State *L)
return 1;
}
lua_newtable(L);
lua_pushboolean(L, modmgr.isConsistent());
@ -500,7 +504,25 @@ int ModApiMainMenu::l_check_mod_configuration(lua_State *L)
index++;
}
lua_setfield(L, -2, "satisfied_mods");
return 1;
}
/******************************************************************************/
int ModApiMainMenu::l_get_content_translation(lua_State *L)
{
GUIEngine* engine = getGuiEngine(L);
sanity_check(engine != NULL);
std::string path = luaL_checkstring(L, 1);
std::string domain = luaL_checkstring(L, 2);
std::string string = luaL_checkstring(L, 3);
std::string lang = gettext("LANG_CODE");
if (lang == "LANG_CODE")
lang = "";
auto *translations = engine->getContentTranslations(path, domain, lang);
string = wide_to_utf8(translate_string(utf8_to_wide(string), translations));
lua_pushstring(L, string.c_str());
return 1;
}
@ -911,6 +933,17 @@ int ModApiMainMenu::l_get_video_drivers(lua_State *L)
return 1;
}
/******************************************************************************/
int ModApiMainMenu::l_get_language(lua_State *L)
{
std::string lang = gettext("LANG_CODE");
if (lang == "LANG_CODE")
lang = "";
lua_pushstring(L, lang.c_str());
return 1;
}
/******************************************************************************/
int ModApiMainMenu::l_gettext(lua_State *L)
{
@ -1102,6 +1135,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(get_games);
API_FCT(get_content_info);
API_FCT(check_mod_configuration);
API_FCT(get_content_translation);
API_FCT(start);
API_FCT(close);
API_FCT(show_keys_menu);
@ -1128,6 +1162,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(get_mainmenu_path);
API_FCT(show_path_select_dialog);
API_FCT(download_file);
API_FCT(get_language);
API_FCT(gettext);
API_FCT(get_video_drivers);
API_FCT(get_window_info);
@ -1168,5 +1203,6 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
API_FCT(download_file);
API_FCT(get_min_supp_proto);
API_FCT(get_max_supp_proto);
API_FCT(get_language);
API_FCT(gettext);
}

View File

@ -74,6 +74,8 @@ private:
static int l_get_mapgen_names(lua_State *L);
static int l_get_language(lua_State *L);
static int l_gettext(lua_State *L);
//packages
@ -84,6 +86,8 @@ private:
static int l_check_mod_configuration(lua_State *L);
static int l_get_content_translation(lua_State *L);
//gui
static int l_show_keys_menu(lua_State *L);

View File

@ -115,7 +115,13 @@ int MetaDataRef::l_set_string(lua_State *L)
MetaDataRef *ref = checkAnyMetadata(L, 1);
std::string name = luaL_checkstring(L, 2);
auto str = readParam<std::string_view>(L, 3);
std::string_view str;
if (!lua_isnoneornil(L, 3)) {
str = readParam<std::string_view>(L, 3);
} else {
log_deprecated(L, "Value passed to set_string is nil. This behaviour is"
" undocumented and will result in an error in the future.", 1, true);
}
IMetadata *meta = ref->getmeta(!str.empty());
if (meta != NULL && meta->setString(name, str))

View File

@ -132,9 +132,6 @@ int LuaMinimap::l_show(lua_State *L)
if (!g_settings->getBool("enable_minimap"))
return 1;
Client *client = getClient(L);
assert(client);
LuaMinimap *ref = checkObject<LuaMinimap>(L, 1);
Minimap *m = getobject(ref);
@ -149,9 +146,6 @@ int LuaMinimap::l_show(lua_State *L)
int LuaMinimap::l_hide(lua_State *L)
{
Client *client = getClient(L);
assert(client);
LuaMinimap *ref = checkObject<LuaMinimap>(L, 1);
Minimap *m = getobject(ref);

View File

@ -42,6 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "version.h"
#include "util/hex.h"
#include "util/sha1.h"
#include "util/sha256.h"
#include "util/png.h"
#include <cstdio>
@ -566,6 +567,27 @@ int ModApiUtil::l_sha1(lua_State *L)
return 1;
}
int ModApiUtil::l_sha256(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
auto data = readParam<std::string_view>(L, 1);
bool hex = !lua_isboolean(L, 2) || !readParam<bool>(L, 2);
std::string data_sha256;
data_sha256.resize(SHA256_DIGEST_LENGTH);
SHA256(reinterpret_cast<const unsigned char*>(data.data()), data.size(),
reinterpret_cast<unsigned char *>(data_sha256.data()));
if (hex) {
lua_pushstring(L, hex_encode(data_sha256).c_str());
} else {
lua_pushlstring(L, data_sha256.data(), data_sha256.size());
}
return 1;
}
// colorspec_to_colorstring(colorspec)
int ModApiUtil::l_colorspec_to_colorstring(lua_State *L)
{
@ -690,6 +712,7 @@ void ModApiUtil::Initialize(lua_State *L, int top)
API_FCT(get_version);
API_FCT(sha1);
API_FCT(sha256);
API_FCT(colorspec_to_colorstring);
API_FCT(colorspec_to_bytes);
@ -723,6 +746,7 @@ void ModApiUtil::InitializeClient(lua_State *L, int top)
API_FCT(get_version);
API_FCT(sha1);
API_FCT(sha256);
API_FCT(colorspec_to_colorstring);
API_FCT(colorspec_to_bytes);
@ -766,6 +790,7 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top)
API_FCT(get_version);
API_FCT(sha1);
API_FCT(sha256);
API_FCT(colorspec_to_colorstring);
API_FCT(colorspec_to_bytes);

View File

@ -113,6 +113,9 @@ private:
// sha1(string, raw)
static int l_sha1(lua_State *L);
// sha256(string, raw)
static int l_sha256(lua_State *L);
// colorspec_to_colorstring(colorspec)
static int l_colorspec_to_colorstring(lua_State *L);

View File

@ -646,15 +646,29 @@ void Server::AsyncRunStep(float dtime, bool initial_step)
{
MutexAutoLock lock(m_env_mutex);
// Figure out and report maximum lag to environment
float max_lag = m_env->getMaxLagEstimate();
max_lag *= 0.9998; // Decrease slowly (about half per 5 minutes)
if(dtime > max_lag){
if(dtime > 0.1 && dtime > max_lag * 2.0)
infostream<<"Server: Maximum lag peaked to "<<dtime
<<" s"<<std::endl;
max_lag = dtime;
constexpr float lag_warn_threshold = 2.0f;
// Decrease value gradually, halve it every minute.
if (m_max_lag_decrease.step(dtime, 0.5f)) {
// To reproduce this number enter "solve (x)**(60/0.5) = 0.5"
// into Wolfram Alpha.
max_lag *= 0.99425f;
}
// Report a 20% change to the log but only if we're >10% off target
// also report if we crossed into the warning boundary
if (dtime >= max_lag * 1.2f ||
(max_lag < lag_warn_threshold && dtime >= lag_warn_threshold)) {
const float steplen = getStepSettings().steplen;
if (dtime > steplen * 1.1f) {
auto &to = dtime >= lag_warn_threshold ? warningstream : infostream;
to << "Server: Maximum lag peaked at " << dtime
<< " (steplen=" << steplen << ")" << std::endl;
}
}
max_lag = std::max(max_lag, dtime),
m_env->reportMaxLagEstimate(max_lag);
// Step environment
@ -2264,6 +2278,22 @@ void Server::fadeSound(s32 handle, float step, float gain)
m_playing_sounds.erase(it);
}
void Server::stopAttachedSounds(u16 id)
{
assert(id);
for (auto it = m_playing_sounds.begin(); it != m_playing_sounds.end(); ) {
const ServerPlayingSound &sound = it->second;
if (sound.object == id) {
// Remove sound reference
it = m_playing_sounds.erase(it);
}
else
it++;
}
}
void Server::sendRemoveNode(v3s16 p, std::unordered_set<u16> *far_players,
float far_d_nodes)
{

View File

@ -239,6 +239,7 @@ public:
s32 playSound(ServerPlayingSound &params, bool ephemeral=false);
void stopSound(s32 handle);
void fadeSound(s32 handle, float step, float gain);
void stopAttachedSounds(u16 id);
// Envlock
std::set<std::string> getPlayerEffectivePrivs(const std::string &name);
@ -608,12 +609,14 @@ private:
MutexedVariable<std::string> m_async_fatal_error;
// Some timers
float m_time_of_day_send_timer = 0.0f;
float m_liquid_transform_timer = 0.0f;
float m_liquid_transform_every = 1.0f;
float m_masterserver_timer = 0.0f;
float m_emergethread_trigger_timer = 0.0f;
float m_savemap_timer = 0.0f;
IntervalLimiter m_map_timer_and_unload_interval;
IntervalLimiter m_max_lag_decrease;
// Environment
ServerEnvironment *m_env = nullptr;
@ -661,12 +664,6 @@ private:
// The server mainly operates in this thread
ServerThread *m_thread = nullptr;
/*
Time related stuff
*/
// Timer for sending time of day over network
float m_time_of_day_send_timer = 0.0f;
/*
Client interface
*/

View File

@ -354,18 +354,12 @@ void RemoteClient::GetNextBlocks (
continue;
/*
If block is not close, don't send it unless it is near
ground level.
Block is near ground level if night-time mesh
differs from day-time mesh.
If block is not close, don't send it if it
consists of air only.
*/
if (d >= d_opt) {
if (!block->getIsUnderground() && !block->getDayNightDiff())
if (d >= d_opt && block->isAir())
continue;
}
}
/*
Check occlusion cache first.
*/

View File

@ -203,7 +203,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended)
}
}
if (fabs(m_prop.automatic_rotate) > 0.001f) {
if (std::abs(m_prop.automatic_rotate) > 0.001f) {
m_rotation_add_yaw = modulo360f(m_rotation_add_yaw + dtime * core::RADTODEG *
m_prop.automatic_rotate);
}

View File

@ -1253,10 +1253,7 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode)
return false;
}
// Tell the object about removal
obj->removingFromEnvironment();
// Deregister in scripting api
m_script->removeObjectReference(obj);
processActiveObjectRemove(obj, id);
// Delete active object
return true;
@ -1976,10 +1973,7 @@ void ServerEnvironment::removeRemovedObjects()
}
}
// Tell the object about removal
obj->removingFromEnvironment();
// Deregister in scripting api
m_script->removeObjectReference(obj);
processActiveObjectRemove(obj, id);
// Delete
return true;
@ -2216,10 +2210,7 @@ void ServerEnvironment::deactivateFarObjects(bool _force_delete)
return false;
}
// Tell the object about removal
obj->removingFromEnvironment();
// Deregister in scripting api
m_script->removeObjectReference(obj);
processActiveObjectRemove(obj, id);
// Delete active object
return true;
@ -2282,6 +2273,16 @@ bool ServerEnvironment::saveStaticToBlock(
return true;
}
void ServerEnvironment::processActiveObjectRemove(ServerActiveObject *obj, u16 id)
{
// Tell the object about removal
obj->removingFromEnvironment();
// Deregister in scripting api
m_script->removeObjectReference(obj);
// stop attached sounds
m_server->stopAttachedSounds(id);
}
PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
const std::string &savedir, const Settings &conf)
{

View File

@ -367,7 +367,7 @@ public:
u32 getGameTime() const { return m_game_time; }
void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; }
float getMaxLagEstimate() { return m_max_lag_estimate; }
float getMaxLagEstimate() const { return m_max_lag_estimate; }
std::set<v3s16>* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; }
@ -454,6 +454,8 @@ private:
bool saveStaticToBlock(v3s16 blockpos, u16 store_id,
ServerActiveObject *obj, const StaticObject &s_obj, u32 mod_reason);
void processActiveObjectRemove(ServerActiveObject *obj, u16 id);
/*
Member variables
*/

View File

@ -31,11 +31,11 @@ void TileAnimationParams::serialize(std::ostream &os, u16 protocol_ver) const
if (type == TAT_VERTICAL_FRAMES) {
writeU16(os, vertical_frames.aspect_w);
writeU16(os, vertical_frames.aspect_h);
writeF32(os, need_abs ? fabs(vertical_frames.length) : vertical_frames.length);
writeF32(os, need_abs ? std::abs(vertical_frames.length) : vertical_frames.length);
} else if (type == TAT_SHEET_2D) {
writeU8(os, sheet_2d.frames_w);
writeU8(os, sheet_2d.frames_h);
writeF32(os, need_abs ? fabs(sheet_2d.frame_length) : sheet_2d.frame_length);
writeF32(os, need_abs ? std::abs(sheet_2d.frame_length) : sheet_2d.frame_length);
}
}

View File

@ -54,6 +54,7 @@ const std::wstring &Translations::getTranslation(
void Translations::loadTranslation(const std::string &data)
{
std::istringstream is(data);
std::string textdomain_narrow;
std::wstring textdomain;
std::string line;
@ -70,7 +71,8 @@ void Translations::loadTranslation(const std::string &data)
<< "\"" << std::endl;
continue;
}
textdomain = utf8_to_wide(trim(parts[1]));
textdomain_narrow = trim(parts[1]);
textdomain = utf8_to_wide(textdomain_narrow);
}
if (line.empty() || line[0] == '#')
continue;
@ -116,7 +118,7 @@ void Translations::loadTranslation(const std::string &data)
if (i == wline.length()) {
errorstream << "Malformed translation line \"" << line << "\""
<< std::endl;
<< " in text domain " << textdomain_narrow << std::endl;
continue;
}
i++;

View File

@ -42,9 +42,12 @@ set (UNITTEST_SRCS
PARENT_SCOPE)
set (UNITTEST_CLIENT_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/mesh_compare.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_clientactiveobjectmgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_content_mapblock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_mesh_compare.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp
PARENT_SCOPE)

View File

@ -0,0 +1,103 @@
/*
Minetest
Copyright (C) 2023 Vitaliy Lobachevskiy
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "mesh_compare.h"
#include <algorithm>
#include <map>
#include <stdexcept>
static std::vector<Triangle> expandMesh(const std::vector<video::S3DVertex> &vertices, const std::vector<u16> &indices)
{
const int n_indices = indices.size();
const int n_triangles = n_indices / 3;
if (n_indices % 3)
throw std::invalid_argument("got fractional number of triangles");
std::vector<Triangle> ret(n_triangles);
for (int i_triangle = 0; i_triangle < n_triangles; i_triangle++) {
ret.at(i_triangle) = {
vertices.at(indices.at(3 * i_triangle)),
vertices.at(indices.at(3 * i_triangle + 1)),
vertices.at(indices.at(3 * i_triangle + 2)),
};
}
return ret;
}
/// Sorts triangle vertices, keeping winding order.
static Triangle sortTriangle(Triangle t)
{
if (t[0] < t[1] && t[0] < t[2]) return {t[0], t[1], t[2]};
if (t[1] < t[2] && t[1] < t[0]) return {t[1], t[2], t[0]};
if (t[2] < t[0] && t[2] < t[1]) return {t[2], t[0], t[1]};
throw std::invalid_argument("got bad triangle");
}
static std::vector<Triangle> canonicalizeMesh(const std::vector<video::S3DVertex> &vertices, const std::vector<u16> &indices)
{
std::vector<Triangle> mesh = expandMesh(vertices, indices);
for (auto &triangle: mesh)
triangle = sortTriangle(triangle);
std::sort(std::begin(mesh), std::end(mesh));
return mesh;
}
bool checkMeshEqual(const std::vector<video::S3DVertex> &vertices, const std::vector<u16> &indices, const std::vector<Triangle> &expected)
{
auto actual = canonicalizeMesh(vertices, indices);
return actual == expected;
}
bool checkMeshEqual(const std::vector<video::S3DVertex> &vertices, const std::vector<u16> &indices, const std::vector<Quad> &expected)
{
using QuadRefCount = std::array<int, 4>;
struct QuadRef {
unsigned quad_id;
int quad_part;
};
std::vector<QuadRefCount> refs(expected.size());
std::map<Triangle, QuadRef> tris;
for (unsigned k = 0; k < expected.size(); k++) {
auto &&quad = expected[k];
// There are 2 ways to split a quad into two triangles. So for each quad,
// the mesh must contain either triangles 0 and 1, or triangles 2 and 3,
// from the following list. No more, no less.
tris.insert({sortTriangle({quad[0], quad[1], quad[2]}), {k, 0}});
tris.insert({sortTriangle({quad[0], quad[2], quad[3]}), {k, 1}});
tris.insert({sortTriangle({quad[0], quad[1], quad[3]}), {k, 2}});
tris.insert({sortTriangle({quad[1], quad[2], quad[3]}), {k, 3}});
}
auto actual = canonicalizeMesh(vertices, indices);
for (auto &&tri: actual) {
auto itri = tris.find(tri);
if (itri == tris.end())
return false;
refs[itri->second.quad_id][itri->second.quad_part] += 1;
}
for (unsigned k = 0; k < expected.size(); k++) {
if (refs[k] != QuadRefCount{1, 1, 0, 0} && refs[k] != QuadRefCount{0, 0, 1, 1})
return false;
}
return true;
}

View File

@ -0,0 +1,47 @@
/*
Minetest
Copyright (C) 2023 Vitaliy Lobachevskiy
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <array>
#include <vector>
#include <irrlichttypes.h>
#include <S3DVertex.h>
/// Represents a triangle as three vertices.
/// “Smallest” (according to <) vertex is expected to be first, others should follow in the counter-clockwise order.
using Triangle = std::array<video::S3DVertex, 3>;
/// Represents a quad as four vertices.
/// Vertices should be in the counter-clockwise order.
using Quad = std::array<video::S3DVertex, 4>;
/// Compare two meshes for equality.
/// @param vertices Vertices of the first mesh. Order doesnt matter.
/// @param indices Indices of the first mesh. Triangle order doesnt matter. Vertex order in a triangle only matters for winding.
/// @param expected The second mesh, in an expanded form. Must be sorted.
/// @returns Whether the two meshes are equal.
[[nodiscard]] bool checkMeshEqual(const std::vector<video::S3DVertex> &vertices, const std::vector<u16> &indices, const std::vector<Triangle> &expected);
/// Compare two meshes for equality.
/// @param vertices Vertices of the first mesh. Order doesnt matter.
/// @param indices Indices of the first mesh. Triangle order doesnt matter. Vertex order in a triangle only matters for winding.
/// @param expected The second mesh, in a quad form.
/// @returns Whether the two meshes are equal.
/// @note There are two ways to split a quad into 2 triangles; either is allowed.
[[nodiscard]] bool checkMeshEqual(const std::vector<video::S3DVertex> &vertices, const std::vector<u16> &indices, const std::vector<Quad> &expected);

View File

@ -0,0 +1,268 @@
/*
Minetest
Copyright (C) 2023 Vitaliy Lobachevskiy
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <algorithm>
#include <numeric>
#include "gamedef.h"
#include "dummygamedef.h"
#include "client/content_mapblock.h"
#include "client/mapblock_mesh.h"
#include "client/meshgen/collector.h"
#include "mesh_compare.h"
#include "util/directiontables.h"
namespace {
class MockGameDef : public DummyGameDef {
public:
IWritableItemDefManager *item_mgr() noexcept {
return static_cast<IWritableItemDefManager *>(m_itemdef);
}
NodeDefManager *node_mgr() noexcept {
return const_cast<NodeDefManager *>(m_nodedef);
}
content_t registerNode(ItemDefinition itemdef, ContentFeatures nodedef) {
item_mgr()->registerItem(itemdef);
return node_mgr()->set(nodedef.name, nodedef);
}
void finalize() {
node_mgr()->resolveCrossrefs();
}
MeshMakeData makeSingleNodeMMD(bool smooth_lighting = true, bool for_shaders = true)
{
MeshMakeData data{ndef(), 1, for_shaders};
data.setSmoothLighting(smooth_lighting);
data.m_blockpos = {0, 0, 0};
for (s16 x = -1; x <= 1; x++)
for (s16 y = -1; y <= 1; y++)
for (s16 z = -1; z <= 1; z++)
data.m_vmanip.setNode({x, y, z}, {CONTENT_AIR, 0, 0});
return data;
}
content_t addSimpleNode(std::string name, u32 texture)
{
ItemDefinition itemdef;
itemdef.type = ITEM_NODE;
itemdef.name = "test:" + name;
itemdef.description = name;
ContentFeatures f;
f.name = itemdef.name;
f.drawtype = NDT_NORMAL;
f.solidness = 2;
f.alpha = ALPHAMODE_OPAQUE;
for (TileDef &tiledef : f.tiledef)
tiledef.name = name + ".png";
for (TileSpec &tile : f.tiles)
tile.layers[0].texture_id = texture;
return registerNode(itemdef, f);
}
content_t addLiquidSource(std::string name, u32 texture)
{
ItemDefinition itemdef;
itemdef.type = ITEM_NODE;
itemdef.name = "test:" + name + "_source";
itemdef.description = name;
ContentFeatures f;
f.name = itemdef.name;
f.drawtype = NDT_LIQUID;
f.solidness = 1;
f.alpha = ALPHAMODE_BLEND;
f.light_propagates = true;
f.param_type = CPT_LIGHT;
f.liquid_type = LIQUID_SOURCE;
f.liquid_viscosity = 4;
f.groups["liquids"] = 3;
f.liquid_alternative_source = "test:" + name + "_source";
f.liquid_alternative_flowing = "test:" + name + "_flowing";
for (TileDef &tiledef : f.tiledef)
tiledef.name = name + ".png";
for (TileSpec &tile : f.tiles)
tile.layers[0].texture_id = texture;
return registerNode(itemdef, f);
}
content_t addLiquidFlowing(std::string name, u32 texture_top, u32 texture_side)
{
ItemDefinition itemdef;
itemdef.type = ITEM_NODE;
itemdef.name = "test:" + name + "_flowing";
itemdef.description = name;
ContentFeatures f;
f.name = itemdef.name;
f.drawtype = NDT_FLOWINGLIQUID;
f.solidness = 0;
f.alpha = ALPHAMODE_BLEND;
f.light_propagates = true;
f.param_type = CPT_LIGHT;
f.liquid_type = LIQUID_FLOWING;
f.liquid_viscosity = 4;
f.groups["liquids"] = 3;
f.liquid_alternative_source = "test:" + name + "_source";
f.liquid_alternative_flowing = "test:" + name + "_flowing";
f.tiledef_special[0].name = name + "_top.png";
f.tiledef_special[1].name = name + "_side.png";
f.special_tiles[0].layers[0].texture_id = texture_top;
f.special_tiles[1].layers[0].texture_id = texture_side;
return registerNode(itemdef, f);
}
};
void set_light_decode_table()
{
u8 table[LIGHT_SUN + 1] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
};
memcpy(const_cast<u8 *>(light_decode_table), table, sizeof(table));
}
class TestMapblockMeshGenerator : public TestBase {
public:
TestMapblockMeshGenerator() { TestManager::registerTestModule(this); }
const char *getName() override { return "TestMapblockMeshGenerator"; }
void runTests(IGameDef *gamedef) override;
void testSimpleNode();
void testSurroundedNode();
void testInterliquidSame();
void testInterliquidDifferent();
};
static TestMapblockMeshGenerator g_test_instance;
void TestMapblockMeshGenerator::runTests(IGameDef *gamedef)
{
set_light_decode_table();
TEST(testSimpleNode);
TEST(testSurroundedNode);
TEST(testInterliquidSame);
TEST(testInterliquidDifferent);
}
namespace quad {
constexpr float h = BS / 2.0f;
const Quad zp{{{{-h, -h, h}, {0, 0, 1}, 0, {1, 1}}, {{h, -h, h}, {0, 0, 1}, 0, {0, 1}}, {{h, h, h}, {0, 0, 1}, 0, {0, 0}}, {{-h, h, h}, {0, 0, 1}, 0, {1, 0}}}};
const Quad yp{{{{-h, h, -h}, {0, 1, 0}, 0, {0, 1}}, {{-h, h, h}, {0, 1, 0}, 0, {0, 0}}, {{h, h, h}, {0, 1, 0}, 0, {1, 0}}, {{h, h, -h}, {0, 1, 0}, 0, {1, 1}}}};
const Quad xp{{{{h, -h, -h}, {1, 0, 0}, 0, {0, 1}}, {{h, h, -h}, {1, 0, 0}, 0, {0, 0}}, {{h, h, h}, {1, 0, 0}, 0, {1, 0}}, {{h, -h, h}, {1, 0, 0}, 0, {1, 1}}}};
const Quad zn{{{{-h, -h, -h}, {0, 0, -1}, 0, {0, 1}}, {{-h, h, -h}, {0, 0, -1}, 0, {0, 0}}, {{h, h, -h}, {0, 0, -1}, 0, {1, 0}}, {{h, -h, -h}, {0, 0, -1}, 0, {1, 1}}}};
const Quad yn{{{{-h, -h, -h}, {0, -1, 0}, 0, {0, 0}}, {{h, -h, -h}, {0, -1, 0}, 0, {1, 0}}, {{h, -h, h}, {0, -1, 0}, 0, {1, 1}}, {{-h, -h, h}, {0, -1, 0}, 0, {0, 1}}}};
const Quad xn{{{{-h, -h, -h}, {-1, 0, 0}, 0, {1, 1}}, {{-h, -h, h}, {-1, 0, 0}, 0, {0, 1}}, {{-h, h, h}, {-1, 0, 0}, 0, {0, 0}}, {{-h, h, -h}, {-1, 0, 0}, 0, {1, 0}}}};
}
void TestMapblockMeshGenerator::testSimpleNode()
{
MockGameDef gamedef;
content_t stone = gamedef.addSimpleNode("stone", 42);
gamedef.finalize();
MeshMakeData data = gamedef.makeSingleNodeMMD();
data.m_vmanip.setNode({0, 0, 0}, {stone, 0, 0});
MeshCollector col{{}};
MapblockMeshGenerator mg{&data, &col, nullptr};
mg.generate();
UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1);
UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0);
auto &&buf = col.prebuffers[0][0];
UASSERTEQ(u32, buf.layer.texture_id, 42);
UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::xp, quad::yn, quad::yp, quad::zn, quad::zp}));
}
void TestMapblockMeshGenerator::testSurroundedNode()
{
MockGameDef gamedef;
content_t stone = gamedef.addSimpleNode("stone", 42);
content_t wood = gamedef.addSimpleNode("wood", 13);
gamedef.finalize();
MeshMakeData data = gamedef.makeSingleNodeMMD();
data.m_vmanip.setNode({0, 0, 0}, {stone, 0, 0});
data.m_vmanip.setNode({1, 0, 0}, {wood, 0, 0});
MeshCollector col{{}};
MapblockMeshGenerator mg{&data, &col, nullptr};
mg.generate();
UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1);
UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0);
auto &&buf = col.prebuffers[0][0];
UASSERTEQ(u32, buf.layer.texture_id, 42);
UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::yn, quad::yp, quad::zn, quad::zp}));
}
void TestMapblockMeshGenerator::testInterliquidSame()
{
MockGameDef gamedef;
auto water = gamedef.addLiquidSource("water", 42);
gamedef.finalize();
MeshMakeData data = gamedef.makeSingleNodeMMD();
data.m_vmanip.setNode({0, 0, 0}, {water, 0, 0});
data.m_vmanip.setNode({1, 0, 0}, {water, 0, 0});
MeshCollector col{{}};
MapblockMeshGenerator mg{&data, &col, nullptr};
mg.generate();
UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1);
UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0);
auto &&buf = col.prebuffers[0][0];
UASSERTEQ(u32, buf.layer.texture_id, 42);
UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::yn, quad::yp, quad::zn, quad::zp}));
}
void TestMapblockMeshGenerator::testInterliquidDifferent()
{
MockGameDef gamedef;
auto water = gamedef.addLiquidSource("water", 42);
auto lava = gamedef.addLiquidSource("lava", 13);
gamedef.finalize();
MeshMakeData data = gamedef.makeSingleNodeMMD();
data.m_vmanip.setNode({0, 0, 0}, {water, 0, 0});
data.m_vmanip.setNode({0, 0, 1}, {lava, 0, 0});
MeshCollector col{{}};
MapblockMeshGenerator mg{&data, &col, nullptr};
mg.generate();
UASSERTEQ(std::size_t, col.prebuffers[0].size(), 1);
UASSERTEQ(std::size_t, col.prebuffers[1].size(), 0);
auto &&buf = col.prebuffers[0][0];
UASSERTEQ(u32, buf.layer.texture_id, 42);
UASSERT(checkMeshEqual(buf.vertices, buf.indices, {quad::xn, quad::xp, quad::yn, quad::yp, quad::zn, quad::zp}));
}
}

View File

@ -104,7 +104,7 @@ void TestDataStructures::testMap1()
UASSERT(t0.deleted);
UASSERT(!t1.copied);
UASSERT(!t1.deleted);
if (once |= 1)
if ((once |= 1))
break;
}
UASSERT(once);

View File

@ -0,0 +1,152 @@
/*
Minetest
Copyright (C) 2023 Vitaliy Lobachevskiy
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "mesh_compare.h"
// This is a self-test to ensure proper functionality of the vertex
// building functions (`Triangle`, `Quad`) and its validation function
// `checkMeshEqual` in preparation for the tests in test_content_mapblock.cpp
class TestMeshCompare : public TestBase {
public:
TestMeshCompare() { TestManager::registerTestModule(this); }
const char *getName() override { return "TestMeshCompare"; }
void runTests(IGameDef *gamedef) override {
TEST(testTriangle);
TEST(testQuad);
}
void testTriangle() {
UASSERT(checkMeshEqual({
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
}, {0, 1, 2}, {
Triangle{{
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
}},
}));
UASSERT(checkMeshEqual({
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
}, {2, 0, 1}, {
Triangle{{
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
}},
}));
UASSERT(!checkMeshEqual({
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
}, {0, 2, 1}, {
Triangle{{
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
}},
}));
UASSERT(checkMeshEqual({
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
}, {0, 1, 2}, {
Triangle{{
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
}},
}));
UASSERT(!checkMeshEqual({
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
}, {0, 1, 2}, {
Triangle{{
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
}},
}));
}
void testQuad() {
UASSERT(checkMeshEqual({
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}},
}, {0, 1, 2, 0, 2, 3}, {
Quad{{
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}},
}},
}));
UASSERT(checkMeshEqual({
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}},
}, {2, 3, 0, 1, 2, 0}, {
Quad{{
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}},
}},
}));
UASSERT(checkMeshEqual({
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}},
}, {2, 3, 1, 0, 1, 3}, {
Quad{{
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}},
}},
}));
UASSERT(checkMeshEqual({
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}},
}, {3, 0, 1, 1, 2, 3}, {
Quad{{
{{1., 0., 0.}, {3., 0., 0.}, 1, {0., 0.}},
{{0., 1., 0.}, {2., 0., 0.}, 2, {0., 0.}},
{{0., 0., 1.}, {1., 0., 0.}, 3, {0., 0.}},
{{1., -1., 1.}, {4., 0., 0.}, 4, {0., 0.}},
}},
}));
}
};
static TestMeshCompare mesh_compare_test;

View File

@ -350,12 +350,10 @@ class IntervalLimiter
public:
IntervalLimiter() = default;
/*
dtime: time from last call to this method
wanted_interval: interval wanted
return value:
true: action should be skipped
false: action should be done
/**
@param dtime time from last call to this method
@param wanted_interval interval wanted
@return true if action should be done
*/
bool step(float dtime, float wanted_interval)
{

View File

@ -43,13 +43,19 @@ class Translations;
( (unsigned int)(x) <= 0x7e))
// Checks whether a value is in a Unicode private use area
#define IS_PRIVATE_USE_CHAR(x) \
(((wchar_t)(x) >= 0xE000 && \
(wchar_t)(x) <= 0xF8FF) || \
((wchar_t)(x) >= 0xF0000 && \
#define IS_PRIVATE_USE_CHAR16(x) \
((wchar_t)(x) >= 0xE000 && \
(wchar_t)(x) <= 0xF8FF)
#define IS_PRIVATE_USE_CHAR32(x) \
(((wchar_t)(x) >= 0xF0000 && \
(wchar_t)(x) <= 0xFFFFD) || \
((wchar_t)(x) >= 0x100000 && \
(wchar_t)(x) <= 0x10FFFD)) \
(wchar_t)(x) <= 0x10FFFD))
#if WCHAR_MAX > 0xFFFF
#define IS_PRIVATE_USE_CHAR(x) (IS_PRIVATE_USE_CHAR16(x) || IS_PRIVATE_USE_CHAR32(x))
#else
#define IS_PRIVATE_USE_CHAR(x) IS_PRIVATE_USE_CHAR16(x)
#endif
// Checks whether a byte is an inner byte for an utf-8 multibyte sequence
#define IS_UTF8_MULTB_INNER(x) \

View File

@ -4,8 +4,8 @@ aa86abc3eb054d74d5fe15996f281cf84230a61b4ab7b3a702ab7dbb71e1203f curl-8.5.0-win
acf901e93aedbcfa92eb3aab1def252676af845b1747ca5c3e7c5866576168cc freetype-2.13.2-win64.zip
41b10766de2773f0f0851fde16b363024685e0397f4bb2e5cd2a7be196960a01 gettext-0.20.2-win32.zip
1ceed167ff16fea944f76ab6ea2969160c71a67419259b17c9c523e7a01eb883 gettext-0.20.2-win64.zip
6b37e3f734c48caa7ba8756abed63c5171bf54f4f679a1dc3869ec2bd94be48d irrlicht-1.9.0mt14-win32.zip
5aca6d711881d3d6a4d5da4b53b2cf0e73a0c113b8776730bbfa3f3bc4aebea7 irrlicht-1.9.0mt14-win64.zip
14bb60cbf9dd93e906d9c9117a99d19a500cda6bcfd6fc125e04f3c75778acc2 irrlicht-1.9.0mt15-win32.zip
31edd6a0af033b9ab455c5e019748cfa7e0cf167c9cbc5957227e72e971c2565 irrlicht-1.9.0mt15-win64.zip
f54e9a577e2db47ed28f4a01e74181d2c607627c551d30f48263e01b59e84f67 libleveldb-1.23-win32.zip
2f039848a4e6c05a2347fe5a7fa63c430dd08d1bc88235645a863c859e14f5f8 libleveldb-1.23-win64.zip
0df94afb8efa361cceb132ecf9491720afbc45ba844a7b1c94607295829b53ca libogg-1.3.5-win32.zip

View File

@ -425,6 +425,7 @@ def generate_template(folder, mod_name):
sources = sorted(list(sources), key=str.lower)
newSources = []
for i in sources:
i = "/".join(os.path.split(i)).lstrip("/")
newSources.append(f"{symbol_source_prefix} {i} {symbol_source_suffix}")
dOut[d] = newSources