From b6dbd3fc7ad0c90d04b0f56161e48a6b83da67a0 Mon Sep 17 00:00:00 2001 From: Deve Date: Sun, 27 Aug 2023 01:30:22 +0200 Subject: [PATCH] Add MultiCraft changes in src --- CMakeLists.txt | 30 +- lib/luachacha/CMakeLists.txt | 2 + lib/luachacha/chacha.c | 210 ++ lib/luachacha/ecrypt-config.h | 272 ++ lib/luachacha/ecrypt-machine.h | 46 + lib/luachacha/ecrypt-portable.h | 303 +++ lib/luachacha/ecrypt-sync.h | 290 ++ lib/luachacha/lchacha.c | 79 + lib/luautf8/CMakeLists.txt | 2 + lib/luautf8/lutf8lib.c | 1300 +++++++++ lib/luautf8/unidata.h | 3753 ++++++++++++++++++++++++++ src/CMakeLists.txt | 49 +- src/activeobject.h | 7 +- src/ban.h | 6 + src/chat.cpp | 33 +- src/chat.h | 51 +- src/client/camera.cpp | 22 +- src/client/camera.h | 2 +- src/client/client.cpp | 46 +- src/client/client.h | 10 +- src/client/clientlauncher.cpp | 61 +- src/client/clientmap.h | 1 + src/client/clouds.cpp | 215 +- src/client/content_cao.cpp | 44 +- src/client/content_mapblock.cpp | 92 +- src/client/content_mapblock.h | 5 +- src/client/game.cpp | 569 ++-- src/client/gameui.cpp | 89 +- src/client/gameui.h | 5 +- src/client/guiscalingfilter.cpp | 49 +- src/client/guiscalingfilter.h | 8 +- src/client/hud.cpp | 94 +- src/client/hud.h | 1 + src/client/inputhandler.cpp | 68 +- src/client/inputhandler.h | 30 + src/client/joystick_controller.cpp | 352 +++ src/client/joystick_controller.h | 44 + src/client/keycode.h | 2 + src/client/localplayer.cpp | 88 +- src/client/localplayer.h | 2 + src/client/mapblock_mesh.cpp | 9 - src/client/minimap.cpp | 25 +- src/client/particles.cpp | 1 + src/client/render/anaglyph.cpp | 2 +- src/client/renderingengine.cpp | 126 +- src/client/renderingengine.h | 7 +- src/client/shader.cpp | 21 +- src/client/sky.cpp | 6 +- src/client/sound_openal.cpp | 15 +- src/client/sound_openal.h | 5 +- src/client/tile.cpp | 31 +- src/client/tile.h | 7 +- src/client/wieldmesh.cpp | 83 +- src/client/wieldmesh.h | 11 - src/clientiface.cpp | 76 +- src/clientiface.h | 20 +- src/cmake_config.h.in | 3 + src/config.h | 7 +- src/constants.h | 6 +- src/content/packages.cpp | 69 + src/content/packages.h | 52 + src/content/subgames.cpp | 21 +- src/content/subgames.h | 6 +- src/debug.cpp | 14 + src/defaultsettings.cpp | 390 ++- src/filesys.cpp | 47 +- src/filesys.h | 4 +- src/gettext.cpp | 25 +- src/gui/CMakeLists.txt | 1 - src/gui/StyleSpec.h | 55 +- src/gui/guiAnimatedImage.cpp | 59 +- src/gui/guiAnimatedImage.h | 25 +- src/gui/guiBackgroundImage.cpp | 16 +- src/gui/guiButton.cpp | 15 +- src/gui/guiButtonImage.cpp | 27 +- src/gui/guiButtonImage.h | 10 +- src/gui/guiChatConsole.cpp | 1047 ++++++- src/gui/guiChatConsole.h | 135 +- src/gui/guiConfirmRegistration.cpp | 117 +- src/gui/guiConfirmRegistration.h | 8 +- src/gui/guiEditBox.cpp | 257 +- src/gui/guiEditBox.h | 13 +- src/gui/guiEditBoxWithScrollbar.cpp | 23 +- src/gui/guiEngine.cpp | 80 +- src/gui/guiEngine.h | 2 + src/gui/guiFormSpecMenu.cpp | 381 ++- src/gui/guiFormSpecMenu.h | 7 +- src/gui/guiHyperText.cpp | 3 +- src/gui/guiHyperText.h | 3 +- src/gui/guiInventoryList.cpp | 95 +- src/gui/guiInventoryList.h | 13 + src/gui/guiKeyChangeMenu.cpp | 105 +- src/gui/guiKeyChangeMenu.h | 6 +- src/gui/guiScrollBar.cpp | 158 +- src/gui/guiScrollBar.h | 14 + src/gui/guiSkin.cpp | 12 +- src/gui/guiTable.cpp | 24 +- src/gui/guiTable.h | 11 + src/gui/guiVolumeChange.cpp | 101 +- src/gui/guiVolumeChange.h | 3 + src/gui/mainmenumanager.h | 13 +- src/gui/modalMenu.cpp | 163 +- src/gui/modalMenu.h | 10 +- src/gui/touchscreengui.cpp | 588 ++-- src/gui/touchscreengui.h | 107 +- src/httpfetch.cpp | 2 +- src/hud.h | 4 + src/irrlicht_changes/static_text.cpp | 11 +- src/irrlicht_changes/static_text.h | 5 +- src/itemdef.cpp | 27 +- src/light.cpp | 2 +- src/log.cpp | 28 + src/main.cpp | 53 +- src/map.cpp | 81 +- src/mapblock.cpp | 6 +- src/mapblock.h | 3 +- src/mapgen/CMakeLists.txt | 1 + src/mapgen/mapgen.cpp | 8 +- src/mapgen/mapgen.h | 5 +- src/mapgen/mapgen_v7p.cpp | 435 +++ src/mapgen/mapgen_v7p.h | 92 + src/mapgen/mg_schematic.cpp | 35 +- src/mapgen/mg_schematic.h | 4 +- src/network/clientpackethandler.cpp | 19 +- src/network/connection.cpp | 4 + src/network/connectionthreads.cpp | 6 +- src/network/connectionthreads.h | 6 + src/network/networkpacket.cpp | 18 +- src/network/networkpacket.h | 8 + src/network/networkprotocol.h | 23 +- src/network/serveropcodes.cpp | 2 +- src/network/serverpackethandler.cpp | 173 +- src/network/socket.cpp | 5 + src/nodedef.cpp | 205 +- src/nodedef.h | 7 + src/nodemetadata.cpp | 12 +- src/nodemetadata.h | 6 +- src/noise.cpp | 89 +- src/object_properties.cpp | 80 +- src/object_properties.h | 4 +- src/porting.cpp | 35 +- src/porting.h | 29 +- src/porting_android.cpp | 406 ++- src/porting_android.h | 61 +- src/script/common/c_content.cpp | 31 +- src/script/common/c_converter.h | 4 +- src/script/cpp_api/s_async.h | 7 + src/script/cpp_api/s_base.cpp | 40 +- src/script/cpp_api/s_security.cpp | 13 +- src/script/cpp_api/s_server.cpp | 16 + src/script/cpp_api/s_server.h | 3 + src/script/lua_api/l_env.cpp | 46 +- src/script/lua_api/l_env.h | 8 +- src/script/lua_api/l_mainmenu.cpp | 103 +- src/script/lua_api/l_mainmenu.h | 13 +- src/script/lua_api/l_mapgen.cpp | 23 + src/script/lua_api/l_server.cpp | 32 +- src/script/lua_api/l_server.h | 3 + src/script/lua_api/l_storage.cpp | 4 +- src/script/lua_api/l_util.cpp | 90 + src/script/lua_api/l_util.h | 11 + src/script/scripting_client.cpp | 2 + src/script/scripting_mainmenu.cpp | 4 +- src/script/scripting_server.cpp | 2 + src/server.cpp | 403 ++- src/server.h | 38 +- src/server/luaentity_sao.cpp | 45 +- src/server/luaentity_sao.h | 2 +- src/server/mods.cpp | 33 +- src/server/player_sao.cpp | 94 +- src/server/player_sao.h | 5 +- src/server/serverinventorymgr.h | 3 +- src/server/unit_sao.cpp | 93 +- src/server/unit_sao.h | 15 +- src/serverenvironment.cpp | 44 +- src/serverenvironment.h | 27 +- src/serverlist.cpp | 52 +- src/skyparams.h | 8 +- src/sound.h | 6 + src/terminal_chat_console.h | 6 + src/threading/CMakeLists.txt | 2 + src/threading/sdl_semaphore.cpp | 71 + src/threading/sdl_semaphore.h | 48 + src/threading/sdl_thread.cpp | 153 ++ src/threading/sdl_thread.h | 130 + src/threading/semaphore.cpp | 3 + src/threading/semaphore.h | 6 + src/threading/thread.cpp | 3 + src/threading/thread.h | 5 + src/tileanimation.cpp | 20 +- src/tool.cpp | 9 +- src/unittest/test_threading.cpp | 7 + src/util/areastore.h | 2 +- src/util/container.h | 6 + src/util/serialize.h | 51 + src/util/string.cpp | 103 +- src/util/string.h | 7 + src/util/thread.h | 6 + 198 files changed, 14763 insertions(+), 2326 deletions(-) create mode 100644 lib/luachacha/CMakeLists.txt create mode 100644 lib/luachacha/chacha.c create mode 100644 lib/luachacha/ecrypt-config.h create mode 100644 lib/luachacha/ecrypt-machine.h create mode 100644 lib/luachacha/ecrypt-portable.h create mode 100644 lib/luachacha/ecrypt-sync.h create mode 100644 lib/luachacha/lchacha.c create mode 100644 lib/luautf8/CMakeLists.txt create mode 100644 lib/luautf8/lutf8lib.c create mode 100644 lib/luautf8/unidata.h create mode 100644 src/content/packages.cpp create mode 100644 src/content/packages.h create mode 100644 src/mapgen/mapgen_v7p.cpp create mode 100644 src/mapgen/mapgen_v7p.h create mode 100644 src/threading/sdl_semaphore.cpp create mode 100644 src/threading/sdl_semaphore.h create mode 100644 src/threading/sdl_thread.cpp create mode 100644 src/threading/sdl_thread.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fc0af96e0..f331b613b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,22 +8,26 @@ else() endif() # This can be read from ${PROJECT_NAME} after project() is called -project(minetest) -set(PROJECT_NAME_CAPITALIZED "Minetest") +project(multicraft) +set(PROJECT_NAME_CAPITALIZED "MultiCraft") -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(GCC_MINIMUM_VERSION "5.1") set(CLANG_MINIMUM_VERSION "3.5") # Also remember to set PROTOCOL_VERSION in network/networkprotocol.h when releasing -set(VERSION_MAJOR 5) -set(VERSION_MINOR 5) -set(VERSION_PATCH 0) +set(VERSION_MAJOR 2) +set(VERSION_MINOR 0) +set(VERSION_PATCH 4) set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") # Change to false for releases set(DEVELOPMENT_BUILD FALSE) +set(ENABLE_UPDATE_CHECKER (NOT ${DEVELOPMENT_BUILD}) CACHE BOOL + "Whether to enable update checks by default") + set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") if(VERSION_EXTRA) set(VERSION_STRING "${VERSION_STRING}-${VERSION_EXTRA}") @@ -50,7 +54,7 @@ set(RUN_IN_PLACE ${DEFAULT_RUN_IN_PLACE} CACHE BOOL set(BUILD_CLIENT TRUE CACHE BOOL "Build client") set(BUILD_SERVER FALSE CACHE BOOL "Build server") -set(BUILD_UNITTESTS TRUE CACHE BOOL "Build unittests") +set(BUILD_UNITTESTS FALSE CACHE BOOL "Build unittests") set(WARN_ALL TRUE CACHE BOOL "Enable -Wall for Release build") @@ -186,16 +190,15 @@ install(FILES "doc/client_lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs") install(FILES "doc/menu_lua_api.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs") install(FILES "doc/texture_packs.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs") install(FILES "doc/world_format.txt" DESTINATION "${DOCDIR}" COMPONENT "Docs") -install(FILES "minetest.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}") +install(FILES "multicraft.conf.example" DESTINATION "${EXAMPLE_CONF_DIR}") if(UNIX AND NOT APPLE) install(FILES "doc/minetest.6" "doc/minetestserver.6" DESTINATION "${MANDIR}/man6") install(FILES "misc/net.minetest.minetest.desktop" DESTINATION "${XDG_APPS_DIR}") install(FILES "misc/net.minetest.minetest.appdata.xml" DESTINATION "${APPDATADIR}") - install(FILES "misc/minetest.svg" DESTINATION "${ICONDIR}/hicolor/scalable/apps") - install(FILES "misc/minetest-xorg-icon-128.png" + install(FILES "misc/multicraft-xorg-icon-128.png" DESTINATION "${ICONDIR}/hicolor/128x128/apps" - RENAME "minetest.png") + RENAME "multicraft.png") endif() if(APPLE) @@ -207,6 +210,9 @@ endif() find_package(GMP REQUIRED) find_package(Json REQUIRED) find_package(Lua REQUIRED) +add_subdirectory(lib/luautf8) +add_subdirectory(lib/luachacha) + if(NOT USE_LUAJIT) set(LUA_BIT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/bitop) set(LUA_BIT_LIBRARY bitop) @@ -284,7 +290,7 @@ if(WIN32) set(CPACK_CREATE_DESKTOP_LINKS ${PROJECT_NAME}) set(CPACK_PACKAGING_INSTALL_PREFIX "/${PROJECT_NAME_CAPITALIZED}") - set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/misc/minetest-icon.ico") + set(CPACK_WIX_PRODUCT_ICON "${CMAKE_CURRENT_SOURCE_DIR}/misc/multicraft-icon.ico") # Supported languages can be found at # http://wixtoolset.org/documentation/manual/v3/wixui/wixui_localization.html #set(CPACK_WIX_CULTURES "ar-SA,bg-BG,ca-ES,hr-HR,cs-CZ,da-DK,nl-NL,en-US,et-EE,fi-FI,fr-FR,de-DE") diff --git a/lib/luachacha/CMakeLists.txt b/lib/luachacha/CMakeLists.txt new file mode 100644 index 000000000..57c888643 --- /dev/null +++ b/lib/luachacha/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(luachacha STATIC chacha.c lchacha.c) +target_include_directories(luachacha PUBLIC ${LUA_INCLUDE_DIR}) diff --git a/lib/luachacha/chacha.c b/lib/luachacha/chacha.c new file mode 100644 index 000000000..b2ec98c74 --- /dev/null +++ b/lib/luachacha/chacha.c @@ -0,0 +1,210 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#include "ecrypt-sync.h" +#include + +#define ROTATE(v,c) (ROTL32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +void ECRYPT_init(void) +{ + return; +} + +static const char sigma[16] = "expand 32-byte k"; +static const char tau[16] = "expand 16-byte k"; + +void ECRYPT_keysetup(ECRYPT_ctx *x,const u8 *k,u32 kbits,u32 ivbits) +{ + const char *constants; + + x->input[4] = U8TO32_LITTLE(k + 0); + x->input[5] = U8TO32_LITTLE(k + 4); + x->input[6] = U8TO32_LITTLE(k + 8); + x->input[7] = U8TO32_LITTLE(k + 12); + if (kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + x->input[8] = U8TO32_LITTLE(k + 0); + x->input[9] = U8TO32_LITTLE(k + 4); + x->input[10] = U8TO32_LITTLE(k + 8); + x->input[11] = U8TO32_LITTLE(k + 12); + x->input[0] = U8TO32_LITTLE(constants + 0); + x->input[1] = U8TO32_LITTLE(constants + 4); + x->input[2] = U8TO32_LITTLE(constants + 8); + x->input[3] = U8TO32_LITTLE(constants + 12); +} + +void ECRYPT_ivsetup(ECRYPT_ctx *x,const u8 *iv,const u8* counter) +{ + x->input[12] = (counter == NULL) ? 0 : U8TO32_LITTLE(counter + 0); + x->input[13] = (counter == NULL) ? 0 : U8TO32_LITTLE(counter + 4); + x->input[14] = U8TO32_LITTLE(iv + 0); + x->input[15] = U8TO32_LITTLE(iv + 4); +} + +void ECRYPT_IETF_ivsetup(ECRYPT_ctx *x,const u8 *iv,const u8* counter) +{ + x->input[12] = (counter == NULL) ? 0 : U8TO32_LITTLE(counter); + x->input[13] = U8TO32_LITTLE(iv + 0); + x->input[14] = U8TO32_LITTLE(iv + 4); + x->input[15] = U8TO32_LITTLE(iv + 8); +} + +void ECRYPT_encrypt_bytes(ECRYPT_ctx *x,const u8 *m,u8 *c,u32 bytes,int rounds) +{ + u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + u8 *ctarget; + u8 tmp[64]; + int i; + + if (!bytes) return; + + j0 = x->input[0]; + j1 = x->input[1]; + j2 = x->input[2]; + j3 = x->input[3]; + j4 = x->input[4]; + j5 = x->input[5]; + j6 = x->input[6]; + j7 = x->input[7]; + j8 = x->input[8]; + j9 = x->input[9]; + j10 = x->input[10]; + j11 = x->input[11]; + j12 = x->input[12]; + j13 = x->input[13]; + j14 = x->input[14]; + j15 = x->input[15]; + + for (;;) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = rounds;i > 0;i -= 2) { + QUARTERROUND( x0, x4, x8,x12) + QUARTERROUND( x1, x5, x9,x13) + QUARTERROUND( x2, x6,x10,x14) + QUARTERROUND( x3, x7,x11,x15) + QUARTERROUND( x0, x5,x10,x15) + QUARTERROUND( x1, x6,x11,x12) + QUARTERROUND( x2, x7, x8,x13) + QUARTERROUND( x3, x4, x9,x14) + } + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + + x0 = XOR(x0,U8TO32_LITTLE(m + 0)); + x1 = XOR(x1,U8TO32_LITTLE(m + 4)); + x2 = XOR(x2,U8TO32_LITTLE(m + 8)); + x3 = XOR(x3,U8TO32_LITTLE(m + 12)); + x4 = XOR(x4,U8TO32_LITTLE(m + 16)); + x5 = XOR(x5,U8TO32_LITTLE(m + 20)); + x6 = XOR(x6,U8TO32_LITTLE(m + 24)); + x7 = XOR(x7,U8TO32_LITTLE(m + 28)); + x8 = XOR(x8,U8TO32_LITTLE(m + 32)); + x9 = XOR(x9,U8TO32_LITTLE(m + 36)); + x10 = XOR(x10,U8TO32_LITTLE(m + 40)); + x11 = XOR(x11,U8TO32_LITTLE(m + 44)); + x12 = XOR(x12,U8TO32_LITTLE(m + 48)); + x13 = XOR(x13,U8TO32_LITTLE(m + 52)); + x14 = XOR(x14,U8TO32_LITTLE(m + 56)); + x15 = XOR(x15,U8TO32_LITTLE(m + 60)); + + j12 = PLUSONE(j12); + if (!j12) { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0,x0); + U32TO8_LITTLE(c + 4,x1); + U32TO8_LITTLE(c + 8,x2); + U32TO8_LITTLE(c + 12,x3); + U32TO8_LITTLE(c + 16,x4); + U32TO8_LITTLE(c + 20,x5); + U32TO8_LITTLE(c + 24,x6); + U32TO8_LITTLE(c + 28,x7); + U32TO8_LITTLE(c + 32,x8); + U32TO8_LITTLE(c + 36,x9); + U32TO8_LITTLE(c + 40,x10); + U32TO8_LITTLE(c + 44,x11); + U32TO8_LITTLE(c + 48,x12); + U32TO8_LITTLE(c + 52,x13); + U32TO8_LITTLE(c + 56,x14); + U32TO8_LITTLE(c + 60,x15); + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) ctarget[i] = c[i]; + } + x->input[12] = j12; + x->input[13] = j13; + return; + } + bytes -= 64; + c += 64; + m += 64; + } +} + +void ECRYPT_decrypt_bytes(ECRYPT_ctx *x,const u8 *c,u8 *m,u32 bytes) +{ + ECRYPT_encrypt_bytes(x,c,m,bytes,8); +} + +void ECRYPT_keystream_bytes(ECRYPT_ctx *x,u8 *stream,u32 bytes) +{ + u32 i; + for (i = 0;i < bytes;++i) stream[i] = 0; + ECRYPT_encrypt_bytes(x,stream,stream,bytes,8); +} diff --git a/lib/luachacha/ecrypt-config.h b/lib/luachacha/ecrypt-config.h new file mode 100644 index 000000000..b0c3ebbe1 --- /dev/null +++ b/lib/luachacha/ecrypt-config.h @@ -0,0 +1,272 @@ +/* ecrypt-config.h */ + +/* *** Normally, it should not be necessary to edit this file. *** */ + +#ifndef ECRYPT_CONFIG +#define ECRYPT_CONFIG + +/* ------------------------------------------------------------------------- */ + +/* Guess the endianness of the target architecture. */ + +/* + * The LITTLE endian machines: + */ +#if defined(__ultrix) /* Older MIPS */ +#define ECRYPT_LITTLE_ENDIAN +#elif defined(__alpha) /* Alpha */ +#define ECRYPT_LITTLE_ENDIAN +#elif defined(i386) /* x86 (gcc) */ +#define ECRYPT_LITTLE_ENDIAN +#elif defined(__i386) /* x86 (gcc) */ +#define ECRYPT_LITTLE_ENDIAN +#elif defined(_M_IX86) /* x86 (MSC, Borland) */ +#define ECRYPT_LITTLE_ENDIAN +#elif defined(_MSC_VER) /* x86 (surely MSC) */ +#define ECRYPT_LITTLE_ENDIAN +#elif defined(__INTEL_COMPILER) /* x86 (surely Intel compiler icl.exe) */ +#define ECRYPT_LITTLE_ENDIAN + +/* + * The BIG endian machines: + */ +#elif defined(sun) /* Newer Sparc's */ +#define ECRYPT_BIG_ENDIAN +#elif defined(__ppc__) /* PowerPC */ +#define ECRYPT_BIG_ENDIAN + +/* + * Finally machines with UNKNOWN endianness: + */ +#elif defined (_AIX) /* RS6000 */ +#define ECRYPT_UNKNOWN +#elif defined(__hpux) /* HP-PA */ +#define ECRYPT_UNKNOWN +#elif defined(__aux) /* 68K */ +#define ECRYPT_UNKNOWN +#elif defined(__dgux) /* 88K (but P6 in latest boxes) */ +#define ECRYPT_UNKNOWN +#elif defined(__sgi) /* Newer MIPS */ +#define ECRYPT_UNKNOWN +#else /* Any other processor */ +#define ECRYPT_UNKNOWN +#endif + +/* ------------------------------------------------------------------------- */ + +/* + * Find minimal-width types to store 8-bit, 16-bit, 32-bit, and 64-bit + * integers. + * + * Note: to enable 64-bit types on 32-bit compilers, it might be + * necessary to switch from ISO C90 mode to ISO C99 mode (e.g., gcc + * -std=c99). + */ + +#include + +/* --- check char --- */ + +#if (UCHAR_MAX / 0xFU > 0xFU) +#ifndef I8T +#define I8T char +#define U8C(v) (v##U) + +#if (UCHAR_MAX == 0xFFU) +#define ECRYPT_I8T_IS_BYTE +#endif + +#endif + +#if (UCHAR_MAX / 0xFFU > 0xFFU) +#ifndef I16T +#define I16T char +#define U16C(v) (v##U) +#endif + +#if (UCHAR_MAX / 0xFFFFU > 0xFFFFU) +#ifndef I32T +#define I32T char +#define U32C(v) (v##U) +#endif + +#if (UCHAR_MAX / 0xFFFFFFFFU > 0xFFFFFFFFU) +#ifndef I64T +#define I64T char +#define U64C(v) (v##U) +#define ECRYPT_NATIVE64 +#endif + +#endif +#endif +#endif +#endif + +/* --- check short --- */ + +#if (USHRT_MAX / 0xFU > 0xFU) +#ifndef I8T +#define I8T short +#define U8C(v) (v##U) + +#if (USHRT_MAX == 0xFFU) +#define ECRYPT_I8T_IS_BYTE +#endif + +#endif + +#if (USHRT_MAX / 0xFFU > 0xFFU) +#ifndef I16T +#define I16T short +#define U16C(v) (v##U) +#endif + +#if (USHRT_MAX / 0xFFFFU > 0xFFFFU) +#ifndef I32T +#define I32T short +#define U32C(v) (v##U) +#endif + +#if (USHRT_MAX / 0xFFFFFFFFU > 0xFFFFFFFFU) +#ifndef I64T +#define I64T short +#define U64C(v) (v##U) +#define ECRYPT_NATIVE64 +#endif + +#endif +#endif +#endif +#endif + +/* --- check int --- */ + +#if (UINT_MAX / 0xFU > 0xFU) +#ifndef I8T +#define I8T int +#define U8C(v) (v##U) + +#if (ULONG_MAX == 0xFFU) +#define ECRYPT_I8T_IS_BYTE +#endif + +#endif + +#if (UINT_MAX / 0xFFU > 0xFFU) +#ifndef I16T +#define I16T int +#define U16C(v) (v##U) +#endif + +#if (UINT_MAX / 0xFFFFU > 0xFFFFU) +#ifndef I32T +#define I32T int +#define U32C(v) (v##U) +#endif + +#if (UINT_MAX / 0xFFFFFFFFU > 0xFFFFFFFFU) +#ifndef I64T +#define I64T int +#define U64C(v) (v##U) +#define ECRYPT_NATIVE64 +#endif + +#endif +#endif +#endif +#endif + +/* --- check long --- */ + +#if (ULONG_MAX / 0xFUL > 0xFUL) +#ifndef I8T +#define I8T long +#define U8C(v) (v##UL) + +#if (ULONG_MAX == 0xFFUL) +#define ECRYPT_I8T_IS_BYTE +#endif + +#endif + +#if (ULONG_MAX / 0xFFUL > 0xFFUL) +#ifndef I16T +#define I16T long +#define U16C(v) (v##UL) +#endif + +#if (ULONG_MAX / 0xFFFFUL > 0xFFFFUL) +#ifndef I32T +#define I32T long +#define U32C(v) (v##UL) +#endif + +#if (ULONG_MAX / 0xFFFFFFFFUL > 0xFFFFFFFFUL) +#ifndef I64T +#define I64T long +#define U64C(v) (v##UL) +#define ECRYPT_NATIVE64 +#endif + +#endif +#endif +#endif +#endif + +/* --- check long long --- */ + +#ifdef ULLONG_MAX + +#if (ULLONG_MAX / 0xFULL > 0xFULL) +#ifndef I8T +#define I8T long long +#define U8C(v) (v##ULL) + +#if (ULLONG_MAX == 0xFFULL) +#define ECRYPT_I8T_IS_BYTE +#endif + +#endif + +#if (ULLONG_MAX / 0xFFULL > 0xFFULL) +#ifndef I16T +#define I16T long long +#define U16C(v) (v##ULL) +#endif + +#if (ULLONG_MAX / 0xFFFFULL > 0xFFFFULL) +#ifndef I32T +#define I32T long long +#define U32C(v) (v##ULL) +#endif + +#if (ULLONG_MAX / 0xFFFFFFFFULL > 0xFFFFFFFFULL) +#ifndef I64T +#define I64T long long +#define U64C(v) (v##ULL) +#endif + +#endif +#endif +#endif +#endif + +#endif + +/* --- check __int64 --- */ + +#ifdef _UI64_MAX + +#ifndef I64T +#if (_UI64_MAX / 0xFFFFFFFFui64 > 0xFFFFFFFFui64) +#define I64T __int64 +#define U64C(v) (v##ui64) +#endif + +#endif + +#endif + +/* ------------------------------------------------------------------------- */ + +#endif diff --git a/lib/luachacha/ecrypt-machine.h b/lib/luachacha/ecrypt-machine.h new file mode 100644 index 000000000..3e550d024 --- /dev/null +++ b/lib/luachacha/ecrypt-machine.h @@ -0,0 +1,46 @@ +/* ecrypt-machine.h */ + +/* + * This file is included by 'ecrypt-portable.h'. It allows to override + * the default macros for specific platforms. Please carefully check + * the machine code generated by your compiler (with optimisations + * turned on) before deciding to edit this file. + */ + +/* ------------------------------------------------------------------------- */ + +#if (defined(ECRYPT_DEFAULT_ROT) && !defined(ECRYPT_MACHINE_ROT)) + +#define ECRYPT_MACHINE_ROT + +#if (defined(WIN32) && defined(_MSC_VER)) + +#undef ROTL32 +#undef ROTR32 +#undef ROTL64 +#undef ROTR64 + +#include + +#define ROTL32(v, n) _lrotl(v, n) +#define ROTR32(v, n) _lrotr(v, n) +#define ROTL64(v, n) _rotl64(v, n) +#define ROTR64(v, n) _rotr64(v, n) + +#endif + +#endif + +/* ------------------------------------------------------------------------- */ + +#if (defined(ECRYPT_DEFAULT_SWAP) && !defined(ECRYPT_MACHINE_SWAP)) + +#define ECRYPT_MACHINE_SWAP + +/* + * If you want to overwrite the default swap macros, put it here. And so on. + */ + +#endif + +/* ------------------------------------------------------------------------- */ diff --git a/lib/luachacha/ecrypt-portable.h b/lib/luachacha/ecrypt-portable.h new file mode 100644 index 000000000..607fee1d0 --- /dev/null +++ b/lib/luachacha/ecrypt-portable.h @@ -0,0 +1,303 @@ +/* ecrypt-portable.h */ + +/* + * WARNING: the conversions defined below are implemented as macros, + * and should be used carefully. They should NOT be used with + * parameters which perform some action. E.g., the following two lines + * are not equivalent: + * + * 1) ++x; y = ROTL32(x, n); + * 2) y = ROTL32(++x, n); + */ + +/* + * *** Please do not edit this file. *** + * + * The default macros can be overridden for specific architectures by + * editing 'ecrypt-machine.h'. + */ + +#ifndef ECRYPT_PORTABLE +#define ECRYPT_PORTABLE + +#include "ecrypt-config.h" + +/* ------------------------------------------------------------------------- */ + +/* + * The following types are defined (if available): + * + * u8: unsigned integer type, at least 8 bits + * u16: unsigned integer type, at least 16 bits + * u32: unsigned integer type, at least 32 bits + * u64: unsigned integer type, at least 64 bits + * + * s8, s16, s32, s64 -> signed counterparts of u8, u16, u32, u64 + * + * The selection of minimum-width integer types is taken care of by + * 'ecrypt-config.h'. Note: to enable 64-bit types on 32-bit + * compilers, it might be necessary to switch from ISO C90 mode to ISO + * C99 mode (e.g., gcc -std=c99). + */ + +#ifdef I8T +typedef signed I8T s8; +typedef unsigned I8T u8; +#endif + +#ifdef I16T +typedef signed I16T s16; +typedef unsigned I16T u16; +#endif + +#ifdef I32T +typedef signed I32T s32; +typedef unsigned I32T u32; +#endif + +#ifdef I64T +typedef signed I64T s64; +typedef unsigned I64T u64; +#endif + +/* + * The following macros are used to obtain exact-width results. + */ + +#define U8V(v) ((u8)(v) & U8C(0xFF)) +#define U16V(v) ((u16)(v) & U16C(0xFFFF)) +#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF)) +#define U64V(v) ((u64)(v) & U64C(0xFFFFFFFFFFFFFFFF)) + +/* ------------------------------------------------------------------------- */ + +/* + * The following macros return words with their bits rotated over n + * positions to the left/right. + */ + +#define ECRYPT_DEFAULT_ROT + +#define ROTL8(v, n) \ + (U8V((v) << (n)) | ((v) >> (8 - (n)))) + +#define ROTL16(v, n) \ + (U16V((v) << (n)) | ((v) >> (16 - (n)))) + +#define ROTL32(v, n) \ + (U32V((v) << (n)) | ((v) >> (32 - (n)))) + +#define ROTL64(v, n) \ + (U64V((v) << (n)) | ((v) >> (64 - (n)))) + +#define ROTR8(v, n) ROTL8(v, 8 - (n)) +#define ROTR16(v, n) ROTL16(v, 16 - (n)) +#define ROTR32(v, n) ROTL32(v, 32 - (n)) +#define ROTR64(v, n) ROTL64(v, 64 - (n)) + +#include "ecrypt-machine.h" + +/* ------------------------------------------------------------------------- */ + +/* + * The following macros return a word with bytes in reverse order. + */ + +#define ECRYPT_DEFAULT_SWAP + +#define SWAP16(v) \ + ROTL16(v, 8) + +#define SWAP32(v) \ + ((ROTL32(v, 8) & U32C(0x00FF00FF)) | \ + (ROTL32(v, 24) & U32C(0xFF00FF00))) + +#ifdef ECRYPT_NATIVE64 +#define SWAP64(v) \ + ((ROTL64(v, 8) & U64C(0x000000FF000000FF)) | \ + (ROTL64(v, 24) & U64C(0x0000FF000000FF00)) | \ + (ROTL64(v, 40) & U64C(0x00FF000000FF0000)) | \ + (ROTL64(v, 56) & U64C(0xFF000000FF000000))) +#else +#define SWAP64(v) \ + (((u64)SWAP32(U32V(v)) << 32) | (u64)SWAP32(U32V(v >> 32))) +#endif + +#include "ecrypt-machine.h" + +#define ECRYPT_DEFAULT_WTOW + +#ifdef ECRYPT_LITTLE_ENDIAN +#define U16TO16_LITTLE(v) (v) +#define U32TO32_LITTLE(v) (v) +#define U64TO64_LITTLE(v) (v) + +#define U16TO16_BIG(v) SWAP16(v) +#define U32TO32_BIG(v) SWAP32(v) +#define U64TO64_BIG(v) SWAP64(v) +#endif + +#ifdef ECRYPT_BIG_ENDIAN +#define U16TO16_LITTLE(v) SWAP16(v) +#define U32TO32_LITTLE(v) SWAP32(v) +#define U64TO64_LITTLE(v) SWAP64(v) + +#define U16TO16_BIG(v) (v) +#define U32TO32_BIG(v) (v) +#define U64TO64_BIG(v) (v) +#endif + +#include "ecrypt-machine.h" + +/* + * The following macros load words from an array of bytes with + * different types of endianness, and vice versa. + */ + +#define ECRYPT_DEFAULT_BTOW + +#if (!defined(ECRYPT_UNKNOWN) && defined(ECRYPT_I8T_IS_BYTE)) + +#define U8TO16_LITTLE(p) U16TO16_LITTLE(((u16*)(p))[0]) +#define U8TO32_LITTLE(p) U32TO32_LITTLE(((u32*)(p))[0]) +#define U8TO64_LITTLE(p) U64TO64_LITTLE(((u64*)(p))[0]) + +#define U8TO16_BIG(p) U16TO16_BIG(((u16*)(p))[0]) +#define U8TO32_BIG(p) U32TO32_BIG(((u32*)(p))[0]) +#define U8TO64_BIG(p) U64TO64_BIG(((u64*)(p))[0]) + +#define U16TO8_LITTLE(p, v) (((u16*)(p))[0] = U16TO16_LITTLE(v)) +#define U32TO8_LITTLE(p, v) (((u32*)(p))[0] = U32TO32_LITTLE(v)) +#define U64TO8_LITTLE(p, v) (((u64*)(p))[0] = U64TO64_LITTLE(v)) + +#define U16TO8_BIG(p, v) (((u16*)(p))[0] = U16TO16_BIG(v)) +#define U32TO8_BIG(p, v) (((u32*)(p))[0] = U32TO32_BIG(v)) +#define U64TO8_BIG(p, v) (((u64*)(p))[0] = U64TO64_BIG(v)) + +#else + +#define U8TO16_LITTLE(p) \ + (((u16)((p)[0]) ) | \ + ((u16)((p)[1]) << 8)) + +#define U8TO32_LITTLE(p) \ + (((u32)((p)[0]) ) | \ + ((u32)((p)[1]) << 8) | \ + ((u32)((p)[2]) << 16) | \ + ((u32)((p)[3]) << 24)) + +#ifdef ECRYPT_NATIVE64 +#define U8TO64_LITTLE(p) \ + (((u64)((p)[0]) ) | \ + ((u64)((p)[1]) << 8) | \ + ((u64)((p)[2]) << 16) | \ + ((u64)((p)[3]) << 24) | \ + ((u64)((p)[4]) << 32) | \ + ((u64)((p)[5]) << 40) | \ + ((u64)((p)[6]) << 48) | \ + ((u64)((p)[7]) << 56)) +#else +#define U8TO64_LITTLE(p) \ + ((u64)U8TO32_LITTLE(p) | ((u64)U8TO32_LITTLE((p) + 4) << 32)) +#endif + +#define U8TO16_BIG(p) \ + (((u16)((p)[0]) << 8) | \ + ((u16)((p)[1]) )) + +#define U8TO32_BIG(p) \ + (((u32)((p)[0]) << 24) | \ + ((u32)((p)[1]) << 16) | \ + ((u32)((p)[2]) << 8) | \ + ((u32)((p)[3]) )) + +#ifdef ECRYPT_NATIVE64 +#define U8TO64_BIG(p) \ + (((u64)((p)[0]) << 56) | \ + ((u64)((p)[1]) << 48) | \ + ((u64)((p)[2]) << 40) | \ + ((u64)((p)[3]) << 32) | \ + ((u64)((p)[4]) << 24) | \ + ((u64)((p)[5]) << 16) | \ + ((u64)((p)[6]) << 8) | \ + ((u64)((p)[7]) )) +#else +#define U8TO64_BIG(p) \ + (((u64)U8TO32_BIG(p) << 32) | (u64)U8TO32_BIG((p) + 4)) +#endif + +#define U16TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + } while (0) + +#define U32TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + } while (0) + +#ifdef ECRYPT_NATIVE64 +#define U64TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + (p)[4] = U8V((v) >> 32); \ + (p)[5] = U8V((v) >> 40); \ + (p)[6] = U8V((v) >> 48); \ + (p)[7] = U8V((v) >> 56); \ + } while (0) +#else +#define U64TO8_LITTLE(p, v) \ + do { \ + U32TO8_LITTLE((p), U32V((v) )); \ + U32TO8_LITTLE((p) + 4, U32V((v) >> 32)); \ + } while (0) +#endif + +#define U16TO8_BIG(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + } while (0) + +#define U32TO8_BIG(p, v) \ + do { \ + (p)[0] = U8V((v) >> 24); \ + (p)[1] = U8V((v) >> 16); \ + (p)[2] = U8V((v) >> 8); \ + (p)[3] = U8V((v) ); \ + } while (0) + +#ifdef ECRYPT_NATIVE64 +#define U64TO8_BIG(p, v) \ + do { \ + (p)[0] = U8V((v) >> 56); \ + (p)[1] = U8V((v) >> 48); \ + (p)[2] = U8V((v) >> 40); \ + (p)[3] = U8V((v) >> 32); \ + (p)[4] = U8V((v) >> 24); \ + (p)[5] = U8V((v) >> 16); \ + (p)[6] = U8V((v) >> 8); \ + (p)[7] = U8V((v) ); \ + } while (0) +#else +#define U64TO8_BIG(p, v) \ + do { \ + U32TO8_BIG((p), U32V((v) >> 32)); \ + U32TO8_BIG((p) + 4, U32V((v) )); \ + } while (0) +#endif + +#endif + +#include "ecrypt-machine.h" + +/* ------------------------------------------------------------------------- */ + +#endif diff --git a/lib/luachacha/ecrypt-sync.h b/lib/luachacha/ecrypt-sync.h new file mode 100644 index 000000000..8c780d338 --- /dev/null +++ b/lib/luachacha/ecrypt-sync.h @@ -0,0 +1,290 @@ +/* ecrypt-sync.h */ + +/* + * Header file for synchronous stream ciphers without authentication + * mechanism. + * + * *** Please only edit parts marked with "[edit]". *** + */ + +#ifndef ECRYPT_SYNC +#define ECRYPT_SYNC + +#include "ecrypt-portable.h" + +/* ------------------------------------------------------------------------- */ + +/* Cipher parameters */ + +/* + * The name of your cipher. + */ +#define ECRYPT_NAME "ChaCha8" +#define ECRYPT_PROFILE "_____" + +/* + * Specify which key and IV sizes are supported by your cipher. A user + * should be able to enumerate the supported sizes by running the + * following code: + * + * for (i = 0; ECRYPT_KEYSIZE(i) <= ECRYPT_MAXKEYSIZE; ++i) + * { + * keysize = ECRYPT_KEYSIZE(i); + * + * ... + * } + * + * All sizes are in bits. + */ + +#define ECRYPT_MAXKEYSIZE 256 /* [edit] */ +#define ECRYPT_KEYSIZE(i) (128 + (i)*128) /* [edit] */ + +#define ECRYPT_MAXIVSIZE 64 /* [edit] */ +#define ECRYPT_IVSIZE(i) (64 + (i)*64) /* [edit] */ + +/* ------------------------------------------------------------------------- */ + +/* Data structures */ + +/* + * ECRYPT_ctx is the structure containing the representation of the + * internal state of your cipher. + */ + +typedef struct +{ + u32 input[16]; /* could be compressed */ + /* + * [edit] + * + * Put here all state variable needed during the encryption process. + */ +} ECRYPT_ctx; + +/* ------------------------------------------------------------------------- */ + +/* Mandatory functions */ + +/* + * Key and message independent initialization. This function will be + * called once when the program starts (e.g., to build expanded S-box + * tables). + */ +void ECRYPT_init(); + +/* + * Key setup. It is the user's responsibility to select the values of + * keysize and ivsize from the set of supported values specified + * above. + */ +void ECRYPT_keysetup( + ECRYPT_ctx* ctx, + const u8* key, + u32 keysize, /* Key size in bits. */ + u32 ivsize); /* IV size in bits. */ + +/* + * IV setup. After having called ECRYPT_keysetup(), the user is + * allowed to call ECRYPT_ivsetup() different times in order to + * encrypt/decrypt different messages with the same key but different + * IV's. + */ +void ECRYPT_ivsetup( + ECRYPT_ctx* ctx, + const u8* iv, + const u8* counter); + +/* + * IV setup for the IETF version of ChaCha (RFC 7539) + */ +void ECRYPT_IETF_ivsetup( + ECRYPT_ctx* ctx, + const u8* iv, + const u8* counter); + +/* + * Encryption/decryption of arbitrary length messages. + * + * For efficiency reasons, the API provides two types of + * encrypt/decrypt functions. The ECRYPT_encrypt_bytes() function + * (declared here) encrypts byte strings of arbitrary length, while + * the ECRYPT_encrypt_blocks() function (defined later) only accepts + * lengths which are multiples of ECRYPT_BLOCKLENGTH. + * + * The user is allowed to make multiple calls to + * ECRYPT_encrypt_blocks() to incrementally encrypt a long message, + * but he is NOT allowed to make additional encryption calls once he + * has called ECRYPT_encrypt_bytes() (unless he starts a new message + * of course). For example, this sequence of calls is acceptable: + * + * ECRYPT_keysetup(); + * + * ECRYPT_ivsetup(); + * ECRYPT_encrypt_blocks(); + * ECRYPT_encrypt_blocks(); + * ECRYPT_encrypt_bytes(); + * + * ECRYPT_ivsetup(); + * ECRYPT_encrypt_blocks(); + * ECRYPT_encrypt_blocks(); + * + * ECRYPT_ivsetup(); + * ECRYPT_encrypt_bytes(); + * + * The following sequence is not: + * + * ECRYPT_keysetup(); + * ECRYPT_ivsetup(); + * ECRYPT_encrypt_blocks(); + * ECRYPT_encrypt_bytes(); + * ECRYPT_encrypt_blocks(); + */ + +void ECRYPT_encrypt_bytes( + ECRYPT_ctx* ctx, + const u8* plaintext, + u8* ciphertext, + u32 msglen, /* Message length in bytes. */ + int rounds +); + +void ECRYPT_decrypt_bytes( + ECRYPT_ctx* ctx, + const u8* ciphertext, + u8* plaintext, + u32 msglen); /* Message length in bytes. */ + +/* ------------------------------------------------------------------------- */ + +/* Optional features */ + +/* + * For testing purposes it can sometimes be useful to have a function + * which immediately generates keystream without having to provide it + * with a zero plaintext. If your cipher cannot provide this function + * (e.g., because it is not strictly a synchronous cipher), please + * reset the ECRYPT_GENERATES_KEYSTREAM flag. + */ + +#define ECRYPT_GENERATES_KEYSTREAM +#ifdef ECRYPT_GENERATES_KEYSTREAM + +void ECRYPT_keystream_bytes( + ECRYPT_ctx* ctx, + u8* keystream, + u32 length); /* Length of keystream in bytes. */ + +#endif + +/* ------------------------------------------------------------------------- */ + +/* Optional optimizations */ + +/* + * By default, the functions in this section are implemented using + * calls to functions declared above. However, you might want to + * implement them differently for performance reasons. + */ + +/* + * All-in-one encryption/decryption of (short) packets. + * + * The default definitions of these functions can be found in + * "ecrypt-sync.c". If you want to implement them differently, please + * undef the ECRYPT_USES_DEFAULT_ALL_IN_ONE flag. + */ +#define ECRYPT_USES_DEFAULT_ALL_IN_ONE /* [edit] */ + +void ECRYPT_encrypt_packet( + ECRYPT_ctx* ctx, + const u8* iv, + const u8* plaintext, + u8* ciphertext, + u32 msglen); + +void ECRYPT_decrypt_packet( + ECRYPT_ctx* ctx, + const u8* iv, + const u8* ciphertext, + u8* plaintext, + u32 msglen); + +/* + * Encryption/decryption of blocks. + * + * By default, these functions are defined as macros. If you want to + * provide a different implementation, please undef the + * ECRYPT_USES_DEFAULT_BLOCK_MACROS flag and implement the functions + * declared below. + */ + +#define ECRYPT_BLOCKLENGTH 64 /* [edit] */ + +#define ECRYPT_USES_DEFAULT_BLOCK_MACROS /* [edit] */ +#ifdef ECRYPT_USES_DEFAULT_BLOCK_MACROS + +#define ECRYPT_encrypt_blocks(ctx, plaintext, ciphertext, blocks) \ + ECRYPT_encrypt_bytes(ctx, plaintext, ciphertext, \ + (blocks) * ECRYPT_BLOCKLENGTH) + +#define ECRYPT_decrypt_blocks(ctx, ciphertext, plaintext, blocks) \ + ECRYPT_decrypt_bytes(ctx, ciphertext, plaintext, \ + (blocks) * ECRYPT_BLOCKLENGTH) + +#ifdef ECRYPT_GENERATES_KEYSTREAM + +#define ECRYPT_keystream_blocks(ctx, keystream, blocks) \ + ECRYPT_keystream_bytes(ctx, keystream, \ + (blocks) * ECRYPT_BLOCKLENGTH) + +#endif + +#else + +void ECRYPT_encrypt_blocks( + ECRYPT_ctx* ctx, + const u8* plaintext, + u8* ciphertext, + u32 blocks); /* Message length in blocks. */ + +void ECRYPT_decrypt_blocks( + ECRYPT_ctx* ctx, + const u8* ciphertext, + u8* plaintext, + u32 blocks); /* Message length in blocks. */ + +#ifdef ECRYPT_GENERATES_KEYSTREAM + +void ECRYPT_keystream_blocks( + ECRYPT_ctx* ctx, + const u8* keystream, + u32 blocks); /* Keystream length in blocks. */ + +#endif + +#endif + +/* + * If your cipher can be implemented in different ways, you can use + * the ECRYPT_VARIANT parameter to allow the user to choose between + * them at compile time (e.g., gcc -DECRYPT_VARIANT=3 ...). Please + * only use this possibility if you really think it could make a + * significant difference and keep the number of variants + * (ECRYPT_MAXVARIANT) as small as possible (definitely not more than + * 10). Note also that all variants should have exactly the same + * external interface (i.e., the same ECRYPT_BLOCKLENGTH, etc.). + */ +#define ECRYPT_MAXVARIANT 1 /* [edit] */ + +#ifndef ECRYPT_VARIANT +#define ECRYPT_VARIANT 1 +#endif + +#if (ECRYPT_VARIANT > ECRYPT_MAXVARIANT) +#error this variant does not exist +#endif + +/* ------------------------------------------------------------------------- */ + +#endif diff --git a/lib/luachacha/lchacha.c b/lib/luachacha/lchacha.c new file mode 100644 index 000000000..ae5308308 --- /dev/null +++ b/lib/luachacha/lchacha.c @@ -0,0 +1,79 @@ +#include +#include + +#include +#include +#include + +#include "ecrypt-sync.h" + +#if LUA_VERSION_NUM == 501 +#define l_setfuncs(L, funcs) luaL_register(L, NULL, funcs) +#else +#define l_setfuncs(L, funcs) luaL_setfuncs(L, funcs, 0) +#endif + +static int chacha_generic_crypt(lua_State *L, bool is_ietf) +{ + ECRYPT_ctx ctx; + const char *key, *iv, *plaintext, *counter; + char *ciphertext; + size_t keysize, ivsize, msglen, countersize; + + int rounds = luaL_checkinteger(L, 1); + + /* IETF only normalizes ChaCha 20. */ + if (rounds != 20 && (is_ietf || (rounds % 2 != 0))) + return luaL_error(L, "invalid number of rounds: %d", rounds); + + luaL_checktype(L, 2, LUA_TSTRING); + luaL_checktype(L, 3, LUA_TSTRING); + luaL_checktype(L, 4, LUA_TSTRING); + + key = lua_tolstring(L, 2, &keysize); + iv = lua_tolstring(L, 3, &ivsize); + plaintext = lua_tolstring(L, 4, &msglen); + counter = luaL_optlstring(L, 5, NULL, &countersize); + + if (ivsize != (is_ietf ? 12 : 8)) + return luaL_error(L, "invalid IV size: %dB", (int)ivsize); + + if (keysize != 32 && (is_ietf || keysize != 16)) + return luaL_error(L, "invalid key size: %dB", (int)keysize); + + if (counter && countersize != (is_ietf ? 4 : 8)) + return luaL_error(L, "invalid counter size: %dB", (int)countersize); + + if (msglen == 0) { lua_pushlstring(L, "", 0); return 1; } + + ciphertext = malloc(msglen); + if (!ciphertext) return luaL_error(L, "OOM"); + + /* keysize and ivsize are in bits */ + ECRYPT_keysetup(&ctx, (u8*)key, 8 * keysize, 8 * ivsize); + if (is_ietf) ECRYPT_IETF_ivsetup(&ctx, (u8*)iv, (u8*)counter); + else ECRYPT_ivsetup(&ctx, (u8*)iv, (u8*)counter); + ECRYPT_encrypt_bytes(&ctx, (u8*)plaintext, (u8*)ciphertext, msglen, rounds); + + lua_pushlstring(L, ciphertext, msglen); + free(ciphertext); + return 1; +} + +static int chacha_ref_crypt(lua_State *L) +{ return chacha_generic_crypt(L, false); } + +static int chacha_ietf_crypt(lua_State *L) +{ return chacha_generic_crypt(L, true); } + +int luaopen_chacha(lua_State *L) +{ + struct luaL_Reg l[] = { + { "ref_crypt", chacha_ref_crypt }, + { "ietf_crypt", chacha_ietf_crypt }, + { NULL, NULL } + }; + lua_newtable(L); + l_setfuncs(L, l); + return 1; +} diff --git a/lib/luautf8/CMakeLists.txt b/lib/luautf8/CMakeLists.txt new file mode 100644 index 000000000..c6d099c0a --- /dev/null +++ b/lib/luautf8/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(luautf8 STATIC lutf8lib.c) +target_include_directories(luautf8 PUBLIC ${LUA_INCLUDE_DIR}) diff --git a/lib/luautf8/lutf8lib.c b/lib/luautf8/lutf8lib.c new file mode 100644 index 000000000..bbded71c0 --- /dev/null +++ b/lib/luautf8/lutf8lib.c @@ -0,0 +1,1300 @@ +/* vim: set ft=c nu et sw=2 fdc=2 fdm=syntax : */ +#define LUA_LIB +#include +#include +#include + + +#include +#include + +#include "unidata.h" + +/* UTF-8 string operations */ + +#define UTF8_BUFFSZ 8 +#define UTF8_MAX 0x7FFFFFFFu +#define UTF8_MAXCP 0x10FFFFu +#define iscont(p) ((*(p) & 0xC0) == 0x80) +#define CAST(tp,expr) ((tp)(expr)) + +#ifndef LUA_QL +# define LUA_QL(x) "'" x "'" +#endif + +static int utf8_invalid (utfint ch) +{ return (ch > UTF8_MAXCP || (0xD800u <= ch && ch <= 0xDFFFu)); } + +static size_t utf8_encode (char *buff, utfint x) { + int n = 1; /* number of bytes put in buffer (backwards) */ + lua_assert(x <= UTF8_MAX); + if (x < 0x80) /* ascii? */ + buff[UTF8_BUFFSZ - 1] = x & 0x7F; + else { /* need continuation bytes */ + utfint mfb = 0x3f; /* maximum that fits in first byte */ + do { /* add continuation bytes */ + buff[UTF8_BUFFSZ - (n++)] = 0x80 | (x & 0x3f); + x >>= 6; /* remove added bits */ + mfb >>= 1; /* now there is one less bit available in first byte */ + } while (x > mfb); /* still needs continuation byte? */ + buff[UTF8_BUFFSZ - n] = ((~mfb << 1) | x) & 0xFF; /* add first byte */ + } + return n; +} + +static const char *utf8_decode (const char *s, utfint *val, int strict) { + static const utfint limits[] = + {~0u, 0x80u, 0x800u, 0x10000u, 0x200000u, 0x4000000u}; + unsigned int c = (unsigned char)s[0]; + utfint res = 0; /* final result */ + if (c < 0x80) /* ascii? */ + res = c; + else { + int count = 0; /* to count number of continuation bytes */ + for (; c & 0x40; c <<= 1) { /* while it needs continuation bytes... */ + unsigned int cc = (unsigned char)s[++count]; /* read next byte */ + if ((cc & 0xC0) != 0x80) /* not a continuation byte? */ + return NULL; /* invalid byte sequence */ + res = (res << 6) | (cc & 0x3F); /* add lower 6 bits from cont. byte */ + } + res |= ((utfint)(c & 0x7F) << (count * 5)); /* add first byte */ + if (count > 5 || res > UTF8_MAX || res < limits[count]) + return NULL; /* invalid byte sequence */ + s += count; /* skip continuation bytes read */ + } + if (strict) { + /* check for invalid code points; too large or surrogates */ + if (res > UTF8_MAXCP || (0xD800u <= res && res <= 0xDFFFu)) + return NULL; + } + if (val) *val = res; + return s + 1; /* +1 to include first byte */ +} + +static const char *utf8_prev (const char *s, const char *e) { + while (s < e && iscont(e - 1)) --e; + return s < e ? e - 1 : s; +} + +static const char *utf8_next (const char *s, const char *e) { + while (s < e && iscont(s + 1)) ++s; + return s < e ? s + 1 : e; +} + +static size_t utf8_length (const char *s, const char *e) { + size_t i; + for (i = 0; s < e; ++i) + s = utf8_next(s, e); + return i; +} + +static const char *utf8_offset (const char *s, const char *e, lua_Integer offset, lua_Integer idx) { + const char *p = s + offset - 1; + if (idx >= 0) { + while (p < e && idx > 0) + p = utf8_next(p, e), --idx; + return idx == 0 ? p : NULL; + } else { + while (s < p && idx < 0) + p = utf8_prev(s, p), ++idx; + return idx == 0 ? p : NULL; + } +} + +static const char *utf8_relat (const char *s, const char *e, int idx) { + return idx >= 0 ? + utf8_offset(s, e, 1, idx - 1) : + utf8_offset(s, e, e-s+1, idx); +} + +static int utf8_range(const char *s, const char *e, lua_Integer *i, lua_Integer *j) { + const char *ps = utf8_relat(s, e, CAST(int, *i)); + const char *pe = utf8_relat(s, e, CAST(int, *j)); + *i = (ps ? ps : (*i > 0 ? e : s)) - s; + *j = (pe ? utf8_next(pe, e) : (*j > 0 ? e : s)) - s; + return *i < *j; +} + + +/* Unicode character categories */ + +#define table_size(t) (sizeof(t)/sizeof((t)[0])) + +#define utf8_categories(X) \ + X('a', alpha) \ + X('c', cntrl) \ + X('d', digit) \ + X('l', lower) \ + X('p', punct) \ + X('s', space) \ + X('t', compose) \ + X('u', upper) \ + X('x', xdigit) + +#define utf8_converters(X) \ + X(lower) \ + X(upper) \ + X(title) \ + X(fold) + +static int find_in_range (range_table *t, size_t size, utfint ch) { + size_t begin, end; + + begin = 0; + end = size; + + while (begin < end) { + size_t mid = (begin + end) / 2; + if (t[mid].last < ch) + begin = mid + 1; + else if (t[mid].first > ch) + end = mid; + else + return (ch - t[mid].first) % t[mid].step == 0; + } + + return 0; +} + +static int convert_char (conv_table *t, size_t size, utfint ch) { + size_t begin, end; + + begin = 0; + end = size; + + while (begin < end) { + size_t mid = (begin + end) / 2; + if (t[mid].last < ch) + begin = mid + 1; + else if (t[mid].first > ch) + end = mid; + else if ((ch - t[mid].first) % t[mid].step == 0) + return ch + t[mid].offset; + else + return ch; + } + + return ch; +} + +#define define_category(cls, name) static int utf8_is##name (utfint ch)\ +{ return find_in_range(name##_table, table_size(name##_table), ch); } +#define define_converter(name) static utfint utf8_to##name (utfint ch) \ +{ return convert_char(to##name##_table, table_size(to##name##_table), ch); } +utf8_categories(define_category) +utf8_converters(define_converter) +#undef define_category +#undef define_converter + +static int utf8_isgraph (utfint ch) { + if (find_in_range(space_table, table_size(space_table), ch)) + return 0; + if (find_in_range(graph_table, table_size(graph_table), ch)) + return 1; + if (find_in_range(compose_table, table_size(compose_table), ch)) + return 1; + return 0; +} + +static int utf8_isalnum (utfint ch) { + if (find_in_range(alpha_table, table_size(alpha_table), ch)) + return 1; + if (find_in_range(alnum_extend_table, table_size(alnum_extend_table), ch)) + return 1; + return 0; +} + +static int utf8_width (utfint ch, int ambi_is_single) { + if (find_in_range(doublewidth_table, table_size(doublewidth_table), ch)) + return 2; + if (find_in_range(ambiwidth_table, table_size(ambiwidth_table), ch)) + return ambi_is_single ? 1 : 2; + if (find_in_range(compose_table, table_size(compose_table), ch)) + return 0; + if (find_in_range(unprintable_table, table_size(unprintable_table), ch)) + return 0; + return 1; +} + + +/* string module compatible interface */ + +static int typeerror (lua_State *L, int idx, const char *tname) +{ return luaL_error(L, "%s expected, got %s", tname, luaL_typename(L, idx)); } + +static const char *check_utf8 (lua_State *L, int idx, const char **end) { + size_t len; + const char *s = luaL_checklstring(L, idx, &len); + if (end) *end = s+len; + return s; +} + +static const char *to_utf8 (lua_State *L, int idx, const char **end) { + size_t len; + const char *s = lua_tolstring(L, idx, &len); + if (end) *end = s+len; + return s; +} + +static const char *utf8_safe_decode (lua_State *L, const char *p, utfint *pval) { + p = utf8_decode(p, pval, 0); + if (p == NULL) luaL_error(L, "invalid UTF-8 code"); + return p; +} + +static void add_utf8char (luaL_Buffer *b, utfint ch) { + char buff[UTF8_BUFFSZ]; + size_t n = utf8_encode(buff, ch); + luaL_addlstring(b, buff+UTF8_BUFFSZ-n, n); +} + +static lua_Integer byte_relat (lua_Integer pos, size_t len) { + if (pos >= 0) return pos; + else if (0u - (size_t)pos > len) return 0; + else return (lua_Integer)len + pos + 1; +} + +static int Lutf8_len (lua_State *L) { + size_t len, n; + const char *s = luaL_checklstring(L, 1, &len), *p, *e; + lua_Integer posi = byte_relat(luaL_optinteger(L, 2, 1), len); + lua_Integer pose = byte_relat(luaL_optinteger(L, 3, -1), len); + int lax = lua_toboolean(L, 4); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 2, + "initial position out of string"); + luaL_argcheck(L, --pose < (lua_Integer)len, 3, + "final position out of string"); + for (n = 0, p=s+posi, e=s+pose+1; p < e; ++n) { + if (lax) + p = utf8_next(p, e); + else { + utfint ch; + const char *np = utf8_decode(p, &ch, !lax); + if (np == NULL || utf8_invalid(ch)) { + lua_pushnil(L); + lua_pushinteger(L, p - s + 1); + return 2; + } + p = np; + } + } + lua_pushinteger(L, n); + return 1; +} + +static int Lutf8_sub (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + lua_Integer posi = luaL_checkinteger(L, 2); + lua_Integer pose = luaL_optinteger(L, 3, -1); + if (utf8_range(s, e, &posi, &pose)) + lua_pushlstring(L, s+posi, pose-posi); + else + lua_pushliteral(L, ""); + return 1; +} + +static int Lutf8_reverse (lua_State *L) { + luaL_Buffer b; + const char *prev, *pprev, *ends, *e, *s = check_utf8(L, 1, &e); + (void) ends; + int lax = lua_toboolean(L, 2); + luaL_buffinit(L, &b); + if (lax) { + for (prev = e; s < prev; e = prev) { + prev = utf8_prev(s, prev); + luaL_addlstring(&b, prev, e-prev); + } + } else { + for (prev = e; s < prev; prev = pprev) { + utfint code = 0; + ends = utf8_safe_decode(L, pprev = utf8_prev(s, prev), &code); + assert(ends == prev); + if (utf8_invalid(code)) + return luaL_error(L, "invalid UTF-8 code"); + if (!utf8_iscompose(code)) { + luaL_addlstring(&b, pprev, e-pprev); + e = pprev; + } + } + } + luaL_pushresult(&b); + return 1; +} + +static int Lutf8_byte (lua_State *L) { + size_t n = 0; + const char *e, *s = check_utf8(L, 1, &e); + lua_Integer posi = luaL_optinteger(L, 2, 1); + lua_Integer pose = luaL_optinteger(L, 3, posi); + if (utf8_range(s, e, &posi, &pose)) { + for (e = s + pose, s = s + posi; s < e; ++n) { + utfint ch = 0; + s = utf8_safe_decode(L, s, &ch); + lua_pushinteger(L, ch); + } + } + return CAST(int, n); +} + +static int Lutf8_codepoint (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + size_t len = e-s; + lua_Integer posi = byte_relat(luaL_optinteger(L, 2, 1), len); + lua_Integer pose = byte_relat(luaL_optinteger(L, 3, posi), len); + int lax = lua_toboolean(L, 4); + int n; + const char *se; + luaL_argcheck(L, posi >= 1, 2, "out of range"); + luaL_argcheck(L, pose <= (lua_Integer)len, 3, "out of range"); + if (posi > pose) return 0; /* empty interval; return no values */ + if (pose - posi >= INT_MAX) /* (lua_Integer -> int) overflow? */ + return luaL_error(L, "string slice too long"); + n = (int)(pose - posi + 1); + luaL_checkstack(L, n, "string slice too long"); + n = 0; /* count the number of returns */ + se = s + pose; /* string end */ + for (n = 0, s += posi - 1; s < se;) { + utfint code = 0; + s = utf8_safe_decode(L, s, &code); + if (!lax && utf8_invalid(code)) + return luaL_error(L, "invalid UTF-8 code"); + lua_pushinteger(L, code); + n++; + } + return n; +} + +static int Lutf8_char (lua_State *L) { + int i, n = lua_gettop(L); /* number of arguments */ + luaL_Buffer b; + luaL_buffinit(L, &b); + for (i = 1; i <= n; ++i) { + lua_Integer code = luaL_checkinteger(L, i); + luaL_argcheck(L, code <= UTF8_MAXCP, i, "value out of range"); + add_utf8char(&b, CAST(utfint, code)); + } + luaL_pushresult(&b); + return 1; +} + +#define bind_converter(name) \ +static int Lutf8_##name (lua_State *L) { \ + int t = lua_type(L, 1); \ + if (t == LUA_TNUMBER) \ + lua_pushinteger(L, utf8_to##name(CAST(utfint, lua_tointeger(L, 1)))); \ + else if (t == LUA_TSTRING) { \ + luaL_Buffer b; \ + const char *e, *s = to_utf8(L, 1, &e); \ + luaL_buffinit(L, &b); \ + while (s < e) { \ + utfint ch = 0; \ + s = utf8_safe_decode(L, s, &ch); \ + add_utf8char(&b, utf8_to##name(ch)); \ + } \ + luaL_pushresult(&b); \ + } \ + else return typeerror(L, 1, "number/string"); \ + return 1; \ +} +utf8_converters(bind_converter) +#undef bind_converter + + +/* unicode extra interface */ + +static const char *parse_escape (lua_State *L, const char *s, const char *e, int hex, utfint *pch) { + utfint code = 0; + int in_bracket = 0; + if (*s == '{') ++s, in_bracket = 1; + for (; s < e; ++s) { + utfint ch = (unsigned char)*s; + if (ch >= '0' && ch <= '9') ch = ch - '0'; + else if (hex && ch >= 'A' && ch <= 'F') ch = 10 + (ch - 'A'); + else if (hex && ch >= 'a' && ch <= 'f') ch = 10 + (ch - 'a'); + else if (!in_bracket) break; + else if (ch == '}') { ++s; break; } + else luaL_error(L, "invalid escape '%c'", ch); + code *= hex ? 16 : 10; + code += ch; + } + *pch = code; + return s; +} + +static int Lutf8_escape (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + luaL_Buffer b; + luaL_buffinit(L, &b); + while (s < e) { + utfint ch = 0; + s = utf8_safe_decode(L, s, &ch); + if (ch == '%') { + int hex = 0; + switch (*s) { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': case '{': + break; + case 'x': case 'X': hex = 1; /* fall through */ + case 'u': case 'U': if (s+1 < e) { ++s; break; } + /* fall through */ + default: + s = utf8_safe_decode(L, s, &ch); + goto next; + } + s = parse_escape(L, s, e, hex, &ch); + } +next: + add_utf8char(&b, ch); + } + luaL_pushresult(&b); + return 1; +} + +static int Lutf8_insert (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + size_t sublen; + const char *subs; + luaL_Buffer b; + int nargs = 2; + const char *first = e; + if (lua_type(L, 2) == LUA_TNUMBER) { + int idx = (int)lua_tointeger(L, 2); + if (idx != 0) first = utf8_relat(s, e, idx); + luaL_argcheck(L, first, 2, "invalid index"); + ++nargs; + } + subs = luaL_checklstring(L, nargs, &sublen); + luaL_buffinit(L, &b); + luaL_addlstring(&b, s, first-s); + luaL_addlstring(&b, subs, sublen); + luaL_addlstring(&b, first, e-first); + luaL_pushresult(&b); + return 1; +} + +static int Lutf8_remove (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + lua_Integer posi = luaL_optinteger(L, 2, -1); + lua_Integer pose = luaL_optinteger(L, 3, -1); + if (!utf8_range(s, e, &posi, &pose)) + lua_settop(L, 1); + else { + luaL_Buffer b; + luaL_buffinit(L, &b); + luaL_addlstring(&b, s, posi); + luaL_addlstring(&b, s+pose, e-s-pose); + luaL_pushresult(&b); + } + return 1; +} + +static int push_offset (lua_State *L, const char *s, const char *e, lua_Integer offset, lua_Integer idx) { + utfint ch = 0; + const char *p; + if (idx != 0) + p = utf8_offset(s, e, offset, idx); + else if (p = s+offset-1, iscont(p)) + p = utf8_prev(s, p); + if (p == NULL || p == e) return 0; + utf8_decode(p, &ch, 0); + lua_pushinteger(L, p-s+1); + lua_pushinteger(L, ch); + return 2; +} + +static int Lutf8_charpos (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + lua_Integer offset = 1; + if (lua_isnoneornil(L, 3)) { + lua_Integer idx = luaL_optinteger(L, 2, 0); + if (idx > 0) --idx; + else if (idx < 0) offset = e-s+1; + return push_offset(L, s, e, offset, idx); + } + offset = byte_relat(luaL_optinteger(L, 2, 1), e-s); + if (offset < 1) offset = 1; + return push_offset(L, s, e, offset, luaL_checkinteger(L, 3)); +} + +static int Lutf8_offset (lua_State *L) { + size_t len; + const char *s = luaL_checklstring(L, 1, &len); + lua_Integer n = luaL_checkinteger(L, 2); + lua_Integer posi = (n >= 0) ? 1 : len + 1; + posi = byte_relat(luaL_optinteger(L, 3, posi), len); + luaL_argcheck(L, 1 <= posi && --posi <= (lua_Integer)len, 3, + "position out of range"); + if (n == 0) { + /* find beginning of current byte sequence */ + while (posi > 0 && iscont(s + posi)) posi--; + } else { + if (iscont(s + posi)) + return luaL_error(L, "initial position is a continuation byte"); + if (n < 0) { + while (n < 0 && posi > 0) { /* move back */ + do { /* find beginning of previous character */ + posi--; + } while (posi > 0 && iscont(s + posi)); + n++; + } + } else { + n--; /* do not move for 1st character */ + while (n > 0 && posi < (lua_Integer)len) { + do { /* find beginning of next character */ + posi++; + } while (iscont(s + posi)); /* (cannot pass final '\0') */ + n--; + } + } + } + if (n == 0) /* did it find given character? */ + lua_pushinteger(L, posi + 1); + else /* no such character */ + lua_pushnil(L); + return 1; +} + +static int Lutf8_next (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + lua_Integer offset = byte_relat(luaL_optinteger(L, 2, 1), e-s); + lua_Integer idx = luaL_optinteger(L, 3, !lua_isnoneornil(L, 2)); + return push_offset(L, s, e, offset, idx); +} + +static int iter_aux (lua_State *L, int strict) { + const char *e, *s = check_utf8(L, 1, &e); + int n = CAST(int, lua_tointeger(L, 2)); + const char *p = n <= 0 ? s : utf8_next(s+n-1, e); + if (p < e) { + utfint code = 0; + utf8_safe_decode(L, p, &code); + if (strict && utf8_invalid(code)) + return luaL_error(L, "invalid UTF-8 code"); + lua_pushinteger(L, p-s+1); + lua_pushinteger(L, code); + return 2; + } + return 0; /* no more codepoints */ +} + +static int iter_auxstrict (lua_State *L) { return iter_aux(L, 1); } +static int iter_auxlax (lua_State *L) { return iter_aux(L, 0); } + +static int Lutf8_codes (lua_State *L) { + int lax = lua_toboolean(L, 2); + luaL_checkstring(L, 1); + lua_pushcfunction(L, lax ? iter_auxlax : iter_auxstrict); + lua_pushvalue(L, 1); + lua_pushinteger(L, 0); + return 3; +} + +static int Lutf8_width (lua_State *L) { + int t = lua_type(L, 1); + int ambi_is_single = !lua_toboolean(L, 2); + int default_width = CAST(int, luaL_optinteger(L, 3, 0)); + if (t == LUA_TNUMBER) { + size_t chwidth = utf8_width(CAST(utfint, lua_tointeger(L, 1)), ambi_is_single); + if (chwidth == 0) chwidth = default_width; + lua_pushinteger(L, (lua_Integer)chwidth); + } else if (t != LUA_TSTRING) + return typeerror(L, 1, "number/string"); + else { + const char *e, *s = to_utf8(L, 1, &e); + int width = 0; + while (s < e) { + utfint ch = 0; + int chwidth; + s = utf8_safe_decode(L, s, &ch); + chwidth = utf8_width(ch, ambi_is_single); + width += chwidth == 0 ? default_width : chwidth; + } + lua_pushinteger(L, (lua_Integer)width); + } + return 1; +} + +static int Lutf8_widthindex (lua_State *L) { + const char *e, *s = check_utf8(L, 1, &e); + int width = CAST(int, luaL_checkinteger(L, 2)); + int ambi_is_single = !lua_toboolean(L, 3); + int default_width = CAST(int, luaL_optinteger(L, 4, 0)); + size_t idx = 1; + while (s < e) { + utfint ch = 0; + size_t chwidth; + s = utf8_safe_decode(L, s, &ch); + chwidth = utf8_width(ch, ambi_is_single); + if (chwidth == 0) chwidth = default_width; + width -= CAST(int, chwidth); + if (width <= 0) { + lua_pushinteger(L, idx); + lua_pushinteger(L, width + chwidth); + lua_pushinteger(L, chwidth); + return 3; + } + ++idx; + } + lua_pushinteger(L, (lua_Integer)idx); + return 1; +} + +static int Lutf8_ncasecmp (lua_State *L) { + const char *e1, *s1 = check_utf8(L, 1, &e1); + const char *e2, *s2 = check_utf8(L, 2, &e2); + while (s1 < e1 || s2 < e2) { + utfint ch1 = 0, ch2 = 0; + if (s1 == e1) + ch2 = 1; + else if (s2 == e2) + ch1 = 1; + else { + s1 = utf8_safe_decode(L, s1, &ch1); + s2 = utf8_safe_decode(L, s2, &ch2); + ch1 = utf8_tofold(ch1); + ch2 = utf8_tofold(ch2); + } + if (ch1 != ch2) { + lua_pushinteger(L, ch1 > ch2 ? 1 : -1); + return 1; + } + } + lua_pushinteger(L, 0); + return 1; +} + + +/* utf8 pattern matching implement */ + +#ifndef LUA_MAXCAPTURES +# define LUA_MAXCAPTURES 32 +#endif /* LUA_MAXCAPTURES */ + +#define CAP_UNFINISHED (-1) +#define CAP_POSITION (-2) + + +typedef struct MatchState { + int matchdepth; /* control for recursive depth (to avoid C stack overflow) */ + const char *src_init; /* init of source string */ + const char *src_end; /* end ('\0') of source string */ + const char *p_end; /* end ('\0') of pattern */ + lua_State *L; + int level; /* total number of captures (finished or unfinished) */ + struct { + const char *init; + ptrdiff_t len; + } capture[LUA_MAXCAPTURES]; +} MatchState; + +/* recursive function */ +static const char *match (MatchState *ms, const char *s, const char *p); + +/* maximum recursion depth for 'match' */ +#if !defined(MAXCCALLS) +#define MAXCCALLS 200 +#endif + +#define L_ESC '%' +#define SPECIALS "^$*+?.([%-" + +static int check_capture (MatchState *ms, int l) { + l -= '1'; + if (l < 0 || l >= ms->level || ms->capture[l].len == CAP_UNFINISHED) + return luaL_error(ms->L, "invalid capture index %%%d", l + 1); + return l; +} + +static int capture_to_close (MatchState *ms) { + int level = ms->level; + while (--level >= 0) + if (ms->capture[level].len == CAP_UNFINISHED) return level; + return luaL_error(ms->L, "invalid pattern capture"); +} + +static const char *classend (MatchState *ms, const char *p) { + utfint ch = 0; + p = utf8_safe_decode(ms->L, p, &ch); + switch (ch) { + case L_ESC: { + if (p == ms->p_end) + luaL_error(ms->L, "malformed pattern (ends with " LUA_QL("%%") ")"); + return utf8_next(p, ms->p_end); + } + case '[': { + if (*p == '^') p++; + do { /* look for a `]' */ + if (p == ms->p_end) + luaL_error(ms->L, "malformed pattern (missing " LUA_QL("]") ")"); + if (*(p++) == L_ESC && p < ms->p_end) + p++; /* skip escapes (e.g. `%]') */ + } while (*p != ']'); + return p+1; + } + default: { + return p; + } + } +} + +static int match_class (utfint c, utfint cl) { + int res; + switch (utf8_tolower(cl)) { +#define X(cls, name) case cls: res = utf8_is##name(c); break; + utf8_categories(X) +#undef X + case 'g' : res = utf8_isgraph(c); break; + case 'w' : res = utf8_isalnum(c); break; + case 'z' : res = (c == 0); break; /* deprecated option */ + default: return (cl == c); + } + return (utf8_islower(cl) ? res : !res); +} + +static int matchbracketclass (MatchState *ms, utfint c, const char *p, const char *ec) { + int sig = 1; + assert(*p == '['); + if (*++p == '^') { + sig = 0; + p++; /* skip the `^' */ + } + while (p < ec) { + utfint ch = 0; + p = utf8_safe_decode(ms->L, p, &ch); + if (ch == L_ESC) { + p = utf8_safe_decode(ms->L, p, &ch); + if (match_class(c, ch)) + return sig; + } else { + utfint next = 0; + const char *np = utf8_safe_decode(ms->L, p, &next); + if (next == '-' && np < ec) { + p = utf8_safe_decode(ms->L, np, &next); + if (ch <= c && c <= next) + return sig; + } + else if (ch == c) return sig; + } + } + return !sig; +} + +static int singlematch (MatchState *ms, const char *s, const char *p, const char *ep) { + if (s >= ms->src_end) + return 0; + else { + utfint ch=0, pch=0; + utf8_safe_decode(ms->L, s, &ch); + p = utf8_safe_decode(ms->L, p, &pch); + switch (pch) { + case '.': return 1; /* matches any char */ + case L_ESC: utf8_safe_decode(ms->L, p, &pch); + return match_class(ch, pch); + case '[': return matchbracketclass(ms, ch, p-1, ep-1); + default: return pch == ch; + } + } +} + +static const char *matchbalance (MatchState *ms, const char *s, const char **p) { + utfint ch=0, begin=0, end=0; + *p = utf8_safe_decode(ms->L, *p, &begin); + if (*p >= ms->p_end) + luaL_error(ms->L, "malformed pattern " + "(missing arguments to " LUA_QL("%%b") ")"); + *p = utf8_safe_decode(ms->L, *p, &end); + s = utf8_safe_decode(ms->L, s, &ch); + if (ch != begin) return NULL; + else { + int cont = 1; + while (s < ms->src_end) { + s = utf8_safe_decode(ms->L, s, &ch); + if (ch == end) { + if (--cont == 0) return s; + } + else if (ch == begin) cont++; + } + } + return NULL; /* string ends out of balance */ +} + +static const char *max_expand (MatchState *ms, const char *s, const char *p, const char *ep) { + const char *m = s; /* matched end of single match p */ + while (singlematch(ms, m, p, ep)) + m = utf8_next(m, ms->src_end); + /* keeps trying to match with the maximum repetitions */ + while (s <= m) { + const char *res = match(ms, m, ep+1); + if (res) return res; + /* else didn't match; reduce 1 repetition to try again */ + if (s == m) break; + m = utf8_prev(s, m); + } + return NULL; +} + +static const char *min_expand (MatchState *ms, const char *s, const char *p, const char *ep) { + for (;;) { + const char *res = match(ms, s, ep+1); + if (res != NULL) + return res; + else if (singlematch(ms, s, p, ep)) + s = utf8_next(s, ms->src_end); /* try with one more repetition */ + else return NULL; + } +} + +static const char *start_capture (MatchState *ms, const char *s, const char *p, int what) { + const char *res; + int level = ms->level; + if (level >= LUA_MAXCAPTURES) luaL_error(ms->L, "too many captures"); + ms->capture[level].init = s; + ms->capture[level].len = what; + ms->level = level+1; + if ((res=match(ms, s, p)) == NULL) /* match failed? */ + ms->level--; /* undo capture */ + return res; +} + +static const char *end_capture (MatchState *ms, const char *s, const char *p) { + int l = capture_to_close(ms); + const char *res; + ms->capture[l].len = s - ms->capture[l].init; /* close capture */ + if ((res = match(ms, s, p)) == NULL) /* match failed? */ + ms->capture[l].len = CAP_UNFINISHED; /* undo capture */ + return res; +} + +static const char *match_capture (MatchState *ms, const char *s, int l) { + size_t len; + l = check_capture(ms, l); + len = ms->capture[l].len; + if ((size_t)(ms->src_end-s) >= len && + memcmp(ms->capture[l].init, s, len) == 0) + return s+len; + else return NULL; +} + +static const char *match (MatchState *ms, const char *s, const char *p) { + if (ms->matchdepth-- == 0) + luaL_error(ms->L, "pattern too complex"); + init: /* using goto's to optimize tail recursion */ + if (p != ms->p_end) { /* end of pattern? */ + utfint ch = 0; + utf8_safe_decode(ms->L, p, &ch); + switch (ch) { + case '(': { /* start capture */ + if (*(p + 1) == ')') /* position capture? */ + s = start_capture(ms, s, p + 2, CAP_POSITION); + else + s = start_capture(ms, s, p + 1, CAP_UNFINISHED); + break; + } + case ')': { /* end capture */ + s = end_capture(ms, s, p + 1); + break; + } + case '$': { + if ((p + 1) != ms->p_end) /* is the `$' the last char in pattern? */ + goto dflt; /* no; go to default */ + s = (s == ms->src_end) ? s : NULL; /* check end of string */ + break; + } + case L_ESC: { /* escaped sequence not in the format class[*+?-]? */ + const char *prev_p = p; + p = utf8_safe_decode(ms->L, p+1, &ch); + switch (ch) { + case 'b': { /* balanced string? */ + s = matchbalance(ms, s, &p); + if (s != NULL) + goto init; /* return match(ms, s, p + 4); */ + /* else fail (s == NULL) */ + break; + } + case 'f': { /* frontier? */ + const char *ep; utfint previous = 0, current = 0; + if (*p != '[') + luaL_error(ms->L, "missing " LUA_QL("[") " after " + LUA_QL("%%f") " in pattern"); + ep = classend(ms, p); /* points to what is next */ + if (s != ms->src_init) + utf8_decode(utf8_prev(ms->src_init, s), &previous, 0); + if (s != ms->src_end) + utf8_decode(s, ¤t, 0); + if (!matchbracketclass(ms, previous, p, ep - 1) && + matchbracketclass(ms, current, p, ep - 1)) { + p = ep; goto init; /* return match(ms, s, ep); */ + } + s = NULL; /* match failed */ + break; + } + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': { /* capture results (%0-%9)? */ + s = match_capture(ms, s, ch); + if (s != NULL) goto init; /* return match(ms, s, p + 2) */ + break; + } + default: p = prev_p; goto dflt; + } + break; + } + default: dflt: { /* pattern class plus optional suffix */ + const char *ep = classend(ms, p); /* points to optional suffix */ + /* does not match at least once? */ + if (!singlematch(ms, s, p, ep)) { + if (*ep == '*' || *ep == '?' || *ep == '-') { /* accept empty? */ + p = ep + 1; goto init; /* return match(ms, s, ep + 1); */ + } else /* '+' or no suffix */ + s = NULL; /* fail */ + } else { /* matched once */ + const char *next_s = utf8_next(s, ms->src_end); + switch (*ep) { /* handle optional suffix */ + case '?': { /* optional */ + const char *res; + const char *next_ep = utf8_next(ep, ms->p_end); + if ((res = match(ms, next_s, next_ep)) != NULL) + s = res; + else { + p = next_ep; goto init; /* else return match(ms, s, ep + 1); */ + } + break; + } + case '+': /* 1 or more repetitions */ + s = next_s; /* 1 match already done */ + /* fall through */ + case '*': /* 0 or more repetitions */ + s = max_expand(ms, s, p, ep); + break; + case '-': /* 0 or more repetitions (minimum) */ + s = min_expand(ms, s, p, ep); + break; + default: /* no suffix */ + s = next_s; p = ep; goto init; /* return match(ms, s + 1, ep); */ + } + } + break; + } + } + } + ms->matchdepth++; + return s; +} + +static const char *lmemfind (const char *s1, size_t l1, const char *s2, size_t l2) { + if (l2 == 0) return s1; /* empty strings are everywhere */ + else if (l2 > l1) return NULL; /* avoids a negative `l1' */ + else { + const char *init; /* to search for a `*s2' inside `s1' */ + l2--; /* 1st char will be checked by `memchr' */ + l1 = l1-l2; /* `s2' cannot be found after that */ + while (l1 > 0 && (init = (const char *)memchr(s1, *s2, l1)) != NULL) { + init++; /* 1st char is already checked */ + if (memcmp(init, s2+1, l2) == 0) + return init-1; + else { /* correct `l1' and `s1' to try again */ + l1 -= init-s1; + s1 = init; + } + } + return NULL; /* not found */ + } +} + +static int get_index (const char *p, const char *s, const char *e) { + int idx; + for (idx = 0; s < e && s < p; ++idx) + s = utf8_next(s, e); + return s == p ? idx : idx - 1; +} + +static void push_onecapture (MatchState *ms, int i, const char *s, const char *e) { + if (i >= ms->level) { + if (i == 0) /* ms->level == 0, too */ + lua_pushlstring(ms->L, s, e - s); /* add whole match */ + else + luaL_error(ms->L, "invalid capture index"); + } else { + ptrdiff_t l = ms->capture[i].len; + if (l == CAP_UNFINISHED) luaL_error(ms->L, "unfinished capture"); + if (l == CAP_POSITION) { + int idx = get_index(ms->capture[i].init, ms->src_init, ms->src_end); + lua_pushinteger(ms->L, idx+1); + } else + lua_pushlstring(ms->L, ms->capture[i].init, l); + } +} + +static int push_captures (MatchState *ms, const char *s, const char *e) { + int i; + int nlevels = (ms->level == 0 && s) ? 1 : ms->level; + luaL_checkstack(ms->L, nlevels, "too many captures"); + for (i = 0; i < nlevels; i++) + push_onecapture(ms, i, s, e); + return nlevels; /* number of strings pushed */ +} + +/* check whether pattern has no special characters */ +static int nospecials (const char *p, const char * ep) { + while (p < ep) { + if (strpbrk(p, SPECIALS)) + return 0; /* pattern has a special character */ + p += strlen(p) + 1; /* may have more after \0 */ + } + return 1; /* no special chars found */ +} + + +/* utf8 pattern matching interface */ + +static int find_aux (lua_State *L, int find) { + const char *es, *s = check_utf8(L, 1, &es); + const char *ep, *p = check_utf8(L, 2, &ep); + lua_Integer idx = luaL_optinteger(L, 3, 1); + const char *init; + if (!idx) idx = 1; + init = utf8_relat(s, es, CAST(int, idx)); + if (init == NULL) { + if (idx > 0) { + lua_pushnil(L); /* cannot find anything */ + return 1; + } + init = s; + } + /* explicit request or no special characters? */ + if (find && (lua_toboolean(L, 4) || nospecials(p, ep))) { + /* do a plain search */ + const char *s2 = lmemfind(init, es-init, p, ep-p); + if (s2) { + const char *e2 = s2 + (ep - p); + if (iscont(e2)) e2 = utf8_next(e2, es); + lua_pushinteger(L, idx = get_index(s2, s, es) + 1); + lua_pushinteger(L, idx + get_index(e2, s2, es) - 1); + return 2; + } + } else { + MatchState ms; + int anchor = (*p == '^'); + if (anchor) p++; /* skip anchor character */ + if (idx < 0) idx += utf8_length(s, es)+1; /* TODO not very good */ + ms.L = L; + ms.matchdepth = MAXCCALLS; + ms.src_init = s; + ms.src_end = es; + ms.p_end = ep; + do { + const char *res; + ms.level = 0; + assert(ms.matchdepth == MAXCCALLS); + if ((res=match(&ms, init, p)) != NULL) { + if (find) { + lua_pushinteger(L, idx); /* start */ + lua_pushinteger(L, idx + utf8_length(init, res) - 1); /* end */ + return push_captures(&ms, NULL, 0) + 2; + } else + return push_captures(&ms, init, res); + } + if (init == es) break; + idx += 1; + init = utf8_next(init, es); + } while (init <= es && !anchor); + } + lua_pushnil(L); /* not found */ + return 1; +} + +static int Lutf8_find (lua_State *L) { return find_aux(L, 1); } +static int Lutf8_match (lua_State *L) { return find_aux(L, 0); } + +static int gmatch_aux (lua_State *L) { + MatchState ms; + const char *es, *s = check_utf8(L, lua_upvalueindex(1), &es); + const char *ep, *p = check_utf8(L, lua_upvalueindex(2), &ep); + const char *src; + ms.L = L; + ms.matchdepth = MAXCCALLS; + ms.src_init = s; + ms.src_end = es; + ms.p_end = ep; + for (src = s + (size_t)lua_tointeger(L, lua_upvalueindex(3)); + src <= ms.src_end; + src = utf8_next(src, ms.src_end)) { + const char *e; + ms.level = 0; + assert(ms.matchdepth == MAXCCALLS); + if ((e = match(&ms, src, p)) != NULL) { + lua_Integer newstart = e-s; + if (e == src) newstart++; /* empty match? go at least one position */ + lua_pushinteger(L, newstart); + lua_replace(L, lua_upvalueindex(3)); + return push_captures(&ms, src, e); + } + if (src == ms.src_end) break; + } + return 0; /* not found */ +} + +static int Lutf8_gmatch (lua_State *L) { + luaL_checkstring(L, 1); + luaL_checkstring(L, 2); + lua_settop(L, 2); + lua_pushinteger(L, 0); + lua_pushcclosure(L, gmatch_aux, 3); + return 1; +} + +static void add_s (MatchState *ms, luaL_Buffer *b, const char *s, const char *e) { + const char *new_end, *news = to_utf8(ms->L, 3, &new_end); + while (news < new_end) { + utfint ch = 0; + news = utf8_safe_decode(ms->L, news, &ch); + if (ch != L_ESC) + add_utf8char(b, ch); + else { + news = utf8_safe_decode(ms->L, news, &ch); /* skip ESC */ + if (!utf8_isdigit(ch)) { + if (ch != L_ESC) + luaL_error(ms->L, "invalid use of " LUA_QL("%c") + " in replacement string", L_ESC); + add_utf8char(b, ch); + } else if (ch == '0') + luaL_addlstring(b, s, e-s); + else { + push_onecapture(ms, ch-'1', s, e); + luaL_addvalue(b); /* add capture to accumulated result */ + } + } + } +} + +static void add_value (MatchState *ms, luaL_Buffer *b, const char *s, const char *e, int tr) { + lua_State *L = ms->L; + switch (tr) { + case LUA_TFUNCTION: { + int n; + lua_pushvalue(L, 3); + n = push_captures(ms, s, e); + lua_call(L, n, 1); + break; + } + case LUA_TTABLE: { + push_onecapture(ms, 0, s, e); + lua_gettable(L, 3); + break; + } + default: { /* LUA_TNUMBER or LUA_TSTRING */ + add_s(ms, b, s, e); + return; + } + } + if (!lua_toboolean(L, -1)) { /* nil or false? */ + lua_pop(L, 1); + lua_pushlstring(L, s, e - s); /* keep original text */ + } else if (!lua_isstring(L, -1)) + luaL_error(L, "invalid replacement value (a %s)", luaL_typename(L, -1)); + luaL_addvalue(b); /* add result to accumulator */ +} + +static int Lutf8_gsub (lua_State *L) { + const char *es, *s = check_utf8(L, 1, &es); + const char *ep, *p = check_utf8(L, 2, &ep); + int tr = lua_type(L, 3); + lua_Integer max_s = luaL_optinteger(L, 4, (es-s)+1); + int anchor = (*p == '^'); + lua_Integer n = 0; + MatchState ms; + luaL_Buffer b; + luaL_argcheck(L, tr == LUA_TNUMBER || tr == LUA_TSTRING || + tr == LUA_TFUNCTION || tr == LUA_TTABLE, 3, + "string/function/table expected"); + luaL_buffinit(L, &b); + if (anchor) p++; /* skip anchor character */ + ms.L = L; + ms.matchdepth = MAXCCALLS; + ms.src_init = s; + ms.src_end = es; + ms.p_end = ep; + while (n < max_s) { + const char *e; + ms.level = 0; + assert(ms.matchdepth == MAXCCALLS); + e = match(&ms, s, p); + if (e) { + n++; + add_value(&ms, &b, s, e, tr); + } + if (e && e > s) /* non empty match? */ + s = e; /* skip it */ + else if (s < es) { + utfint ch = 0; + s = utf8_safe_decode(L, s, &ch); + add_utf8char(&b, ch); + } else break; + if (anchor) break; + } + luaL_addlstring(&b, s, es-s); + luaL_pushresult(&b); + lua_pushinteger(L, n); /* number of substitutions */ + return 2; +} + + +/* lua module import interface */ + +#if LUA_VERSION_NUM >= 502 +static const char UTF8PATT[] = "[\0-\x7F\xC2-\xF4][\x80-\xBF]*"; +#else +static const char UTF8PATT[] = "[%z\1-\x7F\xC2-\xF4][\x80-\xBF]*"; +#endif + +LUALIB_API int luaopen_utf8 (lua_State *L) { + luaL_Reg libs[] = { +#define ENTRY(name) { #name, Lutf8_##name } + ENTRY(offset), + ENTRY(codes), + ENTRY(codepoint), + + ENTRY(len), + ENTRY(sub), + ENTRY(reverse), + ENTRY(lower), + ENTRY(upper), + ENTRY(title), + ENTRY(fold), + ENTRY(byte), + ENTRY(char), + ENTRY(escape), + ENTRY(insert), + ENTRY(remove), + ENTRY(charpos), + ENTRY(next), + ENTRY(width), + ENTRY(widthindex), + ENTRY(ncasecmp), + ENTRY(find), + ENTRY(gmatch), + ENTRY(gsub), + ENTRY(match), +#undef ENTRY + { NULL, NULL } + }; + +#if LUA_VERSION_NUM >= 502 + luaL_newlib(L, libs); +#else + luaL_register(L, "utf8", libs); +#endif + + lua_pushlstring(L, UTF8PATT, sizeof(UTF8PATT)-1); + lua_setfield(L, -2, "charpattern"); + + return 1; +} + +/* win32cc: flags+='-Wall -Wextra -s -O2 -mdll -DLUA_BUILD_AS_DLL' + * win32cc: libs+='-llua54.dll' output='lua-utf8.dll' + * win32cc: run='lua.exe test.lua' + * maccc: run='lua -- test_compat.lua' + * maccc: flags+='-g --coverage -bundle -undefined dynamic_lookup' output='lua-utf8.so' */ + diff --git a/lib/luautf8/unidata.h b/lib/luautf8/unidata.h new file mode 100644 index 000000000..b615dff4b --- /dev/null +++ b/lib/luautf8/unidata.h @@ -0,0 +1,3753 @@ +/* + * unidata.h - generated by parseucd.lua + */ +#ifndef unidata_h +#define unidata_h + +#ifndef utfint +# define utfint utfint +typedef unsigned int utfint; +#endif + +typedef struct range_table { + utfint first; + utfint last; + int step; +} range_table; + +typedef struct conv_table { + utfint first; + utfint last; + int step; + int offset; +} conv_table; + +static struct range_table alpha_table[] = { + { 0x41, 0x5A, 1 }, + { 0x61, 0x7A, 1 }, + { 0xAA, 0xB5, 11 }, + { 0xBA, 0xC0, 6 }, + { 0xC1, 0xD6, 1 }, + { 0xD8, 0xF6, 1 }, + { 0xF8, 0x2C1, 1 }, + { 0x2C6, 0x2D1, 1 }, + { 0x2E0, 0x2E4, 1 }, + { 0x2EC, 0x2EE, 2 }, + { 0x345, 0x370, 43 }, + { 0x371, 0x374, 1 }, + { 0x376, 0x377, 1 }, + { 0x37A, 0x37D, 1 }, + { 0x37F, 0x386, 7 }, + { 0x388, 0x38A, 1 }, + { 0x38C, 0x38E, 2 }, + { 0x38F, 0x3A1, 1 }, + { 0x3A3, 0x3F5, 1 }, + { 0x3F7, 0x481, 1 }, + { 0x48A, 0x52F, 1 }, + { 0x531, 0x556, 1 }, + { 0x559, 0x560, 7 }, + { 0x561, 0x588, 1 }, + { 0x5B0, 0x5BD, 1 }, + { 0x5BF, 0x5C1, 2 }, + { 0x5C2, 0x5C4, 2 }, + { 0x5C5, 0x5C7, 2 }, + { 0x5D0, 0x5EA, 1 }, + { 0x5EF, 0x5F2, 1 }, + { 0x610, 0x61A, 1 }, + { 0x620, 0x657, 1 }, + { 0x659, 0x65F, 1 }, + { 0x66E, 0x6D3, 1 }, + { 0x6D5, 0x6DC, 1 }, + { 0x6E1, 0x6E8, 1 }, + { 0x6ED, 0x6EF, 1 }, + { 0x6FA, 0x6FC, 1 }, + { 0x6FF, 0x710, 17 }, + { 0x711, 0x73F, 1 }, + { 0x74D, 0x7B1, 1 }, + { 0x7CA, 0x7EA, 1 }, + { 0x7F4, 0x7F5, 1 }, + { 0x7FA, 0x800, 6 }, + { 0x801, 0x817, 1 }, + { 0x81A, 0x82C, 1 }, + { 0x840, 0x858, 1 }, + { 0x860, 0x86A, 1 }, + { 0x870, 0x887, 1 }, + { 0x889, 0x88E, 1 }, + { 0x8A0, 0x8C9, 1 }, + { 0x8D4, 0x8DF, 1 }, + { 0x8E3, 0x8E9, 1 }, + { 0x8F0, 0x93B, 1 }, + { 0x93D, 0x94C, 1 }, + { 0x94E, 0x950, 1 }, + { 0x955, 0x963, 1 }, + { 0x971, 0x983, 1 }, + { 0x985, 0x98C, 1 }, + { 0x98F, 0x990, 1 }, + { 0x993, 0x9A8, 1 }, + { 0x9AA, 0x9B0, 1 }, + { 0x9B2, 0x9B6, 4 }, + { 0x9B7, 0x9B9, 1 }, + { 0x9BD, 0x9C4, 1 }, + { 0x9C7, 0x9C8, 1 }, + { 0x9CB, 0x9CC, 1 }, + { 0x9CE, 0x9D7, 9 }, + { 0x9DC, 0x9DD, 1 }, + { 0x9DF, 0x9E3, 1 }, + { 0x9F0, 0x9F1, 1 }, + { 0x9FC, 0xA01, 5 }, + { 0xA02, 0xA03, 1 }, + { 0xA05, 0xA0A, 1 }, + { 0xA0F, 0xA10, 1 }, + { 0xA13, 0xA28, 1 }, + { 0xA2A, 0xA30, 1 }, + { 0xA32, 0xA33, 1 }, + { 0xA35, 0xA36, 1 }, + { 0xA38, 0xA39, 1 }, + { 0xA3E, 0xA42, 1 }, + { 0xA47, 0xA48, 1 }, + { 0xA4B, 0xA4C, 1 }, + { 0xA51, 0xA59, 8 }, + { 0xA5A, 0xA5C, 1 }, + { 0xA5E, 0xA70, 18 }, + { 0xA71, 0xA75, 1 }, + { 0xA81, 0xA83, 1 }, + { 0xA85, 0xA8D, 1 }, + { 0xA8F, 0xA91, 1 }, + { 0xA93, 0xAA8, 1 }, + { 0xAAA, 0xAB0, 1 }, + { 0xAB2, 0xAB3, 1 }, + { 0xAB5, 0xAB9, 1 }, + { 0xABD, 0xAC5, 1 }, + { 0xAC7, 0xAC9, 1 }, + { 0xACB, 0xACC, 1 }, + { 0xAD0, 0xAE0, 16 }, + { 0xAE1, 0xAE3, 1 }, + { 0xAF9, 0xAFC, 1 }, + { 0xB01, 0xB03, 1 }, + { 0xB05, 0xB0C, 1 }, + { 0xB0F, 0xB10, 1 }, + { 0xB13, 0xB28, 1 }, + { 0xB2A, 0xB30, 1 }, + { 0xB32, 0xB33, 1 }, + { 0xB35, 0xB39, 1 }, + { 0xB3D, 0xB44, 1 }, + { 0xB47, 0xB48, 1 }, + { 0xB4B, 0xB4C, 1 }, + { 0xB56, 0xB57, 1 }, + { 0xB5C, 0xB5D, 1 }, + { 0xB5F, 0xB63, 1 }, + { 0xB71, 0xB82, 17 }, + { 0xB83, 0xB85, 2 }, + { 0xB86, 0xB8A, 1 }, + { 0xB8E, 0xB90, 1 }, + { 0xB92, 0xB95, 1 }, + { 0xB99, 0xB9A, 1 }, + { 0xB9C, 0xB9E, 2 }, + { 0xB9F, 0xBA3, 4 }, + { 0xBA4, 0xBA8, 4 }, + { 0xBA9, 0xBAA, 1 }, + { 0xBAE, 0xBB9, 1 }, + { 0xBBE, 0xBC2, 1 }, + { 0xBC6, 0xBC8, 1 }, + { 0xBCA, 0xBCC, 1 }, + { 0xBD0, 0xBD7, 7 }, + { 0xC00, 0xC0C, 1 }, + { 0xC0E, 0xC10, 1 }, + { 0xC12, 0xC28, 1 }, + { 0xC2A, 0xC39, 1 }, + { 0xC3D, 0xC44, 1 }, + { 0xC46, 0xC48, 1 }, + { 0xC4A, 0xC4C, 1 }, + { 0xC55, 0xC56, 1 }, + { 0xC58, 0xC5A, 1 }, + { 0xC5D, 0xC60, 3 }, + { 0xC61, 0xC63, 1 }, + { 0xC80, 0xC83, 1 }, + { 0xC85, 0xC8C, 1 }, + { 0xC8E, 0xC90, 1 }, + { 0xC92, 0xCA8, 1 }, + { 0xCAA, 0xCB3, 1 }, + { 0xCB5, 0xCB9, 1 }, + { 0xCBD, 0xCC4, 1 }, + { 0xCC6, 0xCC8, 1 }, + { 0xCCA, 0xCCC, 1 }, + { 0xCD5, 0xCD6, 1 }, + { 0xCDD, 0xCDE, 1 }, + { 0xCE0, 0xCE3, 1 }, + { 0xCF1, 0xCF3, 1 }, + { 0xD00, 0xD0C, 1 }, + { 0xD0E, 0xD10, 1 }, + { 0xD12, 0xD3A, 1 }, + { 0xD3D, 0xD44, 1 }, + { 0xD46, 0xD48, 1 }, + { 0xD4A, 0xD4C, 1 }, + { 0xD4E, 0xD54, 6 }, + { 0xD55, 0xD57, 1 }, + { 0xD5F, 0xD63, 1 }, + { 0xD7A, 0xD7F, 1 }, + { 0xD81, 0xD83, 1 }, + { 0xD85, 0xD96, 1 }, + { 0xD9A, 0xDB1, 1 }, + { 0xDB3, 0xDBB, 1 }, + { 0xDBD, 0xDC0, 3 }, + { 0xDC1, 0xDC6, 1 }, + { 0xDCF, 0xDD4, 1 }, + { 0xDD6, 0xDD8, 2 }, + { 0xDD9, 0xDDF, 1 }, + { 0xDF2, 0xDF3, 1 }, + { 0xE01, 0xE3A, 1 }, + { 0xE40, 0xE46, 1 }, + { 0xE4D, 0xE81, 52 }, + { 0xE82, 0xE86, 2 }, + { 0xE87, 0xE8A, 1 }, + { 0xE8C, 0xEA3, 1 }, + { 0xEA5, 0xEA7, 2 }, + { 0xEA8, 0xEB9, 1 }, + { 0xEBB, 0xEBD, 1 }, + { 0xEC0, 0xEC4, 1 }, + { 0xEC6, 0xECD, 7 }, + { 0xEDC, 0xEDF, 1 }, + { 0xF00, 0xF40, 64 }, + { 0xF41, 0xF47, 1 }, + { 0xF49, 0xF6C, 1 }, + { 0xF71, 0xF83, 1 }, + { 0xF88, 0xF97, 1 }, + { 0xF99, 0xFBC, 1 }, + { 0x1000, 0x1036, 1 }, + { 0x1038, 0x103B, 3 }, + { 0x103C, 0x103F, 1 }, + { 0x1050, 0x108F, 1 }, + { 0x109A, 0x109D, 1 }, + { 0x10A0, 0x10C5, 1 }, + { 0x10C7, 0x10CD, 6 }, + { 0x10D0, 0x10FA, 1 }, + { 0x10FC, 0x1248, 1 }, + { 0x124A, 0x124D, 1 }, + { 0x1250, 0x1256, 1 }, + { 0x1258, 0x125A, 2 }, + { 0x125B, 0x125D, 1 }, + { 0x1260, 0x1288, 1 }, + { 0x128A, 0x128D, 1 }, + { 0x1290, 0x12B0, 1 }, + { 0x12B2, 0x12B5, 1 }, + { 0x12B8, 0x12BE, 1 }, + { 0x12C0, 0x12C2, 2 }, + { 0x12C3, 0x12C5, 1 }, + { 0x12C8, 0x12D6, 1 }, + { 0x12D8, 0x1310, 1 }, + { 0x1312, 0x1315, 1 }, + { 0x1318, 0x135A, 1 }, + { 0x1380, 0x138F, 1 }, + { 0x13A0, 0x13F5, 1 }, + { 0x13F8, 0x13FD, 1 }, + { 0x1401, 0x166C, 1 }, + { 0x166F, 0x167F, 1 }, + { 0x1681, 0x169A, 1 }, + { 0x16A0, 0x16EA, 1 }, + { 0x16EE, 0x16F8, 1 }, + { 0x1700, 0x1713, 1 }, + { 0x171F, 0x1733, 1 }, + { 0x1740, 0x1753, 1 }, + { 0x1760, 0x176C, 1 }, + { 0x176E, 0x1770, 1 }, + { 0x1772, 0x1773, 1 }, + { 0x1780, 0x17B3, 1 }, + { 0x17B6, 0x17C8, 1 }, + { 0x17D7, 0x17DC, 5 }, + { 0x1820, 0x1878, 1 }, + { 0x1880, 0x18AA, 1 }, + { 0x18B0, 0x18F5, 1 }, + { 0x1900, 0x191E, 1 }, + { 0x1920, 0x192B, 1 }, + { 0x1930, 0x1938, 1 }, + { 0x1950, 0x196D, 1 }, + { 0x1970, 0x1974, 1 }, + { 0x1980, 0x19AB, 1 }, + { 0x19B0, 0x19C9, 1 }, + { 0x1A00, 0x1A1B, 1 }, + { 0x1A20, 0x1A5E, 1 }, + { 0x1A61, 0x1A74, 1 }, + { 0x1AA7, 0x1ABF, 24 }, + { 0x1AC0, 0x1ACC, 12 }, + { 0x1ACD, 0x1ACE, 1 }, + { 0x1B00, 0x1B33, 1 }, + { 0x1B35, 0x1B43, 1 }, + { 0x1B45, 0x1B4C, 1 }, + { 0x1B80, 0x1BA9, 1 }, + { 0x1BAC, 0x1BAF, 1 }, + { 0x1BBA, 0x1BE5, 1 }, + { 0x1BE7, 0x1BF1, 1 }, + { 0x1C00, 0x1C36, 1 }, + { 0x1C4D, 0x1C4F, 1 }, + { 0x1C5A, 0x1C7D, 1 }, + { 0x1C80, 0x1C88, 1 }, + { 0x1C90, 0x1CBA, 1 }, + { 0x1CBD, 0x1CBF, 1 }, + { 0x1CE9, 0x1CEC, 1 }, + { 0x1CEE, 0x1CF3, 1 }, + { 0x1CF5, 0x1CF6, 1 }, + { 0x1CFA, 0x1D00, 6 }, + { 0x1D01, 0x1DBF, 1 }, + { 0x1DE7, 0x1DF4, 1 }, + { 0x1E00, 0x1F15, 1 }, + { 0x1F18, 0x1F1D, 1 }, + { 0x1F20, 0x1F45, 1 }, + { 0x1F48, 0x1F4D, 1 }, + { 0x1F50, 0x1F57, 1 }, + { 0x1F59, 0x1F5F, 2 }, + { 0x1F60, 0x1F7D, 1 }, + { 0x1F80, 0x1FB4, 1 }, + { 0x1FB6, 0x1FBC, 1 }, + { 0x1FBE, 0x1FC2, 4 }, + { 0x1FC3, 0x1FC4, 1 }, + { 0x1FC6, 0x1FCC, 1 }, + { 0x1FD0, 0x1FD3, 1 }, + { 0x1FD6, 0x1FDB, 1 }, + { 0x1FE0, 0x1FEC, 1 }, + { 0x1FF2, 0x1FF4, 1 }, + { 0x1FF6, 0x1FFC, 1 }, + { 0x2071, 0x207F, 14 }, + { 0x2090, 0x209C, 1 }, + { 0x2102, 0x2107, 5 }, + { 0x210A, 0x2113, 1 }, + { 0x2115, 0x2119, 4 }, + { 0x211A, 0x211D, 1 }, + { 0x2124, 0x212A, 2 }, + { 0x212B, 0x212D, 1 }, + { 0x212F, 0x2139, 1 }, + { 0x213C, 0x213F, 1 }, + { 0x2145, 0x2149, 1 }, + { 0x214E, 0x2160, 18 }, + { 0x2161, 0x2188, 1 }, + { 0x24B6, 0x24E9, 1 }, + { 0x2C00, 0x2CE4, 1 }, + { 0x2CEB, 0x2CEE, 1 }, + { 0x2CF2, 0x2CF3, 1 }, + { 0x2D00, 0x2D25, 1 }, + { 0x2D27, 0x2D2D, 6 }, + { 0x2D30, 0x2D67, 1 }, + { 0x2D6F, 0x2D80, 17 }, + { 0x2D81, 0x2D96, 1 }, + { 0x2DA0, 0x2DA6, 1 }, + { 0x2DA8, 0x2DAE, 1 }, + { 0x2DB0, 0x2DB6, 1 }, + { 0x2DB8, 0x2DBE, 1 }, + { 0x2DC0, 0x2DC6, 1 }, + { 0x2DC8, 0x2DCE, 1 }, + { 0x2DD0, 0x2DD6, 1 }, + { 0x2DD8, 0x2DDE, 1 }, + { 0x2DE0, 0x2DFF, 1 }, + { 0x2E2F, 0x3005, 470 }, + { 0x3006, 0x3007, 1 }, + { 0x3021, 0x3029, 1 }, + { 0x3031, 0x3035, 1 }, + { 0x3038, 0x303C, 1 }, + { 0x3041, 0x3096, 1 }, + { 0x309D, 0x309F, 1 }, + { 0x30A1, 0x30FA, 1 }, + { 0x30FC, 0x30FF, 1 }, + { 0x3105, 0x312F, 1 }, + { 0x3131, 0x318E, 1 }, + { 0x31A0, 0x31BF, 1 }, + { 0x31F0, 0x31FF, 1 }, + { 0x3400, 0x4DBF, 1 }, + { 0x4E00, 0xA48C, 1 }, + { 0xA4D0, 0xA4FD, 1 }, + { 0xA500, 0xA60C, 1 }, + { 0xA610, 0xA61F, 1 }, + { 0xA62A, 0xA62B, 1 }, + { 0xA640, 0xA66E, 1 }, + { 0xA674, 0xA67B, 1 }, + { 0xA67F, 0xA6EF, 1 }, + { 0xA717, 0xA71F, 1 }, + { 0xA722, 0xA788, 1 }, + { 0xA78B, 0xA7CA, 1 }, + { 0xA7D0, 0xA7D1, 1 }, + { 0xA7D3, 0xA7D5, 2 }, + { 0xA7D6, 0xA7D9, 1 }, + { 0xA7F2, 0xA805, 1 }, + { 0xA807, 0xA827, 1 }, + { 0xA840, 0xA873, 1 }, + { 0xA880, 0xA8C3, 1 }, + { 0xA8C5, 0xA8F2, 45 }, + { 0xA8F3, 0xA8F7, 1 }, + { 0xA8FB, 0xA8FD, 2 }, + { 0xA8FE, 0xA8FF, 1 }, + { 0xA90A, 0xA92A, 1 }, + { 0xA930, 0xA952, 1 }, + { 0xA960, 0xA97C, 1 }, + { 0xA980, 0xA9B2, 1 }, + { 0xA9B4, 0xA9BF, 1 }, + { 0xA9CF, 0xA9E0, 17 }, + { 0xA9E1, 0xA9EF, 1 }, + { 0xA9FA, 0xA9FE, 1 }, + { 0xAA00, 0xAA36, 1 }, + { 0xAA40, 0xAA4D, 1 }, + { 0xAA60, 0xAA76, 1 }, + { 0xAA7A, 0xAABE, 1 }, + { 0xAAC0, 0xAAC2, 2 }, + { 0xAADB, 0xAADD, 1 }, + { 0xAAE0, 0xAAEF, 1 }, + { 0xAAF2, 0xAAF5, 1 }, + { 0xAB01, 0xAB06, 1 }, + { 0xAB09, 0xAB0E, 1 }, + { 0xAB11, 0xAB16, 1 }, + { 0xAB20, 0xAB26, 1 }, + { 0xAB28, 0xAB2E, 1 }, + { 0xAB30, 0xAB5A, 1 }, + { 0xAB5C, 0xAB69, 1 }, + { 0xAB70, 0xABEA, 1 }, + { 0xAC00, 0xD7A3, 1 }, + { 0xD7B0, 0xD7C6, 1 }, + { 0xD7CB, 0xD7FB, 1 }, + { 0xF900, 0xFA6D, 1 }, + { 0xFA70, 0xFAD9, 1 }, + { 0xFB00, 0xFB06, 1 }, + { 0xFB13, 0xFB17, 1 }, + { 0xFB1D, 0xFB28, 1 }, + { 0xFB2A, 0xFB36, 1 }, + { 0xFB38, 0xFB3C, 1 }, + { 0xFB3E, 0xFB40, 2 }, + { 0xFB41, 0xFB43, 2 }, + { 0xFB44, 0xFB46, 2 }, + { 0xFB47, 0xFBB1, 1 }, + { 0xFBD3, 0xFD3D, 1 }, + { 0xFD50, 0xFD8F, 1 }, + { 0xFD92, 0xFDC7, 1 }, + { 0xFDF0, 0xFDFB, 1 }, + { 0xFE70, 0xFE74, 1 }, + { 0xFE76, 0xFEFC, 1 }, + { 0xFF21, 0xFF3A, 1 }, + { 0xFF41, 0xFF5A, 1 }, + { 0xFF66, 0xFFBE, 1 }, + { 0xFFC2, 0xFFC7, 1 }, + { 0xFFCA, 0xFFCF, 1 }, + { 0xFFD2, 0xFFD7, 1 }, + { 0xFFDA, 0xFFDC, 1 }, + { 0x10000, 0x1000B, 1 }, + { 0x1000D, 0x10026, 1 }, + { 0x10028, 0x1003A, 1 }, + { 0x1003C, 0x1003D, 1 }, + { 0x1003F, 0x1004D, 1 }, + { 0x10050, 0x1005D, 1 }, + { 0x10080, 0x100FA, 1 }, + { 0x10140, 0x10174, 1 }, + { 0x10280, 0x1029C, 1 }, + { 0x102A0, 0x102D0, 1 }, + { 0x10300, 0x1031F, 1 }, + { 0x1032D, 0x1034A, 1 }, + { 0x10350, 0x1037A, 1 }, + { 0x10380, 0x1039D, 1 }, + { 0x103A0, 0x103C3, 1 }, + { 0x103C8, 0x103CF, 1 }, + { 0x103D1, 0x103D5, 1 }, + { 0x10400, 0x1049D, 1 }, + { 0x104B0, 0x104D3, 1 }, + { 0x104D8, 0x104FB, 1 }, + { 0x10500, 0x10527, 1 }, + { 0x10530, 0x10563, 1 }, + { 0x10570, 0x1057A, 1 }, + { 0x1057C, 0x1058A, 1 }, + { 0x1058C, 0x10592, 1 }, + { 0x10594, 0x10595, 1 }, + { 0x10597, 0x105A1, 1 }, + { 0x105A3, 0x105B1, 1 }, + { 0x105B3, 0x105B9, 1 }, + { 0x105BB, 0x105BC, 1 }, + { 0x10600, 0x10736, 1 }, + { 0x10740, 0x10755, 1 }, + { 0x10760, 0x10767, 1 }, + { 0x10780, 0x10785, 1 }, + { 0x10787, 0x107B0, 1 }, + { 0x107B2, 0x107BA, 1 }, + { 0x10800, 0x10805, 1 }, + { 0x10808, 0x1080A, 2 }, + { 0x1080B, 0x10835, 1 }, + { 0x10837, 0x10838, 1 }, + { 0x1083C, 0x1083F, 3 }, + { 0x10840, 0x10855, 1 }, + { 0x10860, 0x10876, 1 }, + { 0x10880, 0x1089E, 1 }, + { 0x108E0, 0x108F2, 1 }, + { 0x108F4, 0x108F5, 1 }, + { 0x10900, 0x10915, 1 }, + { 0x10920, 0x10939, 1 }, + { 0x10980, 0x109B7, 1 }, + { 0x109BE, 0x109BF, 1 }, + { 0x10A00, 0x10A03, 1 }, + { 0x10A05, 0x10A06, 1 }, + { 0x10A0C, 0x10A13, 1 }, + { 0x10A15, 0x10A17, 1 }, + { 0x10A19, 0x10A35, 1 }, + { 0x10A60, 0x10A7C, 1 }, + { 0x10A80, 0x10A9C, 1 }, + { 0x10AC0, 0x10AC7, 1 }, + { 0x10AC9, 0x10AE4, 1 }, + { 0x10B00, 0x10B35, 1 }, + { 0x10B40, 0x10B55, 1 }, + { 0x10B60, 0x10B72, 1 }, + { 0x10B80, 0x10B91, 1 }, + { 0x10C00, 0x10C48, 1 }, + { 0x10C80, 0x10CB2, 1 }, + { 0x10CC0, 0x10CF2, 1 }, + { 0x10D00, 0x10D27, 1 }, + { 0x10E80, 0x10EA9, 1 }, + { 0x10EAB, 0x10EAC, 1 }, + { 0x10EB0, 0x10EB1, 1 }, + { 0x10F00, 0x10F1C, 1 }, + { 0x10F27, 0x10F30, 9 }, + { 0x10F31, 0x10F45, 1 }, + { 0x10F70, 0x10F81, 1 }, + { 0x10FB0, 0x10FC4, 1 }, + { 0x10FE0, 0x10FF6, 1 }, + { 0x11000, 0x11045, 1 }, + { 0x11071, 0x11075, 1 }, + { 0x11080, 0x110B8, 1 }, + { 0x110C2, 0x110D0, 14 }, + { 0x110D1, 0x110E8, 1 }, + { 0x11100, 0x11132, 1 }, + { 0x11144, 0x11147, 1 }, + { 0x11150, 0x11172, 1 }, + { 0x11176, 0x11180, 10 }, + { 0x11181, 0x111BF, 1 }, + { 0x111C1, 0x111C4, 1 }, + { 0x111CE, 0x111CF, 1 }, + { 0x111DA, 0x111DC, 2 }, + { 0x11200, 0x11211, 1 }, + { 0x11213, 0x11234, 1 }, + { 0x11237, 0x1123E, 7 }, + { 0x1123F, 0x11241, 1 }, + { 0x11280, 0x11286, 1 }, + { 0x11288, 0x1128A, 2 }, + { 0x1128B, 0x1128D, 1 }, + { 0x1128F, 0x1129D, 1 }, + { 0x1129F, 0x112A8, 1 }, + { 0x112B0, 0x112E8, 1 }, + { 0x11300, 0x11303, 1 }, + { 0x11305, 0x1130C, 1 }, + { 0x1130F, 0x11310, 1 }, + { 0x11313, 0x11328, 1 }, + { 0x1132A, 0x11330, 1 }, + { 0x11332, 0x11333, 1 }, + { 0x11335, 0x11339, 1 }, + { 0x1133D, 0x11344, 1 }, + { 0x11347, 0x11348, 1 }, + { 0x1134B, 0x1134C, 1 }, + { 0x11350, 0x11357, 7 }, + { 0x1135D, 0x11363, 1 }, + { 0x11400, 0x11441, 1 }, + { 0x11443, 0x11445, 1 }, + { 0x11447, 0x1144A, 1 }, + { 0x1145F, 0x11461, 1 }, + { 0x11480, 0x114C1, 1 }, + { 0x114C4, 0x114C5, 1 }, + { 0x114C7, 0x11580, 185 }, + { 0x11581, 0x115B5, 1 }, + { 0x115B8, 0x115BE, 1 }, + { 0x115D8, 0x115DD, 1 }, + { 0x11600, 0x1163E, 1 }, + { 0x11640, 0x11644, 4 }, + { 0x11680, 0x116B5, 1 }, + { 0x116B8, 0x11700, 72 }, + { 0x11701, 0x1171A, 1 }, + { 0x1171D, 0x1172A, 1 }, + { 0x11740, 0x11746, 1 }, + { 0x11800, 0x11838, 1 }, + { 0x118A0, 0x118DF, 1 }, + { 0x118FF, 0x11906, 1 }, + { 0x11909, 0x1190C, 3 }, + { 0x1190D, 0x11913, 1 }, + { 0x11915, 0x11916, 1 }, + { 0x11918, 0x11935, 1 }, + { 0x11937, 0x11938, 1 }, + { 0x1193B, 0x1193C, 1 }, + { 0x1193F, 0x11942, 1 }, + { 0x119A0, 0x119A7, 1 }, + { 0x119AA, 0x119D7, 1 }, + { 0x119DA, 0x119DF, 1 }, + { 0x119E1, 0x119E3, 2 }, + { 0x119E4, 0x11A00, 28 }, + { 0x11A01, 0x11A32, 1 }, + { 0x11A35, 0x11A3E, 1 }, + { 0x11A50, 0x11A97, 1 }, + { 0x11A9D, 0x11AB0, 19 }, + { 0x11AB1, 0x11AF8, 1 }, + { 0x11C00, 0x11C08, 1 }, + { 0x11C0A, 0x11C36, 1 }, + { 0x11C38, 0x11C3E, 1 }, + { 0x11C40, 0x11C72, 50 }, + { 0x11C73, 0x11C8F, 1 }, + { 0x11C92, 0x11CA7, 1 }, + { 0x11CA9, 0x11CB6, 1 }, + { 0x11D00, 0x11D06, 1 }, + { 0x11D08, 0x11D09, 1 }, + { 0x11D0B, 0x11D36, 1 }, + { 0x11D3A, 0x11D3C, 2 }, + { 0x11D3D, 0x11D3F, 2 }, + { 0x11D40, 0x11D41, 1 }, + { 0x11D43, 0x11D46, 3 }, + { 0x11D47, 0x11D60, 25 }, + { 0x11D61, 0x11D65, 1 }, + { 0x11D67, 0x11D68, 1 }, + { 0x11D6A, 0x11D8E, 1 }, + { 0x11D90, 0x11D91, 1 }, + { 0x11D93, 0x11D96, 1 }, + { 0x11D98, 0x11EE0, 328 }, + { 0x11EE1, 0x11EF6, 1 }, + { 0x11F00, 0x11F10, 1 }, + { 0x11F12, 0x11F3A, 1 }, + { 0x11F3E, 0x11F40, 1 }, + { 0x11FB0, 0x12000, 80 }, + { 0x12001, 0x12399, 1 }, + { 0x12400, 0x1246E, 1 }, + { 0x12480, 0x12543, 1 }, + { 0x12F90, 0x12FF0, 1 }, + { 0x13000, 0x1342F, 1 }, + { 0x13441, 0x13446, 1 }, + { 0x14400, 0x14646, 1 }, + { 0x16800, 0x16A38, 1 }, + { 0x16A40, 0x16A5E, 1 }, + { 0x16A70, 0x16ABE, 1 }, + { 0x16AD0, 0x16AED, 1 }, + { 0x16B00, 0x16B2F, 1 }, + { 0x16B40, 0x16B43, 1 }, + { 0x16B63, 0x16B77, 1 }, + { 0x16B7D, 0x16B8F, 1 }, + { 0x16E40, 0x16E7F, 1 }, + { 0x16F00, 0x16F4A, 1 }, + { 0x16F4F, 0x16F87, 1 }, + { 0x16F8F, 0x16F9F, 1 }, + { 0x16FE0, 0x16FE1, 1 }, + { 0x16FE3, 0x16FF0, 13 }, + { 0x16FF1, 0x17000, 15 }, + { 0x17001, 0x187F7, 1 }, + { 0x18800, 0x18CD5, 1 }, + { 0x18D00, 0x18D08, 1 }, + { 0x1AFF0, 0x1AFF3, 1 }, + { 0x1AFF5, 0x1AFFB, 1 }, + { 0x1AFFD, 0x1AFFE, 1 }, + { 0x1B000, 0x1B122, 1 }, + { 0x1B132, 0x1B150, 30 }, + { 0x1B151, 0x1B152, 1 }, + { 0x1B155, 0x1B164, 15 }, + { 0x1B165, 0x1B167, 1 }, + { 0x1B170, 0x1B2FB, 1 }, + { 0x1BC00, 0x1BC6A, 1 }, + { 0x1BC70, 0x1BC7C, 1 }, + { 0x1BC80, 0x1BC88, 1 }, + { 0x1BC90, 0x1BC99, 1 }, + { 0x1BC9E, 0x1D400, 5986 }, + { 0x1D401, 0x1D454, 1 }, + { 0x1D456, 0x1D49C, 1 }, + { 0x1D49E, 0x1D49F, 1 }, + { 0x1D4A2, 0x1D4A5, 3 }, + { 0x1D4A6, 0x1D4A9, 3 }, + { 0x1D4AA, 0x1D4AC, 1 }, + { 0x1D4AE, 0x1D4B9, 1 }, + { 0x1D4BB, 0x1D4BD, 2 }, + { 0x1D4BE, 0x1D4C3, 1 }, + { 0x1D4C5, 0x1D505, 1 }, + { 0x1D507, 0x1D50A, 1 }, + { 0x1D50D, 0x1D514, 1 }, + { 0x1D516, 0x1D51C, 1 }, + { 0x1D51E, 0x1D539, 1 }, + { 0x1D53B, 0x1D53E, 1 }, + { 0x1D540, 0x1D544, 1 }, + { 0x1D546, 0x1D54A, 4 }, + { 0x1D54B, 0x1D550, 1 }, + { 0x1D552, 0x1D6A5, 1 }, + { 0x1D6A8, 0x1D6C0, 1 }, + { 0x1D6C2, 0x1D6DA, 1 }, + { 0x1D6DC, 0x1D6FA, 1 }, + { 0x1D6FC, 0x1D714, 1 }, + { 0x1D716, 0x1D734, 1 }, + { 0x1D736, 0x1D74E, 1 }, + { 0x1D750, 0x1D76E, 1 }, + { 0x1D770, 0x1D788, 1 }, + { 0x1D78A, 0x1D7A8, 1 }, + { 0x1D7AA, 0x1D7C2, 1 }, + { 0x1D7C4, 0x1D7CB, 1 }, + { 0x1DF00, 0x1DF1E, 1 }, + { 0x1DF25, 0x1DF2A, 1 }, + { 0x1E000, 0x1E006, 1 }, + { 0x1E008, 0x1E018, 1 }, + { 0x1E01B, 0x1E021, 1 }, + { 0x1E023, 0x1E024, 1 }, + { 0x1E026, 0x1E02A, 1 }, + { 0x1E030, 0x1E06D, 1 }, + { 0x1E08F, 0x1E100, 113 }, + { 0x1E101, 0x1E12C, 1 }, + { 0x1E137, 0x1E13D, 1 }, + { 0x1E14E, 0x1E290, 322 }, + { 0x1E291, 0x1E2AD, 1 }, + { 0x1E2C0, 0x1E2EB, 1 }, + { 0x1E4D0, 0x1E4EB, 1 }, + { 0x1E7E0, 0x1E7E6, 1 }, + { 0x1E7E8, 0x1E7EB, 1 }, + { 0x1E7ED, 0x1E7EE, 1 }, + { 0x1E7F0, 0x1E7FE, 1 }, + { 0x1E800, 0x1E8C4, 1 }, + { 0x1E900, 0x1E943, 1 }, + { 0x1E947, 0x1E94B, 4 }, + { 0x1EE00, 0x1EE03, 1 }, + { 0x1EE05, 0x1EE1F, 1 }, + { 0x1EE21, 0x1EE22, 1 }, + { 0x1EE24, 0x1EE27, 3 }, + { 0x1EE29, 0x1EE32, 1 }, + { 0x1EE34, 0x1EE37, 1 }, + { 0x1EE39, 0x1EE3B, 2 }, + { 0x1EE42, 0x1EE47, 5 }, + { 0x1EE49, 0x1EE4D, 2 }, + { 0x1EE4E, 0x1EE4F, 1 }, + { 0x1EE51, 0x1EE52, 1 }, + { 0x1EE54, 0x1EE57, 3 }, + { 0x1EE59, 0x1EE61, 2 }, + { 0x1EE62, 0x1EE64, 2 }, + { 0x1EE67, 0x1EE6A, 1 }, + { 0x1EE6C, 0x1EE72, 1 }, + { 0x1EE74, 0x1EE77, 1 }, + { 0x1EE79, 0x1EE7C, 1 }, + { 0x1EE7E, 0x1EE80, 2 }, + { 0x1EE81, 0x1EE89, 1 }, + { 0x1EE8B, 0x1EE9B, 1 }, + { 0x1EEA1, 0x1EEA3, 1 }, + { 0x1EEA5, 0x1EEA9, 1 }, + { 0x1EEAB, 0x1EEBB, 1 }, + { 0x1F130, 0x1F149, 1 }, + { 0x1F150, 0x1F169, 1 }, + { 0x1F170, 0x1F189, 1 }, + { 0x20000, 0x2A6DF, 1 }, + { 0x2A700, 0x2B739, 1 }, + { 0x2B740, 0x2B81D, 1 }, + { 0x2B820, 0x2CEA1, 1 }, + { 0x2CEB0, 0x2EBE0, 1 }, + { 0x2F800, 0x2FA1D, 1 }, + { 0x30000, 0x3134A, 1 }, + { 0x31350, 0x323AF, 1 }, +}; + +static struct range_table lower_table[] = { + { 0x61, 0x7A, 1 }, + { 0xAA, 0xB5, 11 }, + { 0xBA, 0xDF, 37 }, + { 0xE0, 0xF6, 1 }, + { 0xF8, 0xFF, 1 }, + { 0x101, 0x137, 2 }, + { 0x138, 0x148, 2 }, + { 0x149, 0x177, 2 }, + { 0x17A, 0x17E, 2 }, + { 0x17F, 0x180, 1 }, + { 0x183, 0x185, 2 }, + { 0x188, 0x18C, 4 }, + { 0x18D, 0x192, 5 }, + { 0x195, 0x199, 4 }, + { 0x19A, 0x19B, 1 }, + { 0x19E, 0x1A1, 3 }, + { 0x1A3, 0x1A5, 2 }, + { 0x1A8, 0x1AA, 2 }, + { 0x1AB, 0x1AD, 2 }, + { 0x1B0, 0x1B4, 4 }, + { 0x1B6, 0x1B9, 3 }, + { 0x1BA, 0x1BD, 3 }, + { 0x1BE, 0x1BF, 1 }, + { 0x1C6, 0x1CC, 3 }, + { 0x1CE, 0x1DC, 2 }, + { 0x1DD, 0x1EF, 2 }, + { 0x1F0, 0x1F3, 3 }, + { 0x1F5, 0x1F9, 4 }, + { 0x1FB, 0x233, 2 }, + { 0x234, 0x239, 1 }, + { 0x23C, 0x23F, 3 }, + { 0x240, 0x242, 2 }, + { 0x247, 0x24F, 2 }, + { 0x250, 0x293, 1 }, + { 0x295, 0x2B8, 1 }, + { 0x2C0, 0x2C1, 1 }, + { 0x2E0, 0x2E4, 1 }, + { 0x345, 0x371, 44 }, + { 0x373, 0x377, 4 }, + { 0x37A, 0x37D, 1 }, + { 0x390, 0x3AC, 28 }, + { 0x3AD, 0x3CE, 1 }, + { 0x3D0, 0x3D1, 1 }, + { 0x3D5, 0x3D7, 1 }, + { 0x3D9, 0x3EF, 2 }, + { 0x3F0, 0x3F3, 1 }, + { 0x3F5, 0x3FB, 3 }, + { 0x3FC, 0x430, 52 }, + { 0x431, 0x45F, 1 }, + { 0x461, 0x481, 2 }, + { 0x48B, 0x4BF, 2 }, + { 0x4C2, 0x4CE, 2 }, + { 0x4CF, 0x52F, 2 }, + { 0x560, 0x588, 1 }, + { 0x10D0, 0x10FA, 1 }, + { 0x10FC, 0x10FF, 1 }, + { 0x13F8, 0x13FD, 1 }, + { 0x1C80, 0x1C88, 1 }, + { 0x1D00, 0x1DBF, 1 }, + { 0x1E01, 0x1E95, 2 }, + { 0x1E96, 0x1E9D, 1 }, + { 0x1E9F, 0x1EFF, 2 }, + { 0x1F00, 0x1F07, 1 }, + { 0x1F10, 0x1F15, 1 }, + { 0x1F20, 0x1F27, 1 }, + { 0x1F30, 0x1F37, 1 }, + { 0x1F40, 0x1F45, 1 }, + { 0x1F50, 0x1F57, 1 }, + { 0x1F60, 0x1F67, 1 }, + { 0x1F70, 0x1F7D, 1 }, + { 0x1F80, 0x1F87, 1 }, + { 0x1F90, 0x1F97, 1 }, + { 0x1FA0, 0x1FA7, 1 }, + { 0x1FB0, 0x1FB4, 1 }, + { 0x1FB6, 0x1FB7, 1 }, + { 0x1FBE, 0x1FC2, 4 }, + { 0x1FC3, 0x1FC4, 1 }, + { 0x1FC6, 0x1FC7, 1 }, + { 0x1FD0, 0x1FD3, 1 }, + { 0x1FD6, 0x1FD7, 1 }, + { 0x1FE0, 0x1FE7, 1 }, + { 0x1FF2, 0x1FF4, 1 }, + { 0x1FF6, 0x1FF7, 1 }, + { 0x2071, 0x207F, 14 }, + { 0x2090, 0x209C, 1 }, + { 0x210A, 0x210E, 4 }, + { 0x210F, 0x2113, 4 }, + { 0x212F, 0x2139, 5 }, + { 0x213C, 0x213D, 1 }, + { 0x2146, 0x2149, 1 }, + { 0x214E, 0x2170, 34 }, + { 0x2171, 0x217F, 1 }, + { 0x2184, 0x24D0, 844 }, + { 0x24D1, 0x24E9, 1 }, + { 0x2C30, 0x2C5F, 1 }, + { 0x2C61, 0x2C65, 4 }, + { 0x2C66, 0x2C6C, 2 }, + { 0x2C71, 0x2C73, 2 }, + { 0x2C74, 0x2C76, 2 }, + { 0x2C77, 0x2C7D, 1 }, + { 0x2C81, 0x2CE3, 2 }, + { 0x2CE4, 0x2CEC, 8 }, + { 0x2CEE, 0x2CF3, 5 }, + { 0x2D00, 0x2D25, 1 }, + { 0x2D27, 0x2D2D, 6 }, + { 0xA641, 0xA66D, 2 }, + { 0xA681, 0xA69B, 2 }, + { 0xA69C, 0xA69D, 1 }, + { 0xA723, 0xA72F, 2 }, + { 0xA730, 0xA731, 1 }, + { 0xA733, 0xA76F, 2 }, + { 0xA770, 0xA778, 1 }, + { 0xA77A, 0xA77C, 2 }, + { 0xA77F, 0xA787, 2 }, + { 0xA78C, 0xA78E, 2 }, + { 0xA791, 0xA793, 2 }, + { 0xA794, 0xA795, 1 }, + { 0xA797, 0xA7A9, 2 }, + { 0xA7AF, 0xA7B5, 6 }, + { 0xA7B7, 0xA7C3, 2 }, + { 0xA7C8, 0xA7CA, 2 }, + { 0xA7D1, 0xA7D9, 2 }, + { 0xA7F2, 0xA7F4, 1 }, + { 0xA7F6, 0xA7F8, 2 }, + { 0xA7F9, 0xA7FA, 1 }, + { 0xAB30, 0xAB5A, 1 }, + { 0xAB5C, 0xAB69, 1 }, + { 0xAB70, 0xABBF, 1 }, + { 0xFB00, 0xFB06, 1 }, + { 0xFB13, 0xFB17, 1 }, + { 0xFF41, 0xFF5A, 1 }, + { 0x10428, 0x1044F, 1 }, + { 0x104D8, 0x104FB, 1 }, + { 0x10597, 0x105A1, 1 }, + { 0x105A3, 0x105B1, 1 }, + { 0x105B3, 0x105B9, 1 }, + { 0x105BB, 0x105BC, 1 }, + { 0x10780, 0x10783, 3 }, + { 0x10784, 0x10785, 1 }, + { 0x10787, 0x107B0, 1 }, + { 0x107B2, 0x107BA, 1 }, + { 0x10CC0, 0x10CF2, 1 }, + { 0x118C0, 0x118DF, 1 }, + { 0x16E60, 0x16E7F, 1 }, + { 0x1D41A, 0x1D433, 1 }, + { 0x1D44E, 0x1D454, 1 }, + { 0x1D456, 0x1D467, 1 }, + { 0x1D482, 0x1D49B, 1 }, + { 0x1D4B6, 0x1D4B9, 1 }, + { 0x1D4BB, 0x1D4BD, 2 }, + { 0x1D4BE, 0x1D4C3, 1 }, + { 0x1D4C5, 0x1D4CF, 1 }, + { 0x1D4EA, 0x1D503, 1 }, + { 0x1D51E, 0x1D537, 1 }, + { 0x1D552, 0x1D56B, 1 }, + { 0x1D586, 0x1D59F, 1 }, + { 0x1D5BA, 0x1D5D3, 1 }, + { 0x1D5EE, 0x1D607, 1 }, + { 0x1D622, 0x1D63B, 1 }, + { 0x1D656, 0x1D66F, 1 }, + { 0x1D68A, 0x1D6A5, 1 }, + { 0x1D6C2, 0x1D6DA, 1 }, + { 0x1D6DC, 0x1D6E1, 1 }, + { 0x1D6FC, 0x1D714, 1 }, + { 0x1D716, 0x1D71B, 1 }, + { 0x1D736, 0x1D74E, 1 }, + { 0x1D750, 0x1D755, 1 }, + { 0x1D770, 0x1D788, 1 }, + { 0x1D78A, 0x1D78F, 1 }, + { 0x1D7AA, 0x1D7C2, 1 }, + { 0x1D7C4, 0x1D7C9, 1 }, + { 0x1D7CB, 0x1DF00, 1845 }, + { 0x1DF01, 0x1DF09, 1 }, + { 0x1DF0B, 0x1DF1E, 1 }, + { 0x1DF25, 0x1DF2A, 1 }, + { 0x1E030, 0x1E06D, 1 }, + { 0x1E922, 0x1E943, 1 }, +}; + +static struct range_table upper_table[] = { + { 0x41, 0x5A, 1 }, + { 0xC0, 0xD6, 1 }, + { 0xD8, 0xDE, 1 }, + { 0x100, 0x136, 2 }, + { 0x139, 0x147, 2 }, + { 0x14A, 0x178, 2 }, + { 0x179, 0x17D, 2 }, + { 0x181, 0x182, 1 }, + { 0x184, 0x186, 2 }, + { 0x187, 0x189, 2 }, + { 0x18A, 0x18B, 1 }, + { 0x18E, 0x191, 1 }, + { 0x193, 0x194, 1 }, + { 0x196, 0x198, 1 }, + { 0x19C, 0x19D, 1 }, + { 0x19F, 0x1A0, 1 }, + { 0x1A2, 0x1A6, 2 }, + { 0x1A7, 0x1A9, 2 }, + { 0x1AC, 0x1AE, 2 }, + { 0x1AF, 0x1B1, 2 }, + { 0x1B2, 0x1B3, 1 }, + { 0x1B5, 0x1B7, 2 }, + { 0x1B8, 0x1BC, 4 }, + { 0x1C4, 0x1CD, 3 }, + { 0x1CF, 0x1DB, 2 }, + { 0x1DE, 0x1EE, 2 }, + { 0x1F1, 0x1F4, 3 }, + { 0x1F6, 0x1F8, 1 }, + { 0x1FA, 0x232, 2 }, + { 0x23A, 0x23B, 1 }, + { 0x23D, 0x23E, 1 }, + { 0x241, 0x243, 2 }, + { 0x244, 0x246, 1 }, + { 0x248, 0x24E, 2 }, + { 0x370, 0x372, 2 }, + { 0x376, 0x37F, 9 }, + { 0x386, 0x388, 2 }, + { 0x389, 0x38A, 1 }, + { 0x38C, 0x38E, 2 }, + { 0x38F, 0x391, 2 }, + { 0x392, 0x3A1, 1 }, + { 0x3A3, 0x3AB, 1 }, + { 0x3CF, 0x3D2, 3 }, + { 0x3D3, 0x3D4, 1 }, + { 0x3D8, 0x3EE, 2 }, + { 0x3F4, 0x3F7, 3 }, + { 0x3F9, 0x3FA, 1 }, + { 0x3FD, 0x42F, 1 }, + { 0x460, 0x480, 2 }, + { 0x48A, 0x4C0, 2 }, + { 0x4C1, 0x4CD, 2 }, + { 0x4D0, 0x52E, 2 }, + { 0x531, 0x556, 1 }, + { 0x10A0, 0x10C5, 1 }, + { 0x10C7, 0x10CD, 6 }, + { 0x13A0, 0x13F5, 1 }, + { 0x1C90, 0x1CBA, 1 }, + { 0x1CBD, 0x1CBF, 1 }, + { 0x1E00, 0x1E94, 2 }, + { 0x1E9E, 0x1EFE, 2 }, + { 0x1F08, 0x1F0F, 1 }, + { 0x1F18, 0x1F1D, 1 }, + { 0x1F28, 0x1F2F, 1 }, + { 0x1F38, 0x1F3F, 1 }, + { 0x1F48, 0x1F4D, 1 }, + { 0x1F59, 0x1F5F, 2 }, + { 0x1F68, 0x1F6F, 1 }, + { 0x1FB8, 0x1FBB, 1 }, + { 0x1FC8, 0x1FCB, 1 }, + { 0x1FD8, 0x1FDB, 1 }, + { 0x1FE8, 0x1FEC, 1 }, + { 0x1FF8, 0x1FFB, 1 }, + { 0x2102, 0x2107, 5 }, + { 0x210B, 0x210D, 1 }, + { 0x2110, 0x2112, 1 }, + { 0x2115, 0x2119, 4 }, + { 0x211A, 0x211D, 1 }, + { 0x2124, 0x212A, 2 }, + { 0x212B, 0x212D, 1 }, + { 0x2130, 0x2133, 1 }, + { 0x213E, 0x213F, 1 }, + { 0x2145, 0x2160, 27 }, + { 0x2161, 0x216F, 1 }, + { 0x2183, 0x24B6, 819 }, + { 0x24B7, 0x24CF, 1 }, + { 0x2C00, 0x2C2F, 1 }, + { 0x2C60, 0x2C62, 2 }, + { 0x2C63, 0x2C64, 1 }, + { 0x2C67, 0x2C6D, 2 }, + { 0x2C6E, 0x2C70, 1 }, + { 0x2C72, 0x2C75, 3 }, + { 0x2C7E, 0x2C80, 1 }, + { 0x2C82, 0x2CE2, 2 }, + { 0x2CEB, 0x2CED, 2 }, + { 0x2CF2, 0xA640, 31054 }, + { 0xA642, 0xA66C, 2 }, + { 0xA680, 0xA69A, 2 }, + { 0xA722, 0xA72E, 2 }, + { 0xA732, 0xA76E, 2 }, + { 0xA779, 0xA77D, 2 }, + { 0xA77E, 0xA786, 2 }, + { 0xA78B, 0xA78D, 2 }, + { 0xA790, 0xA792, 2 }, + { 0xA796, 0xA7AA, 2 }, + { 0xA7AB, 0xA7AE, 1 }, + { 0xA7B0, 0xA7B4, 1 }, + { 0xA7B6, 0xA7C4, 2 }, + { 0xA7C5, 0xA7C7, 1 }, + { 0xA7C9, 0xA7D0, 7 }, + { 0xA7D6, 0xA7D8, 2 }, + { 0xA7F5, 0xFF21, 22316 }, + { 0xFF22, 0xFF3A, 1 }, + { 0x10400, 0x10427, 1 }, + { 0x104B0, 0x104D3, 1 }, + { 0x10570, 0x1057A, 1 }, + { 0x1057C, 0x1058A, 1 }, + { 0x1058C, 0x10592, 1 }, + { 0x10594, 0x10595, 1 }, + { 0x10C80, 0x10CB2, 1 }, + { 0x118A0, 0x118BF, 1 }, + { 0x16E40, 0x16E5F, 1 }, + { 0x1D400, 0x1D419, 1 }, + { 0x1D434, 0x1D44D, 1 }, + { 0x1D468, 0x1D481, 1 }, + { 0x1D49C, 0x1D49E, 2 }, + { 0x1D49F, 0x1D4A5, 3 }, + { 0x1D4A6, 0x1D4A9, 3 }, + { 0x1D4AA, 0x1D4AC, 1 }, + { 0x1D4AE, 0x1D4B5, 1 }, + { 0x1D4D0, 0x1D4E9, 1 }, + { 0x1D504, 0x1D505, 1 }, + { 0x1D507, 0x1D50A, 1 }, + { 0x1D50D, 0x1D514, 1 }, + { 0x1D516, 0x1D51C, 1 }, + { 0x1D538, 0x1D539, 1 }, + { 0x1D53B, 0x1D53E, 1 }, + { 0x1D540, 0x1D544, 1 }, + { 0x1D546, 0x1D54A, 4 }, + { 0x1D54B, 0x1D550, 1 }, + { 0x1D56C, 0x1D585, 1 }, + { 0x1D5A0, 0x1D5B9, 1 }, + { 0x1D5D4, 0x1D5ED, 1 }, + { 0x1D608, 0x1D621, 1 }, + { 0x1D63C, 0x1D655, 1 }, + { 0x1D670, 0x1D689, 1 }, + { 0x1D6A8, 0x1D6C0, 1 }, + { 0x1D6E2, 0x1D6FA, 1 }, + { 0x1D71C, 0x1D734, 1 }, + { 0x1D756, 0x1D76E, 1 }, + { 0x1D790, 0x1D7A8, 1 }, + { 0x1D7CA, 0x1E900, 4406 }, + { 0x1E901, 0x1E921, 1 }, + { 0x1F130, 0x1F149, 1 }, + { 0x1F150, 0x1F169, 1 }, + { 0x1F170, 0x1F189, 1 }, +}; + +static struct range_table xdigit_table[] = { + { 0x30, 0x39, 1 }, + { 0x41, 0x46, 1 }, + { 0x61, 0x66, 1 }, + { 0xFF10, 0xFF19, 1 }, + { 0xFF21, 0xFF26, 1 }, + { 0xFF41, 0xFF46, 1 }, +}; + +static struct range_table space_table[] = { + { 0x9, 0xD, 1 }, + { 0x20, 0x85, 101 }, + { 0xA0, 0x1680, 5600 }, + { 0x2000, 0x200A, 1 }, + { 0x2028, 0x2029, 1 }, + { 0x202F, 0x205F, 48 }, + { 0x3000, 0x3000, 1 }, +}; + +static struct range_table unprintable_table[] = { + { 0xAD, 0x34F, 674 }, + { 0x61C, 0x115F, 2883 }, + { 0x1160, 0x17B4, 1620 }, + { 0x17B5, 0x180B, 86 }, + { 0x180C, 0x180F, 1 }, + { 0x200B, 0x200F, 1 }, + { 0x202A, 0x202E, 1 }, + { 0x2060, 0x206F, 1 }, + { 0x3164, 0xFE00, 52380 }, + { 0xFE01, 0xFE0F, 1 }, + { 0xFEFF, 0xFFA0, 161 }, + { 0xFFF0, 0xFFF8, 1 }, + { 0x1BCA0, 0x1BCA3, 1 }, + { 0x1D173, 0x1D17A, 1 }, + { 0xE0000, 0xE0FFF, 1 }, +}; + +static struct range_table graph_table[] = { + { 0x20, 0x7E, 1 }, + { 0xA0, 0xAC, 1 }, + { 0xAE, 0x2FF, 1 }, + { 0x370, 0x377, 1 }, + { 0x37A, 0x37F, 1 }, + { 0x384, 0x38A, 1 }, + { 0x38C, 0x38E, 2 }, + { 0x38F, 0x3A1, 1 }, + { 0x3A3, 0x482, 1 }, + { 0x48A, 0x52F, 1 }, + { 0x531, 0x556, 1 }, + { 0x559, 0x58A, 1 }, + { 0x58D, 0x58F, 1 }, + { 0x5BE, 0x5C0, 2 }, + { 0x5C3, 0x5C6, 3 }, + { 0x5D0, 0x5EA, 1 }, + { 0x5EF, 0x5F4, 1 }, + { 0x606, 0x60F, 1 }, + { 0x61B, 0x61D, 2 }, + { 0x61E, 0x64A, 1 }, + { 0x660, 0x66F, 1 }, + { 0x671, 0x6D5, 1 }, + { 0x6DE, 0x6E5, 7 }, + { 0x6E6, 0x6E9, 3 }, + { 0x6EE, 0x70D, 1 }, + { 0x710, 0x712, 2 }, + { 0x713, 0x72F, 1 }, + { 0x74D, 0x7A5, 1 }, + { 0x7B1, 0x7C0, 15 }, + { 0x7C1, 0x7EA, 1 }, + { 0x7F4, 0x7FA, 1 }, + { 0x7FE, 0x815, 1 }, + { 0x81A, 0x824, 10 }, + { 0x828, 0x830, 8 }, + { 0x831, 0x83E, 1 }, + { 0x840, 0x858, 1 }, + { 0x85E, 0x860, 2 }, + { 0x861, 0x86A, 1 }, + { 0x870, 0x88E, 1 }, + { 0x8A0, 0x8C9, 1 }, + { 0x903, 0x939, 1 }, + { 0x93B, 0x93D, 2 }, + { 0x93E, 0x940, 1 }, + { 0x949, 0x94C, 1 }, + { 0x94E, 0x950, 1 }, + { 0x958, 0x961, 1 }, + { 0x964, 0x980, 1 }, + { 0x982, 0x983, 1 }, + { 0x985, 0x98C, 1 }, + { 0x98F, 0x990, 1 }, + { 0x993, 0x9A8, 1 }, + { 0x9AA, 0x9B0, 1 }, + { 0x9B2, 0x9B6, 4 }, + { 0x9B7, 0x9B9, 1 }, + { 0x9BD, 0x9BF, 2 }, + { 0x9C0, 0x9C7, 7 }, + { 0x9C8, 0x9CB, 3 }, + { 0x9CC, 0x9CE, 2 }, + { 0x9DC, 0x9DD, 1 }, + { 0x9DF, 0x9E1, 1 }, + { 0x9E6, 0x9FD, 1 }, + { 0xA03, 0xA05, 2 }, + { 0xA06, 0xA0A, 1 }, + { 0xA0F, 0xA10, 1 }, + { 0xA13, 0xA28, 1 }, + { 0xA2A, 0xA30, 1 }, + { 0xA32, 0xA33, 1 }, + { 0xA35, 0xA36, 1 }, + { 0xA38, 0xA39, 1 }, + { 0xA3E, 0xA40, 1 }, + { 0xA59, 0xA5C, 1 }, + { 0xA5E, 0xA66, 8 }, + { 0xA67, 0xA6F, 1 }, + { 0xA72, 0xA74, 1 }, + { 0xA76, 0xA83, 13 }, + { 0xA85, 0xA8D, 1 }, + { 0xA8F, 0xA91, 1 }, + { 0xA93, 0xAA8, 1 }, + { 0xAAA, 0xAB0, 1 }, + { 0xAB2, 0xAB3, 1 }, + { 0xAB5, 0xAB9, 1 }, + { 0xABD, 0xAC0, 1 }, + { 0xAC9, 0xACB, 2 }, + { 0xACC, 0xAD0, 4 }, + { 0xAE0, 0xAE1, 1 }, + { 0xAE6, 0xAF1, 1 }, + { 0xAF9, 0xB02, 9 }, + { 0xB03, 0xB05, 2 }, + { 0xB06, 0xB0C, 1 }, + { 0xB0F, 0xB10, 1 }, + { 0xB13, 0xB28, 1 }, + { 0xB2A, 0xB30, 1 }, + { 0xB32, 0xB33, 1 }, + { 0xB35, 0xB39, 1 }, + { 0xB3D, 0xB40, 3 }, + { 0xB47, 0xB48, 1 }, + { 0xB4B, 0xB4C, 1 }, + { 0xB5C, 0xB5D, 1 }, + { 0xB5F, 0xB61, 1 }, + { 0xB66, 0xB77, 1 }, + { 0xB83, 0xB85, 2 }, + { 0xB86, 0xB8A, 1 }, + { 0xB8E, 0xB90, 1 }, + { 0xB92, 0xB95, 1 }, + { 0xB99, 0xB9A, 1 }, + { 0xB9C, 0xB9E, 2 }, + { 0xB9F, 0xBA3, 4 }, + { 0xBA4, 0xBA8, 4 }, + { 0xBA9, 0xBAA, 1 }, + { 0xBAE, 0xBB9, 1 }, + { 0xBBF, 0xBC1, 2 }, + { 0xBC2, 0xBC6, 4 }, + { 0xBC7, 0xBC8, 1 }, + { 0xBCA, 0xBCC, 1 }, + { 0xBD0, 0xBE6, 22 }, + { 0xBE7, 0xBFA, 1 }, + { 0xC01, 0xC03, 1 }, + { 0xC05, 0xC0C, 1 }, + { 0xC0E, 0xC10, 1 }, + { 0xC12, 0xC28, 1 }, + { 0xC2A, 0xC39, 1 }, + { 0xC3D, 0xC41, 4 }, + { 0xC42, 0xC44, 1 }, + { 0xC58, 0xC5A, 1 }, + { 0xC5D, 0xC60, 3 }, + { 0xC61, 0xC66, 5 }, + { 0xC67, 0xC6F, 1 }, + { 0xC77, 0xC80, 1 }, + { 0xC82, 0xC8C, 1 }, + { 0xC8E, 0xC90, 1 }, + { 0xC92, 0xCA8, 1 }, + { 0xCAA, 0xCB3, 1 }, + { 0xCB5, 0xCB9, 1 }, + { 0xCBD, 0xCBE, 1 }, + { 0xCC0, 0xCC1, 1 }, + { 0xCC3, 0xCC4, 1 }, + { 0xCC7, 0xCC8, 1 }, + { 0xCCA, 0xCCB, 1 }, + { 0xCDD, 0xCDE, 1 }, + { 0xCE0, 0xCE1, 1 }, + { 0xCE6, 0xCEF, 1 }, + { 0xCF1, 0xCF3, 1 }, + { 0xD02, 0xD0C, 1 }, + { 0xD0E, 0xD10, 1 }, + { 0xD12, 0xD3A, 1 }, + { 0xD3D, 0xD3F, 2 }, + { 0xD40, 0xD46, 6 }, + { 0xD47, 0xD48, 1 }, + { 0xD4A, 0xD4C, 1 }, + { 0xD4E, 0xD4F, 1 }, + { 0xD54, 0xD56, 1 }, + { 0xD58, 0xD61, 1 }, + { 0xD66, 0xD7F, 1 }, + { 0xD82, 0xD83, 1 }, + { 0xD85, 0xD96, 1 }, + { 0xD9A, 0xDB1, 1 }, + { 0xDB3, 0xDBB, 1 }, + { 0xDBD, 0xDC0, 3 }, + { 0xDC1, 0xDC6, 1 }, + { 0xDD0, 0xDD1, 1 }, + { 0xDD8, 0xDDE, 1 }, + { 0xDE6, 0xDEF, 1 }, + { 0xDF2, 0xDF4, 1 }, + { 0xE01, 0xE30, 1 }, + { 0xE32, 0xE33, 1 }, + { 0xE3F, 0xE46, 1 }, + { 0xE4F, 0xE5B, 1 }, + { 0xE81, 0xE82, 1 }, + { 0xE84, 0xE86, 2 }, + { 0xE87, 0xE8A, 1 }, + { 0xE8C, 0xEA3, 1 }, + { 0xEA5, 0xEA7, 2 }, + { 0xEA8, 0xEB0, 1 }, + { 0xEB2, 0xEB3, 1 }, + { 0xEBD, 0xEC0, 3 }, + { 0xEC1, 0xEC4, 1 }, + { 0xEC6, 0xED0, 10 }, + { 0xED1, 0xED9, 1 }, + { 0xEDC, 0xEDF, 1 }, + { 0xF00, 0xF17, 1 }, + { 0xF1A, 0xF34, 1 }, + { 0xF36, 0xF3A, 2 }, + { 0xF3B, 0xF47, 1 }, + { 0xF49, 0xF6C, 1 }, + { 0xF7F, 0xF85, 6 }, + { 0xF88, 0xF8C, 1 }, + { 0xFBE, 0xFC5, 1 }, + { 0xFC7, 0xFCC, 1 }, + { 0xFCE, 0xFDA, 1 }, + { 0x1000, 0x102C, 1 }, + { 0x1031, 0x1038, 7 }, + { 0x103B, 0x103C, 1 }, + { 0x103F, 0x1057, 1 }, + { 0x105A, 0x105D, 1 }, + { 0x1061, 0x1070, 1 }, + { 0x1075, 0x1081, 1 }, + { 0x1083, 0x1084, 1 }, + { 0x1087, 0x108C, 1 }, + { 0x108E, 0x109C, 1 }, + { 0x109E, 0x10C5, 1 }, + { 0x10C7, 0x10CD, 6 }, + { 0x10D0, 0x1248, 1 }, + { 0x124A, 0x124D, 1 }, + { 0x1250, 0x1256, 1 }, + { 0x1258, 0x125A, 2 }, + { 0x125B, 0x125D, 1 }, + { 0x1260, 0x1288, 1 }, + { 0x128A, 0x128D, 1 }, + { 0x1290, 0x12B0, 1 }, + { 0x12B2, 0x12B5, 1 }, + { 0x12B8, 0x12BE, 1 }, + { 0x12C0, 0x12C2, 2 }, + { 0x12C3, 0x12C5, 1 }, + { 0x12C8, 0x12D6, 1 }, + { 0x12D8, 0x1310, 1 }, + { 0x1312, 0x1315, 1 }, + { 0x1318, 0x135A, 1 }, + { 0x1360, 0x137C, 1 }, + { 0x1380, 0x1399, 1 }, + { 0x13A0, 0x13F5, 1 }, + { 0x13F8, 0x13FD, 1 }, + { 0x1400, 0x169C, 1 }, + { 0x16A0, 0x16F8, 1 }, + { 0x1700, 0x1711, 1 }, + { 0x1715, 0x171F, 10 }, + { 0x1720, 0x1731, 1 }, + { 0x1734, 0x1736, 1 }, + { 0x1740, 0x1751, 1 }, + { 0x1760, 0x176C, 1 }, + { 0x176E, 0x1770, 1 }, + { 0x1780, 0x17B3, 1 }, + { 0x17B6, 0x17BE, 8 }, + { 0x17BF, 0x17C5, 1 }, + { 0x17C7, 0x17C8, 1 }, + { 0x17D4, 0x17DC, 1 }, + { 0x17E0, 0x17E9, 1 }, + { 0x17F0, 0x17F9, 1 }, + { 0x1800, 0x180A, 1 }, + { 0x1810, 0x1819, 1 }, + { 0x1820, 0x1878, 1 }, + { 0x1880, 0x1884, 1 }, + { 0x1887, 0x18A8, 1 }, + { 0x18AA, 0x18B0, 6 }, + { 0x18B1, 0x18F5, 1 }, + { 0x1900, 0x191E, 1 }, + { 0x1923, 0x1926, 1 }, + { 0x1929, 0x192B, 1 }, + { 0x1930, 0x1931, 1 }, + { 0x1933, 0x1938, 1 }, + { 0x1940, 0x1944, 4 }, + { 0x1945, 0x196D, 1 }, + { 0x1970, 0x1974, 1 }, + { 0x1980, 0x19AB, 1 }, + { 0x19B0, 0x19C9, 1 }, + { 0x19D0, 0x19DA, 1 }, + { 0x19DE, 0x1A16, 1 }, + { 0x1A19, 0x1A1A, 1 }, + { 0x1A1E, 0x1A55, 1 }, + { 0x1A57, 0x1A61, 10 }, + { 0x1A63, 0x1A64, 1 }, + { 0x1A6D, 0x1A72, 1 }, + { 0x1A80, 0x1A89, 1 }, + { 0x1A90, 0x1A99, 1 }, + { 0x1AA0, 0x1AAD, 1 }, + { 0x1B04, 0x1B33, 1 }, + { 0x1B3B, 0x1B3D, 2 }, + { 0x1B3E, 0x1B41, 1 }, + { 0x1B43, 0x1B4C, 1 }, + { 0x1B50, 0x1B6A, 1 }, + { 0x1B74, 0x1B7E, 1 }, + { 0x1B82, 0x1BA1, 1 }, + { 0x1BA6, 0x1BA7, 1 }, + { 0x1BAA, 0x1BAE, 4 }, + { 0x1BAF, 0x1BE5, 1 }, + { 0x1BE7, 0x1BEA, 3 }, + { 0x1BEB, 0x1BEC, 1 }, + { 0x1BEE, 0x1BF2, 4 }, + { 0x1BF3, 0x1BFC, 9 }, + { 0x1BFD, 0x1C2B, 1 }, + { 0x1C34, 0x1C35, 1 }, + { 0x1C3B, 0x1C49, 1 }, + { 0x1C4D, 0x1C88, 1 }, + { 0x1C90, 0x1CBA, 1 }, + { 0x1CBD, 0x1CC7, 1 }, + { 0x1CD3, 0x1CE1, 14 }, + { 0x1CE9, 0x1CEC, 1 }, + { 0x1CEE, 0x1CF3, 1 }, + { 0x1CF5, 0x1CF7, 1 }, + { 0x1CFA, 0x1D00, 6 }, + { 0x1D01, 0x1DBF, 1 }, + { 0x1E00, 0x1F15, 1 }, + { 0x1F18, 0x1F1D, 1 }, + { 0x1F20, 0x1F45, 1 }, + { 0x1F48, 0x1F4D, 1 }, + { 0x1F50, 0x1F57, 1 }, + { 0x1F59, 0x1F5F, 2 }, + { 0x1F60, 0x1F7D, 1 }, + { 0x1F80, 0x1FB4, 1 }, + { 0x1FB6, 0x1FC4, 1 }, + { 0x1FC6, 0x1FD3, 1 }, + { 0x1FD6, 0x1FDB, 1 }, + { 0x1FDD, 0x1FEF, 1 }, + { 0x1FF2, 0x1FF4, 1 }, + { 0x1FF6, 0x1FFE, 1 }, + { 0x2000, 0x200A, 1 }, + { 0x2010, 0x2027, 1 }, + { 0x202F, 0x205F, 1 }, + { 0x2070, 0x2071, 1 }, + { 0x2074, 0x208E, 1 }, + { 0x2090, 0x209C, 1 }, + { 0x20A0, 0x20C0, 1 }, + { 0x2100, 0x218B, 1 }, + { 0x2190, 0x2426, 1 }, + { 0x2440, 0x244A, 1 }, + { 0x2460, 0x2B73, 1 }, + { 0x2B76, 0x2B95, 1 }, + { 0x2B97, 0x2CEE, 1 }, + { 0x2CF2, 0x2CF3, 1 }, + { 0x2CF9, 0x2D25, 1 }, + { 0x2D27, 0x2D2D, 6 }, + { 0x2D30, 0x2D67, 1 }, + { 0x2D6F, 0x2D70, 1 }, + { 0x2D80, 0x2D96, 1 }, + { 0x2DA0, 0x2DA6, 1 }, + { 0x2DA8, 0x2DAE, 1 }, + { 0x2DB0, 0x2DB6, 1 }, + { 0x2DB8, 0x2DBE, 1 }, + { 0x2DC0, 0x2DC6, 1 }, + { 0x2DC8, 0x2DCE, 1 }, + { 0x2DD0, 0x2DD6, 1 }, + { 0x2DD8, 0x2DDE, 1 }, + { 0x2E00, 0x2E5D, 1 }, + { 0x2E80, 0x2E99, 1 }, + { 0x2E9B, 0x2EF3, 1 }, + { 0x2F00, 0x2FD5, 1 }, + { 0x2FF0, 0x2FFB, 1 }, + { 0x3000, 0x3029, 1 }, + { 0x3030, 0x303F, 1 }, + { 0x3041, 0x3096, 1 }, + { 0x309B, 0x30FF, 1 }, + { 0x3105, 0x312F, 1 }, + { 0x3131, 0x318E, 1 }, + { 0x3190, 0x31E3, 1 }, + { 0x31F0, 0x321E, 1 }, + { 0x3220, 0xA48C, 1 }, + { 0xA490, 0xA4C6, 1 }, + { 0xA4D0, 0xA62B, 1 }, + { 0xA640, 0xA66E, 1 }, + { 0xA673, 0xA67E, 11 }, + { 0xA67F, 0xA69D, 1 }, + { 0xA6A0, 0xA6EF, 1 }, + { 0xA6F2, 0xA6F7, 1 }, + { 0xA700, 0xA7CA, 1 }, + { 0xA7D0, 0xA7D1, 1 }, + { 0xA7D3, 0xA7D5, 2 }, + { 0xA7D6, 0xA7D9, 1 }, + { 0xA7F2, 0xA801, 1 }, + { 0xA803, 0xA805, 1 }, + { 0xA807, 0xA80A, 1 }, + { 0xA80C, 0xA824, 1 }, + { 0xA827, 0xA82B, 1 }, + { 0xA830, 0xA839, 1 }, + { 0xA840, 0xA877, 1 }, + { 0xA880, 0xA8C3, 1 }, + { 0xA8CE, 0xA8D9, 1 }, + { 0xA8F2, 0xA8FE, 1 }, + { 0xA900, 0xA925, 1 }, + { 0xA92E, 0xA946, 1 }, + { 0xA952, 0xA953, 1 }, + { 0xA95F, 0xA97C, 1 }, + { 0xA983, 0xA9B2, 1 }, + { 0xA9B4, 0xA9B5, 1 }, + { 0xA9BA, 0xA9BB, 1 }, + { 0xA9BE, 0xA9CD, 1 }, + { 0xA9CF, 0xA9D9, 1 }, + { 0xA9DE, 0xA9E4, 1 }, + { 0xA9E6, 0xA9FE, 1 }, + { 0xAA00, 0xAA28, 1 }, + { 0xAA2F, 0xAA30, 1 }, + { 0xAA33, 0xAA34, 1 }, + { 0xAA40, 0xAA42, 1 }, + { 0xAA44, 0xAA4B, 1 }, + { 0xAA4D, 0xAA50, 3 }, + { 0xAA51, 0xAA59, 1 }, + { 0xAA5C, 0xAA7B, 1 }, + { 0xAA7D, 0xAAAF, 1 }, + { 0xAAB1, 0xAAB5, 4 }, + { 0xAAB6, 0xAAB9, 3 }, + { 0xAABA, 0xAABD, 1 }, + { 0xAAC0, 0xAAC2, 2 }, + { 0xAADB, 0xAAEB, 1 }, + { 0xAAEE, 0xAAF5, 1 }, + { 0xAB01, 0xAB06, 1 }, + { 0xAB09, 0xAB0E, 1 }, + { 0xAB11, 0xAB16, 1 }, + { 0xAB20, 0xAB26, 1 }, + { 0xAB28, 0xAB2E, 1 }, + { 0xAB30, 0xAB6B, 1 }, + { 0xAB70, 0xABE4, 1 }, + { 0xABE6, 0xABE7, 1 }, + { 0xABE9, 0xABEC, 1 }, + { 0xABF0, 0xABF9, 1 }, + { 0xAC00, 0xD7A3, 1 }, + { 0xD7B0, 0xD7C6, 1 }, + { 0xD7CB, 0xD7FB, 1 }, + { 0xF900, 0xFA6D, 1 }, + { 0xFA70, 0xFAD9, 1 }, + { 0xFB00, 0xFB06, 1 }, + { 0xFB13, 0xFB17, 1 }, + { 0xFB1D, 0xFB1F, 2 }, + { 0xFB20, 0xFB36, 1 }, + { 0xFB38, 0xFB3C, 1 }, + { 0xFB3E, 0xFB40, 2 }, + { 0xFB41, 0xFB43, 2 }, + { 0xFB44, 0xFB46, 2 }, + { 0xFB47, 0xFBC2, 1 }, + { 0xFBD3, 0xFD8F, 1 }, + { 0xFD92, 0xFDC7, 1 }, + { 0xFDCF, 0xFDF0, 33 }, + { 0xFDF1, 0xFDFF, 1 }, + { 0xFE10, 0xFE19, 1 }, + { 0xFE30, 0xFE52, 1 }, + { 0xFE54, 0xFE66, 1 }, + { 0xFE68, 0xFE6B, 1 }, + { 0xFE70, 0xFE74, 1 }, + { 0xFE76, 0xFEFC, 1 }, + { 0xFF01, 0xFF9D, 1 }, + { 0xFFA0, 0xFFBE, 1 }, + { 0xFFC2, 0xFFC7, 1 }, + { 0xFFCA, 0xFFCF, 1 }, + { 0xFFD2, 0xFFD7, 1 }, + { 0xFFDA, 0xFFDC, 1 }, + { 0xFFE0, 0xFFE6, 1 }, + { 0xFFE8, 0xFFEE, 1 }, + { 0xFFFC, 0xFFFD, 1 }, + { 0x10000, 0x1000B, 1 }, + { 0x1000D, 0x10026, 1 }, + { 0x10028, 0x1003A, 1 }, + { 0x1003C, 0x1003D, 1 }, + { 0x1003F, 0x1004D, 1 }, + { 0x10050, 0x1005D, 1 }, + { 0x10080, 0x100FA, 1 }, + { 0x10100, 0x10102, 1 }, + { 0x10107, 0x10133, 1 }, + { 0x10137, 0x1018E, 1 }, + { 0x10190, 0x1019C, 1 }, + { 0x101A0, 0x101D0, 48 }, + { 0x101D1, 0x101FC, 1 }, + { 0x10280, 0x1029C, 1 }, + { 0x102A0, 0x102D0, 1 }, + { 0x102E1, 0x102FB, 1 }, + { 0x10300, 0x10323, 1 }, + { 0x1032D, 0x1034A, 1 }, + { 0x10350, 0x10375, 1 }, + { 0x10380, 0x1039D, 1 }, + { 0x1039F, 0x103C3, 1 }, + { 0x103C8, 0x103D5, 1 }, + { 0x10400, 0x1049D, 1 }, + { 0x104A0, 0x104A9, 1 }, + { 0x104B0, 0x104D3, 1 }, + { 0x104D8, 0x104FB, 1 }, + { 0x10500, 0x10527, 1 }, + { 0x10530, 0x10563, 1 }, + { 0x1056F, 0x1057A, 1 }, + { 0x1057C, 0x1058A, 1 }, + { 0x1058C, 0x10592, 1 }, + { 0x10594, 0x10595, 1 }, + { 0x10597, 0x105A1, 1 }, + { 0x105A3, 0x105B1, 1 }, + { 0x105B3, 0x105B9, 1 }, + { 0x105BB, 0x105BC, 1 }, + { 0x10600, 0x10736, 1 }, + { 0x10740, 0x10755, 1 }, + { 0x10760, 0x10767, 1 }, + { 0x10780, 0x10785, 1 }, + { 0x10787, 0x107B0, 1 }, + { 0x107B2, 0x107BA, 1 }, + { 0x10800, 0x10805, 1 }, + { 0x10808, 0x1080A, 2 }, + { 0x1080B, 0x10835, 1 }, + { 0x10837, 0x10838, 1 }, + { 0x1083C, 0x1083F, 3 }, + { 0x10840, 0x10855, 1 }, + { 0x10857, 0x1089E, 1 }, + { 0x108A7, 0x108AF, 1 }, + { 0x108E0, 0x108F2, 1 }, + { 0x108F4, 0x108F5, 1 }, + { 0x108FB, 0x1091B, 1 }, + { 0x1091F, 0x10939, 1 }, + { 0x1093F, 0x10980, 65 }, + { 0x10981, 0x109B7, 1 }, + { 0x109BC, 0x109CF, 1 }, + { 0x109D2, 0x10A00, 1 }, + { 0x10A10, 0x10A13, 1 }, + { 0x10A15, 0x10A17, 1 }, + { 0x10A19, 0x10A35, 1 }, + { 0x10A40, 0x10A48, 1 }, + { 0x10A50, 0x10A58, 1 }, + { 0x10A60, 0x10A9F, 1 }, + { 0x10AC0, 0x10AE4, 1 }, + { 0x10AEB, 0x10AF6, 1 }, + { 0x10B00, 0x10B35, 1 }, + { 0x10B39, 0x10B55, 1 }, + { 0x10B58, 0x10B72, 1 }, + { 0x10B78, 0x10B91, 1 }, + { 0x10B99, 0x10B9C, 1 }, + { 0x10BA9, 0x10BAF, 1 }, + { 0x10C00, 0x10C48, 1 }, + { 0x10C80, 0x10CB2, 1 }, + { 0x10CC0, 0x10CF2, 1 }, + { 0x10CFA, 0x10D23, 1 }, + { 0x10D30, 0x10D39, 1 }, + { 0x10E60, 0x10E7E, 1 }, + { 0x10E80, 0x10EA9, 1 }, + { 0x10EAD, 0x10EB0, 3 }, + { 0x10EB1, 0x10F00, 79 }, + { 0x10F01, 0x10F27, 1 }, + { 0x10F30, 0x10F45, 1 }, + { 0x10F51, 0x10F59, 1 }, + { 0x10F70, 0x10F81, 1 }, + { 0x10F86, 0x10F89, 1 }, + { 0x10FB0, 0x10FCB, 1 }, + { 0x10FE0, 0x10FF6, 1 }, + { 0x11000, 0x11002, 2 }, + { 0x11003, 0x11037, 1 }, + { 0x11047, 0x1104D, 1 }, + { 0x11052, 0x1106F, 1 }, + { 0x11071, 0x11072, 1 }, + { 0x11075, 0x11082, 13 }, + { 0x11083, 0x110B2, 1 }, + { 0x110B7, 0x110B8, 1 }, + { 0x110BB, 0x110BC, 1 }, + { 0x110BE, 0x110C1, 1 }, + { 0x110D0, 0x110E8, 1 }, + { 0x110F0, 0x110F9, 1 }, + { 0x11103, 0x11126, 1 }, + { 0x1112C, 0x11136, 10 }, + { 0x11137, 0x11147, 1 }, + { 0x11150, 0x11172, 1 }, + { 0x11174, 0x11176, 1 }, + { 0x11182, 0x111B5, 1 }, + { 0x111BF, 0x111C8, 1 }, + { 0x111CD, 0x111CE, 1 }, + { 0x111D0, 0x111DF, 1 }, + { 0x111E1, 0x111F4, 1 }, + { 0x11200, 0x11211, 1 }, + { 0x11213, 0x1122E, 1 }, + { 0x11232, 0x11233, 1 }, + { 0x11235, 0x11238, 3 }, + { 0x11239, 0x1123D, 1 }, + { 0x1123F, 0x11240, 1 }, + { 0x11280, 0x11286, 1 }, + { 0x11288, 0x1128A, 2 }, + { 0x1128B, 0x1128D, 1 }, + { 0x1128F, 0x1129D, 1 }, + { 0x1129F, 0x112A9, 1 }, + { 0x112B0, 0x112DE, 1 }, + { 0x112E0, 0x112E2, 1 }, + { 0x112F0, 0x112F9, 1 }, + { 0x11302, 0x11303, 1 }, + { 0x11305, 0x1130C, 1 }, + { 0x1130F, 0x11310, 1 }, + { 0x11313, 0x11328, 1 }, + { 0x1132A, 0x11330, 1 }, + { 0x11332, 0x11333, 1 }, + { 0x11335, 0x11339, 1 }, + { 0x1133D, 0x11341, 2 }, + { 0x11342, 0x11344, 1 }, + { 0x11347, 0x11348, 1 }, + { 0x1134B, 0x1134D, 1 }, + { 0x11350, 0x1135D, 13 }, + { 0x1135E, 0x11363, 1 }, + { 0x11400, 0x11437, 1 }, + { 0x11440, 0x11441, 1 }, + { 0x11445, 0x11447, 2 }, + { 0x11448, 0x1145B, 1 }, + { 0x1145D, 0x1145F, 2 }, + { 0x11460, 0x11461, 1 }, + { 0x11480, 0x114AF, 1 }, + { 0x114B1, 0x114B2, 1 }, + { 0x114B9, 0x114BB, 2 }, + { 0x114BC, 0x114BE, 2 }, + { 0x114C1, 0x114C4, 3 }, + { 0x114C5, 0x114C7, 1 }, + { 0x114D0, 0x114D9, 1 }, + { 0x11580, 0x115AE, 1 }, + { 0x115B0, 0x115B1, 1 }, + { 0x115B8, 0x115BB, 1 }, + { 0x115BE, 0x115C1, 3 }, + { 0x115C2, 0x115DB, 1 }, + { 0x11600, 0x11632, 1 }, + { 0x1163B, 0x1163C, 1 }, + { 0x1163E, 0x11641, 3 }, + { 0x11642, 0x11644, 1 }, + { 0x11650, 0x11659, 1 }, + { 0x11660, 0x1166C, 1 }, + { 0x11680, 0x116AA, 1 }, + { 0x116AC, 0x116AE, 2 }, + { 0x116AF, 0x116B6, 7 }, + { 0x116B8, 0x116B9, 1 }, + { 0x116C0, 0x116C9, 1 }, + { 0x11700, 0x1171A, 1 }, + { 0x11720, 0x11721, 1 }, + { 0x11726, 0x11730, 10 }, + { 0x11731, 0x11746, 1 }, + { 0x11800, 0x1182E, 1 }, + { 0x11838, 0x1183B, 3 }, + { 0x118A0, 0x118F2, 1 }, + { 0x118FF, 0x11906, 1 }, + { 0x11909, 0x1190C, 3 }, + { 0x1190D, 0x11913, 1 }, + { 0x11915, 0x11916, 1 }, + { 0x11918, 0x1192F, 1 }, + { 0x11931, 0x11935, 1 }, + { 0x11937, 0x11938, 1 }, + { 0x1193D, 0x1193F, 2 }, + { 0x11940, 0x11942, 1 }, + { 0x11944, 0x11946, 1 }, + { 0x11950, 0x11959, 1 }, + { 0x119A0, 0x119A7, 1 }, + { 0x119AA, 0x119D3, 1 }, + { 0x119DC, 0x119DF, 1 }, + { 0x119E1, 0x119E4, 1 }, + { 0x11A00, 0x11A0B, 11 }, + { 0x11A0C, 0x11A32, 1 }, + { 0x11A39, 0x11A3A, 1 }, + { 0x11A3F, 0x11A46, 1 }, + { 0x11A50, 0x11A57, 7 }, + { 0x11A58, 0x11A5C, 4 }, + { 0x11A5D, 0x11A89, 1 }, + { 0x11A97, 0x11A9A, 3 }, + { 0x11A9B, 0x11AA2, 1 }, + { 0x11AB0, 0x11AF8, 1 }, + { 0x11B00, 0x11B09, 1 }, + { 0x11C00, 0x11C08, 1 }, + { 0x11C0A, 0x11C2F, 1 }, + { 0x11C3E, 0x11C40, 2 }, + { 0x11C41, 0x11C45, 1 }, + { 0x11C50, 0x11C6C, 1 }, + { 0x11C70, 0x11C8F, 1 }, + { 0x11CA9, 0x11CB1, 8 }, + { 0x11CB4, 0x11D00, 76 }, + { 0x11D01, 0x11D06, 1 }, + { 0x11D08, 0x11D09, 1 }, + { 0x11D0B, 0x11D30, 1 }, + { 0x11D46, 0x11D50, 10 }, + { 0x11D51, 0x11D59, 1 }, + { 0x11D60, 0x11D65, 1 }, + { 0x11D67, 0x11D68, 1 }, + { 0x11D6A, 0x11D8E, 1 }, + { 0x11D93, 0x11D94, 1 }, + { 0x11D96, 0x11D98, 2 }, + { 0x11DA0, 0x11DA9, 1 }, + { 0x11EE0, 0x11EF2, 1 }, + { 0x11EF5, 0x11EF8, 1 }, + { 0x11F02, 0x11F10, 1 }, + { 0x11F12, 0x11F35, 1 }, + { 0x11F3E, 0x11F3F, 1 }, + { 0x11F41, 0x11F43, 2 }, + { 0x11F44, 0x11F59, 1 }, + { 0x11FB0, 0x11FC0, 16 }, + { 0x11FC1, 0x11FF1, 1 }, + { 0x11FFF, 0x12399, 1 }, + { 0x12400, 0x1246E, 1 }, + { 0x12470, 0x12474, 1 }, + { 0x12480, 0x12543, 1 }, + { 0x12F90, 0x12FF2, 1 }, + { 0x13000, 0x1342F, 1 }, + { 0x13441, 0x13446, 1 }, + { 0x14400, 0x14646, 1 }, + { 0x16800, 0x16A38, 1 }, + { 0x16A40, 0x16A5E, 1 }, + { 0x16A60, 0x16A69, 1 }, + { 0x16A6E, 0x16ABE, 1 }, + { 0x16AC0, 0x16AC9, 1 }, + { 0x16AD0, 0x16AED, 1 }, + { 0x16AF5, 0x16B00, 11 }, + { 0x16B01, 0x16B2F, 1 }, + { 0x16B37, 0x16B45, 1 }, + { 0x16B50, 0x16B59, 1 }, + { 0x16B5B, 0x16B61, 1 }, + { 0x16B63, 0x16B77, 1 }, + { 0x16B7D, 0x16B8F, 1 }, + { 0x16E40, 0x16E9A, 1 }, + { 0x16F00, 0x16F4A, 1 }, + { 0x16F50, 0x16F87, 1 }, + { 0x16F93, 0x16F9F, 1 }, + { 0x16FE0, 0x16FE3, 1 }, + { 0x16FF0, 0x16FF1, 1 }, + { 0x17000, 0x187F7, 1 }, + { 0x18800, 0x18CD5, 1 }, + { 0x18D00, 0x18D08, 1 }, + { 0x1AFF0, 0x1AFF3, 1 }, + { 0x1AFF5, 0x1AFFB, 1 }, + { 0x1AFFD, 0x1AFFE, 1 }, + { 0x1B000, 0x1B122, 1 }, + { 0x1B132, 0x1B150, 30 }, + { 0x1B151, 0x1B152, 1 }, + { 0x1B155, 0x1B164, 15 }, + { 0x1B165, 0x1B167, 1 }, + { 0x1B170, 0x1B2FB, 1 }, + { 0x1BC00, 0x1BC6A, 1 }, + { 0x1BC70, 0x1BC7C, 1 }, + { 0x1BC80, 0x1BC88, 1 }, + { 0x1BC90, 0x1BC99, 1 }, + { 0x1BC9C, 0x1BC9F, 3 }, + { 0x1CF50, 0x1CFC3, 1 }, + { 0x1D000, 0x1D0F5, 1 }, + { 0x1D100, 0x1D126, 1 }, + { 0x1D129, 0x1D164, 1 }, + { 0x1D166, 0x1D16A, 4 }, + { 0x1D16B, 0x1D16D, 1 }, + { 0x1D183, 0x1D184, 1 }, + { 0x1D18C, 0x1D1A9, 1 }, + { 0x1D1AE, 0x1D1EA, 1 }, + { 0x1D200, 0x1D241, 1 }, + { 0x1D245, 0x1D2C0, 123 }, + { 0x1D2C1, 0x1D2D3, 1 }, + { 0x1D2E0, 0x1D2F3, 1 }, + { 0x1D300, 0x1D356, 1 }, + { 0x1D360, 0x1D378, 1 }, + { 0x1D400, 0x1D454, 1 }, + { 0x1D456, 0x1D49C, 1 }, + { 0x1D49E, 0x1D49F, 1 }, + { 0x1D4A2, 0x1D4A5, 3 }, + { 0x1D4A6, 0x1D4A9, 3 }, + { 0x1D4AA, 0x1D4AC, 1 }, + { 0x1D4AE, 0x1D4B9, 1 }, + { 0x1D4BB, 0x1D4BD, 2 }, + { 0x1D4BE, 0x1D4C3, 1 }, + { 0x1D4C5, 0x1D505, 1 }, + { 0x1D507, 0x1D50A, 1 }, + { 0x1D50D, 0x1D514, 1 }, + { 0x1D516, 0x1D51C, 1 }, + { 0x1D51E, 0x1D539, 1 }, + { 0x1D53B, 0x1D53E, 1 }, + { 0x1D540, 0x1D544, 1 }, + { 0x1D546, 0x1D54A, 4 }, + { 0x1D54B, 0x1D550, 1 }, + { 0x1D552, 0x1D6A5, 1 }, + { 0x1D6A8, 0x1D7CB, 1 }, + { 0x1D7CE, 0x1D9FF, 1 }, + { 0x1DA37, 0x1DA3A, 1 }, + { 0x1DA6D, 0x1DA74, 1 }, + { 0x1DA76, 0x1DA83, 1 }, + { 0x1DA85, 0x1DA8B, 1 }, + { 0x1DF00, 0x1DF1E, 1 }, + { 0x1DF25, 0x1DF2A, 1 }, + { 0x1E030, 0x1E06D, 1 }, + { 0x1E100, 0x1E12C, 1 }, + { 0x1E137, 0x1E13D, 1 }, + { 0x1E140, 0x1E149, 1 }, + { 0x1E14E, 0x1E14F, 1 }, + { 0x1E290, 0x1E2AD, 1 }, + { 0x1E2C0, 0x1E2EB, 1 }, + { 0x1E2F0, 0x1E2F9, 1 }, + { 0x1E2FF, 0x1E4D0, 465 }, + { 0x1E4D1, 0x1E4EB, 1 }, + { 0x1E4F0, 0x1E4F9, 1 }, + { 0x1E7E0, 0x1E7E6, 1 }, + { 0x1E7E8, 0x1E7EB, 1 }, + { 0x1E7ED, 0x1E7EE, 1 }, + { 0x1E7F0, 0x1E7FE, 1 }, + { 0x1E800, 0x1E8C4, 1 }, + { 0x1E8C7, 0x1E8CF, 1 }, + { 0x1E900, 0x1E943, 1 }, + { 0x1E94B, 0x1E950, 5 }, + { 0x1E951, 0x1E959, 1 }, + { 0x1E95E, 0x1E95F, 1 }, + { 0x1EC71, 0x1ECB4, 1 }, + { 0x1ED01, 0x1ED3D, 1 }, + { 0x1EE00, 0x1EE03, 1 }, + { 0x1EE05, 0x1EE1F, 1 }, + { 0x1EE21, 0x1EE22, 1 }, + { 0x1EE24, 0x1EE27, 3 }, + { 0x1EE29, 0x1EE32, 1 }, + { 0x1EE34, 0x1EE37, 1 }, + { 0x1EE39, 0x1EE3B, 2 }, + { 0x1EE42, 0x1EE47, 5 }, + { 0x1EE49, 0x1EE4D, 2 }, + { 0x1EE4E, 0x1EE4F, 1 }, + { 0x1EE51, 0x1EE52, 1 }, + { 0x1EE54, 0x1EE57, 3 }, + { 0x1EE59, 0x1EE61, 2 }, + { 0x1EE62, 0x1EE64, 2 }, + { 0x1EE67, 0x1EE6A, 1 }, + { 0x1EE6C, 0x1EE72, 1 }, + { 0x1EE74, 0x1EE77, 1 }, + { 0x1EE79, 0x1EE7C, 1 }, + { 0x1EE7E, 0x1EE80, 2 }, + { 0x1EE81, 0x1EE89, 1 }, + { 0x1EE8B, 0x1EE9B, 1 }, + { 0x1EEA1, 0x1EEA3, 1 }, + { 0x1EEA5, 0x1EEA9, 1 }, + { 0x1EEAB, 0x1EEBB, 1 }, + { 0x1EEF0, 0x1EEF1, 1 }, + { 0x1F000, 0x1F02B, 1 }, + { 0x1F030, 0x1F093, 1 }, + { 0x1F0A0, 0x1F0AE, 1 }, + { 0x1F0B1, 0x1F0BF, 1 }, + { 0x1F0C1, 0x1F0CF, 1 }, + { 0x1F0D1, 0x1F0F5, 1 }, + { 0x1F100, 0x1F1AD, 1 }, + { 0x1F1E6, 0x1F202, 1 }, + { 0x1F210, 0x1F23B, 1 }, + { 0x1F240, 0x1F248, 1 }, + { 0x1F250, 0x1F251, 1 }, + { 0x1F260, 0x1F265, 1 }, + { 0x1F300, 0x1F6D7, 1 }, + { 0x1F6DC, 0x1F6EC, 1 }, + { 0x1F6F0, 0x1F6FC, 1 }, + { 0x1F700, 0x1F776, 1 }, + { 0x1F77B, 0x1F7D9, 1 }, + { 0x1F7E0, 0x1F7EB, 1 }, + { 0x1F7F0, 0x1F800, 16 }, + { 0x1F801, 0x1F80B, 1 }, + { 0x1F810, 0x1F847, 1 }, + { 0x1F850, 0x1F859, 1 }, + { 0x1F860, 0x1F887, 1 }, + { 0x1F890, 0x1F8AD, 1 }, + { 0x1F8B0, 0x1F8B1, 1 }, + { 0x1F900, 0x1FA53, 1 }, + { 0x1FA60, 0x1FA6D, 1 }, + { 0x1FA70, 0x1FA7C, 1 }, + { 0x1FA80, 0x1FA88, 1 }, + { 0x1FA90, 0x1FABD, 1 }, + { 0x1FABF, 0x1FAC5, 1 }, + { 0x1FACE, 0x1FADB, 1 }, + { 0x1FAE0, 0x1FAE8, 1 }, + { 0x1FAF0, 0x1FAF8, 1 }, + { 0x1FB00, 0x1FB92, 1 }, + { 0x1FB94, 0x1FBCA, 1 }, + { 0x1FBF0, 0x1FBF9, 1 }, + { 0x20000, 0x2A6DF, 1 }, + { 0x2A700, 0x2B739, 1 }, + { 0x2B740, 0x2B81D, 1 }, + { 0x2B820, 0x2CEA1, 1 }, + { 0x2CEB0, 0x2EBE0, 1 }, + { 0x2F800, 0x2FA1D, 1 }, + { 0x30000, 0x3134A, 1 }, + { 0x31350, 0x323AF, 1 }, +}; + +static struct range_table compose_table[] = { + { 0x300, 0x36F, 1 }, + { 0x483, 0x489, 1 }, + { 0x591, 0x5BD, 1 }, + { 0x5BF, 0x5C1, 2 }, + { 0x5C2, 0x5C4, 2 }, + { 0x5C5, 0x5C7, 2 }, + { 0x610, 0x61A, 1 }, + { 0x64B, 0x65F, 1 }, + { 0x670, 0x6D6, 102 }, + { 0x6D7, 0x6DC, 1 }, + { 0x6DF, 0x6E4, 1 }, + { 0x6E7, 0x6E8, 1 }, + { 0x6EA, 0x6ED, 1 }, + { 0x711, 0x730, 31 }, + { 0x731, 0x74A, 1 }, + { 0x7A6, 0x7B0, 1 }, + { 0x7EB, 0x7F3, 1 }, + { 0x7FD, 0x816, 25 }, + { 0x817, 0x819, 1 }, + { 0x81B, 0x823, 1 }, + { 0x825, 0x827, 1 }, + { 0x829, 0x82D, 1 }, + { 0x859, 0x85B, 1 }, + { 0x898, 0x89F, 1 }, + { 0x8CA, 0x8E1, 1 }, + { 0x8E3, 0x902, 1 }, + { 0x93A, 0x93C, 2 }, + { 0x941, 0x948, 1 }, + { 0x94D, 0x951, 4 }, + { 0x952, 0x957, 1 }, + { 0x962, 0x963, 1 }, + { 0x981, 0x9BC, 59 }, + { 0x9BE, 0x9C1, 3 }, + { 0x9C2, 0x9C4, 1 }, + { 0x9CD, 0x9D7, 10 }, + { 0x9E2, 0x9E3, 1 }, + { 0x9FE, 0xA01, 3 }, + { 0xA02, 0xA3C, 58 }, + { 0xA41, 0xA42, 1 }, + { 0xA47, 0xA48, 1 }, + { 0xA4B, 0xA4D, 1 }, + { 0xA51, 0xA70, 31 }, + { 0xA71, 0xA75, 4 }, + { 0xA81, 0xA82, 1 }, + { 0xABC, 0xAC1, 5 }, + { 0xAC2, 0xAC5, 1 }, + { 0xAC7, 0xAC8, 1 }, + { 0xACD, 0xAE2, 21 }, + { 0xAE3, 0xAFA, 23 }, + { 0xAFB, 0xAFF, 1 }, + { 0xB01, 0xB3C, 59 }, + { 0xB3E, 0xB3F, 1 }, + { 0xB41, 0xB44, 1 }, + { 0xB4D, 0xB55, 8 }, + { 0xB56, 0xB57, 1 }, + { 0xB62, 0xB63, 1 }, + { 0xB82, 0xBBE, 60 }, + { 0xBC0, 0xBCD, 13 }, + { 0xBD7, 0xC00, 41 }, + { 0xC04, 0xC3C, 56 }, + { 0xC3E, 0xC40, 1 }, + { 0xC46, 0xC48, 1 }, + { 0xC4A, 0xC4D, 1 }, + { 0xC55, 0xC56, 1 }, + { 0xC62, 0xC63, 1 }, + { 0xC81, 0xCBC, 59 }, + { 0xCBF, 0xCC2, 3 }, + { 0xCC6, 0xCCC, 6 }, + { 0xCCD, 0xCD5, 8 }, + { 0xCD6, 0xCE2, 12 }, + { 0xCE3, 0xD00, 29 }, + { 0xD01, 0xD3B, 58 }, + { 0xD3C, 0xD3E, 2 }, + { 0xD41, 0xD44, 1 }, + { 0xD4D, 0xD57, 10 }, + { 0xD62, 0xD63, 1 }, + { 0xD81, 0xDCA, 73 }, + { 0xDCF, 0xDD2, 3 }, + { 0xDD3, 0xDD4, 1 }, + { 0xDD6, 0xDDF, 9 }, + { 0xE31, 0xE34, 3 }, + { 0xE35, 0xE3A, 1 }, + { 0xE47, 0xE4E, 1 }, + { 0xEB1, 0xEB4, 3 }, + { 0xEB5, 0xEBC, 1 }, + { 0xEC8, 0xECE, 1 }, + { 0xF18, 0xF19, 1 }, + { 0xF35, 0xF39, 2 }, + { 0xF71, 0xF7E, 1 }, + { 0xF80, 0xF84, 1 }, + { 0xF86, 0xF87, 1 }, + { 0xF8D, 0xF97, 1 }, + { 0xF99, 0xFBC, 1 }, + { 0xFC6, 0x102D, 103 }, + { 0x102E, 0x1030, 1 }, + { 0x1032, 0x1037, 1 }, + { 0x1039, 0x103A, 1 }, + { 0x103D, 0x103E, 1 }, + { 0x1058, 0x1059, 1 }, + { 0x105E, 0x1060, 1 }, + { 0x1071, 0x1074, 1 }, + { 0x1082, 0x1085, 3 }, + { 0x1086, 0x108D, 7 }, + { 0x109D, 0x135D, 704 }, + { 0x135E, 0x135F, 1 }, + { 0x1712, 0x1714, 1 }, + { 0x1732, 0x1733, 1 }, + { 0x1752, 0x1753, 1 }, + { 0x1772, 0x1773, 1 }, + { 0x17B4, 0x17B5, 1 }, + { 0x17B7, 0x17BD, 1 }, + { 0x17C6, 0x17C9, 3 }, + { 0x17CA, 0x17D3, 1 }, + { 0x17DD, 0x180B, 46 }, + { 0x180C, 0x180D, 1 }, + { 0x180F, 0x1885, 118 }, + { 0x1886, 0x18A9, 35 }, + { 0x1920, 0x1922, 1 }, + { 0x1927, 0x1928, 1 }, + { 0x1932, 0x1939, 7 }, + { 0x193A, 0x193B, 1 }, + { 0x1A17, 0x1A18, 1 }, + { 0x1A1B, 0x1A56, 59 }, + { 0x1A58, 0x1A5E, 1 }, + { 0x1A60, 0x1A62, 2 }, + { 0x1A65, 0x1A6C, 1 }, + { 0x1A73, 0x1A7C, 1 }, + { 0x1A7F, 0x1AB0, 49 }, + { 0x1AB1, 0x1ACE, 1 }, + { 0x1B00, 0x1B03, 1 }, + { 0x1B34, 0x1B3A, 1 }, + { 0x1B3C, 0x1B42, 6 }, + { 0x1B6B, 0x1B73, 1 }, + { 0x1B80, 0x1B81, 1 }, + { 0x1BA2, 0x1BA5, 1 }, + { 0x1BA8, 0x1BA9, 1 }, + { 0x1BAB, 0x1BAD, 1 }, + { 0x1BE6, 0x1BE8, 2 }, + { 0x1BE9, 0x1BED, 4 }, + { 0x1BEF, 0x1BF1, 1 }, + { 0x1C2C, 0x1C33, 1 }, + { 0x1C36, 0x1C37, 1 }, + { 0x1CD0, 0x1CD2, 1 }, + { 0x1CD4, 0x1CE0, 1 }, + { 0x1CE2, 0x1CE8, 1 }, + { 0x1CED, 0x1CF4, 7 }, + { 0x1CF8, 0x1CF9, 1 }, + { 0x1DC0, 0x1DFF, 1 }, + { 0x200C, 0x20D0, 196 }, + { 0x20D1, 0x20F0, 1 }, + { 0x2CEF, 0x2CF1, 1 }, + { 0x2D7F, 0x2DE0, 97 }, + { 0x2DE1, 0x2DFF, 1 }, + { 0x302A, 0x302F, 1 }, + { 0x3099, 0x309A, 1 }, + { 0xA66F, 0xA672, 1 }, + { 0xA674, 0xA67D, 1 }, + { 0xA69E, 0xA69F, 1 }, + { 0xA6F0, 0xA6F1, 1 }, + { 0xA802, 0xA806, 4 }, + { 0xA80B, 0xA825, 26 }, + { 0xA826, 0xA82C, 6 }, + { 0xA8C4, 0xA8C5, 1 }, + { 0xA8E0, 0xA8F1, 1 }, + { 0xA8FF, 0xA926, 39 }, + { 0xA927, 0xA92D, 1 }, + { 0xA947, 0xA951, 1 }, + { 0xA980, 0xA982, 1 }, + { 0xA9B3, 0xA9B6, 3 }, + { 0xA9B7, 0xA9B9, 1 }, + { 0xA9BC, 0xA9BD, 1 }, + { 0xA9E5, 0xAA29, 68 }, + { 0xAA2A, 0xAA2E, 1 }, + { 0xAA31, 0xAA32, 1 }, + { 0xAA35, 0xAA36, 1 }, + { 0xAA43, 0xAA4C, 9 }, + { 0xAA7C, 0xAAB0, 52 }, + { 0xAAB2, 0xAAB4, 1 }, + { 0xAAB7, 0xAAB8, 1 }, + { 0xAABE, 0xAABF, 1 }, + { 0xAAC1, 0xAAEC, 43 }, + { 0xAAED, 0xAAF6, 9 }, + { 0xABE5, 0xABE8, 3 }, + { 0xABED, 0xFB1E, 20273 }, + { 0xFE00, 0xFE0F, 1 }, + { 0xFE20, 0xFE2F, 1 }, + { 0xFF9E, 0xFF9F, 1 }, + { 0x101FD, 0x102E0, 227 }, + { 0x10376, 0x1037A, 1 }, + { 0x10A01, 0x10A03, 1 }, + { 0x10A05, 0x10A06, 1 }, + { 0x10A0C, 0x10A0F, 1 }, + { 0x10A38, 0x10A3A, 1 }, + { 0x10A3F, 0x10AE5, 166 }, + { 0x10AE6, 0x10D24, 574 }, + { 0x10D25, 0x10D27, 1 }, + { 0x10EAB, 0x10EAC, 1 }, + { 0x10EFD, 0x10EFF, 1 }, + { 0x10F46, 0x10F50, 1 }, + { 0x10F82, 0x10F85, 1 }, + { 0x11001, 0x11038, 55 }, + { 0x11039, 0x11046, 1 }, + { 0x11070, 0x11073, 3 }, + { 0x11074, 0x1107F, 11 }, + { 0x11080, 0x11081, 1 }, + { 0x110B3, 0x110B6, 1 }, + { 0x110B9, 0x110BA, 1 }, + { 0x110C2, 0x11100, 62 }, + { 0x11101, 0x11102, 1 }, + { 0x11127, 0x1112B, 1 }, + { 0x1112D, 0x11134, 1 }, + { 0x11173, 0x11180, 13 }, + { 0x11181, 0x111B6, 53 }, + { 0x111B7, 0x111BE, 1 }, + { 0x111C9, 0x111CC, 1 }, + { 0x111CF, 0x1122F, 96 }, + { 0x11230, 0x11231, 1 }, + { 0x11234, 0x11236, 2 }, + { 0x11237, 0x1123E, 7 }, + { 0x11241, 0x112DF, 158 }, + { 0x112E3, 0x112EA, 1 }, + { 0x11300, 0x11301, 1 }, + { 0x1133B, 0x1133C, 1 }, + { 0x1133E, 0x11340, 2 }, + { 0x11357, 0x11366, 15 }, + { 0x11367, 0x1136C, 1 }, + { 0x11370, 0x11374, 1 }, + { 0x11438, 0x1143F, 1 }, + { 0x11442, 0x11444, 1 }, + { 0x11446, 0x1145E, 24 }, + { 0x114B0, 0x114B3, 3 }, + { 0x114B4, 0x114B8, 1 }, + { 0x114BA, 0x114BD, 3 }, + { 0x114BF, 0x114C0, 1 }, + { 0x114C2, 0x114C3, 1 }, + { 0x115AF, 0x115B2, 3 }, + { 0x115B3, 0x115B5, 1 }, + { 0x115BC, 0x115BD, 1 }, + { 0x115BF, 0x115C0, 1 }, + { 0x115DC, 0x115DD, 1 }, + { 0x11633, 0x1163A, 1 }, + { 0x1163D, 0x1163F, 2 }, + { 0x11640, 0x116AB, 107 }, + { 0x116AD, 0x116B0, 3 }, + { 0x116B1, 0x116B5, 1 }, + { 0x116B7, 0x1171D, 102 }, + { 0x1171E, 0x1171F, 1 }, + { 0x11722, 0x11725, 1 }, + { 0x11727, 0x1172B, 1 }, + { 0x1182F, 0x11837, 1 }, + { 0x11839, 0x1183A, 1 }, + { 0x11930, 0x1193B, 11 }, + { 0x1193C, 0x1193E, 2 }, + { 0x11943, 0x119D4, 145 }, + { 0x119D5, 0x119D7, 1 }, + { 0x119DA, 0x119DB, 1 }, + { 0x119E0, 0x11A01, 33 }, + { 0x11A02, 0x11A0A, 1 }, + { 0x11A33, 0x11A38, 1 }, + { 0x11A3B, 0x11A3E, 1 }, + { 0x11A47, 0x11A51, 10 }, + { 0x11A52, 0x11A56, 1 }, + { 0x11A59, 0x11A5B, 1 }, + { 0x11A8A, 0x11A96, 1 }, + { 0x11A98, 0x11A99, 1 }, + { 0x11C30, 0x11C36, 1 }, + { 0x11C38, 0x11C3D, 1 }, + { 0x11C3F, 0x11C92, 83 }, + { 0x11C93, 0x11CA7, 1 }, + { 0x11CAA, 0x11CB0, 1 }, + { 0x11CB2, 0x11CB3, 1 }, + { 0x11CB5, 0x11CB6, 1 }, + { 0x11D31, 0x11D36, 1 }, + { 0x11D3A, 0x11D3C, 2 }, + { 0x11D3D, 0x11D3F, 2 }, + { 0x11D40, 0x11D45, 1 }, + { 0x11D47, 0x11D90, 73 }, + { 0x11D91, 0x11D95, 4 }, + { 0x11D97, 0x11EF3, 348 }, + { 0x11EF4, 0x11F00, 12 }, + { 0x11F01, 0x11F36, 53 }, + { 0x11F37, 0x11F3A, 1 }, + { 0x11F40, 0x11F42, 2 }, + { 0x13440, 0x13447, 7 }, + { 0x13448, 0x13455, 1 }, + { 0x16AF0, 0x16AF4, 1 }, + { 0x16B30, 0x16B36, 1 }, + { 0x16F4F, 0x16F8F, 64 }, + { 0x16F90, 0x16F92, 1 }, + { 0x16FE4, 0x1BC9D, 19641 }, + { 0x1BC9E, 0x1CF00, 4706 }, + { 0x1CF01, 0x1CF2D, 1 }, + { 0x1CF30, 0x1CF46, 1 }, + { 0x1D165, 0x1D167, 2 }, + { 0x1D168, 0x1D169, 1 }, + { 0x1D16E, 0x1D172, 1 }, + { 0x1D17B, 0x1D182, 1 }, + { 0x1D185, 0x1D18B, 1 }, + { 0x1D1AA, 0x1D1AD, 1 }, + { 0x1D242, 0x1D244, 1 }, + { 0x1DA00, 0x1DA36, 1 }, + { 0x1DA3B, 0x1DA6C, 1 }, + { 0x1DA75, 0x1DA84, 15 }, + { 0x1DA9B, 0x1DA9F, 1 }, + { 0x1DAA1, 0x1DAAF, 1 }, + { 0x1E000, 0x1E006, 1 }, + { 0x1E008, 0x1E018, 1 }, + { 0x1E01B, 0x1E021, 1 }, + { 0x1E023, 0x1E024, 1 }, + { 0x1E026, 0x1E02A, 1 }, + { 0x1E08F, 0x1E130, 161 }, + { 0x1E131, 0x1E136, 1 }, + { 0x1E2AE, 0x1E2EC, 62 }, + { 0x1E2ED, 0x1E2EF, 1 }, + { 0x1E4EC, 0x1E4EF, 1 }, + { 0x1E8D0, 0x1E8D6, 1 }, + { 0x1E944, 0x1E94A, 1 }, + { 0xE0020, 0xE007F, 1 }, + { 0xE0100, 0xE01EF, 1 }, +}; + +static struct range_table cntrl_table[] = { + { 0x0, 0x1F, 1 }, + { 0x7F, 0x9F, 1 }, + { 0xAD, 0x600, 1363 }, + { 0x601, 0x605, 1 }, + { 0x61C, 0x6DD, 193 }, + { 0x70F, 0x890, 385 }, + { 0x891, 0x8E2, 81 }, + { 0x180E, 0x200B, 2045 }, + { 0x200C, 0x200F, 1 }, + { 0x202A, 0x202E, 1 }, + { 0x2060, 0x2064, 1 }, + { 0x2066, 0x206F, 1 }, + { 0xE000, 0xE000, 0 }, + { 0xE001, 0xF8FF, 1 }, + { 0xFEFF, 0xFFF9, 250 }, + { 0xFFFA, 0xFFFB, 1 }, + { 0x110BD, 0x110CD, 16 }, + { 0x13430, 0x1343F, 1 }, + { 0x1BCA0, 0x1BCA3, 1 }, + { 0x1D173, 0x1D17A, 1 }, + { 0xE0001, 0xE0020, 31 }, + { 0xE0021, 0xE007F, 1 }, + { 0xF0000, 0xF0000, 0 }, + { 0xF0001, 0xFFFFD, 1 }, + { 0x100000, 0x100000, 0 }, + { 0x100001, 0x10FFFD, 1 }, +}; + +static struct range_table digit_table[] = { + { 0x30, 0x39, 1 }, + { 0x660, 0x669, 1 }, + { 0x6F0, 0x6F9, 1 }, + { 0x7C0, 0x7C9, 1 }, + { 0x966, 0x96F, 1 }, + { 0x9E6, 0x9EF, 1 }, + { 0xA66, 0xA6F, 1 }, + { 0xAE6, 0xAEF, 1 }, + { 0xB66, 0xB6F, 1 }, + { 0xBE6, 0xBEF, 1 }, + { 0xC66, 0xC6F, 1 }, + { 0xCE6, 0xCEF, 1 }, + { 0xD66, 0xD6F, 1 }, + { 0xDE6, 0xDEF, 1 }, + { 0xE50, 0xE59, 1 }, + { 0xED0, 0xED9, 1 }, + { 0xF20, 0xF29, 1 }, + { 0x1040, 0x1049, 1 }, + { 0x1090, 0x1099, 1 }, + { 0x17E0, 0x17E9, 1 }, + { 0x1810, 0x1819, 1 }, + { 0x1946, 0x194F, 1 }, + { 0x19D0, 0x19D9, 1 }, + { 0x1A80, 0x1A89, 1 }, + { 0x1A90, 0x1A99, 1 }, + { 0x1B50, 0x1B59, 1 }, + { 0x1BB0, 0x1BB9, 1 }, + { 0x1C40, 0x1C49, 1 }, + { 0x1C50, 0x1C59, 1 }, + { 0xA620, 0xA629, 1 }, + { 0xA8D0, 0xA8D9, 1 }, + { 0xA900, 0xA909, 1 }, + { 0xA9D0, 0xA9D9, 1 }, + { 0xA9F0, 0xA9F9, 1 }, + { 0xAA50, 0xAA59, 1 }, + { 0xABF0, 0xABF9, 1 }, + { 0xFF10, 0xFF19, 1 }, + { 0x104A0, 0x104A9, 1 }, + { 0x10D30, 0x10D39, 1 }, + { 0x11066, 0x1106F, 1 }, + { 0x110F0, 0x110F9, 1 }, + { 0x11136, 0x1113F, 1 }, + { 0x111D0, 0x111D9, 1 }, + { 0x112F0, 0x112F9, 1 }, + { 0x11450, 0x11459, 1 }, + { 0x114D0, 0x114D9, 1 }, + { 0x11650, 0x11659, 1 }, + { 0x116C0, 0x116C9, 1 }, + { 0x11730, 0x11739, 1 }, + { 0x118E0, 0x118E9, 1 }, + { 0x11950, 0x11959, 1 }, + { 0x11C50, 0x11C59, 1 }, + { 0x11D50, 0x11D59, 1 }, + { 0x11DA0, 0x11DA9, 1 }, + { 0x11F50, 0x11F59, 1 }, + { 0x16A60, 0x16A69, 1 }, + { 0x16AC0, 0x16AC9, 1 }, + { 0x16B50, 0x16B59, 1 }, + { 0x1D7CE, 0x1D7FF, 1 }, + { 0x1E140, 0x1E149, 1 }, + { 0x1E2F0, 0x1E2F9, 1 }, + { 0x1E4F0, 0x1E4F9, 1 }, + { 0x1E950, 0x1E959, 1 }, + { 0x1FBF0, 0x1FBF9, 1 }, +}; + +static struct range_table alnum_extend_table[] = { + { 0x30, 0x39, 1 }, + { 0xB2, 0xB3, 1 }, + { 0xB9, 0xBC, 3 }, + { 0xBD, 0xBE, 1 }, + { 0x660, 0x669, 1 }, + { 0x6F0, 0x6F9, 1 }, + { 0x7C0, 0x7C9, 1 }, + { 0x966, 0x96F, 1 }, + { 0x9E6, 0x9EF, 1 }, + { 0x9F4, 0x9F9, 1 }, + { 0xA66, 0xA6F, 1 }, + { 0xAE6, 0xAEF, 1 }, + { 0xB66, 0xB6F, 1 }, + { 0xB72, 0xB77, 1 }, + { 0xBE6, 0xBF2, 1 }, + { 0xC66, 0xC6F, 1 }, + { 0xC78, 0xC7E, 1 }, + { 0xCE6, 0xCEF, 1 }, + { 0xD58, 0xD5E, 1 }, + { 0xD66, 0xD78, 1 }, + { 0xDE6, 0xDEF, 1 }, + { 0xE50, 0xE59, 1 }, + { 0xED0, 0xED9, 1 }, + { 0xF20, 0xF33, 1 }, + { 0x1040, 0x1049, 1 }, + { 0x1090, 0x1099, 1 }, + { 0x1369, 0x137C, 1 }, + { 0x16EE, 0x16F0, 1 }, + { 0x17E0, 0x17E9, 1 }, + { 0x17F0, 0x17F9, 1 }, + { 0x1810, 0x1819, 1 }, + { 0x1946, 0x194F, 1 }, + { 0x19D0, 0x19DA, 1 }, + { 0x1A80, 0x1A89, 1 }, + { 0x1A90, 0x1A99, 1 }, + { 0x1B50, 0x1B59, 1 }, + { 0x1BB0, 0x1BB9, 1 }, + { 0x1C40, 0x1C49, 1 }, + { 0x1C50, 0x1C59, 1 }, + { 0x2070, 0x2074, 4 }, + { 0x2075, 0x2079, 1 }, + { 0x2080, 0x2089, 1 }, + { 0x2150, 0x2182, 1 }, + { 0x2185, 0x2189, 1 }, + { 0x2460, 0x249B, 1 }, + { 0x24EA, 0x24FF, 1 }, + { 0x2776, 0x2793, 1 }, + { 0x2CFD, 0x3007, 778 }, + { 0x3021, 0x3029, 1 }, + { 0x3038, 0x303A, 1 }, + { 0x3192, 0x3195, 1 }, + { 0x3220, 0x3229, 1 }, + { 0x3248, 0x324F, 1 }, + { 0x3251, 0x325F, 1 }, + { 0x3280, 0x3289, 1 }, + { 0x32B1, 0x32BF, 1 }, + { 0xA620, 0xA629, 1 }, + { 0xA6E6, 0xA6EF, 1 }, + { 0xA830, 0xA835, 1 }, + { 0xA8D0, 0xA8D9, 1 }, + { 0xA900, 0xA909, 1 }, + { 0xA9D0, 0xA9D9, 1 }, + { 0xA9F0, 0xA9F9, 1 }, + { 0xAA50, 0xAA59, 1 }, + { 0xABF0, 0xABF9, 1 }, + { 0xFF10, 0xFF19, 1 }, + { 0x10107, 0x10133, 1 }, + { 0x10140, 0x10178, 1 }, + { 0x1018A, 0x1018B, 1 }, + { 0x102E1, 0x102FB, 1 }, + { 0x10320, 0x10323, 1 }, + { 0x10341, 0x1034A, 9 }, + { 0x103D1, 0x103D5, 1 }, + { 0x104A0, 0x104A9, 1 }, + { 0x10858, 0x1085F, 1 }, + { 0x10879, 0x1087F, 1 }, + { 0x108A7, 0x108AF, 1 }, + { 0x108FB, 0x108FF, 1 }, + { 0x10916, 0x1091B, 1 }, + { 0x109BC, 0x109BD, 1 }, + { 0x109C0, 0x109CF, 1 }, + { 0x109D2, 0x109FF, 1 }, + { 0x10A40, 0x10A48, 1 }, + { 0x10A7D, 0x10A7E, 1 }, + { 0x10A9D, 0x10A9F, 1 }, + { 0x10AEB, 0x10AEF, 1 }, + { 0x10B58, 0x10B5F, 1 }, + { 0x10B78, 0x10B7F, 1 }, + { 0x10BA9, 0x10BAF, 1 }, + { 0x10CFA, 0x10CFF, 1 }, + { 0x10D30, 0x10D39, 1 }, + { 0x10E60, 0x10E7E, 1 }, + { 0x10F1D, 0x10F26, 1 }, + { 0x10F51, 0x10F54, 1 }, + { 0x10FC5, 0x10FCB, 1 }, + { 0x11052, 0x1106F, 1 }, + { 0x110F0, 0x110F9, 1 }, + { 0x11136, 0x1113F, 1 }, + { 0x111D0, 0x111D9, 1 }, + { 0x111E1, 0x111F4, 1 }, + { 0x112F0, 0x112F9, 1 }, + { 0x11450, 0x11459, 1 }, + { 0x114D0, 0x114D9, 1 }, + { 0x11650, 0x11659, 1 }, + { 0x116C0, 0x116C9, 1 }, + { 0x11730, 0x1173B, 1 }, + { 0x118E0, 0x118F2, 1 }, + { 0x11950, 0x11959, 1 }, + { 0x11C50, 0x11C6C, 1 }, + { 0x11D50, 0x11D59, 1 }, + { 0x11DA0, 0x11DA9, 1 }, + { 0x11F50, 0x11F59, 1 }, + { 0x11FC0, 0x11FD4, 1 }, + { 0x12400, 0x1246E, 1 }, + { 0x16A60, 0x16A69, 1 }, + { 0x16AC0, 0x16AC9, 1 }, + { 0x16B50, 0x16B59, 1 }, + { 0x16B5B, 0x16B61, 1 }, + { 0x16E80, 0x16E96, 1 }, + { 0x1D2C0, 0x1D2D3, 1 }, + { 0x1D2E0, 0x1D2F3, 1 }, + { 0x1D360, 0x1D378, 1 }, + { 0x1D7CE, 0x1D7FF, 1 }, + { 0x1E140, 0x1E149, 1 }, + { 0x1E2F0, 0x1E2F9, 1 }, + { 0x1E4F0, 0x1E4F9, 1 }, + { 0x1E8C7, 0x1E8CF, 1 }, + { 0x1E950, 0x1E959, 1 }, + { 0x1EC71, 0x1ECAB, 1 }, + { 0x1ECAD, 0x1ECAF, 1 }, + { 0x1ECB1, 0x1ECB4, 1 }, + { 0x1ED01, 0x1ED2D, 1 }, + { 0x1ED2F, 0x1ED3D, 1 }, + { 0x1F100, 0x1F10C, 1 }, + { 0x1FBF0, 0x1FBF9, 1 }, +}; + +static struct range_table punct_table[] = { + { 0x21, 0x2F, 1 }, + { 0x3A, 0x40, 1 }, + { 0x5B, 0x60, 1 }, + { 0x7B, 0x7E, 1 }, + { 0xA1, 0xA5, 1 }, + { 0xA7, 0xA8, 1 }, + { 0xAB, 0xAC, 1 }, + { 0xAF, 0xB1, 2 }, + { 0xB4, 0xB6, 2 }, + { 0xB7, 0xB8, 1 }, + { 0xBB, 0xBF, 4 }, + { 0xD7, 0xF7, 32 }, + { 0x2C2, 0x2C5, 1 }, + { 0x2D2, 0x2DF, 1 }, + { 0x2E5, 0x2EB, 1 }, + { 0x2ED, 0x2EF, 2 }, + { 0x2F0, 0x2FF, 1 }, + { 0x375, 0x37E, 9 }, + { 0x384, 0x385, 1 }, + { 0x387, 0x3F6, 111 }, + { 0x55A, 0x55F, 1 }, + { 0x589, 0x58A, 1 }, + { 0x58F, 0x5BE, 47 }, + { 0x5C0, 0x5C6, 3 }, + { 0x5F3, 0x5F4, 1 }, + { 0x606, 0x60D, 1 }, + { 0x61B, 0x61D, 2 }, + { 0x61E, 0x61F, 1 }, + { 0x66A, 0x66D, 1 }, + { 0x6D4, 0x700, 44 }, + { 0x701, 0x70D, 1 }, + { 0x7F7, 0x7F9, 1 }, + { 0x7FE, 0x7FF, 1 }, + { 0x830, 0x83E, 1 }, + { 0x85E, 0x888, 42 }, + { 0x964, 0x965, 1 }, + { 0x970, 0x9F2, 130 }, + { 0x9F3, 0x9FB, 8 }, + { 0x9FD, 0xA76, 121 }, + { 0xAF0, 0xAF1, 1 }, + { 0xBF9, 0xC77, 126 }, + { 0xC84, 0xDF4, 368 }, + { 0xE3F, 0xE4F, 16 }, + { 0xE5A, 0xE5B, 1 }, + { 0xF04, 0xF12, 1 }, + { 0xF14, 0xF3A, 38 }, + { 0xF3B, 0xF3D, 1 }, + { 0xF85, 0xFD0, 75 }, + { 0xFD1, 0xFD4, 1 }, + { 0xFD9, 0xFDA, 1 }, + { 0x104A, 0x104F, 1 }, + { 0x10FB, 0x1360, 613 }, + { 0x1361, 0x1368, 1 }, + { 0x1400, 0x166E, 622 }, + { 0x169B, 0x169C, 1 }, + { 0x16EB, 0x16ED, 1 }, + { 0x1735, 0x1736, 1 }, + { 0x17D4, 0x17D6, 1 }, + { 0x17D8, 0x17DB, 1 }, + { 0x1800, 0x180A, 1 }, + { 0x1944, 0x1945, 1 }, + { 0x1A1E, 0x1A1F, 1 }, + { 0x1AA0, 0x1AA6, 1 }, + { 0x1AA8, 0x1AAD, 1 }, + { 0x1B5A, 0x1B60, 1 }, + { 0x1B7D, 0x1B7E, 1 }, + { 0x1BFC, 0x1BFF, 1 }, + { 0x1C3B, 0x1C3F, 1 }, + { 0x1C7E, 0x1C7F, 1 }, + { 0x1CC0, 0x1CC7, 1 }, + { 0x1CD3, 0x1FBD, 746 }, + { 0x1FBF, 0x1FC1, 1 }, + { 0x1FCD, 0x1FCF, 1 }, + { 0x1FDD, 0x1FDF, 1 }, + { 0x1FED, 0x1FEF, 1 }, + { 0x1FFD, 0x1FFE, 1 }, + { 0x2010, 0x2027, 1 }, + { 0x2030, 0x205E, 1 }, + { 0x207A, 0x207E, 1 }, + { 0x208A, 0x208E, 1 }, + { 0x20A0, 0x20C0, 1 }, + { 0x2118, 0x2140, 40 }, + { 0x2141, 0x2144, 1 }, + { 0x214B, 0x2190, 69 }, + { 0x2191, 0x2194, 1 }, + { 0x219A, 0x219B, 1 }, + { 0x21A0, 0x21A6, 3 }, + { 0x21AE, 0x21CE, 32 }, + { 0x21CF, 0x21D2, 3 }, + { 0x21D4, 0x21F4, 32 }, + { 0x21F5, 0x22FF, 1 }, + { 0x2308, 0x230B, 1 }, + { 0x2320, 0x2321, 1 }, + { 0x2329, 0x232A, 1 }, + { 0x237C, 0x239B, 31 }, + { 0x239C, 0x23B3, 1 }, + { 0x23DC, 0x23E1, 1 }, + { 0x25B7, 0x25C1, 10 }, + { 0x25F8, 0x25FF, 1 }, + { 0x266F, 0x2768, 249 }, + { 0x2769, 0x2775, 1 }, + { 0x27C0, 0x27FF, 1 }, + { 0x2900, 0x2AFF, 1 }, + { 0x2B30, 0x2B44, 1 }, + { 0x2B47, 0x2B4C, 1 }, + { 0x2CF9, 0x2CFC, 1 }, + { 0x2CFE, 0x2CFF, 1 }, + { 0x2D70, 0x2E00, 144 }, + { 0x2E01, 0x2E2E, 1 }, + { 0x2E30, 0x2E4F, 1 }, + { 0x2E52, 0x2E5D, 1 }, + { 0x3001, 0x3003, 1 }, + { 0x3008, 0x3011, 1 }, + { 0x3014, 0x301F, 1 }, + { 0x3030, 0x303D, 13 }, + { 0x309B, 0x309C, 1 }, + { 0x30A0, 0x30FB, 91 }, + { 0xA4FE, 0xA4FF, 1 }, + { 0xA60D, 0xA60F, 1 }, + { 0xA673, 0xA67E, 11 }, + { 0xA6F2, 0xA6F7, 1 }, + { 0xA700, 0xA716, 1 }, + { 0xA720, 0xA721, 1 }, + { 0xA789, 0xA78A, 1 }, + { 0xA838, 0xA874, 60 }, + { 0xA875, 0xA877, 1 }, + { 0xA8CE, 0xA8CF, 1 }, + { 0xA8F8, 0xA8FA, 1 }, + { 0xA8FC, 0xA92E, 50 }, + { 0xA92F, 0xA95F, 48 }, + { 0xA9C1, 0xA9CD, 1 }, + { 0xA9DE, 0xA9DF, 1 }, + { 0xAA5C, 0xAA5F, 1 }, + { 0xAADE, 0xAADF, 1 }, + { 0xAAF0, 0xAAF1, 1 }, + { 0xAB5B, 0xAB6A, 15 }, + { 0xAB6B, 0xABEB, 128 }, + { 0xFB29, 0xFBB2, 137 }, + { 0xFBB3, 0xFBC2, 1 }, + { 0xFD3E, 0xFD3F, 1 }, + { 0xFDFC, 0xFE10, 20 }, + { 0xFE11, 0xFE19, 1 }, + { 0xFE30, 0xFE52, 1 }, + { 0xFE54, 0xFE66, 1 }, + { 0xFE68, 0xFE6B, 1 }, + { 0xFF01, 0xFF0F, 1 }, + { 0xFF1A, 0xFF20, 1 }, + { 0xFF3B, 0xFF40, 1 }, + { 0xFF5B, 0xFF65, 1 }, + { 0xFFE0, 0xFFE3, 1 }, + { 0xFFE5, 0xFFE6, 1 }, + { 0xFFE9, 0xFFEC, 1 }, + { 0x10100, 0x10102, 1 }, + { 0x1039F, 0x103D0, 49 }, + { 0x1056F, 0x10857, 744 }, + { 0x1091F, 0x1093F, 32 }, + { 0x10A50, 0x10A58, 1 }, + { 0x10A7F, 0x10AF0, 113 }, + { 0x10AF1, 0x10AF6, 1 }, + { 0x10B39, 0x10B3F, 1 }, + { 0x10B99, 0x10B9C, 1 }, + { 0x10EAD, 0x10F55, 168 }, + { 0x10F56, 0x10F59, 1 }, + { 0x10F86, 0x10F89, 1 }, + { 0x11047, 0x1104D, 1 }, + { 0x110BB, 0x110BC, 1 }, + { 0x110BE, 0x110C1, 1 }, + { 0x11140, 0x11143, 1 }, + { 0x11174, 0x11175, 1 }, + { 0x111C5, 0x111C8, 1 }, + { 0x111CD, 0x111DB, 14 }, + { 0x111DD, 0x111DF, 1 }, + { 0x11238, 0x1123D, 1 }, + { 0x112A9, 0x1144B, 418 }, + { 0x1144C, 0x1144F, 1 }, + { 0x1145A, 0x1145B, 1 }, + { 0x1145D, 0x114C6, 105 }, + { 0x115C1, 0x115D7, 1 }, + { 0x11641, 0x11643, 1 }, + { 0x11660, 0x1166C, 1 }, + { 0x116B9, 0x1173C, 131 }, + { 0x1173D, 0x1173E, 1 }, + { 0x1183B, 0x11944, 265 }, + { 0x11945, 0x11946, 1 }, + { 0x119E2, 0x11A3F, 93 }, + { 0x11A40, 0x11A46, 1 }, + { 0x11A9A, 0x11A9C, 1 }, + { 0x11A9E, 0x11AA2, 1 }, + { 0x11B00, 0x11B09, 1 }, + { 0x11C41, 0x11C45, 1 }, + { 0x11C70, 0x11C71, 1 }, + { 0x11EF7, 0x11EF8, 1 }, + { 0x11F43, 0x11F4F, 1 }, + { 0x11FDD, 0x11FE0, 1 }, + { 0x11FFF, 0x12470, 1137 }, + { 0x12471, 0x12474, 1 }, + { 0x12FF1, 0x12FF2, 1 }, + { 0x16A6E, 0x16A6F, 1 }, + { 0x16AF5, 0x16B37, 66 }, + { 0x16B38, 0x16B3B, 1 }, + { 0x16B44, 0x16E97, 851 }, + { 0x16E98, 0x16E9A, 1 }, + { 0x16FE2, 0x1BC9F, 19645 }, + { 0x1D6C1, 0x1D6DB, 26 }, + { 0x1D6FB, 0x1D715, 26 }, + { 0x1D735, 0x1D74F, 26 }, + { 0x1D76F, 0x1D789, 26 }, + { 0x1D7A9, 0x1D7C3, 26 }, + { 0x1DA87, 0x1DA8B, 1 }, + { 0x1E2FF, 0x1E95E, 1631 }, + { 0x1E95F, 0x1ECB0, 849 }, + { 0x1EEF0, 0x1EEF1, 1 }, + { 0x1F3FB, 0x1F3FF, 1 }, +}; + +static struct conv_table tolower_table[] = { + { 0x41, 0x5A, 1, 32 }, + { 0xC0, 0xD6, 1, 32 }, + { 0xD8, 0xDE, 1, 32 }, + { 0x100, 0x12E, 2, 1 }, + { 0x130, 0x130, 1, -199 }, + { 0x132, 0x136, 2, 1 }, + { 0x139, 0x147, 2, 1 }, + { 0x14A, 0x176, 2, 1 }, + { 0x178, 0x178, 1, -121 }, + { 0x179, 0x17D, 2, 1 }, + { 0x181, 0x181, 1, 210 }, + { 0x182, 0x184, 2, 1 }, + { 0x186, 0x186, 1, 206 }, + { 0x187, 0x187, 1, 1 }, + { 0x189, 0x18A, 1, 205 }, + { 0x18B, 0x18B, 1, 1 }, + { 0x18E, 0x18E, 1, 79 }, + { 0x18F, 0x18F, 1, 202 }, + { 0x190, 0x190, 1, 203 }, + { 0x191, 0x191, 1, 1 }, + { 0x193, 0x193, 1, 205 }, + { 0x194, 0x194, 1, 207 }, + { 0x196, 0x196, 1, 211 }, + { 0x197, 0x197, 1, 209 }, + { 0x198, 0x198, 1, 1 }, + { 0x19C, 0x19C, 1, 211 }, + { 0x19D, 0x19D, 1, 213 }, + { 0x19F, 0x19F, 1, 214 }, + { 0x1A0, 0x1A4, 2, 1 }, + { 0x1A6, 0x1A6, 1, 218 }, + { 0x1A7, 0x1A7, 1, 1 }, + { 0x1A9, 0x1A9, 1, 218 }, + { 0x1AC, 0x1AC, 1, 1 }, + { 0x1AE, 0x1AE, 1, 218 }, + { 0x1AF, 0x1AF, 1, 1 }, + { 0x1B1, 0x1B2, 1, 217 }, + { 0x1B3, 0x1B5, 2, 1 }, + { 0x1B7, 0x1B7, 1, 219 }, + { 0x1B8, 0x1BC, 4, 1 }, + { 0x1C4, 0x1C4, 1, 2 }, + { 0x1C5, 0x1C5, 1, 1 }, + { 0x1C7, 0x1C7, 1, 2 }, + { 0x1C8, 0x1C8, 1, 1 }, + { 0x1CA, 0x1CA, 1, 2 }, + { 0x1CB, 0x1DB, 2, 1 }, + { 0x1DE, 0x1EE, 2, 1 }, + { 0x1F1, 0x1F1, 1, 2 }, + { 0x1F2, 0x1F4, 2, 1 }, + { 0x1F6, 0x1F6, 1, -97 }, + { 0x1F7, 0x1F7, 1, -56 }, + { 0x1F8, 0x21E, 2, 1 }, + { 0x220, 0x220, 1, -130 }, + { 0x222, 0x232, 2, 1 }, + { 0x23A, 0x23A, 1, 10795 }, + { 0x23B, 0x23B, 1, 1 }, + { 0x23D, 0x23D, 1, -163 }, + { 0x23E, 0x23E, 1, 10792 }, + { 0x241, 0x241, 1, 1 }, + { 0x243, 0x243, 1, -195 }, + { 0x244, 0x244, 1, 69 }, + { 0x245, 0x245, 1, 71 }, + { 0x246, 0x24E, 2, 1 }, + { 0x370, 0x372, 2, 1 }, + { 0x376, 0x376, 1, 1 }, + { 0x37F, 0x37F, 1, 116 }, + { 0x386, 0x386, 1, 38 }, + { 0x388, 0x38A, 1, 37 }, + { 0x38C, 0x38C, 1, 64 }, + { 0x38E, 0x38F, 1, 63 }, + { 0x391, 0x3A1, 1, 32 }, + { 0x3A3, 0x3AB, 1, 32 }, + { 0x3CF, 0x3CF, 1, 8 }, + { 0x3D8, 0x3EE, 2, 1 }, + { 0x3F4, 0x3F4, 1, -60 }, + { 0x3F7, 0x3F7, 1, 1 }, + { 0x3F9, 0x3F9, 1, -7 }, + { 0x3FA, 0x3FA, 1, 1 }, + { 0x3FD, 0x3FF, 1, -130 }, + { 0x400, 0x40F, 1, 80 }, + { 0x410, 0x42F, 1, 32 }, + { 0x460, 0x480, 2, 1 }, + { 0x48A, 0x4BE, 2, 1 }, + { 0x4C0, 0x4C0, 1, 15 }, + { 0x4C1, 0x4CD, 2, 1 }, + { 0x4D0, 0x52E, 2, 1 }, + { 0x531, 0x556, 1, 48 }, + { 0x10A0, 0x10C5, 1, 7264 }, + { 0x10C7, 0x10CD, 6, 7264 }, + { 0x13A0, 0x13EF, 1, 38864 }, + { 0x13F0, 0x13F5, 1, 8 }, + { 0x1C90, 0x1CBA, 1, -3008 }, + { 0x1CBD, 0x1CBF, 1, -3008 }, + { 0x1E00, 0x1E94, 2, 1 }, + { 0x1E9E, 0x1E9E, 1, -7615 }, + { 0x1EA0, 0x1EFE, 2, 1 }, + { 0x1F08, 0x1F0F, 1, -8 }, + { 0x1F18, 0x1F1D, 1, -8 }, + { 0x1F28, 0x1F2F, 1, -8 }, + { 0x1F38, 0x1F3F, 1, -8 }, + { 0x1F48, 0x1F4D, 1, -8 }, + { 0x1F59, 0x1F5F, 2, -8 }, + { 0x1F68, 0x1F6F, 1, -8 }, + { 0x1F88, 0x1F8F, 1, -8 }, + { 0x1F98, 0x1F9F, 1, -8 }, + { 0x1FA8, 0x1FAF, 1, -8 }, + { 0x1FB8, 0x1FB9, 1, -8 }, + { 0x1FBA, 0x1FBB, 1, -74 }, + { 0x1FBC, 0x1FBC, 1, -9 }, + { 0x1FC8, 0x1FCB, 1, -86 }, + { 0x1FCC, 0x1FCC, 1, -9 }, + { 0x1FD8, 0x1FD9, 1, -8 }, + { 0x1FDA, 0x1FDB, 1, -100 }, + { 0x1FE8, 0x1FE9, 1, -8 }, + { 0x1FEA, 0x1FEB, 1, -112 }, + { 0x1FEC, 0x1FEC, 1, -7 }, + { 0x1FF8, 0x1FF9, 1, -128 }, + { 0x1FFA, 0x1FFB, 1, -126 }, + { 0x1FFC, 0x1FFC, 1, -9 }, + { 0x2126, 0x2126, 1, -7517 }, + { 0x212A, 0x212A, 1, -8383 }, + { 0x212B, 0x212B, 1, -8262 }, + { 0x2132, 0x2132, 1, 28 }, + { 0x2160, 0x216F, 1, 16 }, + { 0x2183, 0x2183, 1, 1 }, + { 0x24B6, 0x24CF, 1, 26 }, + { 0x2C00, 0x2C2F, 1, 48 }, + { 0x2C60, 0x2C60, 1, 1 }, + { 0x2C62, 0x2C62, 1, -10743 }, + { 0x2C63, 0x2C63, 1, -3814 }, + { 0x2C64, 0x2C64, 1, -10727 }, + { 0x2C67, 0x2C6B, 2, 1 }, + { 0x2C6D, 0x2C6D, 1, -10780 }, + { 0x2C6E, 0x2C6E, 1, -10749 }, + { 0x2C6F, 0x2C6F, 1, -10783 }, + { 0x2C70, 0x2C70, 1, -10782 }, + { 0x2C72, 0x2C75, 3, 1 }, + { 0x2C7E, 0x2C7F, 1, -10815 }, + { 0x2C80, 0x2CE2, 2, 1 }, + { 0x2CEB, 0x2CED, 2, 1 }, + { 0x2CF2, 0xA640, 31054, 1 }, + { 0xA642, 0xA66C, 2, 1 }, + { 0xA680, 0xA69A, 2, 1 }, + { 0xA722, 0xA72E, 2, 1 }, + { 0xA732, 0xA76E, 2, 1 }, + { 0xA779, 0xA77B, 2, 1 }, + { 0xA77D, 0xA77D, 1, -35332 }, + { 0xA77E, 0xA786, 2, 1 }, + { 0xA78B, 0xA78B, 1, 1 }, + { 0xA78D, 0xA78D, 1, -42280 }, + { 0xA790, 0xA792, 2, 1 }, + { 0xA796, 0xA7A8, 2, 1 }, + { 0xA7AA, 0xA7AA, 1, -42308 }, + { 0xA7AB, 0xA7AB, 1, -42319 }, + { 0xA7AC, 0xA7AC, 1, -42315 }, + { 0xA7AD, 0xA7AD, 1, -42305 }, + { 0xA7AE, 0xA7AE, 1, -42308 }, + { 0xA7B0, 0xA7B0, 1, -42258 }, + { 0xA7B1, 0xA7B1, 1, -42282 }, + { 0xA7B2, 0xA7B2, 1, -42261 }, + { 0xA7B3, 0xA7B3, 1, 928 }, + { 0xA7B4, 0xA7C2, 2, 1 }, + { 0xA7C4, 0xA7C4, 1, -48 }, + { 0xA7C5, 0xA7C5, 1, -42307 }, + { 0xA7C6, 0xA7C6, 1, -35384 }, + { 0xA7C7, 0xA7C9, 2, 1 }, + { 0xA7D0, 0xA7D6, 6, 1 }, + { 0xA7D8, 0xA7F5, 29, 1 }, + { 0xFF21, 0xFF3A, 1, 32 }, + { 0x10400, 0x10427, 1, 40 }, + { 0x104B0, 0x104D3, 1, 40 }, + { 0x10570, 0x1057A, 1, 39 }, + { 0x1057C, 0x1058A, 1, 39 }, + { 0x1058C, 0x10592, 1, 39 }, + { 0x10594, 0x10595, 1, 39 }, + { 0x10C80, 0x10CB2, 1, 64 }, + { 0x118A0, 0x118BF, 1, 32 }, + { 0x16E40, 0x16E5F, 1, 32 }, + { 0x1E900, 0x1E921, 1, 34 }, +}; + +static struct conv_table toupper_table[] = { + { 0x61, 0x7A, 1, -32 }, + { 0xB5, 0xB5, 1, 743 }, + { 0xE0, 0xF6, 1, -32 }, + { 0xF8, 0xFE, 1, -32 }, + { 0xFF, 0xFF, 1, 121 }, + { 0x101, 0x12F, 2, -1 }, + { 0x131, 0x131, 1, -232 }, + { 0x133, 0x137, 2, -1 }, + { 0x13A, 0x148, 2, -1 }, + { 0x14B, 0x177, 2, -1 }, + { 0x17A, 0x17E, 2, -1 }, + { 0x17F, 0x17F, 1, -300 }, + { 0x180, 0x180, 1, 195 }, + { 0x183, 0x185, 2, -1 }, + { 0x188, 0x18C, 4, -1 }, + { 0x192, 0x192, 1, -1 }, + { 0x195, 0x195, 1, 97 }, + { 0x199, 0x199, 1, -1 }, + { 0x19A, 0x19A, 1, 163 }, + { 0x19E, 0x19E, 1, 130 }, + { 0x1A1, 0x1A5, 2, -1 }, + { 0x1A8, 0x1AD, 5, -1 }, + { 0x1B0, 0x1B4, 4, -1 }, + { 0x1B6, 0x1B9, 3, -1 }, + { 0x1BD, 0x1BD, 1, -1 }, + { 0x1BF, 0x1BF, 1, 56 }, + { 0x1C5, 0x1C5, 1, -1 }, + { 0x1C6, 0x1C6, 1, -2 }, + { 0x1C8, 0x1C8, 1, -1 }, + { 0x1C9, 0x1C9, 1, -2 }, + { 0x1CB, 0x1CB, 1, -1 }, + { 0x1CC, 0x1CC, 1, -2 }, + { 0x1CE, 0x1DC, 2, -1 }, + { 0x1DD, 0x1DD, 1, -79 }, + { 0x1DF, 0x1EF, 2, -1 }, + { 0x1F2, 0x1F2, 1, -1 }, + { 0x1F3, 0x1F3, 1, -2 }, + { 0x1F5, 0x1F9, 4, -1 }, + { 0x1FB, 0x21F, 2, -1 }, + { 0x223, 0x233, 2, -1 }, + { 0x23C, 0x23C, 1, -1 }, + { 0x23F, 0x240, 1, 10815 }, + { 0x242, 0x247, 5, -1 }, + { 0x249, 0x24F, 2, -1 }, + { 0x250, 0x250, 1, 10783 }, + { 0x251, 0x251, 1, 10780 }, + { 0x252, 0x252, 1, 10782 }, + { 0x253, 0x253, 1, -210 }, + { 0x254, 0x254, 1, -206 }, + { 0x256, 0x257, 1, -205 }, + { 0x259, 0x259, 1, -202 }, + { 0x25B, 0x25B, 1, -203 }, + { 0x25C, 0x25C, 1, 42319 }, + { 0x260, 0x260, 1, -205 }, + { 0x261, 0x261, 1, 42315 }, + { 0x263, 0x263, 1, -207 }, + { 0x265, 0x265, 1, 42280 }, + { 0x266, 0x266, 1, 42308 }, + { 0x268, 0x268, 1, -209 }, + { 0x269, 0x269, 1, -211 }, + { 0x26A, 0x26A, 1, 42308 }, + { 0x26B, 0x26B, 1, 10743 }, + { 0x26C, 0x26C, 1, 42305 }, + { 0x26F, 0x26F, 1, -211 }, + { 0x271, 0x271, 1, 10749 }, + { 0x272, 0x272, 1, -213 }, + { 0x275, 0x275, 1, -214 }, + { 0x27D, 0x27D, 1, 10727 }, + { 0x280, 0x280, 1, -218 }, + { 0x282, 0x282, 1, 42307 }, + { 0x283, 0x283, 1, -218 }, + { 0x287, 0x287, 1, 42282 }, + { 0x288, 0x288, 1, -218 }, + { 0x289, 0x289, 1, -69 }, + { 0x28A, 0x28B, 1, -217 }, + { 0x28C, 0x28C, 1, -71 }, + { 0x292, 0x292, 1, -219 }, + { 0x29D, 0x29D, 1, 42261 }, + { 0x29E, 0x29E, 1, 42258 }, + { 0x345, 0x345, 1, 84 }, + { 0x371, 0x373, 2, -1 }, + { 0x377, 0x377, 1, -1 }, + { 0x37B, 0x37D, 1, 130 }, + { 0x3AC, 0x3AC, 1, -38 }, + { 0x3AD, 0x3AF, 1, -37 }, + { 0x3B1, 0x3C1, 1, -32 }, + { 0x3C2, 0x3C2, 1, -31 }, + { 0x3C3, 0x3CB, 1, -32 }, + { 0x3CC, 0x3CC, 1, -64 }, + { 0x3CD, 0x3CE, 1, -63 }, + { 0x3D0, 0x3D0, 1, -62 }, + { 0x3D1, 0x3D1, 1, -57 }, + { 0x3D5, 0x3D5, 1, -47 }, + { 0x3D6, 0x3D6, 1, -54 }, + { 0x3D7, 0x3D7, 1, -8 }, + { 0x3D9, 0x3EF, 2, -1 }, + { 0x3F0, 0x3F0, 1, -86 }, + { 0x3F1, 0x3F1, 1, -80 }, + { 0x3F2, 0x3F2, 1, 7 }, + { 0x3F3, 0x3F3, 1, -116 }, + { 0x3F5, 0x3F5, 1, -96 }, + { 0x3F8, 0x3FB, 3, -1 }, + { 0x430, 0x44F, 1, -32 }, + { 0x450, 0x45F, 1, -80 }, + { 0x461, 0x481, 2, -1 }, + { 0x48B, 0x4BF, 2, -1 }, + { 0x4C2, 0x4CE, 2, -1 }, + { 0x4CF, 0x4CF, 1, -15 }, + { 0x4D1, 0x52F, 2, -1 }, + { 0x561, 0x586, 1, -48 }, + { 0x10D0, 0x10FA, 1, 3008 }, + { 0x10FD, 0x10FF, 1, 3008 }, + { 0x13F8, 0x13FD, 1, -8 }, + { 0x1C80, 0x1C80, 1, -6254 }, + { 0x1C81, 0x1C81, 1, -6253 }, + { 0x1C82, 0x1C82, 1, -6244 }, + { 0x1C83, 0x1C84, 1, -6242 }, + { 0x1C85, 0x1C85, 1, -6243 }, + { 0x1C86, 0x1C86, 1, -6236 }, + { 0x1C87, 0x1C87, 1, -6181 }, + { 0x1C88, 0x1C88, 1, 35266 }, + { 0x1D79, 0x1D79, 1, 35332 }, + { 0x1D7D, 0x1D7D, 1, 3814 }, + { 0x1D8E, 0x1D8E, 1, 35384 }, + { 0x1E01, 0x1E95, 2, -1 }, + { 0x1E9B, 0x1E9B, 1, -59 }, + { 0x1EA1, 0x1EFF, 2, -1 }, + { 0x1F00, 0x1F07, 1, 8 }, + { 0x1F10, 0x1F15, 1, 8 }, + { 0x1F20, 0x1F27, 1, 8 }, + { 0x1F30, 0x1F37, 1, 8 }, + { 0x1F40, 0x1F45, 1, 8 }, + { 0x1F51, 0x1F57, 2, 8 }, + { 0x1F60, 0x1F67, 1, 8 }, + { 0x1F70, 0x1F71, 1, 74 }, + { 0x1F72, 0x1F75, 1, 86 }, + { 0x1F76, 0x1F77, 1, 100 }, + { 0x1F78, 0x1F79, 1, 128 }, + { 0x1F7A, 0x1F7B, 1, 112 }, + { 0x1F7C, 0x1F7D, 1, 126 }, + { 0x1F80, 0x1F87, 1, 8 }, + { 0x1F90, 0x1F97, 1, 8 }, + { 0x1FA0, 0x1FA7, 1, 8 }, + { 0x1FB0, 0x1FB1, 1, 8 }, + { 0x1FB3, 0x1FB3, 1, 9 }, + { 0x1FBE, 0x1FBE, 1, -7205 }, + { 0x1FC3, 0x1FC3, 1, 9 }, + { 0x1FD0, 0x1FD1, 1, 8 }, + { 0x1FE0, 0x1FE1, 1, 8 }, + { 0x1FE5, 0x1FE5, 1, 7 }, + { 0x1FF3, 0x1FF3, 1, 9 }, + { 0x214E, 0x214E, 1, -28 }, + { 0x2170, 0x217F, 1, -16 }, + { 0x2184, 0x2184, 1, -1 }, + { 0x24D0, 0x24E9, 1, -26 }, + { 0x2C30, 0x2C5F, 1, -48 }, + { 0x2C61, 0x2C61, 1, -1 }, + { 0x2C65, 0x2C65, 1, -10795 }, + { 0x2C66, 0x2C66, 1, -10792 }, + { 0x2C68, 0x2C6C, 2, -1 }, + { 0x2C73, 0x2C76, 3, -1 }, + { 0x2C81, 0x2CE3, 2, -1 }, + { 0x2CEC, 0x2CEE, 2, -1 }, + { 0x2CF3, 0x2CF3, 1, -1 }, + { 0x2D00, 0x2D25, 1, -7264 }, + { 0x2D27, 0x2D2D, 6, -7264 }, + { 0xA641, 0xA66D, 2, -1 }, + { 0xA681, 0xA69B, 2, -1 }, + { 0xA723, 0xA72F, 2, -1 }, + { 0xA733, 0xA76F, 2, -1 }, + { 0xA77A, 0xA77C, 2, -1 }, + { 0xA77F, 0xA787, 2, -1 }, + { 0xA78C, 0xA791, 5, -1 }, + { 0xA793, 0xA793, 1, -1 }, + { 0xA794, 0xA794, 1, 48 }, + { 0xA797, 0xA7A9, 2, -1 }, + { 0xA7B5, 0xA7C3, 2, -1 }, + { 0xA7C8, 0xA7CA, 2, -1 }, + { 0xA7D1, 0xA7D7, 6, -1 }, + { 0xA7D9, 0xA7F6, 29, -1 }, + { 0xAB53, 0xAB53, 1, -928 }, + { 0xAB70, 0xABBF, 1, -38864 }, + { 0xFF41, 0xFF5A, 1, -32 }, + { 0x10428, 0x1044F, 1, -40 }, + { 0x104D8, 0x104FB, 1, -40 }, + { 0x10597, 0x105A1, 1, -39 }, + { 0x105A3, 0x105B1, 1, -39 }, + { 0x105B3, 0x105B9, 1, -39 }, + { 0x105BB, 0x105BC, 1, -39 }, + { 0x10CC0, 0x10CF2, 1, -64 }, + { 0x118C0, 0x118DF, 1, -32 }, + { 0x16E60, 0x16E7F, 1, -32 }, + { 0x1E922, 0x1E943, 1, -34 }, +}; + +static struct conv_table totitle_table[] = { + { 0x61, 0x7A, 1, -32 }, + { 0xB5, 0xB5, 1, 743 }, + { 0xE0, 0xF6, 1, -32 }, + { 0xF8, 0xFE, 1, -32 }, + { 0xFF, 0xFF, 1, 121 }, + { 0x101, 0x12F, 2, -1 }, + { 0x131, 0x131, 1, -232 }, + { 0x133, 0x137, 2, -1 }, + { 0x13A, 0x148, 2, -1 }, + { 0x14B, 0x177, 2, -1 }, + { 0x17A, 0x17E, 2, -1 }, + { 0x17F, 0x17F, 1, -300 }, + { 0x180, 0x180, 1, 195 }, + { 0x183, 0x185, 2, -1 }, + { 0x188, 0x18C, 4, -1 }, + { 0x192, 0x192, 1, -1 }, + { 0x195, 0x195, 1, 97 }, + { 0x199, 0x199, 1, -1 }, + { 0x19A, 0x19A, 1, 163 }, + { 0x19E, 0x19E, 1, 130 }, + { 0x1A1, 0x1A5, 2, -1 }, + { 0x1A8, 0x1AD, 5, -1 }, + { 0x1B0, 0x1B4, 4, -1 }, + { 0x1B6, 0x1B9, 3, -1 }, + { 0x1BD, 0x1BD, 1, -1 }, + { 0x1BF, 0x1BF, 1, 56 }, + { 0x1C4, 0x1C4, 1, 1 }, + { 0x1C5, 0x1C5, 1, 0 }, + { 0x1C6, 0x1C6, 1, -1 }, + { 0x1C7, 0x1C7, 1, 1 }, + { 0x1C8, 0x1C8, 1, 0 }, + { 0x1C9, 0x1C9, 1, -1 }, + { 0x1CA, 0x1CA, 1, 1 }, + { 0x1CB, 0x1CB, 1, 0 }, + { 0x1CC, 0x1DC, 2, -1 }, + { 0x1DD, 0x1DD, 1, -79 }, + { 0x1DF, 0x1EF, 2, -1 }, + { 0x1F1, 0x1F1, 1, 1 }, + { 0x1F2, 0x1F2, 1, 0 }, + { 0x1F3, 0x1F5, 2, -1 }, + { 0x1F9, 0x21F, 2, -1 }, + { 0x223, 0x233, 2, -1 }, + { 0x23C, 0x23C, 1, -1 }, + { 0x23F, 0x240, 1, 10815 }, + { 0x242, 0x247, 5, -1 }, + { 0x249, 0x24F, 2, -1 }, + { 0x250, 0x250, 1, 10783 }, + { 0x251, 0x251, 1, 10780 }, + { 0x252, 0x252, 1, 10782 }, + { 0x253, 0x253, 1, -210 }, + { 0x254, 0x254, 1, -206 }, + { 0x256, 0x257, 1, -205 }, + { 0x259, 0x259, 1, -202 }, + { 0x25B, 0x25B, 1, -203 }, + { 0x25C, 0x25C, 1, 42319 }, + { 0x260, 0x260, 1, -205 }, + { 0x261, 0x261, 1, 42315 }, + { 0x263, 0x263, 1, -207 }, + { 0x265, 0x265, 1, 42280 }, + { 0x266, 0x266, 1, 42308 }, + { 0x268, 0x268, 1, -209 }, + { 0x269, 0x269, 1, -211 }, + { 0x26A, 0x26A, 1, 42308 }, + { 0x26B, 0x26B, 1, 10743 }, + { 0x26C, 0x26C, 1, 42305 }, + { 0x26F, 0x26F, 1, -211 }, + { 0x271, 0x271, 1, 10749 }, + { 0x272, 0x272, 1, -213 }, + { 0x275, 0x275, 1, -214 }, + { 0x27D, 0x27D, 1, 10727 }, + { 0x280, 0x280, 1, -218 }, + { 0x282, 0x282, 1, 42307 }, + { 0x283, 0x283, 1, -218 }, + { 0x287, 0x287, 1, 42282 }, + { 0x288, 0x288, 1, -218 }, + { 0x289, 0x289, 1, -69 }, + { 0x28A, 0x28B, 1, -217 }, + { 0x28C, 0x28C, 1, -71 }, + { 0x292, 0x292, 1, -219 }, + { 0x29D, 0x29D, 1, 42261 }, + { 0x29E, 0x29E, 1, 42258 }, + { 0x345, 0x345, 1, 84 }, + { 0x371, 0x373, 2, -1 }, + { 0x377, 0x377, 1, -1 }, + { 0x37B, 0x37D, 1, 130 }, + { 0x3AC, 0x3AC, 1, -38 }, + { 0x3AD, 0x3AF, 1, -37 }, + { 0x3B1, 0x3C1, 1, -32 }, + { 0x3C2, 0x3C2, 1, -31 }, + { 0x3C3, 0x3CB, 1, -32 }, + { 0x3CC, 0x3CC, 1, -64 }, + { 0x3CD, 0x3CE, 1, -63 }, + { 0x3D0, 0x3D0, 1, -62 }, + { 0x3D1, 0x3D1, 1, -57 }, + { 0x3D5, 0x3D5, 1, -47 }, + { 0x3D6, 0x3D6, 1, -54 }, + { 0x3D7, 0x3D7, 1, -8 }, + { 0x3D9, 0x3EF, 2, -1 }, + { 0x3F0, 0x3F0, 1, -86 }, + { 0x3F1, 0x3F1, 1, -80 }, + { 0x3F2, 0x3F2, 1, 7 }, + { 0x3F3, 0x3F3, 1, -116 }, + { 0x3F5, 0x3F5, 1, -96 }, + { 0x3F8, 0x3FB, 3, -1 }, + { 0x430, 0x44F, 1, -32 }, + { 0x450, 0x45F, 1, -80 }, + { 0x461, 0x481, 2, -1 }, + { 0x48B, 0x4BF, 2, -1 }, + { 0x4C2, 0x4CE, 2, -1 }, + { 0x4CF, 0x4CF, 1, -15 }, + { 0x4D1, 0x52F, 2, -1 }, + { 0x561, 0x586, 1, -48 }, + { 0x10D0, 0x10FA, 1, 0 }, + { 0x10FD, 0x10FF, 1, 0 }, + { 0x13F8, 0x13FD, 1, -8 }, + { 0x1C80, 0x1C80, 1, -6254 }, + { 0x1C81, 0x1C81, 1, -6253 }, + { 0x1C82, 0x1C82, 1, -6244 }, + { 0x1C83, 0x1C84, 1, -6242 }, + { 0x1C85, 0x1C85, 1, -6243 }, + { 0x1C86, 0x1C86, 1, -6236 }, + { 0x1C87, 0x1C87, 1, -6181 }, + { 0x1C88, 0x1C88, 1, 35266 }, + { 0x1D79, 0x1D79, 1, 35332 }, + { 0x1D7D, 0x1D7D, 1, 3814 }, + { 0x1D8E, 0x1D8E, 1, 35384 }, + { 0x1E01, 0x1E95, 2, -1 }, + { 0x1E9B, 0x1E9B, 1, -59 }, + { 0x1EA1, 0x1EFF, 2, -1 }, + { 0x1F00, 0x1F07, 1, 8 }, + { 0x1F10, 0x1F15, 1, 8 }, + { 0x1F20, 0x1F27, 1, 8 }, + { 0x1F30, 0x1F37, 1, 8 }, + { 0x1F40, 0x1F45, 1, 8 }, + { 0x1F51, 0x1F57, 2, 8 }, + { 0x1F60, 0x1F67, 1, 8 }, + { 0x1F70, 0x1F71, 1, 74 }, + { 0x1F72, 0x1F75, 1, 86 }, + { 0x1F76, 0x1F77, 1, 100 }, + { 0x1F78, 0x1F79, 1, 128 }, + { 0x1F7A, 0x1F7B, 1, 112 }, + { 0x1F7C, 0x1F7D, 1, 126 }, + { 0x1F80, 0x1F87, 1, 8 }, + { 0x1F90, 0x1F97, 1, 8 }, + { 0x1FA0, 0x1FA7, 1, 8 }, + { 0x1FB0, 0x1FB1, 1, 8 }, + { 0x1FB3, 0x1FB3, 1, 9 }, + { 0x1FBE, 0x1FBE, 1, -7205 }, + { 0x1FC3, 0x1FC3, 1, 9 }, + { 0x1FD0, 0x1FD1, 1, 8 }, + { 0x1FE0, 0x1FE1, 1, 8 }, + { 0x1FE5, 0x1FE5, 1, 7 }, + { 0x1FF3, 0x1FF3, 1, 9 }, + { 0x214E, 0x214E, 1, -28 }, + { 0x2170, 0x217F, 1, -16 }, + { 0x2184, 0x2184, 1, -1 }, + { 0x24D0, 0x24E9, 1, -26 }, + { 0x2C30, 0x2C5F, 1, -48 }, + { 0x2C61, 0x2C61, 1, -1 }, + { 0x2C65, 0x2C65, 1, -10795 }, + { 0x2C66, 0x2C66, 1, -10792 }, + { 0x2C68, 0x2C6C, 2, -1 }, + { 0x2C73, 0x2C76, 3, -1 }, + { 0x2C81, 0x2CE3, 2, -1 }, + { 0x2CEC, 0x2CEE, 2, -1 }, + { 0x2CF3, 0x2CF3, 1, -1 }, + { 0x2D00, 0x2D25, 1, -7264 }, + { 0x2D27, 0x2D2D, 6, -7264 }, + { 0xA641, 0xA66D, 2, -1 }, + { 0xA681, 0xA69B, 2, -1 }, + { 0xA723, 0xA72F, 2, -1 }, + { 0xA733, 0xA76F, 2, -1 }, + { 0xA77A, 0xA77C, 2, -1 }, + { 0xA77F, 0xA787, 2, -1 }, + { 0xA78C, 0xA791, 5, -1 }, + { 0xA793, 0xA793, 1, -1 }, + { 0xA794, 0xA794, 1, 48 }, + { 0xA797, 0xA7A9, 2, -1 }, + { 0xA7B5, 0xA7C3, 2, -1 }, + { 0xA7C8, 0xA7CA, 2, -1 }, + { 0xA7D1, 0xA7D7, 6, -1 }, + { 0xA7D9, 0xA7F6, 29, -1 }, + { 0xAB53, 0xAB53, 1, -928 }, + { 0xAB70, 0xABBF, 1, -38864 }, + { 0xFF41, 0xFF5A, 1, -32 }, + { 0x10428, 0x1044F, 1, -40 }, + { 0x104D8, 0x104FB, 1, -40 }, + { 0x10597, 0x105A1, 1, -39 }, + { 0x105A3, 0x105B1, 1, -39 }, + { 0x105B3, 0x105B9, 1, -39 }, + { 0x105BB, 0x105BC, 1, -39 }, + { 0x10CC0, 0x10CF2, 1, -64 }, + { 0x118C0, 0x118DF, 1, -32 }, + { 0x16E60, 0x16E7F, 1, -32 }, + { 0x1E922, 0x1E943, 1, -34 }, +}; + +static struct conv_table tofold_table[] = { + { 0x41, 0x5A, 1, 32 }, + { 0xB5, 0xB5, 1, 775 }, + { 0xC0, 0xD6, 1, 32 }, + { 0xD8, 0xDE, 1, 32 }, + { 0x100, 0x12E, 2, 1 }, + { 0x132, 0x136, 2, 1 }, + { 0x139, 0x147, 2, 1 }, + { 0x14A, 0x176, 2, 1 }, + { 0x178, 0x178, 1, -121 }, + { 0x179, 0x17D, 2, 1 }, + { 0x17F, 0x17F, 1, -268 }, + { 0x181, 0x181, 1, 210 }, + { 0x182, 0x184, 2, 1 }, + { 0x186, 0x186, 1, 206 }, + { 0x187, 0x187, 1, 1 }, + { 0x189, 0x18A, 1, 205 }, + { 0x18B, 0x18B, 1, 1 }, + { 0x18E, 0x18E, 1, 79 }, + { 0x18F, 0x18F, 1, 202 }, + { 0x190, 0x190, 1, 203 }, + { 0x191, 0x191, 1, 1 }, + { 0x193, 0x193, 1, 205 }, + { 0x194, 0x194, 1, 207 }, + { 0x196, 0x196, 1, 211 }, + { 0x197, 0x197, 1, 209 }, + { 0x198, 0x198, 1, 1 }, + { 0x19C, 0x19C, 1, 211 }, + { 0x19D, 0x19D, 1, 213 }, + { 0x19F, 0x19F, 1, 214 }, + { 0x1A0, 0x1A4, 2, 1 }, + { 0x1A6, 0x1A6, 1, 218 }, + { 0x1A7, 0x1A7, 1, 1 }, + { 0x1A9, 0x1A9, 1, 218 }, + { 0x1AC, 0x1AC, 1, 1 }, + { 0x1AE, 0x1AE, 1, 218 }, + { 0x1AF, 0x1AF, 1, 1 }, + { 0x1B1, 0x1B2, 1, 217 }, + { 0x1B3, 0x1B5, 2, 1 }, + { 0x1B7, 0x1B7, 1, 219 }, + { 0x1B8, 0x1BC, 4, 1 }, + { 0x1C4, 0x1C4, 1, 2 }, + { 0x1C5, 0x1C5, 1, 1 }, + { 0x1C7, 0x1C7, 1, 2 }, + { 0x1C8, 0x1C8, 1, 1 }, + { 0x1CA, 0x1CA, 1, 2 }, + { 0x1CB, 0x1DB, 2, 1 }, + { 0x1DE, 0x1EE, 2, 1 }, + { 0x1F1, 0x1F1, 1, 2 }, + { 0x1F2, 0x1F4, 2, 1 }, + { 0x1F6, 0x1F6, 1, -97 }, + { 0x1F7, 0x1F7, 1, -56 }, + { 0x1F8, 0x21E, 2, 1 }, + { 0x220, 0x220, 1, -130 }, + { 0x222, 0x232, 2, 1 }, + { 0x23A, 0x23A, 1, 10795 }, + { 0x23B, 0x23B, 1, 1 }, + { 0x23D, 0x23D, 1, -163 }, + { 0x23E, 0x23E, 1, 10792 }, + { 0x241, 0x241, 1, 1 }, + { 0x243, 0x243, 1, -195 }, + { 0x244, 0x244, 1, 69 }, + { 0x245, 0x245, 1, 71 }, + { 0x246, 0x24E, 2, 1 }, + { 0x345, 0x345, 1, 116 }, + { 0x370, 0x372, 2, 1 }, + { 0x376, 0x376, 1, 1 }, + { 0x37F, 0x37F, 1, 116 }, + { 0x386, 0x386, 1, 38 }, + { 0x388, 0x38A, 1, 37 }, + { 0x38C, 0x38C, 1, 64 }, + { 0x38E, 0x38F, 1, 63 }, + { 0x391, 0x3A1, 1, 32 }, + { 0x3A3, 0x3AB, 1, 32 }, + { 0x3C2, 0x3C2, 1, 1 }, + { 0x3CF, 0x3CF, 1, 8 }, + { 0x3D0, 0x3D0, 1, -30 }, + { 0x3D1, 0x3D1, 1, -25 }, + { 0x3D5, 0x3D5, 1, -15 }, + { 0x3D6, 0x3D6, 1, -22 }, + { 0x3D8, 0x3EE, 2, 1 }, + { 0x3F0, 0x3F0, 1, -54 }, + { 0x3F1, 0x3F1, 1, -48 }, + { 0x3F4, 0x3F4, 1, -60 }, + { 0x3F5, 0x3F5, 1, -64 }, + { 0x3F7, 0x3F7, 1, 1 }, + { 0x3F9, 0x3F9, 1, -7 }, + { 0x3FA, 0x3FA, 1, 1 }, + { 0x3FD, 0x3FF, 1, -130 }, + { 0x400, 0x40F, 1, 80 }, + { 0x410, 0x42F, 1, 32 }, + { 0x460, 0x480, 2, 1 }, + { 0x48A, 0x4BE, 2, 1 }, + { 0x4C0, 0x4C0, 1, 15 }, + { 0x4C1, 0x4CD, 2, 1 }, + { 0x4D0, 0x52E, 2, 1 }, + { 0x531, 0x556, 1, 48 }, + { 0x10A0, 0x10C5, 1, 7264 }, + { 0x10C7, 0x10CD, 6, 7264 }, + { 0x13F8, 0x13FD, 1, -8 }, + { 0x1C80, 0x1C80, 1, -6222 }, + { 0x1C81, 0x1C81, 1, -6221 }, + { 0x1C82, 0x1C82, 1, -6212 }, + { 0x1C83, 0x1C84, 1, -6210 }, + { 0x1C85, 0x1C85, 1, -6211 }, + { 0x1C86, 0x1C86, 1, -6204 }, + { 0x1C87, 0x1C87, 1, -6180 }, + { 0x1C88, 0x1C88, 1, 35267 }, + { 0x1C90, 0x1CBA, 1, -3008 }, + { 0x1CBD, 0x1CBF, 1, -3008 }, + { 0x1E00, 0x1E94, 2, 1 }, + { 0x1E9B, 0x1E9B, 1, -58 }, + { 0x1E9E, 0x1E9E, 1, -7615 }, + { 0x1EA0, 0x1EFE, 2, 1 }, + { 0x1F08, 0x1F0F, 1, -8 }, + { 0x1F18, 0x1F1D, 1, -8 }, + { 0x1F28, 0x1F2F, 1, -8 }, + { 0x1F38, 0x1F3F, 1, -8 }, + { 0x1F48, 0x1F4D, 1, -8 }, + { 0x1F59, 0x1F5F, 2, -8 }, + { 0x1F68, 0x1F6F, 1, -8 }, + { 0x1F88, 0x1F8F, 1, -8 }, + { 0x1F98, 0x1F9F, 1, -8 }, + { 0x1FA8, 0x1FAF, 1, -8 }, + { 0x1FB8, 0x1FB9, 1, -8 }, + { 0x1FBA, 0x1FBB, 1, -74 }, + { 0x1FBC, 0x1FBC, 1, -9 }, + { 0x1FBE, 0x1FBE, 1, -7173 }, + { 0x1FC8, 0x1FCB, 1, -86 }, + { 0x1FCC, 0x1FCC, 1, -9 }, + { 0x1FD8, 0x1FD9, 1, -8 }, + { 0x1FDA, 0x1FDB, 1, -100 }, + { 0x1FE8, 0x1FE9, 1, -8 }, + { 0x1FEA, 0x1FEB, 1, -112 }, + { 0x1FEC, 0x1FEC, 1, -7 }, + { 0x1FF8, 0x1FF9, 1, -128 }, + { 0x1FFA, 0x1FFB, 1, -126 }, + { 0x1FFC, 0x1FFC, 1, -9 }, + { 0x2126, 0x2126, 1, -7517 }, + { 0x212A, 0x212A, 1, -8383 }, + { 0x212B, 0x212B, 1, -8262 }, + { 0x2132, 0x2132, 1, 28 }, + { 0x2160, 0x216F, 1, 16 }, + { 0x2183, 0x2183, 1, 1 }, + { 0x24B6, 0x24CF, 1, 26 }, + { 0x2C00, 0x2C2F, 1, 48 }, + { 0x2C60, 0x2C60, 1, 1 }, + { 0x2C62, 0x2C62, 1, -10743 }, + { 0x2C63, 0x2C63, 1, -3814 }, + { 0x2C64, 0x2C64, 1, -10727 }, + { 0x2C67, 0x2C6B, 2, 1 }, + { 0x2C6D, 0x2C6D, 1, -10780 }, + { 0x2C6E, 0x2C6E, 1, -10749 }, + { 0x2C6F, 0x2C6F, 1, -10783 }, + { 0x2C70, 0x2C70, 1, -10782 }, + { 0x2C72, 0x2C75, 3, 1 }, + { 0x2C7E, 0x2C7F, 1, -10815 }, + { 0x2C80, 0x2CE2, 2, 1 }, + { 0x2CEB, 0x2CED, 2, 1 }, + { 0x2CF2, 0xA640, 31054, 1 }, + { 0xA642, 0xA66C, 2, 1 }, + { 0xA680, 0xA69A, 2, 1 }, + { 0xA722, 0xA72E, 2, 1 }, + { 0xA732, 0xA76E, 2, 1 }, + { 0xA779, 0xA77B, 2, 1 }, + { 0xA77D, 0xA77D, 1, -35332 }, + { 0xA77E, 0xA786, 2, 1 }, + { 0xA78B, 0xA78B, 1, 1 }, + { 0xA78D, 0xA78D, 1, -42280 }, + { 0xA790, 0xA792, 2, 1 }, + { 0xA796, 0xA7A8, 2, 1 }, + { 0xA7AA, 0xA7AA, 1, -42308 }, + { 0xA7AB, 0xA7AB, 1, -42319 }, + { 0xA7AC, 0xA7AC, 1, -42315 }, + { 0xA7AD, 0xA7AD, 1, -42305 }, + { 0xA7AE, 0xA7AE, 1, -42308 }, + { 0xA7B0, 0xA7B0, 1, -42258 }, + { 0xA7B1, 0xA7B1, 1, -42282 }, + { 0xA7B2, 0xA7B2, 1, -42261 }, + { 0xA7B3, 0xA7B3, 1, 928 }, + { 0xA7B4, 0xA7C2, 2, 1 }, + { 0xA7C4, 0xA7C4, 1, -48 }, + { 0xA7C5, 0xA7C5, 1, -42307 }, + { 0xA7C6, 0xA7C6, 1, -35384 }, + { 0xA7C7, 0xA7C9, 2, 1 }, + { 0xA7D0, 0xA7D6, 6, 1 }, + { 0xA7D8, 0xA7F5, 29, 1 }, + { 0xAB70, 0xABBF, 1, -38864 }, + { 0xFF21, 0xFF3A, 1, 32 }, + { 0x10400, 0x10427, 1, 40 }, + { 0x104B0, 0x104D3, 1, 40 }, + { 0x10570, 0x1057A, 1, 39 }, + { 0x1057C, 0x1058A, 1, 39 }, + { 0x1058C, 0x10592, 1, 39 }, + { 0x10594, 0x10595, 1, 39 }, + { 0x10C80, 0x10CB2, 1, 64 }, + { 0x118A0, 0x118BF, 1, 32 }, + { 0x16E40, 0x16E5F, 1, 32 }, + { 0x1E900, 0x1E921, 1, 34 }, +}; + +static struct range_table doublewidth_table[] = { + { 0x1100, 0x115F, 1 }, + { 0x231A, 0x231B, 1 }, + { 0x2329, 0x232A, 1 }, + { 0x23E9, 0x23EC, 1 }, + { 0x23F0, 0x23F3, 3 }, + { 0x25FD, 0x25FE, 1 }, + { 0x2614, 0x2615, 1 }, + { 0x2648, 0x2653, 1 }, + { 0x267F, 0x2693, 20 }, + { 0x26A1, 0x26AA, 9 }, + { 0x26AB, 0x26BD, 18 }, + { 0x26BE, 0x26C4, 6 }, + { 0x26C5, 0x26CE, 9 }, + { 0x26D4, 0x26EA, 22 }, + { 0x26F2, 0x26F3, 1 }, + { 0x26F5, 0x26FA, 5 }, + { 0x26FD, 0x2705, 8 }, + { 0x270A, 0x270B, 1 }, + { 0x2728, 0x274C, 36 }, + { 0x274E, 0x2753, 5 }, + { 0x2754, 0x2755, 1 }, + { 0x2757, 0x2795, 62 }, + { 0x2796, 0x2797, 1 }, + { 0x27B0, 0x27BF, 15 }, + { 0x2B1B, 0x2B1C, 1 }, + { 0x2B50, 0x2B55, 5 }, + { 0x2E80, 0x2E99, 1 }, + { 0x2E9B, 0x2EF3, 1 }, + { 0x2F00, 0x2FD5, 1 }, + { 0x2FF0, 0x2FFB, 1 }, + { 0x3000, 0x303E, 1 }, + { 0x3041, 0x3096, 1 }, + { 0x3099, 0x30FF, 1 }, + { 0x3105, 0x312F, 1 }, + { 0x3131, 0x318E, 1 }, + { 0x3190, 0x31E3, 1 }, + { 0x31F0, 0x321E, 1 }, + { 0x3220, 0x3247, 1 }, + { 0x3250, 0x4DBF, 1 }, + { 0x4E00, 0xA48C, 1 }, + { 0xA490, 0xA4C6, 1 }, + { 0xA960, 0xA97C, 1 }, + { 0xAC00, 0xD7A3, 1 }, + { 0xF900, 0xFAFF, 1 }, + { 0xFE10, 0xFE19, 1 }, + { 0xFE30, 0xFE52, 1 }, + { 0xFE54, 0xFE66, 1 }, + { 0xFE68, 0xFE6B, 1 }, + { 0xFF01, 0xFF60, 1 }, + { 0xFFE0, 0xFFE6, 1 }, + { 0x16FE0, 0x16FE4, 1 }, + { 0x16FF0, 0x16FF1, 1 }, + { 0x17000, 0x187F7, 1 }, + { 0x18800, 0x18CD5, 1 }, + { 0x18D00, 0x18D08, 1 }, + { 0x1AFF0, 0x1AFF3, 1 }, + { 0x1AFF5, 0x1AFFB, 1 }, + { 0x1AFFD, 0x1AFFE, 1 }, + { 0x1B000, 0x1B122, 1 }, + { 0x1B132, 0x1B150, 30 }, + { 0x1B151, 0x1B152, 1 }, + { 0x1B155, 0x1B164, 15 }, + { 0x1B165, 0x1B167, 1 }, + { 0x1B170, 0x1B2FB, 1 }, + { 0x1F004, 0x1F0CF, 203 }, + { 0x1F18E, 0x1F191, 3 }, + { 0x1F192, 0x1F19A, 1 }, + { 0x1F200, 0x1F202, 1 }, + { 0x1F210, 0x1F23B, 1 }, + { 0x1F240, 0x1F248, 1 }, + { 0x1F250, 0x1F251, 1 }, + { 0x1F260, 0x1F265, 1 }, + { 0x1F300, 0x1F320, 1 }, + { 0x1F32D, 0x1F335, 1 }, + { 0x1F337, 0x1F37C, 1 }, + { 0x1F37E, 0x1F393, 1 }, + { 0x1F3A0, 0x1F3CA, 1 }, + { 0x1F3CF, 0x1F3D3, 1 }, + { 0x1F3E0, 0x1F3F0, 1 }, + { 0x1F3F4, 0x1F3F8, 4 }, + { 0x1F3F9, 0x1F43E, 1 }, + { 0x1F440, 0x1F442, 2 }, + { 0x1F443, 0x1F4FC, 1 }, + { 0x1F4FF, 0x1F53D, 1 }, + { 0x1F54B, 0x1F54E, 1 }, + { 0x1F550, 0x1F567, 1 }, + { 0x1F57A, 0x1F595, 27 }, + { 0x1F596, 0x1F5A4, 14 }, + { 0x1F5FB, 0x1F64F, 1 }, + { 0x1F680, 0x1F6C5, 1 }, + { 0x1F6CC, 0x1F6D0, 4 }, + { 0x1F6D1, 0x1F6D2, 1 }, + { 0x1F6D5, 0x1F6D7, 1 }, + { 0x1F6DC, 0x1F6DF, 1 }, + { 0x1F6EB, 0x1F6EC, 1 }, + { 0x1F6F4, 0x1F6FC, 1 }, + { 0x1F7E0, 0x1F7EB, 1 }, + { 0x1F7F0, 0x1F90C, 284 }, + { 0x1F90D, 0x1F93A, 1 }, + { 0x1F93C, 0x1F945, 1 }, + { 0x1F947, 0x1F9FF, 1 }, + { 0x1FA70, 0x1FA7C, 1 }, + { 0x1FA80, 0x1FA88, 1 }, + { 0x1FA90, 0x1FABD, 1 }, + { 0x1FABF, 0x1FAC5, 1 }, + { 0x1FACE, 0x1FADB, 1 }, + { 0x1FAE0, 0x1FAE8, 1 }, + { 0x1FAF0, 0x1FAF8, 1 }, + { 0x20000, 0x2FFFD, 1 }, + { 0x30000, 0x3FFFD, 1 }, +}; + +static struct range_table ambiwidth_table[] = { + { 0xA1, 0xA7, 3 }, + { 0xA8, 0xAA, 2 }, + { 0xAD, 0xAE, 1 }, + { 0xB0, 0xB4, 1 }, + { 0xB6, 0xBA, 1 }, + { 0xBC, 0xBF, 1 }, + { 0xC6, 0xD0, 10 }, + { 0xD7, 0xD8, 1 }, + { 0xDE, 0xE1, 1 }, + { 0xE6, 0xE8, 2 }, + { 0xE9, 0xEA, 1 }, + { 0xEC, 0xED, 1 }, + { 0xF0, 0xF2, 2 }, + { 0xF3, 0xF7, 4 }, + { 0xF8, 0xFA, 1 }, + { 0xFC, 0xFE, 2 }, + { 0x101, 0x111, 16 }, + { 0x113, 0x11B, 8 }, + { 0x126, 0x127, 1 }, + { 0x12B, 0x131, 6 }, + { 0x132, 0x133, 1 }, + { 0x138, 0x13F, 7 }, + { 0x140, 0x142, 1 }, + { 0x144, 0x148, 4 }, + { 0x149, 0x14B, 1 }, + { 0x14D, 0x152, 5 }, + { 0x153, 0x166, 19 }, + { 0x167, 0x16B, 4 }, + { 0x1CE, 0x1DC, 2 }, + { 0x251, 0x261, 16 }, + { 0x2C4, 0x2C7, 3 }, + { 0x2C9, 0x2CB, 1 }, + { 0x2CD, 0x2D0, 3 }, + { 0x2D8, 0x2DB, 1 }, + { 0x2DD, 0x2DF, 2 }, + { 0x300, 0x36F, 1 }, + { 0x391, 0x3A1, 1 }, + { 0x3A3, 0x3A9, 1 }, + { 0x3B1, 0x3C1, 1 }, + { 0x3C3, 0x3C9, 1 }, + { 0x401, 0x410, 15 }, + { 0x411, 0x44F, 1 }, + { 0x451, 0x2010, 7103 }, + { 0x2013, 0x2016, 1 }, + { 0x2018, 0x2019, 1 }, + { 0x201C, 0x201D, 1 }, + { 0x2020, 0x2022, 1 }, + { 0x2024, 0x2027, 1 }, + { 0x2030, 0x2032, 2 }, + { 0x2033, 0x2035, 2 }, + { 0x203B, 0x203E, 3 }, + { 0x2074, 0x207F, 11 }, + { 0x2081, 0x2084, 1 }, + { 0x20AC, 0x2103, 87 }, + { 0x2105, 0x2109, 4 }, + { 0x2113, 0x2116, 3 }, + { 0x2121, 0x2122, 1 }, + { 0x2126, 0x212B, 5 }, + { 0x2153, 0x2154, 1 }, + { 0x215B, 0x215E, 1 }, + { 0x2160, 0x216B, 1 }, + { 0x2170, 0x2179, 1 }, + { 0x2189, 0x2190, 7 }, + { 0x2191, 0x2199, 1 }, + { 0x21B8, 0x21B9, 1 }, + { 0x21D2, 0x21D4, 2 }, + { 0x21E7, 0x2200, 25 }, + { 0x2202, 0x2203, 1 }, + { 0x2207, 0x2208, 1 }, + { 0x220B, 0x220F, 4 }, + { 0x2211, 0x2215, 4 }, + { 0x221A, 0x221D, 3 }, + { 0x221E, 0x2220, 1 }, + { 0x2223, 0x2227, 2 }, + { 0x2228, 0x222C, 1 }, + { 0x222E, 0x2234, 6 }, + { 0x2235, 0x2237, 1 }, + { 0x223C, 0x223D, 1 }, + { 0x2248, 0x224C, 4 }, + { 0x2252, 0x2260, 14 }, + { 0x2261, 0x2264, 3 }, + { 0x2265, 0x2267, 1 }, + { 0x226A, 0x226B, 1 }, + { 0x226E, 0x226F, 1 }, + { 0x2282, 0x2283, 1 }, + { 0x2286, 0x2287, 1 }, + { 0x2295, 0x2299, 4 }, + { 0x22A5, 0x22BF, 26 }, + { 0x2312, 0x2460, 334 }, + { 0x2461, 0x24E9, 1 }, + { 0x24EB, 0x254B, 1 }, + { 0x2550, 0x2573, 1 }, + { 0x2580, 0x258F, 1 }, + { 0x2592, 0x2595, 1 }, + { 0x25A0, 0x25A1, 1 }, + { 0x25A3, 0x25A9, 1 }, + { 0x25B2, 0x25B3, 1 }, + { 0x25B6, 0x25B7, 1 }, + { 0x25BC, 0x25BD, 1 }, + { 0x25C0, 0x25C1, 1 }, + { 0x25C6, 0x25C8, 1 }, + { 0x25CB, 0x25CE, 3 }, + { 0x25CF, 0x25D1, 1 }, + { 0x25E2, 0x25E5, 1 }, + { 0x25EF, 0x2605, 22 }, + { 0x2606, 0x2609, 3 }, + { 0x260E, 0x260F, 1 }, + { 0x261C, 0x261E, 2 }, + { 0x2640, 0x2642, 2 }, + { 0x2660, 0x2661, 1 }, + { 0x2663, 0x2665, 1 }, + { 0x2667, 0x266A, 1 }, + { 0x266C, 0x266D, 1 }, + { 0x266F, 0x269E, 47 }, + { 0x269F, 0x26BF, 32 }, + { 0x26C6, 0x26CD, 1 }, + { 0x26CF, 0x26D3, 1 }, + { 0x26D5, 0x26E1, 1 }, + { 0x26E3, 0x26E8, 5 }, + { 0x26E9, 0x26EB, 2 }, + { 0x26EC, 0x26F1, 1 }, + { 0x26F4, 0x26F6, 2 }, + { 0x26F7, 0x26F9, 1 }, + { 0x26FB, 0x26FC, 1 }, + { 0x26FE, 0x26FF, 1 }, + { 0x273D, 0x2776, 57 }, + { 0x2777, 0x277F, 1 }, + { 0x2B56, 0x2B59, 1 }, + { 0x3248, 0x324F, 1 }, + { 0xE000, 0xF8FF, 1 }, + { 0xFE00, 0xFE0F, 1 }, + { 0xFFFD, 0x1F100, 61699 }, + { 0x1F101, 0x1F10A, 1 }, + { 0x1F110, 0x1F12D, 1 }, + { 0x1F130, 0x1F169, 1 }, + { 0x1F170, 0x1F18D, 1 }, + { 0x1F18F, 0x1F190, 1 }, + { 0x1F19B, 0x1F1AC, 1 }, + { 0xE0100, 0xE01EF, 1 }, + { 0xF0000, 0xFFFFD, 1 }, + { 0x100000, 0x10FFFD, 1 }, +}; + +#endif /* unidata_h */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dcf9d170f..6397a48d1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -project(minetest) +project(multicraft) INCLUDE(CheckIncludeFiles) INCLUDE(CheckLibraryExists) @@ -32,12 +32,17 @@ if(NOT (BUILD_CLIENT OR BUILD_SERVER)) endif() option(USE_SDL "Use SDL2 for window management" FALSE) +option(USE_STATIC_BUILD "Link additional libraries needed for static build" FALSE) if(USE_SDL) - find_package(SDL2) + if(NOT USE_STATIC_BUILD) + find_package(SDL2) + else() + set(SDL2_FOUND TRUE) + endif() if (SDL2_FOUND) message(STATUS "SDL2 found.") - include_directories(${SDL2_INCLUDEDIR}) + include_directories(${SDL2_INCLUDE_DIR}) else() message(FATAL_ERROR "SDL2 not found.") endif() @@ -193,6 +198,8 @@ if(ENABLE_LEVELDB) endif() endif(ENABLE_LEVELDB) +option(ENABLE_SQLITE "Enable SQLite backend" TRUE) +set(USE_SQLITE TRUE) OPTION(ENABLE_REDIS "Enable Redis backend" TRUE) set(USE_REDIS FALSE) @@ -556,6 +563,8 @@ if(BUILD_CLIENT) ${FREETYPE_LIBRARY} ${PLATFORM_LIBS} ${SDL2_LIBRARIES} + luautf8 + luachacha ) if(NOT USE_LUAJIT) set_target_properties(${PROJECT_NAME} PROPERTIES @@ -565,6 +574,21 @@ if(BUILD_CLIENT) ) endif() + if(USE_SDL) + target_link_libraries( + ${PROJECT_NAME} + ${SDL2_LIBRARIES} + ) + if(USE_STATIC_BUILD AND WIN32) + target_link_libraries( + ${PROJECT_NAME} + winmm + imm32 + setupapi + opengl32 + ) + endif() + endif() if(ENABLE_GLES) target_link_libraries( ${PROJECT_NAME} @@ -582,12 +606,23 @@ if(BUILD_CLIENT) ${PROJECT_NAME} ${GETTEXT_LIBRARY} ) + if(USE_STATIC_BUILD) + target_link_libraries( + ${PROJECT_NAME} + ${GETTEXT_ICONV_LIBRARY} + ) + endif() endif() if(USE_CURL) target_link_libraries( ${PROJECT_NAME} ${CURL_LIBRARY} ) + if(USE_STATIC_BUILD) + if(WIN32) + target_link_libraries(${PROJECT_NAME} bcrypt crypt32 ws2_32) + endif() + endif() endif() if(FREETYPE_PKGCONFIG_FOUND) set_target_properties(${PROJECT_NAME} @@ -634,6 +669,8 @@ if(BUILD_SERVER) ${LUA_BIT_LIBRARY} ${GMP_LIBRARY} ${PLATFORM_LIBS} + luautf8 + luachacha ) set_target_properties(${PROJECT_NAME}server PROPERTIES COMPILE_DEFINITIONS "SERVER") @@ -901,7 +938,7 @@ if(BUILD_CLIENT) if(USE_GETTEXT) foreach(LOCALE ${GETTEXT_USED_LOCALES}) set_mo_paths(MO_BUILD_PATH MO_DEST_PATH ${LOCALE}) - set(MO_BUILD_PATH "${MO_BUILD_PATH}/${PROJECT_NAME}.mo") + set(MO_BUILD_PATH "${MO_BUILD_PATH}/minetest.mo") install(FILES ${MO_BUILD_PATH} DESTINATION ${MO_DEST_PATH}) endforeach() endif() @@ -918,9 +955,9 @@ if (USE_GETTEXT) set(MO_FILES) foreach(LOCALE ${GETTEXT_USED_LOCALES}) - set(PO_FILE_PATH "${GETTEXT_PO_PATH}/${LOCALE}/${PROJECT_NAME}.po") + set(PO_FILE_PATH "${GETTEXT_PO_PATH}/${LOCALE}/minetest.po") set_mo_paths(MO_BUILD_PATH MO_DEST_PATH ${LOCALE}) - set(MO_FILE_PATH "${MO_BUILD_PATH}/${PROJECT_NAME}.mo") + set(MO_FILE_PATH "${MO_BUILD_PATH}/minetest.mo") add_custom_command(OUTPUT ${MO_BUILD_PATH} COMMAND ${CMAKE_COMMAND} -E make_directory ${MO_BUILD_PATH} diff --git a/src/activeobject.h b/src/activeobject.h index d809f2b9a..946a99c3b 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -44,15 +44,18 @@ enum ActiveObjectType { struct ActiveObjectMessage { - ActiveObjectMessage(u16 id_, bool reliable_=true, const std::string &data_ = "") : + ActiveObjectMessage(u16 id_, bool reliable_ = true, + const std::string &data_ = "", const std::string legacy_ = "") : id(id_), reliable(reliable_), - datastring(data_) + datastring(data_), + legacystring(legacy_) {} u16 id; bool reliable; std::string datastring; + std::string legacystring; }; enum ActiveObjectCommand { diff --git a/src/ban.h b/src/ban.h index 0a425e296..975efd2c0 100644 --- a/src/ban.h +++ b/src/ban.h @@ -19,8 +19,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "IrrCompileConfig.h" + #include "util/string.h" +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include "threading/sdl_thread.h" +#else #include "threading/thread.h" +#endif #include "exceptions.h" #include #include diff --git a/src/chat.cpp b/src/chat.cpp index 7a10881b4..1157ee6d2 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -35,6 +35,7 @@ ChatBuffer::ChatBuffer(u32 scrollback): if (m_scrollback == 0) m_scrollback = 1; m_empty_formatted_line.first = true; + m_empty_formatted_line.line_index = 0; m_cache_clickable_chat_weblinks = false; // Curses mode cannot access g_settings here @@ -52,8 +53,9 @@ void ChatBuffer::addLine(const std::wstring &name, const std::wstring &text) { m_lines_modified = true; - ChatLine line(name, text); + ChatLine line(name, text, m_current_line_index); m_unformatted.push_back(line); + m_current_line_index++; if (m_rows > 0) { // m_formatted is valid and must be kept valid @@ -73,6 +75,7 @@ void ChatBuffer::clear() { m_unformatted.clear(); m_formatted.clear(); + m_current_line_index = 0; m_scroll = 0; m_lines_modified = true; } @@ -127,6 +130,8 @@ void ChatBuffer::deleteOldest(u32 count) m_scroll = getBottomScrollPos(); else scrollAbsolute(m_scroll - del_formatted); + + m_del_formatted += del_formatted; } void ChatBuffer::deleteByAge(f32 maxAge) @@ -272,6 +277,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, //EnrichedString line_text(line.text); next_line.first = true; + next_line.line_index = line.line_index; // Set/use forced newline after the last frag in each line bool mark_newline = false; @@ -461,6 +467,7 @@ void ChatPrompt::input(wchar_t ch) clampView(); m_nick_completion_start = 0; m_nick_completion_end = 0; + m_line_modified = true; } void ChatPrompt::input(const std::wstring &str) @@ -470,6 +477,7 @@ void ChatPrompt::input(const std::wstring &str) clampView(); m_nick_completion_start = 0; m_nick_completion_end = 0; + m_line_modified = true; } void ChatPrompt::addToHistory(const std::wstring &line) @@ -494,6 +502,7 @@ void ChatPrompt::clear() m_cursor = 0; m_nick_completion_start = 0; m_nick_completion_end = 0; + m_line_modified = true; } std::wstring ChatPrompt::replace(const std::wstring &line) @@ -504,6 +513,7 @@ std::wstring ChatPrompt::replace(const std::wstring &line) clampView(); m_nick_completion_start = 0; m_nick_completion_end = 0; + m_line_modified = true; return old_line; } @@ -605,6 +615,7 @@ void ChatPrompt::nickCompletion(const std::list& names, bool backwa clampView(); m_nick_completion_start = prefix_start; m_nick_completion_end = prefix_end; + m_line_modified = true; } void ChatPrompt::reformat(u32 cols) @@ -686,6 +697,7 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco m_line.erase(m_cursor, abs(new_cursor - old_cursor)); } m_cursor_len = 0; + m_line_modified = true; break; case CURSOROP_SELECT: if (scope == CURSOROP_SCOPE_LINE) { @@ -705,6 +717,25 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco m_nick_completion_end = 0; } +void ChatPrompt::setCursorPos(int cursor_pos) +{ + s32 length = m_line.size(); + m_cursor = MYMAX(MYMIN(cursor_pos, length), 0); + m_cursor_len = 0; + + clampView(); + + m_nick_completion_start = 0; + m_nick_completion_end = 0; +} + +void ChatPrompt::setViewPosition(int view) +{ + m_view = view; + + clampView(); +} + void ChatPrompt::clampView() { s32 length = m_line.size(); diff --git a/src/chat.h b/src/chat.h index 746d30995..6ee56fb49 100644 --- a/src/chat.h +++ b/src/chat.h @@ -37,16 +37,22 @@ struct ChatLine EnrichedString name; // message text EnrichedString text; + // Line index in ChatLine buffer + int line_index; - ChatLine(const std::wstring &a_name, const std::wstring &a_text): + ChatLine(const std::wstring &a_name, const std::wstring &a_text, + int a_line_index): name(a_name), - text(a_text) + text(a_text), + line_index(a_line_index) { } - ChatLine(const EnrichedString &a_name, const EnrichedString &a_text): + ChatLine(const EnrichedString &a_name, const EnrichedString &a_text, + int a_line_index): name(a_name), - text(a_text) + text(a_text), + line_index(a_line_index) { } }; @@ -69,6 +75,8 @@ struct ChatFormattedLine std::vector fragments; // true if first line of one formatted ChatLine bool first; + // Line index in ChatLine buffer + int line_index; }; class ChatBuffer @@ -118,6 +126,12 @@ public: bool getLinesModified() const { return m_lines_modified; } void resetLinesModified() { m_lines_modified = false; } + s32 getScrollPos() { return m_scroll; } + u32 getColsCount() { return m_cols; } + + u32 getDelFormatted() const { return m_del_formatted; } + void resetDelFormatted() { m_del_formatted = 0; } + // Format a chat line for the given number of columns. // Appends the formatted lines to the destination array and // returns the number of formatted lines. @@ -126,7 +140,6 @@ public: void resize(u32 scrollback); -protected: s32 getTopScrollPos() const; s32 getBottomScrollPos() const; @@ -147,15 +160,20 @@ private: // Empty formatted line, for error returns ChatFormattedLine m_empty_formatted_line; - // Enable clickable chat weblinks - bool m_cache_clickable_chat_weblinks; - // Color of clickable chat weblinks - irr::video::SColor m_cache_chat_weblink_color; - // Whether the lines were modified since last markLinesUnchanged() // Is always set to true when m_unformatted is modified, because that's what // determines the output of getLineCount() and getLine() bool m_lines_modified = true; + + // How many formatted lines have been deleted + u32 m_del_formatted = 0; + + int m_current_line_index = 0; + + // Enable clickable chat weblinks + bool m_cache_clickable_chat_weblinks; + // Color of clickable chat weblinks + irr::video::SColor m_cache_chat_weblink_color; }; class ChatPrompt @@ -197,6 +215,8 @@ public: std::wstring getVisiblePortion() const; // Get cursor position (relative to visible portion). -1 if invalid s32 getVisibleCursorPosition() const; + // Get view position (absolute value) + s32 getViewPosition() const { return m_view; } // Get length of cursor selection s32 getCursorLength() const { return m_cursor_len; } @@ -232,6 +252,14 @@ public: // deletes the word to the left of the cursor. void cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope); + void setCursorPos(int cursor_pos); + void setViewPosition(int view); + + // Functions for keeping track of whether the line was modified by any + // preceding operations + bool getLineModified() const { return m_line_modified; } + void resetLineModified() { m_line_modified = false; } + protected: // set m_view to ensure that 0 <= m_view <= m_cursor < m_view + m_cols // if line can be fully shown, set m_view to zero @@ -263,6 +291,9 @@ private: s32 m_nick_completion_start = 0; // Last nick completion start (index into m_line) s32 m_nick_completion_end = 0; + + // True if line was modified + bool m_line_modified = true; }; class ChatBackend diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 5941a1fb3..4eb7adfd3 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -595,12 +595,11 @@ void Camera::updateViewingRange() m_cameranode->setNearValue(0.1f * BS); #endif - m_draw_control.wanted_range = std::fmin(adjustDist(viewing_range, getFovMax()), 4000); - if (m_draw_control.range_all) { - m_cameranode->setFarValue(100000.0); - return; - } - m_cameranode->setFarValue((viewing_range < 2000) ? 2000 * BS : viewing_range * BS); + if (m_draw_control.extended_range) + viewing_range *= 3; + viewing_range = std::fmin(adjustDist(viewing_range, getFovMax()), 4000); + m_draw_control.wanted_range = viewing_range; + m_cameranode->setFarValue(m_draw_control.range_all ? 100000.0 : std::max(2000.0f, viewing_range) * BS); } void Camera::setDigging(s32 button) @@ -609,15 +608,20 @@ void Camera::setDigging(s32 button) m_digging_button = button; } -void Camera::wield(const ItemStack &item) +void Camera::wield(const ItemStack &item, const bool no_change_anim) { if (item.name != m_wield_item_next.name || item.metadata != m_wield_item_next.metadata) { m_wield_item_next = item; - if (m_wield_change_timer > 0) + if (no_change_anim) { + // Change items immediately + m_wieldnode->setItem(item, m_client); + m_wield_change_timer = 0.125f; + } else if (m_wield_change_timer > 0) { m_wield_change_timer = -m_wield_change_timer; - else if (m_wield_change_timer == 0) + } else if (m_wield_change_timer == 0) { m_wield_change_timer = -0.001; + } } } diff --git a/src/client/camera.h b/src/client/camera.h index 814844667..e9938ac96 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -150,7 +150,7 @@ public: void setDigging(s32 button); // Replace the wielded item mesh - void wield(const ItemStack &item); + void wield(const ItemStack &item, const bool no_change_anim); // Draw the wielded tool. // This has to happen *after* the main scene is drawn. diff --git a/src/client/client.cpp b/src/client/client.cpp index 0574d3da0..d7117e1e2 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -51,7 +51,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "clientmedia.h" #include "version.h" #include "database/database-files.h" +#if USE_SQLITE #include "database/database-sqlite3.h" +#endif #include "serialization.h" #include "guiscalingfilter.h" #include "script/scripting_client.h" @@ -139,6 +141,9 @@ Client::Client( } m_cache_save_interval = g_settings->getU16("server_map_save_interval"); + m_round_screen = g_settings->getU16("round_screen"); + m_hud_scaling = g_settings->getFloat("hud_scaling"); + m_inv_item_anim_enabled = g_settings->getBool("inventory_items_animations"); } void Client::migrateModStorage() @@ -272,7 +277,7 @@ const std::string &Client::getBuiltinLuaPath() const std::string &Client::getClientModsLuaPath() { - static const std::string clientmods_dir = porting::path_share + DIR_DELIM + "clientmods"; + static const std::string clientmods_dir = porting::path_user + DIR_DELIM + "clientmods"; return clientmods_dir; } @@ -294,11 +299,13 @@ void Client::Stop() m_script->on_shutdown(); //request all client managed threads to stop m_mesh_update_thread.stop(); +#if USE_SQLITE // Save local server map if (m_localdb) { infostream << "Local map saving ended." << std::endl; m_localdb->endSave(); } +#endif if (m_mods_loaded) delete m_script; @@ -687,12 +694,14 @@ void Client::step(float dtime) m_mod_storage_database->beginSave(); } +#if USE_SQLITE // Write server map if (m_localdb && m_localdb_save_interval.step(dtime, m_cache_save_interval)) { m_localdb->endSave(); m_localdb->beginSave(); } +#endif } bool Client::loadMedia(const std::string &data, const std::string &filename, @@ -852,9 +861,11 @@ void Client::initLocalMapSaving(const Address &address, #undef set_world_path fs::CreateAllDirs(world_path); +#if USE_SQLITE m_localdb = new MapDatabaseSQLite3(world_path); m_localdb->beginSave(); actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl; +#endif } void Client::ReceiveAll() @@ -1055,14 +1066,14 @@ AuthMechanism Client::choseAuthMech(const u32 mechs) void Client::sendInit(const std::string &playerName) { - NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size())); + NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size()) + 1); // we don't support network compression yet u16 supp_comp_modes = NETPROTO_COMPRESSION_NONE; pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) supp_comp_modes; pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX; - pkt << playerName; + pkt << playerName << (u8) 1; Send(&pkt); } @@ -1239,6 +1250,14 @@ bool Client::canSendChatMessage() const void Client::sendChatMessage(const std::wstring &message) { + // Exempt SSCSM com messages from limits + if (message.find(L"/admin \x01SSCSM_COM\x01", 0) == 0) { + NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); + pkt << message; + Send(&pkt); + return; + } + const s16 max_queue_size = g_settings->getS16("max_out_chat_queue_size"); if (canSendChatMessage()) { u32 now = time(NULL); @@ -1270,12 +1289,18 @@ void Client::clearOutChatQueue() } void Client::sendChangePassword(const std::string &oldpassword, - const std::string &newpassword) + const std::string &newpassword, const bool close_form) { LocalPlayer *player = m_env.getLocalPlayer(); if (player == NULL) return; + if (close_form) { + auto formspec = m_game_ui->getFormspecGUI(); + if (formspec) + formspec->quitMenu(); + } + // get into sudo mode and then send new password to server m_password = oldpassword; m_new_password = newpassword; @@ -1298,13 +1323,20 @@ void Client::sendRespawn() void Client::sendReady() { + const char *platform_name = porting::getPlatformName(); + const std::string sysinfo = porting::get_sysinfo(); + const size_t version_len = strlen(g_version_hash) + 1 + strlen(platform_name) + 1 + sysinfo.size(); NetworkPacket pkt(TOSERVER_CLIENT_READY, - 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash) + 2); + 1 + 1 + 1 + 1 + 2 + sizeof(char) * version_len + 2); pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH - << (u8) 0 << (u16) strlen(g_version_hash); + << (u8) 0 << (u16) version_len; pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash)); + pkt << (u8) 0; + pkt.putRawString(platform_name, (u16) strlen(platform_name)); + pkt << (u8) 0; + pkt.putRawString(sysinfo.c_str(), sysinfo.size()); pkt << (u16)FORMSPEC_API_VERSION; Send(&pkt); } @@ -1719,7 +1751,7 @@ void Client::showUpdateProgressTexture(void *args, u32 progress, u32 max_progres std::wostringstream strm; strm << targs->text_base << L" " << targs->last_percent << L"%..."; m_rendering_engine->draw_load_screen(strm.str(), targs->guienv, targs->tsrc, 0, - 72 + (u16) ((18. / 100.) * (double) targs->last_percent), true); + 72 + (u16) ((18. / 100.) * (double) targs->last_percent)); } } diff --git a/src/client/client.h b/src/client/client.h index 9362a0638..136b97074 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -242,7 +242,7 @@ public: void sendChatMessage(const std::wstring &message); void clearOutChatQueue(); void sendChangePassword(const std::string &oldpassword, - const std::string &newpassword); + const std::string &newpassword, const bool close_form = false); void sendDamage(u16 damage); void sendRespawn(); void sendReady(); @@ -439,6 +439,10 @@ public: { return m_env.getLocalPlayer()->formspec_prepend; } + + const u16 getRoundScreen() { return m_round_screen; } + const f32 getHudScaling() { return m_hud_scaling; } + const bool getInvItemAnimEnabled() { return m_inv_item_anim_enabled; } private: void loadMods(); @@ -605,4 +609,8 @@ private: u32 m_csm_restriction_noderange = 8; std::unique_ptr m_modchannel_mgr; + + u16 m_round_screen; + f32 m_hud_scaling; + bool m_inv_item_anim_enabled; }; diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp index 646f906ef..84691d970 100644 --- a/src/client/clientlauncher.cpp +++ b/src/client/clientlauncher.cpp @@ -80,7 +80,7 @@ ClientLauncher::~ClientLauncher() delete m_rendering_engine; #if USE_SOUND - g_sound_manager_singleton.reset(); + deleteSoundManagerSingleton(g_sound_manager_singleton); #endif } @@ -97,8 +97,11 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) init_args(start_data, cmd_args); #if USE_SOUND - if (g_settings->getBool("enable_sound")) - g_sound_manager_singleton = createSoundManagerSingleton(); + if (g_settings->getBool("enable_sound")) { + // Check if it's already created just in case + if (!g_sound_manager_singleton) + g_sound_manager_singleton = createSoundManagerSingleton(); + } #endif if (!init_engine()) { @@ -120,6 +123,8 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) m_rendering_engine->setupTopLevelWindow(PROJECT_NAME_C); + RenderingEngine::get_raw_device()->getLogger()->setLogLevel(irr::ELL_INFORMATION); + /* This changes the minimum allowed number of vertices in a VBO. Default is 500. @@ -144,30 +149,21 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255, 0, 0, 0)); skin->setColor(gui::EGDC_HIGH_LIGHT, video::SColor(255, 70, 120, 50)); skin->setColor(gui::EGDC_HIGH_LIGHT_TEXT, video::SColor(255, 255, 255, 255)); -#ifdef HAVE_TOUCHSCREENGUI - float density = RenderingEngine::getDisplayDensity(); - skin->setSize(gui::EGDS_CHECK_BOX_WIDTH, (s32)(17.0f * density)); - skin->setSize(gui::EGDS_SCROLLBAR_SIZE, (s32)(14.0f * density)); - skin->setSize(gui::EGDS_WINDOW_BUTTON_WIDTH, (s32)(15.0f * density)); - if (density > 1.5f) { - std::string sprite_path = porting::path_user + "/textures/base/pack/"; - if (density > 3.5f) - sprite_path.append("checkbox_64.png"); - else if (density > 2.0f) - sprite_path.append("checkbox_32.png"); - else - sprite_path.append("checkbox_16.png"); - // Texture dimensions should be a power of 2 - gui::IGUISpriteBank *sprites = skin->getSpriteBank(); - video::IVideoDriver *driver = m_rendering_engine->get_video_driver(); - video::ITexture *sprite_texture = driver->getTexture(sprite_path.c_str()); - if (sprite_texture) { - s32 sprite_id = sprites->addTextureAsSprite(sprite_texture); - if (sprite_id != -1) - skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, sprite_id); - } - } -#endif + float density = RenderingEngine::getDisplayDensity() * g_settings->getFloat("gui_scaling"); + skin->setSize(gui::EGDS_CHECK_BOX_WIDTH, (s32)(18.0f * density)); +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 9 + // Load check icon for the checkbox + // TODO other icons + std::string sprite_path = porting::path_share + DIR_DELIM + "textures" + + DIR_DELIM + "base" + DIR_DELIM + "pack" + DIR_DELIM + "checkbox.png"; + if (auto *sprite_texture = RenderingEngine::get_video_driver()->getTexture(sprite_path.c_str())) { + auto *sprites = skin->getSpriteBank(); + s32 sprite_id = sprites->addTextureAsSprite(sprite_texture); + if (sprite_id != -1) + skin->setIcon(gui::EGDI_CHECK_BOX_CHECKED, sprite_id); + } + #endif + g_fontengine = new FontEngine(guienv); FATAL_ERROR_IF(g_fontengine == NULL, "Font engine creation failed."); @@ -293,6 +289,11 @@ bool ClientLauncher::run(GameStartData &start_data, const Settings &cmd_args) receiver->m_touchscreengui = NULL; #endif +#if defined(__ANDROID__) || defined(__IOS__) + if (!g_gamecallback->shutdown_requested) + porting::notifyExitGame(); +#endif + // If no main menu, show error and exit if (skip_main_menu) { if (!error_message.empty()) { @@ -472,7 +473,7 @@ bool ClientLauncher::launch_game(std::string &error_message, // If using simple singleplayer mode, override if (start_data.isSinglePlayer()) { - start_data.name = "singleplayer"; + start_data.name = "Player"; start_data.password = ""; start_data.socket_port = myrand_range(49152, 65535); } else { @@ -548,9 +549,7 @@ void ClientLauncher::main_menu(MainMenuData *menudata) infostream << "Waited for other menus" << std::endl; // Cursor can be non-visible when coming from the game -#ifndef ANDROID - m_rendering_engine->get_raw_device()->getCursorControl()->setVisible(true); -#endif + input->setCursorVisible(true); /* show main menu */ GUIEngine mymenu(&input->joystick, guiroot, m_rendering_engine, &g_menumgr, menudata, *kill); diff --git a/src/client/clientmap.h b/src/client/clientmap.h index 4c5fc2a8d..46a73b802 100644 --- a/src/client/clientmap.h +++ b/src/client/clientmap.h @@ -29,6 +29,7 @@ struct MapDrawControl { // Overrides limits by drawing everything bool range_all = false; + bool extended_range = false; // Wanted drawing range float wanted_range = 0.0f; // show a wire frame for debugging diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index b87be38cb..3eb20fe1b 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -40,6 +40,21 @@ static void cloud_3d_setting_changed(const std::string &settingname, void *data) ((Clouds *)data)->readSettings(); } +static const std::vector quad_indices = []() { + int quad_count = 0x10000 / 4; // max number of quads that can be drawn with 16-bit indices + std::vector indices; + indices.reserve(quad_count * 6); + for (int k = 0; k < quad_count; k++) { + indices.push_back(4 * k + 0); + indices.push_back(4 * k + 1); + indices.push_back(4 * k + 2); + indices.push_back(4 * k + 2); + indices.push_back(4 * k + 3); + indices.push_back(4 * k + 0); + } + return indices; +}(); + Clouds::Clouds(scene::ISceneManager* mgr, s32 id, u32 seed @@ -101,8 +116,6 @@ void Clouds::render() ScopeProfiler sp(g_profiler, "Clouds::render()", SPT_AVG); - int num_faces_to_draw = m_enable_3d ? 6 : 1; - m_material.setFlag(video::EMF_BACK_FACE_CULLING, m_enable_3d); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); @@ -170,9 +183,14 @@ void Clouds::render() // Read noise - std::vector grid(m_cloud_radius_i * 2 * m_cloud_radius_i * 2); - std::vector vertices; - vertices.reserve(16 * m_cloud_radius_i * m_cloud_radius_i); + const int grid_length = 2 * m_cloud_radius_i; + std::vector grid(grid_length * grid_length); + auto grid_index = [&] (int x, int z) -> int { + return (z + m_cloud_radius_i) * grid_length + (x + m_cloud_radius_i); + }; + auto grid_point = [&] (int x, int z) -> bool { + return grid[grid_index(x, z)]; + }; for(s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) { u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + m_cloud_radius_i; @@ -187,147 +205,78 @@ void Clouds::render() } } -#define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius)) -#define INAREA(x, z, radius) \ - ((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius)) + const float rel_y = m_camera_pos.Y - m_params.height * BS; + const bool draw_top = !m_enable_3d || rel_y >= m_params.thickness * BS; + const bool draw_bottom = rel_y < 0.0f; - for (s16 zi0= -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++) - for (s16 xi0= -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++) + const v3f origin = v3f(world_center_of_drawing_in_noise_f.X, m_params.height * BS, world_center_of_drawing_in_noise_f.Y) - intToFloat(m_camera_offset, BS); + const f32 rx = cloud_size; + // if clouds are flat, the top layer should be at the given height + const f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f; + const f32 rz = cloud_size; + + // std::vector is great but it is slow + // reserve+push/insert is slow because extending needs to check vector size + // resize+direct access is slow because resize initializes the whole vector + // so... malloc! it can't overflow as there can't be too many faces to draw + const int max_quads_per_cell = m_enable_3d ? 4 : 1; + const int max_quad_id = m_enable_3d ? 6 : 1; + video::S3DVertex *buf = (video::S3DVertex *)malloc(grid.size() * max_quads_per_cell * 4 * sizeof(video::S3DVertex)); + video::S3DVertex *pv = buf; + + const v3f faces[6][4] = { + {{0, ry, 0}, {0, ry, rz}, {rx, ry, rz}, {rx, ry, 0}}, // top + {{0, ry, 0}, {rx, ry, 0}, {rx, 0, 0}, {0, 0, 0}}, // back + {{rx, ry, 0}, {rx, ry, rz}, {rx, 0, rz}, {rx, 0, 0}}, // right + {{rx, ry, rz}, {0, ry, rz}, {0, 0, rz}, {rx, 0, rz}}, // front + {{0, ry, rz}, {0, ry, 0}, {0, 0, 0}, {0, 0, rz}}, // left + {{rx, 0, rz}, {0, 0, rz}, {0, 0, 0}, {rx, 0, 0}}, // bottom + }; + const v3f normals[6] = {{0, 1, 0}, {0, 0, -1}, {1, 0, 0}, {0, 0, 1}, {-1, 0, 0}, {0, -1, 0}}; + const video::SColor colors[6] = {c_top, c_side_1, c_side_2, c_side_1, c_side_2, c_bottom}; + const v2f tex_coords[4] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}}; + + // Draw from back to front for proper transparency + for (s16 zi0= 1-(int)m_cloud_radius_i; zi0 < m_cloud_radius_i-1; zi0++) + for (s16 xi0= 1-(int)m_cloud_radius_i; xi0 < m_cloud_radius_i-1; xi0++) { s16 zi = zi0; s16 xi = xi0; - // Draw from back to front for proper transparency - if(zi >= 0) - zi = m_cloud_radius_i - zi - 1; - if(xi >= 0) - xi = m_cloud_radius_i - xi - 1; + if (zi >= 0) + zi = m_cloud_radius_i - zi - 2; + if (xi >= 0) + xi = m_cloud_radius_i - xi - 2; - u32 i = GETINDEX(xi, zi, m_cloud_radius_i); - - if (!grid[i]) + if (!grid_point(xi, zi)) continue; - v2f p0 = v2f(xi,zi)*cloud_size + world_center_of_drawing_in_noise_f; - - video::S3DVertex v[4] = { - video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 1), - video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 1), - video::S3DVertex(0,0,0, 0,0,0, c_top, 1, 0), - video::S3DVertex(0,0,0, 0,0,0, c_top, 0, 0) + bool do_draw[6] = { + draw_top, + zi > 0 && !grid_point(xi, zi - 1), + xi < 0 && !grid_point(xi + 1, zi), + zi < 0 && !grid_point(xi, zi + 1), + xi > 0 && !grid_point(xi - 1, zi), + draw_bottom, }; - const f32 rx = cloud_size / 2.0f; - // if clouds are flat, the top layer should be at the given height - const f32 ry = m_enable_3d ? m_params.thickness * BS : 0.0f; - const f32 rz = cloud_size / 2; - - for(int i=0; iPos = pos + faces[i][k]; + pv->Normal = normals[i]; + pv->Color = colors[i]; + pv->TCoords = tex_coords[k]; + pv++; } } } - int quad_count = vertices.size() / 4; - std::vector indices; - indices.reserve(quad_count * 6); - for (int k = 0; k < quad_count; k++) { - indices.push_back(4 * k + 0); - indices.push_back(4 * k + 1); - indices.push_back(4 * k + 2); - indices.push_back(4 * k + 2); - indices.push_back(4 * k + 3); - indices.push_back(4 * k + 0); - } - driver->drawVertexPrimitiveList(vertices.data(), vertices.size(), indices.data(), 2 * quad_count, + int vertex_count = pv - buf; + int quad_count = vertex_count / 4; + driver->drawVertexPrimitiveList(buf, vertex_count, quad_indices.data(), 2 * quad_count, video::EVT_STANDARD, scene::EPT_TRIANGLES, video::EIT_16BIT); + free(buf); // Restore fog settings driver->setFog(fog_color, fog_type, fog_start, fog_end, fog_density, diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 1d631f3b8..54cb9aa1b 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -964,10 +964,9 @@ void GenericCAO::updateMarker() void GenericCAO::updateNametag() { - if (m_is_local_player) // No nametag for local player - return; - - if (m_prop.nametag.empty() || m_prop.nametag_color.getAlpha() == 0) { + if (m_prop.nametag.empty() || m_prop.nametag_color.getAlpha() == 0 || + (m_is_local_player && + m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST)) { // Delete nametag if (m_nametag) { m_client->getCamera()->removeNametag(m_nametag); @@ -1318,7 +1317,7 @@ void GenericCAO::updateTextures(std::string mod) bool use_bilinear_filter = g_settings->getBool("bilinear_filter"); bool use_anisotropic_filter = g_settings->getBool("anisotropic_filter"); - m_previous_texture_modifier = m_current_texture_modifier; +// m_previous_texture_modifier = m_current_texture_modifier; // otherwise modifiers will overlap due to function design bug m_current_texture_modifier = mod; m_glow = m_prop.glow; @@ -1692,7 +1691,7 @@ void GenericCAO::processMessage(const std::string &data) player->setZoomFOV(m_prop.zoom_fov); } - if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty()) + if (m_is_player && m_prop.nametag.empty()) m_prop.nametag = m_name; if (m_is_local_player) m_prop.show_on_minimap = false; @@ -1852,14 +1851,14 @@ void GenericCAO::processMessage(const std::string &data) { // TODO: Execute defined fast response // As there is no definition, make a smoke puff - ClientSimpleObject *simple = createSmokePuff( + /*ClientSimpleObject *simple = createSmokePuff( m_smgr, m_env, m_position, v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); - m_env->addSimpleObject(simple); + m_env->addSimpleObject(simple);*/ } else if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) { - m_reset_textures_timer = 0.05; + m_reset_textures_timer = 0.1; if(damage >= 2) - m_reset_textures_timer += 0.05 * damage; + m_reset_textures_timer += 0.25 * damage; // Cap damage overlay to 1 second m_reset_textures_timer = std::min(m_reset_textures_timer, 1.0f); updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier); @@ -1912,24 +1911,32 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, time_from_last_punch, punchitem->wear); - if(result.did_punch && result.damage != 0) + if (!itemgroup_get(m_armor_groups, "silent")) { + SimpleSoundSpec spec; + spec.name = "player_punch"; + spec.gain = 1.0f; + m_client->sound()->playSoundAt(spec, false, getPosition()); + } + + s16 damage = result.damage; + if(result.did_punch && damage != 0) { - if(result.damage < m_hp) + if(damage < m_hp) { - m_hp -= result.damage; + m_hp -= damage; } else { m_hp = 0; // TODO: Execute defined fast response // As there is no definition, make a smoke puff - ClientSimpleObject *simple = createSmokePuff( + /*ClientSimpleObject *simple = createSmokePuff( m_smgr, m_env, m_position, v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS); - m_env->addSimpleObject(simple); + m_env->addSimpleObject(simple);*/ } if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) { - m_reset_textures_timer = 0.05; + m_reset_textures_timer = 0.1; if (result.damage >= 2) - m_reset_textures_timer += 0.05 * result.damage; + m_reset_textures_timer += 0.25 * result.damage; // Cap damage overlay to 1 second m_reset_textures_timer = std::min(m_reset_textures_timer, 1.0f); updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier); @@ -1989,6 +1996,9 @@ void GenericCAO::updateMeshCulling() node->setMaterialFlag(video::EMF_FRONT_FACE_CULLING, false); } + + // Show/hide the nametag + updateNametag(); } // Prototype diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 626cd69e6..982efa026 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -150,8 +150,10 @@ void MapblockMeshGenerator::drawQuad(v3f *coords, const v3s16 &normal, // should be (2+2)*6=24 values in the list. The order of // the faces in the list is up-down-right-left-back-front // (compatible with ContentFeatures). +// mask - a bit mask that suppresses drawing of tiles. +// tile i will not be drawn if mask & (1 << i) is 1 void MapblockMeshGenerator::drawCuboid(const aabb3f &box, - TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc) + TileSpec *tiles, int tilecount, const LightInfo *lights, const f32 *txc, u8 mask) { assert(tilecount >= 1 && tilecount <= 6); // pre-condition @@ -274,6 +276,8 @@ void MapblockMeshGenerator::drawCuboid(const aabb3f &box, // Add to mesh collector for (int k = 0; k < 6; ++k) { + if (mask & (1 << k)) + continue; int tileindex = MYMIN(k, tilecount - 1); collector->append(tiles[tileindex], vertices + 4 * k, 4, quad_indices, 6); } @@ -363,7 +367,7 @@ void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 * } void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, - TileSpec *tiles, int tile_count) + TileSpec *tiles, int tile_count, u8 mask) { bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f; f32 texture_coord_buf[24]; @@ -400,12 +404,49 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc, d.Z = (j & 1) ? dz2 : dz1; lights[j] = blendLight(d); } - drawCuboid(box, tiles, tile_count, lights, txc); + drawCuboid(box, tiles, tile_count, lights, txc, mask); } else { - drawCuboid(box, tiles, tile_count, nullptr, txc); + drawCuboid(box, tiles, tile_count, nullptr, txc, mask); } } +u8 MapblockMeshGenerator::getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const +{ + const f32 NODE_BOUNDARY = 0.5 * BS; + + // For an oversized nodebox, return immediately + if (box.MaxEdge.X > NODE_BOUNDARY || + box.MinEdge.X < -NODE_BOUNDARY || + box.MaxEdge.Y > NODE_BOUNDARY || + box.MinEdge.Y < -NODE_BOUNDARY || + box.MaxEdge.Z > NODE_BOUNDARY || + box.MinEdge.Z < -NODE_BOUNDARY) + return 0; + + // We can skip faces at node boundary if the matching neighbor is solid + u8 solid_mask = + (box.MaxEdge.Y == NODE_BOUNDARY ? 1 : 0) | + (box.MinEdge.Y == -NODE_BOUNDARY ? 2 : 0) | + (box.MaxEdge.X == NODE_BOUNDARY ? 4 : 0) | + (box.MinEdge.X == -NODE_BOUNDARY ? 8 : 0) | + (box.MaxEdge.Z == NODE_BOUNDARY ? 16 : 0) | + (box.MinEdge.Z == -NODE_BOUNDARY ? 32 : 0); + + u8 sametype_mask = 0; + if (f->alpha == AlphaMode::ALPHAMODE_OPAQUE) { + // In opaque nodeboxes, faces on opposite sides can cancel + // each other out if there is a matching neighbor of the same type + sametype_mask = + ((solid_mask & 3) == 3 ? 3 : 0) | + ((solid_mask & 12) == 12 ? 12 : 0) | + ((solid_mask & 48) == 48 ? 48 : 0); + } + + // Combine masks with actual neighbors to get the faces to be skipped + return (solid_mask & solid_neighbors) | (sametype_mask & sametype_neighbors); +} + + void MapblockMeshGenerator::prepareLiquidNodeDrawing() { getSpecialTile(0, &tile_liquid_top); @@ -908,7 +949,7 @@ void MapblockMeshGenerator::drawSignlikeNode() { u8 wall = n.getWallMounted(nodedef); useTile(0, MATERIAL_FLAG_CRACK_OVERLAY, MATERIAL_FLAG_BACKFACE_CULLING); - static const float offset = BS / 16; + static const float offset = BS / 48; float size = BS / 2 * f->visual_scale; // Wall at X+ of node v3f vertices[4] = { @@ -1363,13 +1404,38 @@ void MapblockMeshGenerator::drawNodeboxNode() getTile(nodebox_tile_dirs[face], &tiles[face]); } + bool param2_is_rotation = + f->param_type_2 == CPT2_COLORED_FACEDIR || + f->param_type_2 == CPT2_COLORED_WALLMOUNTED || + f->param_type_2 == CPT2_FACEDIR || + f->param_type_2 == CPT2_WALLMOUNTED; + + bool param2_is_level = + f->param_type_2 == CPT2_LEVELED; + // locate possible neighboring nodes to connect to u8 neighbors_set = 0; - if (f->node_box.type == NODEBOX_CONNECTED) { - for (int dir = 0; dir != 6; dir++) { - u8 flag = 1 << dir; - v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir]; - MapNode n2 = data->m_vmanip.getNodeNoEx(p2); + u8 solid_neighbors = 0; + u8 sametype_neighbors = 0; + for (int dir = 0; dir != 6; dir++) { + u8 flag = 1 << dir; + v3s16 p2 = blockpos_nodes + p + nodebox_tile_dirs[dir]; + MapNode n2 = data->m_vmanip.getNodeNoEx(p2); + + // mark neighbors that are the same node type + // and have the same rotation or higher level stored as param2 + if (n2.param0 == n.param0 && + (!param2_is_rotation || n.param2 == n2.param2) && + (!param2_is_level || n.param2 <= n2.param2)) + sametype_neighbors |= flag; + + // mark neighbors that are simple solid blocks + if (nodedef->get(n2).drawtype == NDT_NORMAL) + solid_neighbors |= flag; + + if (f->node_box.type == NODEBOX_CONNECTED) { + p2 = blockpos_nodes + p + nodebox_connection_dirs[dir]; + n2 = data->m_vmanip.getNodeNoEx(p2); if (nodedef->nodeboxConnects(n, n2, flag)) neighbors_set |= flag; } @@ -1377,8 +1443,10 @@ void MapblockMeshGenerator::drawNodeboxNode() std::vector boxes; n.getNodeBoxes(nodedef, &boxes, neighbors_set); - for (auto &box : boxes) - drawAutoLightedCuboid(box, nullptr, tiles, 6); + for (auto &box : boxes) { + u8 mask = getNodeBoxMask(box, solid_neighbors, sametype_neighbors); + drawAutoLightedCuboid(box, nullptr, tiles, 6, mask); + } } void MapblockMeshGenerator::drawMeshNode() diff --git a/src/client/content_mapblock.h b/src/client/content_mapblock.h index 7f58a6764..18259eea2 100644 --- a/src/client/content_mapblock.h +++ b/src/client/content_mapblock.h @@ -100,10 +100,11 @@ public: // cuboid drawing! void drawCuboid(const aabb3f &box, TileSpec *tiles, int tilecount, - const LightInfo *lights , const f32 *txc); + const LightInfo *lights , const f32 *txc, u8 mask = 0); void generateCuboidTextureCoords(aabb3f const &box, f32 *coords); void drawAutoLightedCuboid(aabb3f box, const f32 *txc = NULL, - TileSpec *tiles = NULL, int tile_count = 0); + TileSpec *tiles = NULL, int tile_count = 0, u8 mask = 0); + u8 getNodeBoxMask(aabb3f box, u8 solid_neighbors, u8 sametype_neighbors) const; // liquid-specific bool top_is_same_liquid; diff --git a/src/client/game.cpp b/src/client/game.cpp index 1cca5b344..b0c1f53c4 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -46,7 +46,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/guiConfirmRegistration.h" #include "gui/guiFormSpecMenu.h" #include "gui/guiKeyChangeMenu.h" -#include "gui/guiPasswordChange.h" #include "gui/guiVolumeChange.h" #include "gui/mainmenumanager.h" #include "gui/profilergraph.h" @@ -175,14 +174,39 @@ struct LocalFormspecHandler : public TextDest return; } + if (m_formname == "MT_CHANGE_PW") { + if (fields.find("btn_change_pw") != fields.end()) { + const std::string old_pw = fields.at("old_pw"); + const std::string new_pw = fields.at("new_pw"); + const std::string confirm_pw = fields.at("confirm_pw"); + if (new_pw != confirm_pw) { + g_gamecallback->changePassword(old_pw, new_pw, confirm_pw); + return; + } + m_client->sendChangePassword(old_pw, new_pw, true); + } + + return; + } + if (m_formname == "MT_DEATH_SCREEN") { assert(m_client != 0); m_client->sendRespawn(); return; } - if (m_client->modsLoaded()) - m_client->getScript()->on_formspec_input(m_formname, fields); + if (m_client->modsLoaded()) { + try { + m_client->getScript()->on_formspec_input(m_formname, fields); + } catch (LuaError &e) { + const std::string error_message = std::string("LuaError: ") + e.what() + + strgettext("\nCheck debug.txt for details."); + m_client->setFatalError(error_message); +#ifdef __ANDROID__ + porting::handleError("LuaError (on_formspec_input)", error_message); +#endif + } + } } Client *m_client = nullptr; @@ -335,7 +359,7 @@ public: static void playerDamage(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false); + sm->m_sound->playSound(SimpleSoundSpec("player_damage", 1.0), false); } static void playerFallingDamage(MtEvent *e, void *data) @@ -481,7 +505,7 @@ public: m_sky_bg_color.set(bgcolorfa, services); // Fog distance - float fog_distance = 10000 * BS; + float fog_distance = -1.0f; // sentinel for disabled fog if (m_fog_enabled && !*m_force_fog_off) fog_distance = *m_fog_range; @@ -607,17 +631,23 @@ struct GameRunData { bool dig_instantly; bool digging_blocked; bool reset_jump_timer; + bool disable_fog; float nodig_delay_timer; + float noplace_delay_timer; float dig_time; float dig_time_complete; float repeat_place_timer; float object_hit_delay_timer; float time_from_last_punch; + float pause_game_timer; ClientActiveObject *selected_object; float jump_timer; float damage_flash; float update_draw_list_timer; +#if defined(__MACH__) && defined(__APPLE__) + float item_select_timer; +#endif f32 fog_range; @@ -659,6 +689,9 @@ public: void run(); void shutdown(); +#if defined(__ANDROID__) || defined(__IOS__) + void pauseGame(); +#endif protected: @@ -692,7 +725,7 @@ protected: // Input related void processUserInput(f32 dtime); void processKeyInput(); - void processItemSelection(u16 *new_playeritem); + void processItemSelection(f32 dtime, GameRunData *run_data); void dropSelectedItem(bool single_item = false); void openInventory(); @@ -753,7 +786,7 @@ protected: // Misc void showOverlayMessage(const char *msg, float dtime, int percent, - bool draw_clouds = true); + bool draw_clouds = false); static void settingChangedCallback(const std::string &setting_name, void *data); void readSettings(); @@ -775,7 +808,7 @@ protected: return input->wasKeyReleased(k); } -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) void handleAndroidChatInput(); #endif @@ -787,6 +820,8 @@ private: void showDeathFormspec(); void showPauseMenu(); + void showChangePasswordDialog(std::string old_pw, std::string new_pw, + std::string confirm_pw); void pauseAnimation(); void resumeAnimation(); @@ -909,10 +944,7 @@ private: int m_reset_HW_buffer_counter = 0; #ifdef HAVE_TOUCHSCREENGUI - bool m_cache_hold_aux1; -#endif -#ifdef __ANDROID__ - bool m_android_chat_open; + bool m_cache_touchtarget; #endif }; @@ -948,11 +980,6 @@ Game::Game() : &settingChangedCallback, this); readSettings(); - -#ifdef HAVE_TOUCHSCREENGUI - m_cache_hold_aux1 = false; // This is initialised properly later -#endif - } @@ -981,6 +1008,7 @@ Game::~Game() delete draw_control; clearTextureNameCache(); + setDisableTexturePacks(false); g_settings->deregisterChangedCallback("doubletap_jump", &settingChangedCallback, this); @@ -1074,11 +1102,6 @@ void Game::run() set_light_table(g_settings->getFloat("display_gamma")); -#ifdef HAVE_TOUCHSCREENGUI - m_cache_hold_aux1 = g_settings->getBool("fast_move") - && client->checkPrivilege("fast"); -#endif - irr::core::dimension2d previous_screen_size(g_settings->getU16("screen_w"), g_settings->getU16("screen_h")); @@ -1104,6 +1127,20 @@ void Game::run() // + Sleep time until the wanted FPS are reached draw_times.limit(device, &dtime); +#if defined(__MACH__) && defined(__APPLE__) && !defined(__IOS__) && !defined(__aarch64__) + if (!device->isWindowFocused()) { + if (m_does_lost_focus_pause_game && !isMenuActive()) + showPauseMenu(); + sleep_ms(50); + continue; + } +#else + if (device->isWindowMinimized()) { + sleep_ms(50); + continue; + } +#endif + // Prepare render data for next iteration updateStats(&stats, draw_times, dtime); @@ -1165,7 +1202,7 @@ void Game::shutdown() g_touchscreengui->hide(); #endif - showOverlayMessage(N_("Shutting down..."), 0, 0, false); + showOverlayMessage(N_("Shutting down..."), 0, 100); if (clouds) clouds->drop(); @@ -1244,9 +1281,9 @@ bool Game::init( bool Game::initSound() { #if USE_SOUND - if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) { + if (g_settings->getBool("enable_sound") && g_sound_manager_singleton) { infostream << "Attempting to use OpenAL audio" << std::endl; - sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher); + sound = createOpenALSoundManager(g_sound_manager_singleton, &soundfetcher); if (!sound) infostream << "Failed to initialize OpenAL audio" << std::endl; } else @@ -1311,12 +1348,6 @@ bool Game::createClient(const GameStartData &start_data) return false; bool could_connect, connect_aborted; -#ifdef HAVE_TOUCHSCREENGUI - if (g_touchscreengui) { - g_touchscreengui->init(texture_src); - g_touchscreengui->hide(); - } -#endif if (!connectToServer(start_data, &could_connect, &connect_aborted)) return false; @@ -1329,6 +1360,10 @@ bool Game::createClient(const GameStartData &start_data) return false; } +#if defined(__ANDROID__) || defined(__IOS__) + porting::notifyServerConnect(!simple_singleplayer_mode); +#endif + if (!getServerContent(&connect_aborted)) { if (error_message->empty() && !connect_aborted) { // Should not happen if error messages are set properly @@ -1339,7 +1374,7 @@ bool Game::createClient(const GameStartData &start_data) } auto *scsf = new GameGlobalShaderConstantSetterFactory( - &m_flags.force_fog_off, &runData.fog_range, client); + &runData.disable_fog, &runData.fog_range, client); shader_src->addShaderConstantSetterFactory(scsf); // Update cached textures, meshes and materials @@ -1365,7 +1400,11 @@ bool Game::createClient(const GameStartData &start_data) /* Pre-calculated values */ +#ifndef HAVE_TOUCHSCREENGUI video::ITexture *t = texture_src->getTexture("crack_anylength.png"); +#else + video::ITexture *t = texture_src->getTexture("crack_anylength_touch.png"); +#endif if (t) { v2u32 size = t->getOriginalSize(); crack_animation_length = size.Y / size.X; @@ -1392,10 +1431,11 @@ bool Game::createClient(const GameStartData &start_data) str += L"]"; delete[] text; } +#ifndef NDEBUG str += L" ["; str += driver->getName(); str += L"]"; - +#endif device->setWindowCaption(str.c_str()); LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -1414,7 +1454,7 @@ bool Game::createClient(const GameStartData &start_data) bool Game::initGui() { - m_game_ui->init(); + m_game_ui->init(client); // Remove stale "recent" chat messages from previous connections chat_backend->clearRecentChat(); @@ -1427,10 +1467,11 @@ bool Game::initGui() -1, chat_backend, client, &g_menumgr); #ifdef HAVE_TOUCHSCREENGUI - - if (g_touchscreengui) - g_touchscreengui->show(); - + if (g_touchscreengui) { + g_touchscreengui->init(texture_src); + if (g_touchscreengui->isActive()) + g_touchscreengui->show(); + } #endif return true; @@ -1544,7 +1585,8 @@ bool Game::connectToServer(const GameStartData &start_data, if (client->m_is_registration_confirmation_state) { if (registration_confirmation_shown) { // Keep drawing the GUI - m_rendering_engine->draw_menu_scene(guienv, dtime, true); + ITextureSource *tsrc = client->getTextureSource(); + m_rendering_engine->draw_menu_scene(guienv, tsrc, dtime); } else { registration_confirmation_shown = true; (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1, @@ -1554,7 +1596,7 @@ bool Game::connectToServer(const GameStartData &start_data, } else { wait_time += dtime; // Only time out if we aren't waiting for the server we started - if (!start_data.address.empty() && wait_time > 10) { + if (!start_data.address.empty() && wait_time > 15) { *error_message = gettext("Connection timed out."); errorstream << *error_message << std::endl; break; @@ -1676,6 +1718,9 @@ inline void Game::updateInteractTimers(f32 dtime) if (runData.object_hit_delay_timer >= 0) runData.object_hit_delay_timer -= dtime; + if (runData.noplace_delay_timer >= 0) + runData.noplace_delay_timer -= dtime; + runData.time_from_last_punch += dtime; } @@ -1705,9 +1750,12 @@ inline bool Game::handleCallbacks() } if (g_gamecallback->changepassword_requested) { - (new GUIPasswordChange(guienv, guiroot, -1, - &g_menumgr, client, texture_src))->drop(); + showChangePasswordDialog(g_gamecallback->old_pw_tmp, + g_gamecallback->new_pw_tmp, g_gamecallback->confirm_pw_tmp); g_gamecallback->changepassword_requested = false; + g_gamecallback->old_pw_tmp.clear(); + g_gamecallback->new_pw_tmp.clear(); + g_gamecallback->confirm_pw_tmp.clear(); } if (g_gamecallback->changevolume_requested) { @@ -1861,27 +1909,33 @@ void Game::processUserInput(f32 dtime) } #endif - if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) { - gui_chat_console->closeConsoleAtOnce(); + if (!gui_chat_console->hasFocus() && gui_chat_console->isOpen()) { + gui_chat_console->closeConsole(); } // Input handler step() (used by the random input generator) input->step(dtime); -#ifdef __ANDROID__ - auto formspec = m_game_ui->getFormspecGUI(); - if (formspec) - formspec->getAndroidUIInput(); - else - handleAndroidChatInput(); +#if defined(__ANDROID__) || defined(__IOS__) + if (!porting::hasRealKeyboard()) { + auto formspec = m_game_ui->getFormspecGUI(); + if (formspec) + formspec->getAndroidUIInput(); + else + handleAndroidChatInput(); + } #endif + bool doubletap_jump = m_cache_doubletap_jump; +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + doubletap_jump |= input->sdl_game_controller.isActive(); +#endif // Increase timer for double tap of "keymap_jump" - if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f) + if (doubletap_jump && runData.jump_timer <= 0.15f) runData.jump_timer += dtime; processKeyInput(); - processItemSelection(&runData.new_playeritem); + processItemSelection(dtime, &runData); } @@ -1897,19 +1951,24 @@ void Game::processKeyInput() } else if (wasKeyDown(KeyType::INVENTORY)) { openInventory(); } else if (input->cancelPressed()) { -#ifdef __ANDROID__ - m_android_chat_open = false; +#if defined(__ANDROID__) || defined(__IOS__) + gui_chat_console->setAndroidChatOpen(false); #endif if (!gui_chat_console->isOpenInhibited()) { showPauseMenu(); } } else if (wasKeyDown(KeyType::CHAT)) { - openConsole(0.2, L""); +#if defined(__ANDROID__) || defined(__IOS__) + if (isKeyDown(KeyType::SNEAK)) + m_game_ui->toggleChat(); + else + #endif + openConsole(core::clamp(g_settings->getFloat("console_message_height"), 0.1f, 1.0f), L""); } else if (wasKeyDown(KeyType::CMD)) { - openConsole(0.2, L"/"); + openConsole(core::clamp(g_settings->getFloat("console_message_height"), 0.1f, 1.0f), L"/"); } else if (wasKeyDown(KeyType::CMD_LOCAL)) { if (client->modsLoaded()) - openConsole(0.2, L"."); + openConsole(core::clamp(g_settings->getFloat("console_message_height"), 0.1f, 1.0f), L"."); else m_game_ui->showStatusText(wgettext("Client side scripting is disabled")); } else if (wasKeyDown(KeyType::CONSOLE)) { @@ -1917,6 +1976,11 @@ void Game::processKeyInput() } else if (wasKeyDown(KeyType::FREEMOVE)) { toggleFreeMove(); } else if (wasKeyDown(KeyType::JUMP)) { +#ifdef HAVE_TOUCHSCREENGUI + if (isKeyDown(KeyType::SNEAK) && client->checkPrivilege("fly")) + toggleFast(); + else + #endif toggleFreeMoveAlt(); } else if (wasKeyDown(KeyType::PITCHMOVE)) { togglePitchMove(); @@ -2007,13 +2071,18 @@ void Game::processKeyInput() } } -void Game::processItemSelection(u16 *new_playeritem) +void Game::processItemSelection(f32 dtime, GameRunData *run_data) { +#if defined(__MACH__) && defined(__APPLE__) + if (run_data->item_select_timer) + run_data->item_select_timer = MYMAX(0.0f, run_data->item_select_timer - dtime); +#endif + LocalPlayer *player = client->getEnv().getLocalPlayer(); /* Item selection using mouse wheel */ - *new_playeritem = player->getWieldIndex(); + run_data->new_playeritem = player->getWieldIndex(); s32 wheel = input->getMouseWheel(); u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1, @@ -2027,17 +2096,21 @@ void Game::processItemSelection(u16 *new_playeritem) if (wasKeyDown(KeyType::HOTBAR_PREV)) dir = 1; - if (dir < 0) - *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0; - else if (dir > 0) - *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item; - // else dir == 0 +#if defined(__MACH__) && defined(__APPLE__) + if (dir && !run_data->item_select_timer) { + run_data->item_select_timer = 0.05f; +#else + if (dir) { +#endif + run_data->new_playeritem += dir < 0 ? 1 : max_item; + run_data->new_playeritem %= max_item + 1; + } /* Item selection using hotbar slot keys */ for (u16 i = 0; i <= max_item; i++) { if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) { - *new_playeritem = i; + run_data->new_playeritem = i; break; } } @@ -2068,21 +2141,13 @@ void Game::openInventory() infostream << "Game: Launching inventory" << std::endl; - PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client); - InventoryLocation inventoryloc; inventoryloc.setCurrentPlayer(); - if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { - delete fs_src; - return; - } - - if (fs_src->getForm().empty()) { - delete fs_src; - return; - } + if (client->modsLoaded() && client->getScript()->on_inventory_open(client->getInventory(inventoryloc))) + return; // CSM prevented inventory opening + PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client); TextDest *txt_dst = new TextDestPlayerInventory(client); auto *&formspec = m_game_ui->updateFormspec(""); GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), @@ -2096,10 +2161,19 @@ void Game::openConsole(float scale, const wchar_t *line) { assert(scale > 0.0f && scale <= 1.0f); -#ifdef __ANDROID__ - porting::showInputDialog(gettext("ok"), "", "", 2); - m_android_chat_open = true; -#else + if (gui_chat_console->getAndroidChatOpen()) + return; + +#if defined(__ANDROID__) || defined(__IOS__) + if (!porting::hasRealKeyboard()) { + porting::showInputDialog("", "", 2); + gui_chat_console->setAndroidChatOpen(true); + } + + if (!g_settings->getBool("device_is_tablet")) + return; +#endif + if (gui_chat_console->isOpenInhibited()) return; gui_chat_console->openConsole(scale); @@ -2107,16 +2181,19 @@ void Game::openConsole(float scale, const wchar_t *line) gui_chat_console->setCloseOnEnter(true); gui_chat_console->replaceAndAddToHistory(line); } -#endif } -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) void Game::handleAndroidChatInput() { - if (m_android_chat_open && porting::getInputDialogState() == 0) { + if (gui_chat_console->getAndroidChatOpen() && + porting::getInputDialogState() == 0) { std::string text = porting::getInputDialogValue(); client->typeChatMessage(utf8_to_wide(text)); - m_android_chat_open = false; + gui_chat_console->setAndroidChatOpen(false); + if (!text.empty() && gui_chat_console->isOpen()) { + gui_chat_console->closeConsole(); + } } } #endif @@ -2140,7 +2217,13 @@ void Game::toggleFreeMove() void Game::toggleFreeMoveAlt() { - if (m_cache_doubletap_jump && runData.jump_timer < 0.2f) + bool doubletap_jump = m_cache_doubletap_jump; +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + doubletap_jump |= input->sdl_game_controller.isActive(); +#endif + + if (doubletap_jump && runData.jump_timer < 0.15f && + (!simple_singleplayer_mode || client->checkPrivilege("fly"))) toggleFreeMove(); runData.reset_jump_timer = true; @@ -2175,10 +2258,6 @@ void Game::toggleFast() } else { m_game_ui->showTranslatedStatusText("Fast mode disabled"); } - -#ifdef HAVE_TOUCHSCREENGUI - m_cache_hold_aux1 = fast_move && has_fast_privs; -#endif } @@ -2264,7 +2343,7 @@ void Game::toggleMinimap(bool shift_pressed) u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags; if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) { - m_game_ui->m_flags.show_minimap = false; + m_game_ui->showMinimap(false); } else { // If radar is disabled, try to find a non radar mode or fall back to 0 @@ -2273,8 +2352,7 @@ void Game::toggleMinimap(bool shift_pressed) mapper->getModeDef().type == MINIMAP_TYPE_RADAR) mapper->nextMode(); - m_game_ui->m_flags.show_minimap = mapper->getModeDef().type != - MINIMAP_TYPE_OFF; + m_game_ui->showMinimap(mapper->getModeDef().type != MINIMAP_TYPE_OFF); } // <-- // End of 'not so satifying code' @@ -2339,6 +2417,9 @@ void Game::toggleDebug() m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden"); } } + + // Update the chat text as it may need changing because of rounded screens + m_game_ui->m_chat_text_needs_update = true; } @@ -2388,8 +2469,13 @@ void Game::decreaseViewRange() void Game::toggleFullViewRange() { +#if !defined(__ANDROID__) && !defined(__IOS__) draw_control->range_all = !draw_control->range_all; if (draw_control->range_all) +#else + draw_control->extended_range = !draw_control->extended_range; + if (draw_control->extended_range) +#endif m_game_ui->showTranslatedStatusText("Enabled unlimited viewing range"); else m_game_ui->showTranslatedStatusText("Disabled unlimited viewing range"); @@ -2408,13 +2494,9 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime) if ((device->isWindowActive() && device->isWindowFocused() && !isMenuActive()) || input->isRandom()) { -#ifndef __ANDROID__ if (!input->isRandom()) { - // Mac OSX gets upset if this is set every frame - if (device->getCursorControl()->isVisible()) - device->getCursorControl()->setVisible(false); + input->setCursorVisible(false); } -#endif if (m_first_loop_after_window_activation) { m_first_loop_after_window_activation = false; @@ -2427,12 +2509,7 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime) } else { -#ifndef ANDROID - // Mac OSX gets upset if this is set every frame - if (!device->getCursorControl()->isVisible()) - device->getCursorControl()->setVisible(true); -#endif - + input->setCursorVisible(true); m_first_loop_after_window_activation = true; } @@ -2455,9 +2532,10 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) #ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) { cam->camera_yaw += g_touchscreengui->getYawChange(); - cam->camera_pitch = g_touchscreengui->getPitch(); - } else { + cam->camera_pitch += g_touchscreengui->getPitchChange(); + } #endif + { v2s32 center(driver->getScreenSize().Width / 2, driver->getScreenSize().Height / 2); v2s32 dist = input->getMousePos() - center; @@ -2471,15 +2549,18 @@ 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(); f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale; +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + cam->camera_yaw -= input->sdl_game_controller.getCameraYaw() * c; + cam->camera_pitch += input->sdl_game_controller.getCameraPitch() * c; +#else cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c; cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c; +#endif } cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5); @@ -2505,8 +2586,13 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::PLACE), cam.camera_pitch, cam.camera_yaw, +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + input->sdl_game_controller.getMoveSideward(), + input->sdl_game_controller.getMoveForward() +#else input->getMovementSpeed(), input->getMovementDirection() +#endif ); // autoforward if set: move towards pointed position at maximum speed @@ -2516,17 +2602,6 @@ void Game::updatePlayerControl(const CameraOrientation &cam) control.movement_direction = 0.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) { - control.aux1 = control.aux1 ^ true; - } -#endif - client->setPlayerControl(control); //tt.stop(); @@ -2931,6 +3006,13 @@ void Game::updateChat(f32 dtime) if (buf.getLinesModified()) { buf.resetLinesModified(); m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount()); + gui_chat_console->onLinesModified(); + } + + auto &prompt = chat_backend->getPrompt(); + if (prompt.getLineModified()) { + prompt.resetLineModified(); + gui_chat_console->onPromptModified(); } // Make sure that the size is still correct @@ -3077,7 +3159,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) #ifdef HAVE_TOUCHSCREENGUI - if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { + if (g_touchscreengui && g_touchscreengui->isActive() && m_cache_touchtarget) { shootline = g_touchscreengui->getShootline(); // Scale shootline to the acual distance the player can reach shootline.end = shootline.start @@ -3302,10 +3384,13 @@ void Game::handlePointingAtNode(const PointedThing &pointed, ClientMap &map = client->getEnv().getClientMap(); + bool digging = false; if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG) && !runData.digging_blocked && client->checkPrivilege("interact")) { handleDigging(pointed, nodepos, selected_item, hand_item, dtime); + digging = true; + runData.noplace_delay_timer = 1.0; } // This should be done after digging handling @@ -3323,7 +3408,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, } if ((wasKeyPressed(KeyType::PLACE) || - runData.repeat_place_timer >= m_repeat_place_time) && + runData.repeat_place_timer >= m_repeat_place_time) && !digging && client->checkPrivilege("interact")) { runData.repeat_place_timer = 0; infostream << "Place button pressed while looking at ground" << std::endl; @@ -3556,7 +3641,21 @@ void Game::handlePointingAtObject(const PointedThing &pointed, m_game_ui->setInfoText(infotext); - if (isKeyDown(KeyType::DIG)) { + const ItemDefinition &playeritem_def = + tool_item.getDefinition(itemdef_manager); + bool nohit_enabled = ((ItemGroupList) playeritem_def.groups)["nohit"] != 0; + + bool should_punch = isKeyDown(KeyType::DIG) && !nohit_enabled; + bool should_interact = wasKeyPressed(KeyType::PLACE) || ((wasKeyPressed(KeyType::DIG) && nohit_enabled)); + +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui->isActive()) { + should_punch = wasKeyPressed(KeyType::PLACE) && !nohit_enabled; + should_interact = wasKeyPressed(KeyType::DIG) || ((wasKeyPressed(KeyType::PLACE) && nohit_enabled)); + } +#endif + + if (should_punch) { bool do_punch = false; bool do_punch_damage = false; @@ -3586,8 +3685,8 @@ void Game::handlePointingAtObject(const PointedThing &pointed, if (!disable_send) client->interact(INTERACT_START_DIGGING, pointed); } - } else if (wasKeyDown(KeyType::PLACE)) { - infostream << "Pressed place button while pointing at object" << std::endl; + } else if (should_interact) { + infostream << "Right-clicked object" << std::endl; client->interact(INTERACT_PLACE, pointed); // place } } @@ -3669,6 +3768,8 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, client->setCrack(runData.dig_index, nodepos); } else { infostream << "Digging completed" << std::endl; + runData.noplace_delay_timer = 1.0; + client->interact(INTERACT_DIGGING_COMPLETED, pointed); client->setCrack(-1, v3s16(0, 0, 0)); runData.dig_time = 0; @@ -3741,11 +3842,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, Fog range */ - if (draw_control->range_all) { - runData.fog_range = 100000 * BS; - } else { - runData.fog_range = draw_control->wanted_range * BS; - } + runData.disable_fog = !m_cache_enable_fog || m_flags.force_fog_off || draw_control->range_all; + runData.fog_range = draw_control->wanted_range * BS; /* Calculate general brightness @@ -3830,27 +3928,15 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, Fog */ - if (m_cache_enable_fog) { - driver->setFog( - sky->getBgColor(), - video::EFT_FOG_LINEAR, - runData.fog_range * m_cache_fog_start, - runData.fog_range * 1.0, - 0.01, - false, // pixel fog - true // range fog - ); - } else { - driver->setFog( - sky->getBgColor(), - video::EFT_FOG_LINEAR, - 100000 * BS, - 110000 * BS, - 0.01f, - false, // pixel fog - false // range fog - ); - } + driver->setFog( + sky->getBgColor(), + video::EFT_FOG_LINEAR, + runData.fog_range * m_cache_fog_start, + runData.fog_range * 1.0, + 0.01, + false, // pixel fog + true // range fog + ); /* Damage camera tilt @@ -3887,7 +3973,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, // Update wielded tool ItemStack selected_item, hand_item; ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item); - camera->wield(tool_item); + const ItemDefinition &item_def = tool_item.getDefinition(itemdef_manager); + camera->wield(tool_item, itemgroup_get(item_def.groups, "no_change_anim") > 0); } /* @@ -3957,14 +4044,32 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT)); #ifdef HAVE_TOUCHSCREENGUI - try { - draw_crosshair = !g_settings->getBool("touchtarget"); - } catch (SettingNotFoundException) { - } + draw_crosshair = !m_cache_touchtarget || !g_touchscreengui->isActive(); #endif + + video::SOverrideMaterial &mat = driver->getOverrideMaterial(); +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 + mat.EnableFlags = 0; +#else + mat.reset(); +#endif + if (runData.disable_fog) { + mat.Material.FogEnable = false; + mat.EnableFlags |= video::EMF_FOG_ENABLE; + mat.EnablePasses = scene::ESNRP_SKY_BOX | scene::ESNRP_SOLID | + scene::ESNRP_TRANSPARENT | scene::ESNRP_TRANSPARENT_EFFECT | + scene::ESNRP_SHADOW; + } + m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud, m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair); +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 + mat.EnableFlags = 0; +#else + mat.reset(); +#endif + /* Profiler graph */ @@ -4072,6 +4177,15 @@ void FpsControl::limit(IrrlichtDevice *device, f32 *dtime) device->isWindowFocused() && !g_menumgr.pausesGame() ? g_settings->getFloat("fps_max") : g_settings->getFloat("fps_max_unfocused")); +#if defined(__ANDROID__) || defined(__IOS__) + if (g_menumgr.pausesGame() && !device->isWindowFocused()) + frametime_min = 1000; +#endif +#if defined(__MACH__) && defined(__APPLE__) && !defined(__IOS__) + // FPS limiting causes freezes on macOS + if (!g_menumgr.pausesGame()) + frametime_min = 0; +#endif u64 time = porting::getTimeUs(); @@ -4098,6 +4212,20 @@ void FpsControl::limit(IrrlichtDevice *device, f32 *dtime) *dtime = 0; last_time = time; + +#if defined(__ANDROID__) || defined(__IOS__) + if (g_menumgr.pausesGame()) { + runData.pause_game_timer += *dtime; + float disconnect_time = 180.0f; +#ifdef __IOS__ + disconnect_time = simple_singleplayer_mode ? 60.0f : 120.0f; +#endif + if (runData.pause_game_timer > disconnect_time) { + g_gamecallback->disconnect(); + return; + } + } +#endif } void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds) @@ -4129,6 +4257,10 @@ void Game::readSettings() m_cache_fog_start = g_settings->getFloat("fog_start"); +#ifdef HAVE_TOUCHSCREENGUI + m_cache_touchtarget = g_settings->getBool("touchtarget"); +#endif + m_cache_cam_smoothing = 0; if (g_settings->getBool("cinematic")) m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing"); @@ -4142,6 +4274,19 @@ void Game::readSettings() m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus"); } +#if defined(__ANDROID__) || defined(__IOS__) +void Game::pauseGame() +{ + if (g_menumgr.pausesGame() || !hud) + return; +#ifdef HAVE_TOUCHSCREENGUI + g_touchscreengui->handleReleaseAll(); +#endif + showPauseMenu(); + runData.pause_game_timer = 0; +} +#endif + /****************************************************************************/ /**************************************************************************** Shutdown / cleanup @@ -4173,7 +4318,7 @@ void Game::showDeathFormspec() #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name()) void Game::showPauseMenu() { -#ifdef HAVE_TOUCHSCREENGUI +/*#ifdef HAVE_TOUCHSCREENGUI static const std::string control_text = strgettext("Default Controls:\n" "No menu visible:\n" "- single tap: button activate\n" @@ -4222,37 +4367,56 @@ void Game::showPauseMenu() std::string control_text = std::string(control_text_buf); str_formspec_escape(control_text); -#endif +#endif*/ float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; +#if defined(__ANDROID__) || defined(__IOS__) + bool hasRealKeyboard = porting::hasRealKeyboard(); + if (simple_singleplayer_mode && hasRealKeyboard) + ypos -= 0.6f; +#endif +#ifdef __IOS__ + ypos += 0.5f; +#endif + const bool high_dpi = RenderingEngine::isHighDpi(); + const std::string x2 = high_dpi ? ".x2" : ""; std::ostringstream os; os << "formspec_version[1]" << SIZE_TAG - << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" - << strgettext("Continue") << "]"; + << "no_prepend[]" + << "bgcolor[#00000060;true]" + + << "style_type[image_button_exit,image_button;bgimg=gui/gui_button" << x2 << + ".png;bgimg_middle=" << (high_dpi ? "48" : "32") << ";padding=" << (high_dpi ? "-30" : "-20") << "]" + << "style_type[image_button_exit,image_button:hovered;bgimg=gui/gui_button_hovered" << x2 << ".png]" + << "style_type[image_button_exit,image_button:pressed;bgimg=gui/gui_button_pressed" << x2 << ".png]" + + << "image_button_exit[3.5," << (ypos++) << ";4,0.9;;btn_continue;" + << strgettext("Continue") << ";;false]"; if (!simple_singleplayer_mode) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" - << strgettext("Change Password") << "]"; - } else { - os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; + os << "image_button[3.5," << (ypos++) << ";4,0.9;;btn_change_password;" + << strgettext("Change Password") << ";;false]"; } -#ifndef __ANDROID__ #if USE_SOUND if (g_settings->getBool("enable_sound")) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" - << strgettext("Sound Volume") << "]"; + os << "image_button_exit[3.5," << (ypos++) << ";4,0.9;;btn_sound;" + << strgettext("Sound Volume") << ";;false]"; } #endif - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" - << strgettext("Change Keys") << "]"; +#if defined(__ANDROID__) || defined(__IOS__) + if (hasRealKeyboard) #endif - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" - << strgettext("Exit to Menu") << "]"; - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" - << strgettext("Exit to OS") << "]" - << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" + os << "image_button_exit[3.5," << (ypos++) << ";4,0.9;;btn_key_config;" + << strgettext("Change Keys") << ";;false]"; + os << "image_button_exit[3.5," << (ypos++) << ";4,0.9;;btn_exit_menu;" + << strgettext("Exit to Menu") << ";;false]"; +#if !defined(__ANDROID__) && !defined(__IOS__) + os << "image_button_exit[3.5," << (ypos++) << ";4,0.9;;btn_exit_os;" + << strgettext("Exit to OS") << ";;false]"; +#endif +/* << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" << "\n" << strgettext("Game info:") << "\n"; @@ -4292,7 +4456,7 @@ void Game::showPauseMenu() } } - os << ";]"; + os << ";]"*/; /* Create menu */ /* Note: FormspecFormSource and LocalFormspecHandler * @@ -4306,16 +4470,58 @@ void Game::showPauseMenu() formspec->setFocus("btn_continue"); formspec->doPause = true; + runData.pause_game_timer = 0; if (simple_singleplayer_mode) pauseAnimation(); } +void Game::showChangePasswordDialog(std::string old_pw, std::string new_pw, + std::string confirm_pw) +{ + str_formspec_escape(old_pw); + str_formspec_escape(new_pw); + str_formspec_escape(confirm_pw); + + const bool high_dpi = RenderingEngine::isHighDpi(); + const std::string x2 = high_dpi ? ".x2" : ""; + std::ostringstream os; + os << "formspec_version[5]" + << "size[10.5,7.5]" + << "no_prepend[]" + << "bgcolor[#320000b4;true]" + << "background9[0,0;0,0;bg_common.png;true;40]" + << "pwdfield[1,1.2;8.5,0.8;old_pw;" << strgettext("Old Password") << ":;" << old_pw << "]" + << "pwdfield[1,2.8;8.5,0.8;new_pw;" << strgettext("New Password") << ":;" << new_pw << "]" + << "pwdfield[1,4.4;8.5,0.8;confirm_pw;" << strgettext("Confirm Password") << ":;" << confirm_pw << "]" + << "style_type[image_button_exit,image_button;bgimg=gui/gui_button" << x2 + << ".png;bgimg_middle=" << (high_dpi ? "48" : "32") << ";padding=" << (high_dpi ? "-30" : "-20") << "]" + << "style_type[image_button_exit,image_button:hovered;bgimg=gui/gui_button_hovered" << x2 << ".png]" + << "style_type[image_button_exit,image_button:pressed;bgimg=gui/gui_button_pressed" << x2 << ".png]" + << "image_button[1,5.9;4.1,0.8;;btn_change_pw;" << strgettext("Change") << ";;false]" + << "image_button_exit[5.4,5.9;4.1,0.8;;btn_cancel;" << strgettext("Cancel") << ";;false]"; + + if (new_pw != confirm_pw) + os << "label[1,7.1;\x1b(c@red)" << strgettext("Passwords do not match!") << "]"; + + /* Create menu */ + /* Note: FormspecFormSource and LocalFormspecHandler * + * are deleted by guiFormSpecMenu */ + FormspecFormSource *fs_src = new FormspecFormSource(os.str()); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_CHANGE_PW", client); + + auto *&formspec = m_game_ui->getFormspecGUI(); + GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), + &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound); +} + /****************************************************************************/ /**************************************************************************** extern function for launching the game ****************************************************************************/ /****************************************************************************/ +static Game *g_game = NULL; + void the_game(bool *kill, InputHandler *input, RenderingEngine *rendering_engine, @@ -4325,6 +4531,7 @@ void the_game(bool *kill, bool *reconnect_requested) // Used for local game { Game game; + g_game = &game; /* Make a copy of the server address because if a local singleplayer server * is created then this is updated and we don't want to change the value @@ -4346,11 +4553,27 @@ void the_game(bool *kill, } catch (ServerError &e) { error_message = e.what(); errorstream << "ServerError: " << error_message << std::endl; +#ifdef __ANDROID__ + porting::handleError("ServerError", error_message); +#endif } catch (ModError &e) { // DO NOT TRANSLATE the `ModError`, it's used by ui.lua error_message = std::string("ModError: ") + e.what() + strgettext("\nCheck debug.txt for details."); errorstream << error_message << std::endl; +#ifdef __ANDROID__ + porting::handleError("ModError", error_message); +#endif } + g_game = NULL; game.shutdown(); } + +#if defined(__ANDROID__) || defined(__IOS__) +extern "C" void external_pause_game() +{ + if (!g_game) + return; + g_game->pauseGame(); +} +#endif diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 706a22330..974ccaf65 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -51,7 +51,7 @@ GameUI::GameUI() m_statustext_initial_color = video::SColor(255, 0, 0, 0); } -void GameUI::init() +void GameUI::init(Client *client) { // First line of debug text m_guitext = gui::StaticText::add(guienv, utf8_to_wide(PROJECT_NAME_C).c_str(), @@ -76,11 +76,15 @@ void GameUI::init() // If in debug mode, object debug infos shown here, too. // Located on the left on the screen, below chat. u32 chat_font_height = m_guitext_chat->getActiveFont()->getDimension(L"Ay").Height; + float scale = 1.0f; +#if defined(__ANDROID__) || defined(__APPLE__) + scale = RenderingEngine::getDisplayDensity() * client->getHudScaling() * 0.5f; +#endif m_guitext_info = gui::StaticText::add(guienv, L"", // Size is limited; text will be truncated after 6 lines. core::rect(0, 0, 400, g_fontengine->getTextHeight() * 6) + - v2s32(100, chat_font_height * - (g_settings->getU16("recent_chat_messages") + 3)), + v2s32(100 + client->getRoundScreen(), + chat_font_height * (g_settings->getU16("recent_chat_messages") + 3) * scale), false, true, guiroot); // Status text (displays info when showing and hiding GUI stuff, etc.) @@ -101,6 +105,10 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ const GUIChatConsole *chat_console, float dtime) { v2u32 screensize = RenderingEngine::getWindowSize(); + LocalPlayer *player = client->getEnv().getLocalPlayer(); + v3f player_position = player->getPosition(); + + std::ostringstream os(std::ios_base::binary); // Minimal debug text must only contain info that can't give a gameplay advantage if (m_flags.show_minimal_debug) { @@ -108,7 +116,6 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ m_drawtime_avg *= 0.95f; m_drawtime_avg += 0.05f * (stats.drawtime / 1000); - std::ostringstream os(std::ios_base::binary); os << std::fixed << PROJECT_NAME_C " " << g_version_hash << " | FPS: " << fps @@ -122,26 +129,29 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ << (draw_control->range_all ? "All" : itos(draw_control->wanted_range)) << std::setprecision(2) << " | RTT: " << (client->getRTT() * 1000.0f) << "ms"; - setStaticText(m_guitext, utf8_to_wide(os.str()).c_str()); + } else if (m_flags.show_minimap) { + os << std::setprecision(1) << std::fixed + << "X: " << (player_position.X / BS) + << ", Y: " << (player_position.Y / BS) + << ", Z: " << (player_position.Z / BS); + } + m_guitext->setText(utf8_to_wide(os.str()).c_str()); - m_guitext->setRelativePosition(core::rect(5, 5, screensize.X, - 5 + g_fontengine->getTextHeight())); - } + m_guitext->setRelativePosition(core::rect( + 5 + client->getRoundScreen(), 5, + screensize.X, 5 + g_fontengine->getTextHeight())); // Finally set the guitext visible depending on the flag - m_guitext->setVisible(m_flags.show_minimal_debug); + m_guitext->setVisible(m_flags.show_hud && (m_flags.show_minimal_debug || m_flags.show_minimap)); // Basic debug text also shows info that might give a gameplay advantage if (m_flags.show_basic_debug) { - LocalPlayer *player = client->getEnv().getLocalPlayer(); - v3f player_position = player->getPosition(); - std::ostringstream os(std::ios_base::binary); os << std::setprecision(1) << std::fixed - << "pos: (" << (player_position.X / BS) - << ", " << (player_position.Y / BS) - << ", " << (player_position.Z / BS) - << ") | yaw: " << (wrapDegrees_0_360(cam.camera_yaw)) << "° " + << "X: " << (player_position.X / BS) + << ", Y: " << (player_position.Y / BS) + << ", Z: " << (player_position.Z / BS) + << " | yaw: " << (wrapDegrees_0_360(cam.camera_yaw)) << "° " << yawToDirectionString(cam.camera_yaw) << " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "°" << " | seed: " << ((u64)client->getMapSeed()); @@ -165,7 +175,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ )); } - m_guitext2->setVisible(m_flags.show_basic_debug); + m_guitext2->setVisible(m_flags.show_basic_debug && m_flags.show_hud); setStaticText(m_guitext_info, m_infotext.c_str()); m_guitext_info->setVisible(m_flags.show_hud && g_menumgr.menuCount() == 0); @@ -187,7 +197,10 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ if (!m_statustext.empty()) { s32 status_width = m_guitext_status->getTextWidth(); s32 status_height = m_guitext_status->getTextHeight(); - s32 status_y = screensize.Y - 150; + + s32 status_y = screensize.Y - + 150 * RenderingEngine::getDisplayDensity() * client->getHudScaling(); + s32 status_x = (screensize.X - status_width) / 2; m_guitext_status->setRelativePosition(core::rect(status_x , @@ -202,6 +215,30 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ m_guitext_status->enableOverrideColor(true); } + // Update chat text + if (m_chat_text_needs_update) { + m_chat_text_needs_update = false; + if ((!m_flags.show_hud || (!m_flags.show_minimal_debug && !m_flags.show_minimap)) && + client->getRoundScreen() > 0) { + // Cache the space count + if (!m_space_count) { + // Use spaces to shift the text + const u32 spwidth = g_fontengine->getFont()->getDimension(L" ").Width; + // Divide and round up + m_space_count = (client->getRoundScreen() + spwidth - 1) / spwidth; + } + + EnrichedString padded_chat_text; + for (int i = 0; i < m_space_count; i++) + padded_chat_text.addCharNoColor(L' '); + + padded_chat_text += m_chat_text; + setStaticText(m_guitext_chat, padded_chat_text); + } else { + setStaticText(m_guitext_chat, m_chat_text); + } + } + // Hide chat when console is visible m_guitext_chat->setVisible(isChatVisible() && !chat_console->isVisible()); } @@ -214,6 +251,7 @@ void GameUI::initFlags() void GameUI::showMinimap(bool show) { + m_chat_text_needs_update = m_chat_text_needs_update || show != m_flags.show_minimap; m_flags.show_minimap = show; } @@ -226,8 +264,8 @@ void GameUI::showTranslatedStatusText(const char *str) void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count) { - setStaticText(m_guitext_chat, chat_text); - + m_chat_text = chat_text; + m_chat_text_needs_update = true; m_recent_chat_count = recent_chat_count; } @@ -236,10 +274,12 @@ void GameUI::updateChatSize() // Update gui element size and position s32 chat_y = 5; - if (m_flags.show_minimal_debug) - chat_y += g_fontengine->getLineHeight(); - if (m_flags.show_basic_debug) - chat_y += g_fontengine->getLineHeight(); + if (m_flags.show_hud) { + if (m_flags.show_minimal_debug) + chat_y += g_fontengine->getLineHeight() * 2; + else if (m_flags.show_minimap) + chat_y += g_fontengine->getLineHeight(); + } const v2u32 &window_size = RenderingEngine::getWindowSize(); @@ -297,6 +337,7 @@ void GameUI::toggleHud() showTranslatedStatusText("HUD shown"); else showTranslatedStatusText("HUD hidden"); + m_chat_text_needs_update = true; } void GameUI::toggleProfiler() diff --git a/src/client/gameui.h b/src/client/gameui.h index 2dbbf4622..8c29c8c37 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -63,7 +63,7 @@ public: bool show_profiler_graph = false; }; - void init(); + void init(Client *client); void update(const RunStats &stats, Client *client, MapDrawControl *draw_control, const CameraOrientation &cam, const PointedThing &pointed_old, const GUIChatConsole *chat_console, float dtime); @@ -124,6 +124,9 @@ private: video::SColor m_statustext_initial_color; gui::IGUIStaticText *m_guitext_chat = nullptr; // Chat text + EnrichedString m_chat_text; + bool m_chat_text_needs_update = false; + int m_space_count = 0; u32 m_recent_chat_count = 0; core::rect m_current_chat_size{0, 0, 0, 0}; diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp index 965382db6..6c7dc0674 100644 --- a/src/client/guiscalingfilter.cpp +++ b/src/client/guiscalingfilter.cpp @@ -170,52 +170,61 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, } void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, - const core::rect &rect, const core::rect &middle, - const core::rect *cliprect, const video::SColor *const colors) + const core::rect &destrect, const core::rect &srcrect, + const core::rect &middlerect, const core::rect *cliprect, + const video::SColor *const colors) { - auto originalSize = texture->getOriginalSize(); - core::vector2di lowerRightOffset = core::vector2di(originalSize.Width, originalSize.Height) - middle.LowerRightCorner; + // `-x` is interpreted as `w - x` + core::rect middle = middlerect; + + if (middlerect.LowerRightCorner.X < 0) + middle.LowerRightCorner.X += srcrect.getWidth(); + if (middlerect.LowerRightCorner.Y < 0) + middle.LowerRightCorner.Y += srcrect.getHeight(); + + core::vector2di lower_right_offset = core::vector2di(srcrect.getWidth(), + srcrect.getHeight()) - middle.LowerRightCorner; for (int y = 0; y < 3; ++y) { for (int x = 0; x < 3; ++x) { - core::rect src({0, 0}, originalSize); - core::rect dest = rect; + core::rect src = srcrect; + core::rect dest = destrect; switch (x) { case 0: - dest.LowerRightCorner.X = rect.UpperLeftCorner.X + middle.UpperLeftCorner.X; - src.LowerRightCorner.X = middle.UpperLeftCorner.X; + dest.LowerRightCorner.X = destrect.UpperLeftCorner.X + middle.UpperLeftCorner.X; + src.LowerRightCorner.X = srcrect.UpperLeftCorner.X + middle.UpperLeftCorner.X; break; case 1: dest.UpperLeftCorner.X += middle.UpperLeftCorner.X; - dest.LowerRightCorner.X -= lowerRightOffset.X; - src.UpperLeftCorner.X = middle.UpperLeftCorner.X; - src.LowerRightCorner.X = middle.LowerRightCorner.X; + dest.LowerRightCorner.X -= lower_right_offset.X; + src.UpperLeftCorner.X += middle.UpperLeftCorner.X; + src.LowerRightCorner.X -= lower_right_offset.X; break; case 2: - dest.UpperLeftCorner.X = rect.LowerRightCorner.X - lowerRightOffset.X; - src.UpperLeftCorner.X = middle.LowerRightCorner.X; + dest.UpperLeftCorner.X = destrect.LowerRightCorner.X - lower_right_offset.X; + src.UpperLeftCorner.X = srcrect.LowerRightCorner.X - lower_right_offset.X; break; } switch (y) { case 0: - dest.LowerRightCorner.Y = rect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y; - src.LowerRightCorner.Y = middle.UpperLeftCorner.Y; + dest.LowerRightCorner.Y = destrect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y; + src.LowerRightCorner.Y = srcrect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y; break; case 1: dest.UpperLeftCorner.Y += middle.UpperLeftCorner.Y; - dest.LowerRightCorner.Y -= lowerRightOffset.Y; - src.UpperLeftCorner.Y = middle.UpperLeftCorner.Y; - src.LowerRightCorner.Y = middle.LowerRightCorner.Y; + dest.LowerRightCorner.Y -= lower_right_offset.Y; + src.UpperLeftCorner.Y += middle.UpperLeftCorner.Y; + src.LowerRightCorner.Y -= lower_right_offset.Y; break; case 2: - dest.UpperLeftCorner.Y = rect.LowerRightCorner.Y - lowerRightOffset.Y; - src.UpperLeftCorner.Y = middle.LowerRightCorner.Y; + dest.UpperLeftCorner.Y = destrect.LowerRightCorner.Y - lower_right_offset.Y; + src.UpperLeftCorner.Y = srcrect.LowerRightCorner.Y - lower_right_offset.Y; break; } diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h index 520872a88..5ff8d0932 100644 --- a/src/client/guiscalingfilter.h +++ b/src/client/guiscalingfilter.h @@ -46,13 +46,13 @@ video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::IText */ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, const core::rect &destrect, const core::rect &srcrect, - const core::rect *cliprect = 0, const video::SColor *const colors = 0, - bool usealpha = false); + const core::rect *cliprect = nullptr, + const video::SColor *const colors = nullptr, bool usealpha = false); /* * 9-slice / segment drawing */ void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture, - const core::rect &rect, const core::rect &middle, - const core::rect *cliprect = nullptr, + const core::rect &destrect, const core::rect &srcrect, + const core::rect &middlerect, const core::rect *cliprect = nullptr, const video::SColor *const colors = nullptr); diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 6395ae5f4..c02c16927 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -44,8 +44,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/touchscreengui.h" #endif -#define OBJECT_CROSSHAIR_LINE_SIZE 8 -#define CROSSHAIR_LINE_SIZE 10 +#define OBJECT_CROSSHAIR_LINE_SIZE 16 +#define CROSSHAIR_LINE_SIZE 16 Hud::Hud(Client *client, LocalPlayer *player, Inventory *inventory) @@ -61,6 +61,7 @@ Hud::Hud(Client *client, LocalPlayer *player, RenderingEngine::getDisplayDensity() + 0.5f); m_hotbar_imagesize *= m_hud_scaling; m_padding = m_hotbar_imagesize / 12; + m_hud_move_upwards = g_settings->getU16("hud_move_upwards"); for (auto &hbar_color : hbar_colors) hbar_color = video::SColor(255, 255, 255, 255); @@ -109,7 +110,7 @@ Hud::Hud(Client *client, LocalPlayer *player, if (m_mode == HIGHLIGHT_BOX) { m_selection_material.Thickness = - rangelim(g_settings->getS16("selectionbox_width"), 1, 5); + rangelim(g_settings->getS16("selectionbox_width"), 1, 6); } else if (m_mode == HIGHLIGHT_HALO) { m_selection_material.setTexture(0, tsrc->getTextureForMesh("halo.png")); m_selection_material.setFlag(video::EMF_BACK_FACE_CULLING, true); @@ -360,10 +361,12 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) floor(e->pos.Y * (float) m_screensize.Y + 0.5)); switch (e->type) { case HUD_ELEM_TEXT: { - unsigned int font_size = g_fontengine->getDefaultFontSize(); + float font_size = g_fontengine->getDefaultFontSize(); if (e->size.X > 0) font_size *= e->size.X; + else if (e->size.Y > 0) + font_size = MYMAX(font_size * (float) e->size.Y / 100, 1.0f); #ifdef __ANDROID__ // The text size on Android is not proportional with the actual scaling @@ -448,14 +451,19 @@ void Hud::drawLuaElements(const v3s16 &camera_offset) const video::SColor color(255, 255, 255, 255); const video::SColor colors[] = {color, color, color, color}; core::dimension2di imgsize(texture->getOriginalSize()); - v2s32 dstsize(imgsize.Width * e->scale.X * m_scale_factor, - imgsize.Height * e->scale.Y * m_scale_factor); + v2s32 dstsize(imgsize.Width * e->scale.X, + imgsize.Height * e->scale.Y); if (e->scale.X < 0) dstsize.X = m_screensize.X * (e->scale.X * -0.01); if (e->scale.Y < 0) dstsize.Y = m_screensize.Y * (e->scale.Y * -0.01); + dstsize.X *= m_scale_factor; + dstsize.Y *= m_scale_factor; v2s32 offset((e->align.X - 1.0) * dstsize.X / 2, (e->align.Y - 1.0) * dstsize.Y / 2); + + if ((dstsize.Y + pos.Y + offset.Y + e->offset.Y * m_scale_factor) > m_displaycenter.Y) + offset.Y -= m_hud_move_upwards; core::rect rect(0, 0, dstsize.X, dstsize.Y); rect += pos + offset + v2s32(e->offset.X * m_scale_factor, e->offset.Y * m_scale_factor); @@ -619,26 +627,29 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, } core::dimension2di srcd(stat_texture->getOriginalSize()); + core::dimension2di srcd_bg = stat_texture_bg ? core::dimension2di(stat_texture_bg->getOriginalSize()) : srcd; core::dimension2di dstd; if (size == v2s32()) { dstd = srcd; dstd.Height *= m_scale_factor; dstd.Width *= m_scale_factor; - offset.X *= m_scale_factor; - offset.Y *= m_scale_factor; } else { dstd.Height = size.Y * m_scale_factor; dstd.Width = size.X * m_scale_factor; - offset.X *= m_scale_factor; - offset.Y *= m_scale_factor; } + offset.X *= m_scale_factor; + offset.Y *= m_scale_factor; + v2s32 p = pos; if (corner & HUD_CORNER_LOWER) p -= dstd.Height; p += offset; + if ((pos.Y + offset.Y) > m_displaycenter.Y) + p.Y -= m_hud_move_upwards; + v2s32 steppos; switch (drawdir) { case HUD_DIR_RIGHT_LEFT: @@ -680,7 +691,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, // Need to draw halves: Calculate rectangles srchalfrect = calculate_clipping_rect(srcd, steppos); dsthalfrect = calculate_clipping_rect(dstd, steppos); - srchalfrect2 = calculate_clipping_rect(srcd, steppos * -1); + srchalfrect2 = calculate_clipping_rect(srcd_bg, steppos * -1); dsthalfrect2 = calculate_clipping_rect(dstd, steppos * -1); } @@ -719,7 +730,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, else start_offset = count / 2; for (s32 i = start_offset; i < maxcount / 2; i++) { - core::rect srcrect(0, 0, srcd.Width, srcd.Height); + core::rect srcrect(0, 0, srcd_bg.Width, srcd_bg.Height); core::rect dstrect(0, 0, dstd.Width, dstd.Height); dstrect += p; @@ -731,7 +742,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, if (maxcount % 2 == 1) { draw2DImageFilterScaled(driver, stat_texture_bg, - dsthalfrect + p, srchalfrect, + dsthalfrect + p, calculate_clipping_rect(srcd_bg, steppos), NULL, colors, true); } } @@ -750,7 +761,8 @@ void Hud::drawHotbar(u16 playeritem) { s32 hotbar_itemcount = player->hud_hotbar_itemcount; s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2); - v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3); + v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 2.4); + pos.Y -= m_hud_move_upwards; const v2u32 &window_size = RenderingEngine::getWindowSize(); if ((float) width / (float) window_size.X <= @@ -777,43 +789,49 @@ void Hud::drawHotbar(u16 playeritem) { void Hud::drawCrosshair() { if (pointing_at_object) { + int obj_crosshair_size = (int) (OBJECT_CROSSHAIR_LINE_SIZE * m_scale_factor); if (use_object_crosshair_image) { video::ITexture *object_crosshair = tsrc->getTexture("object_crosshair.png"); - v2u32 size = object_crosshair->getOriginalSize(); - v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2), - m_displaycenter.Y - (size.Y / 2)); - driver->draw2DImage(object_crosshair, lsize, - core::rect(0, 0, size.X, size.Y), - nullptr, crosshair_argb, true); + core::rect rect(m_displaycenter.X - obj_crosshair_size, + m_displaycenter.Y - obj_crosshair_size, + m_displaycenter.X + obj_crosshair_size, + m_displaycenter.Y + obj_crosshair_size); + video::SColor crosshair_color[] = {crosshair_argb, crosshair_argb, + crosshair_argb, crosshair_argb}; + draw2DImageFilterScaled(driver, object_crosshair, rect, + core::rect({0, 0}, object_crosshair->getOriginalSize()), + nullptr, crosshair_color, true); } else { driver->draw2DLine( - m_displaycenter - v2s32(OBJECT_CROSSHAIR_LINE_SIZE, - OBJECT_CROSSHAIR_LINE_SIZE), - m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE, - OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb); + m_displaycenter - v2s32(obj_crosshair_size, obj_crosshair_size), + m_displaycenter + v2s32(obj_crosshair_size, obj_crosshair_size), + crosshair_argb); driver->draw2DLine( - m_displaycenter + v2s32(OBJECT_CROSSHAIR_LINE_SIZE, - -OBJECT_CROSSHAIR_LINE_SIZE), - m_displaycenter + v2s32(-OBJECT_CROSSHAIR_LINE_SIZE, - OBJECT_CROSSHAIR_LINE_SIZE), crosshair_argb); + m_displaycenter + v2s32(obj_crosshair_size, -obj_crosshair_size), + m_displaycenter + v2s32(-obj_crosshair_size, obj_crosshair_size), + crosshair_argb); } return; } + int crosshair_size = (int) (CROSSHAIR_LINE_SIZE * m_scale_factor); if (use_crosshair_image) { video::ITexture *crosshair = tsrc->getTexture("crosshair.png"); - v2u32 size = crosshair->getOriginalSize(); - v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2), - m_displaycenter.Y - (size.Y / 2)); - driver->draw2DImage(crosshair, lsize, - core::rect(0, 0, size.X, size.Y), - nullptr, crosshair_argb, true); + core::rect rect(m_displaycenter.X - crosshair_size, + m_displaycenter.Y - crosshair_size, + m_displaycenter.X + crosshair_size, + m_displaycenter.Y + crosshair_size); + video::SColor crosshair_color[] = {crosshair_argb, crosshair_argb, + crosshair_argb, crosshair_argb}; + draw2DImageFilterScaled(driver, crosshair, rect, + core::rect({0, 0}, crosshair->getOriginalSize()), + nullptr, crosshair_color, true); } else { - driver->draw2DLine(m_displaycenter - v2s32(CROSSHAIR_LINE_SIZE, 0), - m_displaycenter + v2s32(CROSSHAIR_LINE_SIZE, 0), crosshair_argb); - driver->draw2DLine(m_displaycenter - v2s32(0, CROSSHAIR_LINE_SIZE), - m_displaycenter + v2s32(0, CROSSHAIR_LINE_SIZE), crosshair_argb); + driver->draw2DLine(m_displaycenter - v2s32(crosshair_size, 0), + m_displaycenter + v2s32(crosshair_size, 0), crosshair_argb); + driver->draw2DLine(m_displaycenter - v2s32(0, crosshair_size), + m_displaycenter + v2s32(0, crosshair_size), crosshair_argb); } } diff --git a/src/client/hud.h b/src/client/hud.h index 593fe9316..e5c84502c 100644 --- a/src/client/hud.h +++ b/src/client/hud.h @@ -120,6 +120,7 @@ private: v2s32 m_displaycenter; s32 m_hotbar_imagesize; // Takes hud_scaling into account, updated by resizeHotbar() s32 m_padding; // Takes hud_scaling into account, updated by resizeHotbar() + s32 m_hud_move_upwards; video::SColor hbar_colors[4]; std::vector m_selection_boxes; diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 3ded84bf2..d5f2ac593 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -20,8 +20,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "inputhandler.h" +#include "gui/guiChatConsole.h" #include "gui/mainmenumanager.h" #include "hud.h" +#include "settings.h" + +#ifdef __IOS__ +#include "porting_ios.h" +extern "C" void external_pause_game(); +#endif + +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) +#include +#endif void KeyCache::populate_nonchanging() { @@ -98,13 +109,57 @@ void KeyCache::populate() bool MyEventReceiver::OnEvent(const SEvent &event) { +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + if (event.EventType == irr::EET_SDL_CONTROLLER_BUTTON_EVENT || + event.EventType == irr::EET_SDL_CONTROLLER_AXIS_EVENT) { + if (g_settings->getBool("enable_joysticks")) { + sdl_game_controller->translateEvent(event); + input->setCursorVisible(sdl_game_controller->isCursorVisible()); + } + } else if ((event.EventType == irr::EET_MOUSE_INPUT_EVENT && + event.MouseInput.Event == irr::EMIE_MOUSE_MOVED) || + event.EventType == irr::EET_TOUCH_INPUT_EVENT) { + if (!sdl_game_controller->isFakeEvent() && + sdl_game_controller->isActive()) { + sdl_game_controller->setActive(false); + input->setCursorVisible(sdl_game_controller->isCursorVisible()); + } + } +#endif + +#ifdef HAVE_TOUCHSCREENGUI + if (event.EventType == irr::EET_TOUCH_INPUT_EVENT) { + TouchScreenGUI::setActive(true); + if (m_touchscreengui && !isMenuActive()) + m_touchscreengui->show(); + } else if ((event.EventType == irr::EET_MOUSE_INPUT_EVENT && + event.MouseInput.Event == irr::EMIE_MOUSE_MOVED) || + sdl_game_controller->isActive()) { + TouchScreenGUI::setActive(false); + if (m_touchscreengui && !isMenuActive()) + m_touchscreengui->hide(); + } +#endif + +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + if (event.EventType == irr::EET_SDL_CONTROLLER_BUTTON_EVENT) + return true; +#endif + + GUIChatConsole* chat_console = GUIChatConsole::getChatConsole(); + if (chat_console && chat_console->isOpen()) { + bool result = chat_console->preprocessEvent(event); + if (result) + return true; + } + /* React to nothing here if a menu is active */ if (isMenuActive()) { #ifdef HAVE_TOUCHSCREENGUI if (m_touchscreengui) { - m_touchscreengui->Toggle(false); + m_touchscreengui->hide(); } #endif return g_menumgr.preprocessEvent(event); @@ -137,6 +192,15 @@ bool MyEventReceiver::OnEvent(const SEvent &event) return true; #endif +#ifdef __IOS__ + } else if (event.EventType == irr::EET_APPLICATION_EVENT) { + int AppEvent = event.ApplicationEvent.EventType; + ioswrap_events(AppEvent); + if (AppEvent == irr::EAET_WILL_PAUSE) + external_pause_game(); + return true; +#endif + } else if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) { // joystick may be nullptr if game is launched with '--random-input' parameter return joystick && joystick->handleEvent(event.JoystickEvent); @@ -191,8 +255,10 @@ bool MyEventReceiver::OnEvent(const SEvent &event) LL_NONE, // ELL_NONE }; assert(event.LogEvent.Level < ARRLEN(irr_loglev_conv)); +#ifndef NDEBUG g_logger.log(irr_loglev_conv[event.LogEvent.Level], std::string("Irrlicht: ") + event.LogEvent.Text); +#endif return true; } /* always return false in order to continue processing events */ diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 33e38dc21..5a23f6095 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -197,6 +197,11 @@ public: JoystickController *joystick = nullptr; +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + SDLGameController* sdl_game_controller = nullptr; + InputHandler* input = nullptr; +#endif + #ifdef HAVE_TOUCHSCREENGUI TouchScreenGUI *m_touchscreengui; #endif @@ -264,8 +269,13 @@ public: virtual void clear() {} + virtual void setCursorVisible(bool visible) {}; + JoystickController joystick; KeyCache keycache; +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + SDLGameController sdl_game_controller; +#endif }; /* Separated input handler @@ -277,6 +287,10 @@ public: RealInputHandler(MyEventReceiver *receiver) : m_receiver(receiver) { m_receiver->joystick = &joystick; +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + m_receiver->sdl_game_controller = &sdl_game_controller; + m_receiver->input = this; +#endif } virtual ~RealInputHandler() @@ -389,6 +403,22 @@ public: return m_receiver->getMouseWheel(); } + virtual void setCursorVisible(bool visible) + { +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + sdl_game_controller.setCursorVisible(visible); + + if (sdl_game_controller.isActive()) + visible = false; +#endif + IrrlichtDevice *device = RenderingEngine::get_raw_device(); + + if (device->getCursorControl()) { + if (visible != device->getCursorControl()->isVisible()) + device->getCursorControl()->setVisible(visible); + } + } + void clear() { joystick.clear(); diff --git a/src/client/joystick_controller.cpp b/src/client/joystick_controller.cpp index f67f10b5c..534fa63cc 100644 --- a/src/client/joystick_controller.cpp +++ b/src/client/joystick_controller.cpp @@ -20,9 +20,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "joystick_controller.h" #include "irrlichttypes_extrabloated.h" #include "keys.h" +#include "keycode.h" +#include "gui/mainmenumanager.h" #include "settings.h" #include "gettime.h" #include "porting.h" +#include "renderingengine.h" #include "util/string.h" bool JoystickButtonCmb::isTriggered(const irr::SEvent::SJoystickEvent &ev) const @@ -326,3 +329,352 @@ float JoystickController::getMovementSpeed() speed = 1.0f; return speed; } + +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) +bool SDLGameController::m_active = false; +bool SDLGameController::m_cursor_visible = false; + +void SDLGameController::handleMouseMovement(int x, int y) +{ + IrrlichtDevice* device = RenderingEngine::get_raw_device(); + + bool changed = false; + + u32 current_time = device->getTimer()->getRealTime(); + v2s32 mouse_pos = device->getCursorControl()->getPosition(); + int deadzone = g_settings->getU16("joystick_deadzone"); + + if (x > deadzone || x < -deadzone) { + s32 dt = current_time - m_mouse_time; + + mouse_pos.X += (x * dt / 30000); + if (mouse_pos.X < 0) + mouse_pos.X = 0; + if (mouse_pos.X > device->getVideoDriver()->getScreenSize().Width) + mouse_pos.X = device->getVideoDriver()->getScreenSize().Width; + + changed = true; + } + + if (y > deadzone || y < -deadzone) { + s32 dt = current_time - m_mouse_time; + + mouse_pos.Y += (y * dt / 30000); + if (mouse_pos.Y < 0) + mouse_pos.Y = 0; + if (mouse_pos.Y > device->getVideoDriver()->getScreenSize().Height) + mouse_pos.Y = device->getVideoDriver()->getScreenSize().Height; + + changed = true; + } + + if (changed) { + SEvent translated_event; + translated_event.EventType = irr::EET_MOUSE_INPUT_EVENT; + translated_event.MouseInput.Event = irr::EMIE_MOUSE_MOVED; + translated_event.MouseInput.X = mouse_pos.X; + translated_event.MouseInput.Y = mouse_pos.Y; + translated_event.MouseInput.Control = false; + translated_event.MouseInput.Shift = false; + translated_event.MouseInput.ButtonStates = m_button_states; + + sendEvent(translated_event); + device->getCursorControl()->setPosition(mouse_pos.X, mouse_pos.Y); + } + + m_mouse_time = current_time; +} + +void SDLGameController::handleTriggerLeft(s16 value) +{ + IrrlichtDevice* device = RenderingEngine::get_raw_device(); + + int deadzone = g_settings->getU16("joystick_deadzone"); + + SEvent translated_event; + translated_event.EventType = irr::EET_KEY_INPUT_EVENT; + translated_event.KeyInput.Char = 0; + translated_event.KeyInput.Key = getKeySetting("keymap_hotbar_previous").getKeyCode(); + translated_event.KeyInput.Shift = false; + translated_event.KeyInput.Control = false; + + if (value <= deadzone && m_trigger_left_value > deadzone) { + translated_event.KeyInput.PressedDown = false; + sendEvent(translated_event); + } else if (value > deadzone && m_trigger_left_value <= deadzone) { + translated_event.KeyInput.PressedDown = true; + sendEvent(translated_event); + } + + m_trigger_left_value = value; +} + +void SDLGameController::handleTriggerRight(s16 value) +{ + IrrlichtDevice* device = RenderingEngine::get_raw_device(); + + int deadzone = g_settings->getU16("joystick_deadzone"); + + SEvent translated_event; + translated_event.EventType = irr::EET_KEY_INPUT_EVENT; + translated_event.KeyInput.Char = 0; + translated_event.KeyInput.Key = getKeySetting("keymap_hotbar_next").getKeyCode(); + translated_event.KeyInput.Shift = false; + translated_event.KeyInput.Control = false; + + if (value <= deadzone && m_trigger_right_value > deadzone) { + translated_event.KeyInput.PressedDown = false; + sendEvent(translated_event); + } else if (value > deadzone && m_trigger_right_value <= deadzone) { + translated_event.KeyInput.PressedDown = true; + sendEvent(translated_event); + } + + m_trigger_right_value = value; +} + +void SDLGameController::handleMouseClickLeft(bool pressed) +{ + IrrlichtDevice* device = RenderingEngine::get_raw_device(); + + v2s32 mouse_pos = device->getCursorControl()->getPosition(); + + if (pressed) + m_button_states |= irr::EMBSM_LEFT; + else + m_button_states &= ~irr::EMBSM_LEFT; + + SEvent translated_event; + translated_event.EventType = irr::EET_MOUSE_INPUT_EVENT; + translated_event.MouseInput.Event = pressed ? irr::EMIE_LMOUSE_PRESSED_DOWN : irr::EMIE_LMOUSE_LEFT_UP; + translated_event.MouseInput.X = mouse_pos.X; + translated_event.MouseInput.Y = mouse_pos.Y; + translated_event.MouseInput.Control = false; + translated_event.MouseInput.Shift = false; + translated_event.MouseInput.ButtonStates = m_button_states; + + sendEvent(translated_event); +} + +void SDLGameController::handleMouseClickRight(bool pressed) +{ + IrrlichtDevice* device = RenderingEngine::get_raw_device(); + + v2s32 mouse_pos = device->getCursorControl()->getPosition(); + + if (pressed) + m_button_states |= irr::EMBSM_RIGHT; + else + m_button_states &= ~irr::EMBSM_RIGHT; + + SEvent translated_event; + translated_event.EventType = irr::EET_MOUSE_INPUT_EVENT; + translated_event.MouseInput.Event = pressed ? irr::EMIE_RMOUSE_PRESSED_DOWN : irr::EMIE_RMOUSE_LEFT_UP; + translated_event.MouseInput.X = mouse_pos.X; + translated_event.MouseInput.Y = mouse_pos.Y; + translated_event.MouseInput.Control = false; + translated_event.MouseInput.Shift = false; + translated_event.MouseInput.ButtonStates = m_button_states; + + sendEvent(translated_event); +} + +void SDLGameController::handleButton(const SEvent &event) +{ + irr::EKEY_CODE key = KEY_UNKNOWN; + + switch (event.SDLControllerButtonEvent.Button) { + case SDL_CONTROLLER_BUTTON_A: + key = getKeySetting("keymap_jump").getKeyCode(); + break; + case SDL_CONTROLLER_BUTTON_B: + key = getKeySetting("keymap_sneak").getKeyCode(); + break; + case SDL_CONTROLLER_BUTTON_X: + key = getKeySetting("keymap_special1").getKeyCode(); + break; + case SDL_CONTROLLER_BUTTON_Y: + key = getKeySetting("keymap_minimap").getKeyCode(); + break; + case SDL_CONTROLLER_BUTTON_BACK: + key = getKeySetting("keymap_chat").getKeyCode(); + break; + case SDL_CONTROLLER_BUTTON_GUIDE: + break; + case SDL_CONTROLLER_BUTTON_START: + key = KEY_ESCAPE; + break; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + key = getKeySetting("keymap_camera_mode").getKeyCode(); + break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + key = KEY_LBUTTON; + break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + key = KEY_LBUTTON; + break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + key = KEY_RBUTTON; + break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + key = getKeySetting("keymap_rangeselect").getKeyCode(); + break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + key = getKeySetting("keymap_drop").getKeyCode(); + break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + key = KEY_ESCAPE; + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + key = getKeySetting("keymap_inventory").getKeyCode(); + break; + case SDL_CONTROLLER_BUTTON_MISC1: + case SDL_CONTROLLER_BUTTON_PADDLE1: + case SDL_CONTROLLER_BUTTON_PADDLE2: + case SDL_CONTROLLER_BUTTON_PADDLE3: + case SDL_CONTROLLER_BUTTON_PADDLE4: + case SDL_CONTROLLER_BUTTON_TOUCHPAD: + break; + } + + if (key != KEY_UNKNOWN) { + SEvent translated_event; + translated_event.EventType = irr::EET_KEY_INPUT_EVENT; + translated_event.KeyInput.Char = 0; + translated_event.KeyInput.Key = key; + translated_event.KeyInput.PressedDown = event.SDLControllerButtonEvent.Pressed; + translated_event.KeyInput.Shift = false; + translated_event.KeyInput.Control = false; + + sendEvent(translated_event); + } +} + +void SDLGameController::handleButtonInMenu(const SEvent &event) +{ + irr::EKEY_CODE key = KEY_UNKNOWN; + + // Just used game mapping for escape key for now + switch (event.SDLControllerButtonEvent.Button) { + case SDL_CONTROLLER_BUTTON_A: + case SDL_CONTROLLER_BUTTON_B: + case SDL_CONTROLLER_BUTTON_X: + case SDL_CONTROLLER_BUTTON_Y: + case SDL_CONTROLLER_BUTTON_BACK: + case SDL_CONTROLLER_BUTTON_GUIDE: + break; + case SDL_CONTROLLER_BUTTON_START: + key = KEY_ESCAPE; + break; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + case SDL_CONTROLLER_BUTTON_DPAD_UP: + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + key = KEY_ESCAPE; + break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + case SDL_CONTROLLER_BUTTON_MISC1: + case SDL_CONTROLLER_BUTTON_PADDLE1: + case SDL_CONTROLLER_BUTTON_PADDLE2: + case SDL_CONTROLLER_BUTTON_PADDLE3: + case SDL_CONTROLLER_BUTTON_PADDLE4: + case SDL_CONTROLLER_BUTTON_TOUCHPAD: + break; + } + + if (key != KEY_UNKNOWN) { + SEvent translated_event; + translated_event.EventType = irr::EET_KEY_INPUT_EVENT; + translated_event.KeyInput.Char = 0; + translated_event.KeyInput.Key = key; + translated_event.KeyInput.PressedDown = event.SDLControllerButtonEvent.Pressed; + translated_event.KeyInput.Shift = false; + translated_event.KeyInput.Control = false; + + sendEvent(translated_event); + } +} + +void SDLGameController::handlePlayerMovement(int x, int y) +{ + int deadzone = g_settings->getU16("joystick_deadzone"); + + m_move_sideward = x; + if (m_move_sideward < deadzone && m_move_sideward > -deadzone) + m_move_sideward = 0; + else + m_active = true; + + m_move_forward = y; + if (m_move_forward < deadzone && m_move_forward > -deadzone) + m_move_forward = 0; + else + m_active = true; +} + +void SDLGameController::handleCameraOrientation(int x, int y) +{ + int deadzone = g_settings->getU16("joystick_deadzone"); + + m_camera_yaw = x; + if (m_camera_yaw < deadzone && m_camera_yaw > -deadzone) + m_camera_yaw = 0; + else + m_active = true; + + m_camera_pitch = y; + if (m_camera_pitch < deadzone && m_camera_pitch > -deadzone) + m_camera_pitch = 0; + else + m_active = true; +} + +void SDLGameController::sendEvent(const SEvent &event) +{ + m_active = true; + m_is_fake_event = true; + IrrlichtDevice* device = RenderingEngine::get_raw_device(); + device->postEventFromUser(event); + m_is_fake_event = false; +} + +void SDLGameController::translateEvent(const SEvent &event) +{ + if (event.EventType == irr::EET_SDL_CONTROLLER_BUTTON_EVENT) { + if (isMenuActive()) { + if (event.SDLControllerButtonEvent.Button == SDL_CONTROLLER_BUTTON_LEFTSTICK || + event.SDLControllerButtonEvent.Button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) { + handleMouseClickLeft(event.SDLControllerButtonEvent.Pressed); + } else if (event.SDLControllerButtonEvent.Button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) { + handleMouseClickRight(event.SDLControllerButtonEvent.Pressed); + } else { + handleButtonInMenu(event); + } + } else { + if (event.SDLControllerButtonEvent.Button == SDL_CONTROLLER_BUTTON_RIGHTSTICK || + event.SDLControllerButtonEvent.Button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) { + handleMouseClickLeft(event.SDLControllerButtonEvent.Pressed); + } else if (event.SDLControllerButtonEvent.Button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) { + handleMouseClickRight(event.SDLControllerButtonEvent.Pressed); + } else { + handleButton(event); + } + } + } else if (event.EventType == irr::EET_SDL_CONTROLLER_AXIS_EVENT) { + const s16* value = event.SDLControllerAxisEvent.Value; + + if (isMenuActive()) { + handleMouseMovement(value[SDL_CONTROLLER_AXIS_LEFTX], value[SDL_CONTROLLER_AXIS_LEFTY]); + } else { + handleTriggerLeft(value[SDL_CONTROLLER_AXIS_TRIGGERLEFT]); + handleTriggerRight(value[SDL_CONTROLLER_AXIS_TRIGGERRIGHT]); + handlePlayerMovement(value[SDL_CONTROLLER_AXIS_LEFTX], value[SDL_CONTROLLER_AXIS_LEFTY]); + handleCameraOrientation(value[SDL_CONTROLLER_AXIS_RIGHTX], value[SDL_CONTROLLER_AXIS_RIGHTY]); + } + } +} +#endif diff --git a/src/client/joystick_controller.h b/src/client/joystick_controller.h index 1dcc6a025..16977bfb2 100644 --- a/src/client/joystick_controller.h +++ b/src/client/joystick_controller.h @@ -170,3 +170,47 @@ private: std::bitset m_past_keys_pressed; std::bitset m_keys_released; }; + +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) +class SDLGameController +{ +private: + void handleMouseMovement(int x, int y); + void handleTriggerLeft(s16 value); + void handleTriggerRight(s16 value); + void handleMouseClickLeft(bool pressed); + void handleMouseClickRight(bool pressed); + void handleButton(const SEvent &event); + void handleButtonInMenu(const SEvent &event); + void handlePlayerMovement(int x, int y); + void handleCameraOrientation(int x, int y); + void sendEvent(const SEvent &event); + + int m_button_states = 0; + u32 m_mouse_time = 0; + s16 m_trigger_left_value = 0; + s16 m_trigger_right_value = 0; + s16 m_move_sideward = 0; + s16 m_move_forward = 0; + s16 m_camera_yaw = 0; + s16 m_camera_pitch = 0; + + static bool m_active; + static bool m_cursor_visible; + bool m_is_fake_event = false; + +public: + void translateEvent(const SEvent &event); + + s16 getMoveSideward() { return m_move_sideward; } + s16 getMoveForward() { return m_move_forward; } + s16 getCameraYaw() { return m_camera_yaw; } + s16 getCameraPitch() { return m_camera_pitch; } + + void setActive(bool value) { m_active = value; } + static bool isActive() { return m_active; } + void setCursorVisible(bool visible) { m_cursor_visible = visible; } + static bool isCursorVisible() { return m_cursor_visible; } + bool isFakeEvent() { return m_is_fake_event; } +}; +#endif diff --git a/src/client/keycode.h b/src/client/keycode.h index 4efe31d9b..34a12b83f 100644 --- a/src/client/keycode.h +++ b/src/client/keycode.h @@ -44,6 +44,8 @@ public: const char *sym() const; const char *name() const; + irr::EKEY_CODE getKeyCode() { return Key; } + protected: static bool valid_kcode(irr::EKEY_CODE k) { diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index a61e9d7bb..96372c513 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -27,6 +27,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "client.h" #include "content_cao.h" +#include "client/joystick_controller.h" +#include "gui/touchscreengui.h" /* LocalPlayer @@ -172,11 +174,13 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, if (!collision_info || collision_info->empty()) m_standing_node = floatToInt(m_position, BS); +#if 0 // Temporary option for old move code if (!physics_override_new_move) { old_move(dtime, env, pos_max_d, collision_info); return; } +#endif Map *map = &env->getMap(); const NodeDefManager *nodemgr = m_client->ndef(); @@ -292,6 +296,11 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, float player_stepheight = (m_cao == nullptr) ? 0.0f : (touching_ground ? m_cao->getStepHeight() : (0.2f * BS)); +#ifdef HAVE_TOUCHSCREENGUI + if (TouchScreenGUI::isActive() && touching_ground) + player_stepheight += (0.6f * BS); +#endif + v3f accel_f; const v3f initial_position = position; const v3f initial_speed = m_speed; @@ -423,7 +432,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND)); // Set camera impact value to be used for view bobbing - camera_impact = getSpeed().Y * -1; + camera_impact = -initial_speed.Y; } { @@ -627,6 +636,18 @@ void LocalPlayer::applyControl(float dtime, Environment *env) speedH *= control.movement_speed; /* Apply analog input */ + if (!free_move && !in_liquid && !in_liquid_stable && !getParent() && (physics_override_speed != 0)) { + if (!m_sneak_offset && control.sneak) { + eye_offset_first.Y -= 3.0f; + eye_offset_third.Y -= 3.0f; + m_sneak_offset = true; + } else if (m_sneak_offset && !control.sneak) { + eye_offset_first.Y += 3.0f; + eye_offset_third.Y += 3.0f; + m_sneak_offset = false; + } + } + // Acceleration increase f32 incH = 0.0f; // Horizontal (X, Z) f32 incV = 0.0f; // Vertical (Y) @@ -646,11 +667,18 @@ void LocalPlayer::applyControl(float dtime, Environment *env) } float slip_factor = 1.0f; - if (!free_move && !in_liquid && !in_liquid_stable) + float speed_factor = 1.0f; + if (!free_move && !in_liquid && !in_liquid_stable) { slip_factor = getSlipFactor(env, speedH); + speed_factor = getSpeedFactor(env); + } - // Don't sink when swimming in pitch mode - if (pitch_move && in_liquid) { + // Apply speed factor + speedH *= speed_factor; + speedV *= speed_factor; + + // Don't sink when swimming + if (in_liquid) { v3f controlSpeed = speedH + speedV; if (controlSpeed.getLength() > 0.01f) swimming_pitch = true; @@ -751,6 +779,7 @@ void LocalPlayer::accelerate(const v3f &target_speed, const f32 max_increase_H, m_speed += d; } +#if 0 // Temporary option for old move code void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, std::vector *collision_info) @@ -888,6 +917,11 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, // TODO: This shouldn't be hardcoded but decided by the server float player_stepheight = touching_ground ? (BS * 0.6f) : (BS * 0.2f); +#ifdef HAVE_TOUCHSCREENGUI + if (TouchScreenGUI::isActive()) + player_stepheight += (0.6 * BS); +#endif + v3f accel_f; const v3f initial_position = position; const v3f initial_speed = m_speed; @@ -953,7 +987,7 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, std::fabs(player_p2df.Y - node_p2df.Y)); if (distance_f > min_distance_f || - max_axis_distance_f > 0.5f * BS + sneak_max + 0.1f * BS) + max_axis_distance_f > 0.5f * BS + sneak_max + 0.05f * BS) continue; // The node to be sneaked on has to be walkable @@ -1065,6 +1099,7 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, // Autojump handleAutojump(dtime, env, result, initial_position, initial_speed, pos_max_d); } +#endif float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH) { @@ -1085,12 +1120,53 @@ float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH) return 1.0f; } +float LocalPlayer::getSpeedFactor(Environment *env) +{ + int speed_below = 0, speed_above = 0; + v3s16 pos = getStandingNodePos(); + const NodeDefManager *nodemgr = env->getGameDef()->ndef(); + Map *map = &env->getMap(); + + const ContentFeatures &f = nodemgr->get(map->getNode(pos)); + if (f.walkable) + speed_below = itemgroup_get(f.groups, "speed"); + + const ContentFeatures &f2 = nodemgr->get(map->getNode( + pos + v3s16(0, 1, 0))); + speed_above = itemgroup_get(f2.groups, "speed"); + + if (speed_above == 0) { + const ContentFeatures &f3 = nodemgr->get(map->getNode( + pos + v3s16(0, 2, 0))); + speed_above = itemgroup_get(f3.groups, "speed"); + } + + int speed = speed_below + speed_above; + if (speed != 0) + return core::clamp(1.0f + f32(speed) / 100.f, 0.01f, 10.f); + + return 1.0f; +} + void LocalPlayer::handleAutojump(f32 dtime, Environment *env, const collisionMoveResult &result, const v3f &initial_position, const v3f &initial_speed, f32 pos_max_d) { +#ifdef HAVE_TOUCHSCREENGUI + // Touchscreen uses player_stepheight for autojump + if (TouchScreenGUI::isActive()) + return; +#endif + PlayerSettings &player_settings = getPlayerSettings(); - if (!player_settings.autojump) + bool autojump_enabled = player_settings.autojump; + +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + // Force autojump on gamepad + autojump_enabled |= SDLGameController::isActive(); +#endif + + if (!autojump_enabled) return; if (m_autojump) diff --git a/src/client/localplayer.h b/src/client/localplayer.h index be26a6aba..d64fefa1c 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -163,6 +163,7 @@ private: const f32 max_increase_V, const bool use_pitch); bool updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max); float getSlipFactor(Environment *env, const v3f &speedH); + float getSpeedFactor(Environment *env); void handleAutojump(f32 dtime, Environment *env, const collisionMoveResult &result, const v3f &position_before_move, const v3f &speed_before_move, @@ -179,6 +180,7 @@ private: // Whether a "sneak ladder" structure is detected at the players pos // see detectSneakLadder() in the .cpp for more info (always false if disabled) bool m_sneak_ladder_detected = false; + bool m_sneak_offset = false; // ***** Variables for temporary option of the old move code ***** // Stores the max player uplift by m_sneak_node diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 2d4d57fe9..298a5c71f 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -1162,9 +1162,6 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): material.MaterialType = m_shdrsrc->getShaderInfo( p.layer.shader_id).material; p.layer.applyMaterialOptionsWithShaders(material); - if (p.layer.normal_texture) - material.setTexture(1, p.layer.normal_texture); - material.setTexture(2, p.layer.flags_texture); } else { p.layer.applyMaterialOptions(material); } @@ -1267,12 +1264,6 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, const FrameSpec &animation_frame = (*tile.frames)[frame]; buf->getMaterial().setTexture(0, animation_frame.texture); - if (m_enable_shaders) { - if (animation_frame.normal_texture) - buf->getMaterial().setTexture(1, - animation_frame.normal_texture); - buf->getMaterial().setTexture(2, animation_frame.flags_texture); - } } // Day-night transition diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 1a9ea5a31..da04001c1 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -193,12 +193,18 @@ Minimap::Minimap(Client *client) // Initialize minimap modes addMode(MINIMAP_TYPE_OFF); +#if !defined(__ANDROID__) && !defined(__IOS__) addMode(MINIMAP_TYPE_SURFACE, 256); addMode(MINIMAP_TYPE_SURFACE, 128); +#endif addMode(MINIMAP_TYPE_SURFACE, 64); +#if !defined(__ANDROID__) && !defined(__IOS__) addMode(MINIMAP_TYPE_RADAR, 512); addMode(MINIMAP_TYPE_RADAR, 256); addMode(MINIMAP_TYPE_RADAR, 128); +#else + addMode(MINIMAP_TYPE_RADAR, 64); +#endif // Initialize minimap data data = new MinimapData; @@ -356,6 +362,9 @@ void Minimap::addMode(MinimapModeDef mode) porting::mt_snprintf(label_buf, sizeof(label_buf), mode.label.c_str(), zoom); mode.label = label_buf; +#if defined(__ANDROID__) || defined(__IOS__) + mode.label = mode.label.substr(0, mode.label.find(", ")); +#endif } m_modes.push_back(mode); @@ -458,7 +467,7 @@ void Minimap::blitMinimapPixelsToImageSurface( map_image->setPixel(x, data->mode.map_size - z - 1, tilecolor); - u32 h = mmpixel->height; + const u32 h = 255; // full bright heightmap_image->setPixel(x,data->mode.map_size - z - 1, video::SColor(255, h, h, h)); } @@ -518,8 +527,13 @@ video::ITexture *Minimap::getMinimapTexture() if (minimap_mask) { for (s16 y = 0; y < MINIMAP_MAX_SY; y++) for (s16 x = 0; x < MINIMAP_MAX_SX; x++) { - const video::SColor &mask_col = minimap_mask->getPixel(x, y); + video::SColor mask_col = minimap_mask->getPixel(x, y); +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 9 + // Irrlicht 1.9 has some problem with alpha + if (mask_col.getRed() != 255) +#else if (!mask_col.getAlpha()) +#endif minimap_image->setPixel(x, y, video::SColor(0,0,0,0)); } } @@ -579,10 +593,11 @@ void Minimap::drawMinimap() // Non hud managed minimap drawing (legacy minimap) v2u32 screensize = RenderingEngine::getWindowSize(); const u32 size = 0.25 * screensize.Y; + const u32 padding = 10 * RenderingEngine::getDisplayDensity(); drawMinimap(core::rect( - screensize.X - size - 10, 10, - screensize.X - 10, size + 10)); + screensize.X - size - padding, padding, + screensize.X - padding, size + padding)); } void Minimap::drawMinimap(core::rect rect) { @@ -611,7 +626,7 @@ void Minimap::drawMinimap(core::rect rect) { material.setFlag(video::EMF_TRILINEAR_FILTER, true); material.Lighting = false; material.TextureLayer[0].Texture = minimap_texture; - material.TextureLayer[1].Texture = data->heightmap_texture; + material.TextureLayer[1].Texture = data->mode.type == MINIMAP_TYPE_RADAR ? 0 : data->heightmap_texture; if (m_enable_shaders && data->mode.type == MINIMAP_TYPE_SURFACE) { u16 sid = m_shdrsrc->getShader("minimap_shader", TILE_MATERIAL_ALPHA); diff --git a/src/client/particles.cpp b/src/client/particles.cpp index 2e83e373d..fe5660ab7 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -76,6 +76,7 @@ Particle::Particle( m_material.setFlag(video::EMF_BACK_FACE_CULLING, false); m_material.setFlag(video::EMF_BILINEAR_FILTER, false); m_material.setFlag(video::EMF_FOG_ENABLE, true); + m_material.setFlag(video::EMF_ZWRITE_ENABLE, false); m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; m_material.setTexture(0, texture); m_texpos = texpos; diff --git a/src/client/render/anaglyph.cpp b/src/client/render/anaglyph.cpp index 664860548..49e68fcf4 100644 --- a/src/client/render/anaglyph.cpp +++ b/src/client/render/anaglyph.cpp @@ -32,7 +32,7 @@ void RenderingCoreAnaglyph::setupMaterial(int color_mask) video::SOverrideMaterial &mat = driver->getOverrideMaterial(); mat.reset(); mat.Material.ColorMask = color_mask; - mat.EnableFlags = video::EMF_COLOR_MASK; + mat.EnableFlags |= video::EMF_COLOR_MASK; mat.EnablePasses = scene::ESNRP_SKY_BOX | scene::ESNRP_SOLID | scene::ESNRP_TRANSPARENT | scene::ESNRP_TRANSPARENT_EFFECT | scene::ESNRP_SHADOW; diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 642815ad3..0e4c76a22 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include +#include "filesys.h" #include "fontengine.h" #include "client.h" #include "clouds.h" @@ -37,7 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "../gui/guiSkin.h" #if !defined(_WIN32) && !defined(__APPLE__) && !defined(__ANDROID__) && \ - !defined(SERVER) && !defined(__HAIKU__) + !defined(SERVER) && !defined(__HAIKU__) && !defined(__IOS__) #define XORG_USED #endif #ifdef XORG_USED @@ -56,8 +57,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #endif -#if ENABLE_GLES -#include "filesys.h" +#ifdef __ANDROID__ +#include "defaultsettings.h" #endif RenderingEngine *RenderingEngine::s_singleton = nullptr; @@ -91,8 +92,13 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) // Resolution selection bool fullscreen = g_settings->getBool("fullscreen"); +#if defined(__ANDROID__) || defined(__IOS__) + u16 screen_w = 0; + u16 screen_h = 0; + #else u16 screen_w = g_settings->getU16("screen_w"); u16 screen_h = g_settings->getU16("screen_h"); + #endif // bpp, fsaa, vsync bool vsync = g_settings->getBool("vsync"); @@ -132,9 +138,6 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) params.Vsync = vsync; params.EventReceiver = receiver; params.HighPrecisionFPU = true; -#ifdef __ANDROID__ - params.PrivateData = porting::app_global; -#endif #if ENABLE_GLES // there is no standardized path for these on desktop std::string rel_path = std::string("client") + DIR_DELIM @@ -151,6 +154,13 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) gui::EGST_WINDOWS_METALLIC, driver); m_device->getGUIEnvironment()->setSkin(skin); skin->drop(); + +#ifdef __ANDROID__ + // Apply settings according to screen size + // We can get real screen size only after device initialization finished + if (m_device) + set_default_settings(); +#endif } RenderingEngine::~RenderingEngine() @@ -464,6 +474,14 @@ void RenderingEngine::draw_load_screen(const std::wstring &text, gui::IGUIEnvironment *guienv, ITextureSource *tsrc, float dtime, int percent, bool clouds) { +#ifdef __IOS__ + if (m_device->isWindowMinimized()) + return; +#else + if (!m_device->isWindowFocused()) + return; +#endif + v2u32 screensize = getWindowSize(); v2s32 textsize(g_fontengine->getTextWidth(text), g_fontengine->getLineHeight()); @@ -481,31 +499,50 @@ void RenderingEngine::draw_load_screen(const std::wstring &text, get_video_driver()->beginScene( true, true, video::SColor(255, 140, 186, 250)); g_menucloudsmgr->drawAll(); - } else + } else { get_video_driver()->beginScene(true, true, video::SColor(255, 0, 0, 0)); + video::ITexture *background_image = tsrc->getTexture("bg.png"); + + v2u32 screensize = driver->getScreenSize(); + get_video_driver()->draw2DImage(background_image, + irr::core::rect(0, 0, screensize.X * 4, screensize.Y * 4), + irr::core::rect(0, 0, screensize.X, screensize.Y), 0, 0, false); + } // draw progress bar if ((percent >= 0) && (percent <= 100)) { - video::ITexture *progress_img = tsrc->getTexture("progress_bar.png"); + std::string texture_path = porting::path_share + DIR_DELIM + "textures" + + DIR_DELIM + "base" + DIR_DELIM + "pack" + DIR_DELIM; + video::ITexture *progress_img = + driver->getTexture((texture_path + "progress_bar.png").c_str()); video::ITexture *progress_img_bg = - tsrc->getTexture("progress_bar_bg.png"); + driver->getTexture((texture_path + "progress_bar_bg.png").c_str()); + video::ITexture *progress_img_fg = + driver->getTexture((texture_path + "progress_bar_fg.png").c_str()); - if (progress_img && progress_img_bg) { -#ifndef __ANDROID__ + if (progress_img && progress_img_bg && progress_img_fg) { const core::dimension2d &img_size = progress_img_bg->getSize(); - u32 imgW = rangelim(img_size.Width, 200, 600); - u32 imgH = rangelim(img_size.Height, 24, 72); +#if !defined(__ANDROID__) && !defined(__IOS__) + float density = RenderingEngine::getDisplayDensity(); + float gui_scaling = g_settings->getFloat("gui_scaling"); + float scale = density * gui_scaling; + u32 imgW = rangelim(img_size.Width, 256, 1024) * scale; + u32 imgH = rangelim(img_size.Height, 32, 128) * scale; #else - const core::dimension2d img_size(256, 48); - float imgRatio = (float)img_size.Height / img_size.Width; - u32 imgW = screensize.X / 2.2f; - u32 imgH = floor(imgW * imgRatio); + float imgRatio = (float) img_size.Height / img_size.Width; + u32 imgW = screensize.X / 2; + if (!hasNPotSupport()) { + imgW = npot2(imgW); + if (imgW > (screensize.X * 0.7) && imgW >= 1024) + imgW /= 2; + } + u32 imgH = imgW * imgRatio; #endif v2s32 img_pos((screensize.X - imgW) / 2, (screensize.Y - imgH) / 2); - draw2DImageFilterScaled(get_video_driver(), progress_img_bg, + draw2DImageFilterScaled(driver, progress_img_bg, core::rect(img_pos.X, img_pos.Y, img_pos.X + imgW, img_pos.Y + imgH), @@ -513,7 +550,19 @@ void RenderingEngine::draw_load_screen(const std::wstring &text, img_size.Height), 0, 0, true); - draw2DImageFilterScaled(get_video_driver(), progress_img, + const video::SColor color(255, 255 - percent * 2, percent * 2, 25); + const video::SColor colors[] = {color, color, color, color}; + + draw2DImageFilterScaled(driver, progress_img_fg, + core::rect(img_pos.X, img_pos.Y, + img_pos.X + (percent * imgW) / 100, + img_pos.Y + imgH), + core::rect(0, 0, + (percent * img_size.Width) / 100, + img_size.Height), + 0, colors, true); + + draw2DImageFilterScaled(driver, progress_img, core::rect(img_pos.X, img_pos.Y, img_pos.X + (percent * imgW) / 100, img_pos.Y + imgH), @@ -525,7 +574,7 @@ void RenderingEngine::draw_load_screen(const std::wstring &text, } guienv->drawAll(); - get_video_driver()->endScene(); + driver->endScene(); guitext->remove(); } @@ -533,7 +582,7 @@ void RenderingEngine::draw_load_screen(const std::wstring &text, Draws the menu scene including (optional) cloud background. */ void RenderingEngine::draw_menu_scene(gui::IGUIEnvironment *guienv, - float dtime, bool clouds) + ITextureSource *tsrc, float dtime, bool clouds) { bool cloud_menu_background = clouds && g_settings->getBool("menu_clouds"); if (cloud_menu_background) { @@ -542,8 +591,15 @@ void RenderingEngine::draw_menu_scene(gui::IGUIEnvironment *guienv, get_video_driver()->beginScene( true, true, video::SColor(255, 140, 186, 250)); g_menucloudsmgr->drawAll(); - } else + } else { get_video_driver()->beginScene(true, true, video::SColor(255, 0, 0, 0)); + video::ITexture *background_image = tsrc->getTexture("bg.png"); + + v2u32 screensize = driver->getScreenSize(); + get_video_driver()->draw2DImage(background_image, + irr::core::rect(0, 0, screensize.X * 4, screensize.Y * 4), + irr::core::rect(0, 0, screensize.X, screensize.Y), 0, 0, false); + } guienv->drawAll(); get_video_driver()->endScene(); @@ -598,7 +654,7 @@ const VideoDriverInfo &RenderingEngine::getVideoDriverInfo(irr::video::E_DRIVER_ return driver_info_map.at((int)type); } -#ifndef __ANDROID__ +#if !defined(__ANDROID__) && !defined(__IOS__) #if defined(XORG_USED) static float calcDisplayDensity(irr::video::IVideoDriver *driver) @@ -695,14 +751,30 @@ v2u32 RenderingEngine::getDisplaySize() return deskres; } -#else // __ANDROID__ +#else // __ANDROID__/__IOS__ float RenderingEngine::getDisplayDensity() { - return porting::getDisplayDensity(); + static const float density = porting::getDisplayDensity(); + return density; } v2u32 RenderingEngine::getDisplaySize() { - return porting::getDisplaySize(); + const RenderingEngine *engine = RenderingEngine::get_instance(); + if (engine == nullptr) + return v2u32(0, 0); + return engine->getWindowSize(); +} +#endif // __ANDROID__/__IOS__ + +bool RenderingEngine::isHighDpi() +{ +#if defined(__MACH__) && defined(__APPLE__) && !defined(__IOS__) + return g_settings->getFloat("screen_dpi") / 72.0f >= 2; +#elif defined(__IOS__) + float density = RenderingEngine::getDisplayDensity(); + return g_settings->getBool("device_is_tablet") ? (density >= 2) : (density >= 3); +#else + return RenderingEngine::getDisplayDensity() >= 3; +#endif } -#endif // __ANDROID__ diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index b50ab437e..71fcf9bfd 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -56,6 +56,7 @@ public: static const VideoDriverInfo &getVideoDriverInfo(irr::video::E_DRIVER_TYPE type); static float getDisplayDensity(); static v2u32 getDisplaySize(); + static bool isHighDpi(); bool setupTopLevelWindow(const std::string &name); void setupTopLevelXorgWindow(const std::string &name); @@ -106,9 +107,11 @@ public: void draw_load_screen(const std::wstring &text, gui::IGUIEnvironment *guienv, ITextureSource *tsrc, - float dtime = 0, int percent = 0, bool clouds = true); + float dtime = 0, int percent = 0, bool clouds = false); - void draw_menu_scene(gui::IGUIEnvironment *guienv, float dtime, bool clouds); + void draw_menu_scene( + gui::IGUIEnvironment *guienv, ITextureSource *tsrc, + float dtime, bool clouds = false); void draw_scene(video::SColor skycolor, bool show_hud, bool show_minimap, bool draw_wield_tool, bool draw_crosshair); diff --git a/src/client/shader.cpp b/src/client/shader.cpp index aaf00663b..ecd0bfce3 100644 --- a/src/client/shader.cpp +++ b/src/client/shader.cpp @@ -40,7 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/tile.h" #include "config.h" -#if ENABLE_GLES +#if ENABLE_GLES && !defined(__APPLE__) #ifdef _IRR_COMPILE_WITH_OGLES1_ #include #else @@ -49,7 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #else #ifndef __APPLE__ #include -#else +#elif !defined(__IOS__) #define GL_SILENCE_DEPRECATION #include #endif @@ -240,8 +240,6 @@ class MainShaderConstantSetter : public IShaderConstantSetter CachedVertexShaderSetting m_world_view; // Texture matrix CachedVertexShaderSetting m_texture; - // Normal matrix - CachedVertexShaderSetting m_normal; #endif public: @@ -251,7 +249,6 @@ public: #if ENABLE_GLES , m_world_view("mWorldView") , m_texture("mTexture") - , m_normal("mNormal") #endif , m_shadow_view_proj("m_ShadowViewProj") , m_light_direction("v_LightDirection") @@ -286,16 +283,6 @@ public: core::matrix4 texture = driver->getTransform(video::ETS_TEXTURE_0); m_world_view.set(*reinterpret_cast(worldView.pointer()), services); m_texture.set(*reinterpret_cast(texture.pointer()), services); - - core::matrix4 normal; - worldView.getTransposed(normal); - sanity_check(normal.makeInverse()); - float m[9] = { - normal[0], normal[1], normal[2], - normal[4], normal[5], normal[6], - normal[8], normal[9], normal[10], - }; - m_normal.set(m, services); #endif // Set uniforms for Shadow shader @@ -633,7 +620,6 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, uniform highp mat4 mWorldView; uniform highp mat4 mWorldViewProj; uniform mediump mat4 mTexture; - uniform mediump mat3 mNormal; attribute highp vec4 inVertexPosition; attribute lowp vec4 inVertexColor; @@ -656,7 +642,6 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, #define mWorldView gl_ModelViewMatrix #define mWorldViewProj gl_ModelViewProjectionMatrix #define mTexture (gl_TextureMatrix[0]) - #define mNormal gl_NormalMatrix #define inVertexPosition gl_Vertex #define inVertexColor gl_Color @@ -668,7 +653,7 @@ ShaderInfo ShaderSource::generateShader(const std::string &name, } bool use_discard = use_gles; -#ifdef __unix__ +#if defined(__unix__) && !defined(__APPLE__) // For renderers that should use discard instead of GL_ALPHA_TEST const char* gl_renderer = (const char*)glGetString(GL_RENDERER); if (strstr(gl_renderer, "GC7000")) diff --git a/src/client/sky.cpp b/src/client/sky.cpp index 522b13f56..1ae194b56 100644 --- a/src/client/sky.cpp +++ b/src/client/sky.cpp @@ -237,7 +237,7 @@ void Sky::render() } // Draw far cloudy fog thing blended with skycolor - if (m_visible) { + /*if (m_visible) { driver->setMaterial(m_materials[1]); for (u32 j = 0; j < 4; j++) { vertices[0] = video::S3DVertex(-1, -0.02, -1, 0, 0, 1, m_bgcolor, t, t); @@ -260,7 +260,7 @@ void Sky::render() } driver->drawIndexedTriangleList(&vertices[0], 4, indices, 2); } - } + }*/ // Draw stars before sun and moon to be behind them if (m_star_params.visible) @@ -813,7 +813,7 @@ void Sky::updateStars() video::SColor fallback_color = m_star_params.starcolor; // used on GLES 2 “without shaders” PcgRandom rgen(m_seed); - float d = (0.006 / 2) * m_star_params.scale; + float d = (0.003 / 2) * m_star_params.scale; for (u16 i = 0; i < m_star_params.count; i++) { v3f r = v3f( rgen.range(-10000, 10000), diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp index b735eb07c..c8c3cada3 100644 --- a/src/client/sound_openal.cpp +++ b/src/client/sound_openal.cpp @@ -50,7 +50,7 @@ with this program; ifnot, write to the Free Software Foundation, Inc., #define BUFFER_SIZE 30000 -std::shared_ptr g_sound_manager_singleton; +SoundManagerSingleton* g_sound_manager_singleton = nullptr; typedef std::unique_ptr unique_ptr_alcdevice; typedef std::unique_ptr unique_ptr_alccontext; @@ -707,15 +707,22 @@ public: } }; -std::shared_ptr createSoundManagerSingleton() +SoundManagerSingleton* createSoundManagerSingleton() { - auto smg = std::make_shared(); + auto smg = new SoundManagerSingleton(); if (!smg->init()) { - smg.reset(); + delete smg; + smg = nullptr; } return smg; } +void deleteSoundManagerSingleton(SoundManagerSingleton* smg) +{ + delete smg; + smg = nullptr; +} + ISoundManager *createOpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher) { return new OpenALSoundManager(smg, fetcher); diff --git a/src/client/sound_openal.h b/src/client/sound_openal.h index b48ecb189..d50bad4e3 100644 --- a/src/client/sound_openal.h +++ b/src/client/sound_openal.h @@ -24,8 +24,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "sound.h" class SoundManagerSingleton; -extern std::shared_ptr g_sound_manager_singleton; +extern SoundManagerSingleton* g_sound_manager_singleton; -std::shared_ptr createSoundManagerSingleton(); +SoundManagerSingleton* createSoundManagerSingleton(); +void deleteSoundManagerSingleton(SoundManagerSingleton* smg); ISoundManager *createOpenALSoundManager( SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher); diff --git a/src/client/tile.cpp b/src/client/tile.cpp index df5290d0e..3c6860690 100644 --- a/src/client/tile.cpp +++ b/src/client/tile.cpp @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., A cache from texture name to texture path */ MutexedMap g_texturename_to_path_cache; +bool g_disable_texture_packs = false; /* Replaces the filename extension. @@ -1023,14 +1024,9 @@ video::IImage *Align2Npot2(video::IImage *image, unsigned int height = npot2(dim.Height); unsigned int width = npot2(dim.Width); - if (dim.Height == height && dim.Width == width) + if (dim.Width == width) return image; - if (dim.Height > height) - height *= 2; - if (dim.Width > width) - width *= 2; - video::IImage *targetimage = driver->createImage(video::ECF_A8R8G8B8, core::dimension2d(width, height)); @@ -1040,7 +1036,15 @@ video::IImage *Align2Npot2(video::IImage *image, image->drop(); return targetimage; } - +#else +bool hasNPotSupport() +{ +#ifdef __IOS__ + return true; // Irrlicht cares about it on iOS +#endif + static const std::string &driverstring = g_settings->get("video_driver"); + return (driverstring != "ogles1"); // gles3 has NPot Support and used instead of gles2 +} #endif static std::string unescape_string(const std::string &str, const char esc = '\\') @@ -1215,8 +1219,13 @@ bool TextureSource::generateImagePart(std::string part_of_name, It is an image with a number of cracking stages horizontally tiled. */ +#ifndef HAVE_TOUCHSCREENGUI video::IImage *img_crack = m_sourcecache.getOrLoad( "crack_anylength.png"); +#else + video::IImage *img_crack = m_sourcecache.getOrLoad( + "crack_anylength_touch.png"); +#endif if (img_crack) { draw_crack(img_crack, baseimg, @@ -2332,5 +2341,13 @@ video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present) std::vector getTextureDirs() { + if (g_disable_texture_packs) + return {}; return fs::GetRecursiveDirs(g_settings->get("texture_path")); } + + +void setDisableTexturePacks(const bool disable_texture_packs) +{ + g_disable_texture_packs = disable_texture_packs; +} diff --git a/src/client/tile.h b/src/client/tile.h index 6b3a99734..9f40d71bb 100644 --- a/src/client/tile.h +++ b/src/client/tile.h @@ -175,8 +175,6 @@ struct FrameSpec u32 texture_id = 0; video::ITexture *texture = nullptr; - video::ITexture *normal_texture = nullptr; - video::ITexture *flags_texture = nullptr; }; #define MAX_TILE_LAYERS 2 @@ -262,11 +260,8 @@ struct TileLayer // Ordered for size, please do not reorder video::ITexture *texture = nullptr; - video::ITexture *normal_texture = nullptr; - video::ITexture *flags_texture = nullptr; u32 shader_id = 0; - u32 texture_id = 0; u16 animation_frame_length_ms = 0; @@ -326,3 +321,5 @@ struct TileSpec }; std::vector getTextureDirs(); + +void setDisableTexturePacks(const bool disable_texture_packs); diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index 12bbc5c5e..93095d495 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -41,6 +41,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MIN_EXTRUSION_MESH_RESOLUTION 16 #define MAX_EXTRUSION_MESH_RESOLUTION 512 +/*! + * Applies overlays, textures and materials to the given mesh and + * extracts tile colors for colorization. + * \param colors returns the colors of the mesh buffers in the mesh. + */ +static void postProcessCubeMesh(scene::SMesh *mesh, const ContentFeatures &f, + IShaderSource *shader_source, std::string const &shader_name, + std::vector *colors); + + static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) { const f32 r = 0.5; @@ -237,18 +247,6 @@ WieldMeshSceneNode::~WieldMeshSceneNode() g_extrusion_mesh_cache = nullptr; } -void WieldMeshSceneNode::setCube(const ContentFeatures &f, - v3f wield_scale) -{ - scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube(); - scene::SMesh *copy = cloneMesh(cubemesh); - cubemesh->drop(); - postProcessNodeMesh(copy, f, false, true, &m_material_type, &m_colors, true); - changeToMesh(copy); - copy->drop(); - m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR); -} - void WieldMeshSceneNode::setExtruded(const std::string &imagename, const std::string &overlay_name, v3f wield_scale, ITextureSource *tsrc, u8 num_frames) @@ -310,7 +308,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, } static scene::SMesh *createSpecialNodeMesh(Client *client, MapNode n, - std::vector *colors, const ContentFeatures &f) + std::vector *colors, const ContentFeatures &f, std::string const shader_name = {}) { MeshMakeData mesh_make_data(client, false); MeshCollector collector; @@ -333,6 +331,7 @@ static scene::SMesh *createSpecialNodeMesh(Client *client, MapNode n, } gen.renderSingle(n.getContent(), n.getParam2()); + IShaderSource *shader_source = shader_name.empty() ? nullptr : client->getShaderSource(); colors->clear(); scene::SMesh *mesh = new scene::SMesh(); for (auto &prebuffers : collector.prebuffers) @@ -340,14 +339,20 @@ static scene::SMesh *createSpecialNodeMesh(Client *client, MapNode n, if (p.layer.material_flags & MATERIAL_FLAG_ANIMATION) { const FrameSpec &frame = (*p.layer.frames)[0]; p.layer.texture = frame.texture; - p.layer.normal_texture = frame.normal_texture; } for (video::S3DVertex &v : p.vertices) { v.Color.setAlpha(255); } scene::SMeshBuffer *buf = new scene::SMeshBuffer(); buf->Material.setTexture(0, p.layer.texture); - p.layer.applyMaterialOptions(buf->Material); + if (!shader_name.empty()) { + auto shader_id = shader_source->getShader(shader_name, (MaterialType)p.layer.material_type, f.drawtype); + buf->Material.MaterialType = shader_source->getShaderInfo(shader_id).material; + p.layer.applyMaterialOptionsWithShaders(buf->Material); + } else { + p.layer.applyMaterialOptions(buf->Material); + } + buf->Material.MaterialTypeParam = 0.0f; mesh->addMeshBuffer(buf); buf->append(&p.vertices[0], p.vertices.size(), &p.indices[0], p.indices.size()); @@ -430,15 +435,22 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che } case NDT_NORMAL: case NDT_ALLFACES: - case NDT_LIQUID: - setCube(f, def.wield_scale); - break; + case NDT_LIQUID: { + scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube(); + scene::SMesh *copy = cloneMesh(cubemesh); + cubemesh->drop(); + postProcessCubeMesh(copy, f, shdrsrc, "object_shader", &m_colors); + changeToMesh(copy); + copy->drop(); + m_meshnode->setScale(def.wield_scale * WIELD_SCALE_FACTOR); + break; + } default: { // Render non-trivial drawtypes like the actual node MapNode n(id); n.setParam2(def.place_param2); - mesh = createSpecialNodeMesh(client, n, &m_colors, f); + mesh = createSpecialNodeMesh(client, id, &m_colors, f, "object_shader"); changeToMesh(mesh); mesh->drop(); m_meshnode->setScale( @@ -451,8 +463,6 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che u32 material_count = m_meshnode->getMaterialCount(); for (u32 i = 0; i < material_count; ++i) { video::SMaterial &material = m_meshnode->getMaterial(i); - material.MaterialType = m_material_type; - material.MaterialTypeParam = 0.5f; material.setFlag(video::EMF_BACK_FACE_CULLING, cull_backface); material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter); material.setFlag(video::EMF_TRILINEAR_FILTER, m_trilinear_filter); @@ -546,6 +556,7 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh) m_shadow->addNodeToShadowList(m_meshnode); } +// Only used for inventory images void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) { ITextureSource *tsrc = client->getTextureSource(); @@ -593,8 +604,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) } else scaleMesh(mesh, v3f(1.2, 1.2, 1.2)); // add overlays - postProcessNodeMesh(mesh, f, false, false, nullptr, - &result->buffer_colors, true); + postProcessCubeMesh(mesh, f, nullptr, {}, &result->buffer_colors); if (f.drawtype == NDT_ALLFACES) scaleMesh(mesh, v3f(f.visual_scale)); break; @@ -692,9 +702,9 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, return mesh; } -void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, - bool use_shaders, bool set_material, const video::E_MATERIAL_TYPE *mattype, - std::vector *colors, bool apply_scale) +static void postProcessCubeMesh(scene::SMesh *mesh, const ContentFeatures &f, + IShaderSource *shader_source, std::string const &shader_name, + std::vector *colors) { u32 mc = mesh->getMeshBufferCount(); // Allocate colors for existing buffers @@ -721,28 +731,21 @@ void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, (*colors)[i] = ItemPartColor(layer->has_color, layer->color); } video::SMaterial &material = buf->getMaterial(); - if (set_material) + if (shader_source && !shader_name.empty()) { + auto shader_id = shader_source->getShader(shader_name, (MaterialType)layer->material_type, NDT_ALLFACES); + material.MaterialType = shader_source->getShaderInfo(shader_id).material; + layer->applyMaterialOptionsWithShaders(material); + } else { layer->applyMaterialOptions(material); - if (mattype) { - material.MaterialType = *mattype; } + material.MaterialTypeParam = 0.0f; if (layer->animation_frame_count > 1) { const FrameSpec &animation_frame = (*layer->frames)[0]; material.setTexture(0, animation_frame.texture); } else { material.setTexture(0, layer->texture); } - if (use_shaders) { - if (layer->normal_texture) { - if (layer->animation_frame_count > 1) { - const FrameSpec &animation_frame = (*layer->frames)[0]; - material.setTexture(1, animation_frame.normal_texture); - } else - material.setTexture(1, layer->normal_texture); - } - material.setTexture(2, layer->flags_texture); - } - if (apply_scale && tile->world_aligned) { + if (tile->world_aligned) { u32 n = buf->getVertexCount(); for (u32 k = 0; k != n; ++k) buf->getTCoords(k) /= layer->scale; diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h index 2aae8586f..d06f58d78 100644 --- a/src/client/wieldmesh.h +++ b/src/client/wieldmesh.h @@ -133,14 +133,3 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result); scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename, const std::string &overlay_name); - -/*! - * Applies overlays, textures and optionally materials to the given mesh and - * extracts tile colors for colorization. - * \param mattype overrides the buffer's material type, but can also - * be NULL to leave the original material. - * \param colors returns the colors of the mesh buffers in the mesh. - */ -void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, bool use_shaders, - bool set_material, const video::E_MATERIAL_TYPE *mattype, - std::vector *colors, bool apply_scale = false); diff --git a/src/clientiface.cpp b/src/clientiface.cpp index d1ddf520f..cffd916fa 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -472,20 +472,14 @@ void RemoteClient::notifyEvent(ClientStateEvent event) { case CSE_AuthAccept: m_state = CS_AwaitingInit2; - if (chosen_mech == AUTH_MECHANISM_SRP || - chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; case CSE_Disconnect: m_state = CS_Disconnecting; break; case CSE_SetDenied: m_state = CS_Denied; - if (chosen_mech == AUTH_MECHANISM_SRP || - chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; default: myerror << "HelloSent: Invalid client state transition! " << event; @@ -561,9 +555,7 @@ void RemoteClient::notifyEvent(ClientStateEvent event) break; case CSE_SudoSuccess: m_state = CS_SudoMode; - if (chosen_mech == AUTH_MECHANISM_SRP) - srp_verifier_delete((SRPVerifier *) auth_data); - chosen_mech = AUTH_MECHANISM_NONE; + resetChosenMech(); break; /* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */ default: @@ -596,6 +588,15 @@ void RemoteClient::notifyEvent(ClientStateEvent event) } } +void RemoteClient::resetChosenMech() +{ + if (auth_data) { + srp_verifier_delete((SRPVerifier *) auth_data); + auth_data = nullptr; + } + chosen_mech = AUTH_MECHANISM_NONE; +} + u64 RemoteClient::uptime() const { return porting::getTimeS() - m_connection_time; @@ -714,6 +715,59 @@ void ClientInterface::sendToAll(NetworkPacket *pkt) } } +void ClientInterface::sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt, + u16 min_proto_ver) +{ + RecursiveMutexAutoLock clientslock(m_clients_mutex); + for (auto &client_it : m_clients) { + RemoteClient *client = client_it.second; + NetworkPacket *pkt_to_send = nullptr; + + if (client->net_proto_version >= min_proto_ver) { + pkt_to_send = pkt; + } else if (client->net_proto_version != 0) { + pkt_to_send = legacypkt; + } else { + // This will happen if a client is connecting when sendToAllCompat + // is called, this can safely be ignored. + continue; + } + + m_con->Send(client->peer_id, + clientCommandFactoryTable[pkt_to_send->getCommand()].channel, + pkt_to_send, + clientCommandFactoryTable[pkt_to_send->getCommand()].reliable); + } +} + +void ClientInterface::oldSendToAll(NetworkPacket *pkt) +{ + RecursiveMutexAutoLock clientslock(m_clients_mutex); + for (auto &client_it : m_clients) { + RemoteClient *client = client_it.second; + + if (client->net_proto_version != 0 && client->net_proto_version < 37) { + m_con->Send(client->peer_id, + clientCommandFactoryTable[pkt->getCommand()].channel, pkt, + clientCommandFactoryTable[pkt->getCommand()].reliable); + } + } +} + +void ClientInterface::newSendToAll(NetworkPacket *pkt) +{ + RecursiveMutexAutoLock clientslock(m_clients_mutex); + for (auto &client_it : m_clients) { + RemoteClient *client = client_it.second; + + if (client->net_proto_version > 36) { + m_con->Send(client->peer_id, + clientCommandFactoryTable[pkt->getCommand()].channel, pkt, + clientCommandFactoryTable[pkt->getCommand()].reliable); + } + } +} + RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min) { RecursiveMutexAutoLock clientslock(m_clients_mutex); diff --git a/src/clientiface.h b/src/clientiface.h index 75aa95367..b619158aa 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -242,6 +242,8 @@ public: u32 allowed_auth_mechs = 0; u32 allowed_sudo_mechs = 0; + void resetChosenMech(); + bool isSudoMechAllowed(AuthMechanism mech) { return allowed_sudo_mechs & mech; } bool isMechAllowed(AuthMechanism mech) @@ -332,7 +334,15 @@ public: m_version_major = major; m_version_minor = minor; m_version_patch = patch; - m_full_version = full; + m_full_version = full.c_str(); + const size_t pos = full.find('\x00'); + if (pos != std::string::npos) { + m_platform = full.substr(pos + 1).c_str(); + + const size_t pos2 = full.find('\x00', pos + 1); + if (pos2 != std::string::npos) + m_sysinfo = full.substr(pos2 + 1); + } } /* read version information */ @@ -340,6 +350,9 @@ public: u8 getMinor() const { return m_version_minor; } u8 getPatch() const { return m_version_patch; } const std::string &getFullVer() const { return m_full_version; } + + const std::string &getPlatform() const { return m_platform; } + const std::string &getSysInfo() const { return m_sysinfo; } void setLangCode(const std::string &code) { m_lang_code = code; } const std::string &getLangCode() const { return m_lang_code; } @@ -426,6 +439,8 @@ private: u8 m_version_patch = 0; std::string m_full_version = "unknown"; + std::string m_platform = "unknown"; + std::string m_sysinfo = "unknown"; u16 m_deployed_compression = 0; @@ -465,6 +480,9 @@ public: /* send to all clients */ void sendToAll(NetworkPacket *pkt); + void sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt, u16 min_proto_ver); + void oldSendToAll(NetworkPacket *pkt); + void newSendToAll(NetworkPacket *pkt); /* delete a client */ void DeleteClient(session_t peer_id); diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index cf436d6dc..04cf0fdb3 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -15,11 +15,14 @@ #define BUILD_TYPE "@CMAKE_BUILD_TYPE@" #define ICON_DIR "@ICONDIR@" #cmakedefine01 RUN_IN_PLACE +#cmakedefine01 DEVELOPMENT_BUILD +#cmakedefine01 ENABLE_UPDATE_CHECKER #cmakedefine01 USE_GETTEXT #cmakedefine01 USE_CURL #cmakedefine01 USE_SOUND #cmakedefine01 USE_CURSES #cmakedefine01 USE_LEVELDB +#cmakedefine01 USE_SQLITE #cmakedefine01 USE_LUAJIT #cmakedefine01 USE_POSTGRESQL #cmakedefine01 USE_PROMETHEUS diff --git a/src/config.h b/src/config.h index 5e1164642..0bd299a1a 100644 --- a/src/config.h +++ b/src/config.h @@ -11,10 +11,11 @@ #if defined USE_CMAKE_CONFIG_H #include "cmake_config.h" -#elif defined (__ANDROID__) - #define PROJECT_NAME "minetest" - #define PROJECT_NAME_C "Minetest" +#elif defined(__ANDROID__) || defined(__APPLE__) + #define PROJECT_NAME "multicraft" + #define PROJECT_NAME_C "MultiCraft" #define STATIC_SHAREDIR "" + #define ENABLE_UPDATE_CHECKER 1 #define VERSION_STRING STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) STR(VERSION_EXTRA) #ifdef NDEBUG #define BUILD_TYPE "Release" diff --git a/src/constants.h b/src/constants.h index c78d2fb81..fa476fe55 100644 --- a/src/constants.h +++ b/src/constants.h @@ -110,4 +110,8 @@ with this program; if not, write to the Free Software Foundation, Inc., GUI related things */ -#define TTF_DEFAULT_FONT_SIZE (16) +#if defined(__ANDROID__) || defined(__IOS__) +#define TTF_DEFAULT_FONT_SIZE (13) +#else +#define TTF_DEFAULT_FONT_SIZE (18) +#endif diff --git a/src/content/packages.cpp b/src/content/packages.cpp new file mode 100644 index 000000000..531c3d5d6 --- /dev/null +++ b/src/content/packages.cpp @@ -0,0 +1,69 @@ +/* +Minetest +Copyright (C) 2018 rubenwardy + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 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 "content/packages.h" +#include "log.h" +#include "filesys.h" +#include "porting.h" +#include "settings.h" +#include "content/mods.h" +#include "content/subgames.h" + +std::string Package::getDownloadURL(const std::string &baseURL) const +{ + return baseURL + "/packages/" + author + "/" + name + "/releases/" + + std::to_string(release) + "/download/"; +} + +#if USE_CURL +std::vector getPackagesFromURL(const std::string &url) +{ + std::vector extra_headers; + extra_headers.emplace_back("Accept: application/json"); + + Json::Value json = fetchJsonValue(url, &extra_headers); + if (!json.isArray()) { + errorstream << "Invalid JSON download " << std::endl; + return std::vector(); + } + + std::vector packages; + + // Note: `unsigned int` is required to index JSON + for (unsigned int i = 0; i < json.size(); ++i) { + Package package; + + package.author = json[i]["author"].asString(); + package.name = json[i]["name"].asString(); + package.title = json[i]["title"].asString(); + package.type = json[i]["type"].asString(); + package.shortDesc = json[i]["shortDesc"].asString(); + package.release = json[i]["release"].asInt(); + if (json[i].isMember("thumbnail")) + package.thumbnail = json[i]["thumbnail"].asString(); + + if (package.valid()) + packages.push_back(package); + else + errorstream << "Invalid package at " << i << std::endl; + } + + return packages; +} +#endif diff --git a/src/content/packages.h b/src/content/packages.h new file mode 100644 index 000000000..a6098e2b5 --- /dev/null +++ b/src/content/packages.h @@ -0,0 +1,52 @@ +/* +Minetest +Copyright (C) 2018 rubenwardy + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3.0 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 "config.h" +#include "convert_json.h" +#include "irrlichttypes.h" + +struct Package +{ + std::string author; + std::string name; // Technical name + std::string title; + std::string type; // One of "mod", "game", or "txp" + + std::string shortDesc; + u32 release; + std::string thumbnail; + + bool valid() const + { + return !(author.empty() || name.empty() || title.empty() || + type.empty() || release <= 0); + } + + std::string getDownloadURL(const std::string &baseURL) const; +}; + +#if USE_CURL +std::vector getPackagesFromURL(const std::string &url); +#else +inline std::vector getPackagesFromURL(const std::string &url) +{ + return std::vector(); +} +#endif diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index 3e476556c..c0bdf5639 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -37,9 +37,10 @@ with this program; if not, write to the Free Software Foundation, Inc., namespace { -bool getGameMinetestConfig(const std::string &game_path, Settings &conf) +bool getGameMinetestConfig( + const std::string &game_path, Settings &conf, const std::string &file) { - std::string conf_path = game_path + DIR_DELIM + "minetest.conf"; + std::string conf_path = game_path + DIR_DELIM + file; return conf.readConfigFile(conf_path.c_str()); } @@ -136,13 +137,17 @@ SubgameSpec findSubgame(const std::string &id) if (conf.exists("release")) game_release = conf.getS32("release"); + bool moddable = true; + if (conf.exists("moddable")) + moddable = conf.getBool("moddable"); + std::string menuicon_path; #ifndef SERVER menuicon_path = getImagePath( game_path + DIR_DELIM + "menu" + DIR_DELIM + "icon.png"); #endif return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name, - menuicon_path, game_author, game_release); + menuicon_path, game_author, game_release, moddable); } SubgameSpec findWorldSubgame(const std::string &world_path) @@ -341,7 +346,8 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, game_settings = Settings::createLayer(SL_GAME); } - getGameMinetestConfig(gamespec.path, *game_settings); + getGameMinetestConfig(gamespec.path, *game_settings, "minetest.conf"); + getGameMinetestConfig(gamespec.path, *game_settings, "multicraft.conf"); game_settings->removeSecureSettings(); infostream << "Initializing world at " << final_path << std::endl; @@ -355,10 +361,17 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, conf.set("world_name", name); conf.set("gameid", gamespec.id); +#if !defined(__ANDROID__) && !defined(__APPLE__) conf.set("backend", "sqlite3"); conf.set("player_backend", "sqlite3"); conf.set("auth_backend", "sqlite3"); conf.set("mod_storage_backend", "sqlite3"); +#else + conf.set("backend", "leveldb"); + conf.set("player_backend", "leveldb"); + conf.set("auth_backend", "leveldb"); + conf.set("mod_storage_backend", "leveldb"); +#endif conf.setBool("creative_mode", g_settings->getBool("creative_mode")); conf.setBool("enable_damage", g_settings->getBool("enable_damage")); diff --git a/src/content/subgames.h b/src/content/subgames.h index cbc3da619..fa0fe80dc 100644 --- a/src/content/subgames.h +++ b/src/content/subgames.h @@ -35,6 +35,7 @@ struct SubgameSpec std::string gamemods_path; std::set addon_mods_paths; std::string menuicon_path; + bool moddable; SubgameSpec(const std::string &id = "", const std::string &path = "", const std::string &gamemods_path = "", @@ -42,11 +43,12 @@ struct SubgameSpec std::set(), const std::string &name = "", const std::string &menuicon_path = "", - const std::string &author = "", int release = 0) : + const std::string &author = "", int release = 0, + const bool moddable = true) : id(id), name(name), author(author), release(release), path(path), gamemods_path(gamemods_path), addon_mods_paths(addon_mods_paths), - menuicon_path(menuicon_path) + menuicon_path(menuicon_path), moddable(moddable) { } diff --git a/src/debug.cpp b/src/debug.cpp index f8c92b7b4..339537fc5 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -56,7 +56,14 @@ void sanity_check_fn(const char *assertion, const char *file, errorstream << file << ":" << line << ": " << function << ": An engine assumption '" << assertion << "' failed." << std::endl; +#if defined(__ANDROID__) || defined(__IOS__) + std::string capture = "An engine assumption failed: \"" + std::string(assertion) + + "\" in file: " + std::string(file) + ":" + std::to_string(line) + + " (" + std::string(function) + ")"; + porting::finishGame(capture); +#else abort(); +#endif } void fatal_error_fn(const char *msg, const char *file, @@ -71,7 +78,14 @@ void fatal_error_fn(const char *msg, const char *file, errorstream << file << ":" << line << ": " << function << ": A fatal error occurred: " << msg << std::endl; +#if defined(__ANDROID__) || defined(__IOS__) + std::string capture = "A fatal error occurred: \"" + std::string(msg) + + "\" in file: " + std::string(file) + ":" + std::to_string(line) + + " (" + std::string(function) + ")"; + porting::finishGame(capture); +#else abort(); +#endif } #ifdef _MSC_VER diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 87e3d530d..fcf14f5d3 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -23,24 +23,37 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "config.h" #include "constants.h" -#include "porting.h" #include "mapgen/mapgen.h" // Mapgen::setDefaultSettings #include "util/string.h" +#ifdef __ANDROID__ +#include "client/renderingengine.h" +#endif + +#ifdef __APPLE__ +#ifdef __IOS__ +#import "SDVersion.h" +#else +#import +#endif +#endif + void set_default_settings() { - Settings *settings = Settings::createLayer(SL_DEFAULTS); + Settings *settings = Settings::getLayer(SL_DEFAULTS); + if (settings == nullptr) + settings = Settings::createLayer(SL_DEFAULTS); // Client and server settings->setDefault("language", ""); settings->setDefault("name", ""); settings->setDefault("bind_address", ""); - settings->setDefault("serverlist_url", "servers.minetest.net"); + settings->setDefault("serverlist_url", "servers.multicraft.world"); // Client settings->setDefault("address", ""); settings->setDefault("enable_sound", "true"); - settings->setDefault("sound_volume", "0.8"); + settings->setDefault("sound_volume", "1.0"); settings->setDefault("mute_sound", "false"); settings->setDefault("enable_mesh_cache", "false"); settings->setDefault("mesh_generation_interval", "0"); @@ -61,9 +74,9 @@ void set_default_settings() settings->setDefault("curl_file_download_timeout", "300000"); settings->setDefault("curl_verify_cert", "true"); settings->setDefault("enable_remote_media_server", "true"); - settings->setDefault("enable_client_modding", "false"); + settings->setDefault("enable_client_modding", "true"); settings->setDefault("max_out_chat_queue_size", "20"); - settings->setDefault("pause_on_lost_focus", "false"); + settings->setDefault("pause_on_lost_focus", "true"); settings->setDefault("enable_register_confirmation", "true"); settings->setDefault("clickable_chat_weblinks", "false"); settings->setDefault("chat_weblink_color", "#8888FF"); @@ -176,7 +189,7 @@ void set_default_settings() settings->setDefault("near_plane", "0.1"); #endif settings->setDefault("screen_w", "1024"); - settings->setDefault("screen_h", "600"); + settings->setDefault("screen_h", "768"); settings->setDefault("autosave_screensize", "true"); settings->setDefault("fullscreen", "false"); settings->setDefault("vsync", "false"); @@ -207,12 +220,13 @@ void set_default_settings() settings->setDefault("cinematic_camera_smoothing", "0.7"); settings->setDefault("enable_clouds", "true"); settings->setDefault("view_bobbing_amount", "1.0"); - settings->setDefault("fall_bobbing_amount", "0.03"); + settings->setDefault("fall_bobbing_amount", "1.0"); settings->setDefault("enable_3d_clouds", "true"); settings->setDefault("cloud_radius", "12"); settings->setDefault("menu_clouds", "true"); settings->setDefault("opaque_water", "false"); settings->setDefault("console_height", "0.6"); + settings->setDefault("console_message_height", "0.25"); settings->setDefault("console_color", "(0,0,0)"); settings->setDefault("console_alpha", "200"); settings->setDefault("formspec_fullscreen_bg_color", "(0,0,0)"); @@ -220,21 +234,24 @@ void set_default_settings() settings->setDefault("formspec_default_bg_color", "(0,0,0)"); settings->setDefault("formspec_default_bg_opacity", "140"); settings->setDefault("selectionbox_color", "(0,0,0)"); - settings->setDefault("selectionbox_width", "2"); + settings->setDefault("selectionbox_width", "4"); settings->setDefault("node_highlighting", "box"); settings->setDefault("crosshair_color", "(255,255,255)"); settings->setDefault("crosshair_alpha", "255"); - settings->setDefault("recent_chat_messages", "6"); + settings->setDefault("recent_chat_messages", "10"); settings->setDefault("hud_scaling", "1.0"); settings->setDefault("gui_scaling", "1.0"); settings->setDefault("gui_scaling_filter", "false"); settings->setDefault("gui_scaling_filter_txr2img", "true"); settings->setDefault("desynchronize_mapblock_texture_animation", "true"); settings->setDefault("hud_hotbar_max_width", "1.0"); + settings->setDefault("hud_move_upwards", "0"); + settings->setDefault("round_screen", "0"); settings->setDefault("enable_local_map_saving", "false"); settings->setDefault("show_entity_selectionbox", "false"); + settings->setDefault("transparency_sorting", "true"); settings->setDefault("texture_clean_transparent", "false"); - settings->setDefault("texture_min_size", "64"); + settings->setDefault("texture_min_size", "0"); settings->setDefault("ambient_occlusion_gamma", "1.8"); #if ENABLE_GLES settings->setDefault("enable_shaders", "false"); @@ -243,11 +260,11 @@ void set_default_settings() #endif settings->setDefault("enable_particles", "true"); settings->setDefault("arm_inertia", "true"); - settings->setDefault("show_nametag_backgrounds", "true"); + settings->setDefault("show_nametag_backgrounds", "false"); settings->setDefault("enable_minimap", "true"); settings->setDefault("minimap_shape_round", "true"); - settings->setDefault("minimap_double_scan_height", "true"); + settings->setDefault("minimap_double_scan_height", "false"); // Effects settings->setDefault("directional_colored_fog", "true"); @@ -286,39 +303,53 @@ void set_default_settings() settings->setDefault("aux1_descends", "false"); settings->setDefault("doubletap_jump", "false"); settings->setDefault("always_fly_fast", "true"); -#ifdef HAVE_TOUCHSCREENGUI - settings->setDefault("autojump", "true"); -#else settings->setDefault("autojump", "false"); -#endif settings->setDefault("continuous_forward", "false"); - settings->setDefault("enable_joysticks", "false"); +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + settings->setDefault("enable_joysticks", "true"); +#else + settings->setDefault("enable_joysticks", "false"); +#endif settings->setDefault("joystick_id", "0"); settings->setDefault("joystick_type", ""); settings->setDefault("repeat_joystick_button_time", "0.17"); settings->setDefault("joystick_frustum_sensitivity", "170"); - settings->setDefault("joystick_deadzone", "2048"); + settings->setDefault("joystick_deadzone", "4096"); // Main menu settings->setDefault("main_menu_path", ""); settings->setDefault("serverlist_file", "favoriteservers.json"); // General font settings + std::string MultiCraftFont = porting::getDataPath("fonts" DIR_DELIM "MultiCraftFont.ttf"); + +#if !defined(__ANDROID__) && !defined(__APPLE__) settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "Arimo-Regular.ttf")); settings->setDefault("font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Arimo-Italic.ttf")); settings->setDefault("font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Arimo-Bold.ttf")); settings->setDefault("font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Arimo-BoldItalic.ttf")); +#else + settings->setDefault("font_path", MultiCraftFont); + settings->setDefault("font_path_italic", MultiCraftFont); + settings->setDefault("font_path_bold", MultiCraftFont); + settings->setDefault("font_path_bold_italic", MultiCraftFont); +#endif settings->setDefault("font_bold", "false"); settings->setDefault("font_italic", "false"); settings->setDefault("font_shadow", "1"); settings->setDefault("font_shadow_alpha", "127"); settings->setDefault("font_size_divisible_by", "1"); - settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "Cousine-Regular.ttf")); - settings->setDefault("mono_font_path_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-Italic.ttf")); - settings->setDefault("mono_font_path_bold", porting::getDataPath("fonts" DIR_DELIM "Cousine-Bold.ttf")); - settings->setDefault("mono_font_path_bold_italic", porting::getDataPath("fonts" DIR_DELIM "Cousine-BoldItalic.ttf")); + settings->setDefault("mono_font_path", MultiCraftFont); + settings->setDefault("mono_font_path_italic", MultiCraftFont); + settings->setDefault("mono_font_path_bold", MultiCraftFont); + settings->setDefault("mono_font_path_bold_italic", MultiCraftFont); settings->setDefault("mono_font_size_divisible_by", "1"); + +#if !defined(__ANDROID__) && !defined(__APPLE__) settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf")); +#else + settings->setDefault("fallback_font_path", MultiCraftFont); +#endif std::string font_size_str = std::to_string(TTF_DEFAULT_FONT_SIZE); settings->setDefault("font_size", font_size_str); @@ -326,7 +357,7 @@ void set_default_settings() settings->setDefault("chat_font_size", "0"); // Default "font_size" // ContentDB - settings->setDefault("contentdb_url", "https://content.minetest.net"); + settings->setDefault("contentdb_url", "https://content.multicraft.world"); settings->setDefault("contentdb_max_concurrent_downloads", "3"); #ifdef __ANDROID__ @@ -335,10 +366,20 @@ void set_default_settings() settings->setDefault("contentdb_flag_blacklist", "nonfree, desktop_default"); #endif + settings->setDefault("update_information_url", "https://updates.multicraft.world/app.json"); + #if ENABLE_UPDATE_CHECKER + settings->setDefault("update_last_checked", ""); + #else + settings->setDefault("update_last_checked", "disabled"); + #endif // Server + settings->setDefault("compat_player_model", "character.b3d,3d_armor_character.b3d,skinsdb_3d_armor_character_5.b3d"); + settings->setDefault("compat_send_original_model", "true"); + settings->setDefault("disable_texture_packs", "false"); settings->setDefault("disable_escape_sequences", "false"); - settings->setDefault("strip_color_codes", "false"); + settings->setDefault("strip_color_codes", "true"); + settings->setDefault("announce_mt", "true"); #if USE_PROMETHEUS settings->setDefault("prometheus_listener_address", "127.0.0.1:30000"); #endif @@ -347,13 +388,14 @@ void set_default_settings() settings->setDefault("enable_ipv6", "true"); settings->setDefault("ipv6_server", "false"); settings->setDefault("max_packets_per_iteration","1024"); - settings->setDefault("port", "30000"); + settings->setDefault("port", "40000"); + settings->setDefault("enable_protocol_compat", "true"); settings->setDefault("strict_protocol_version_checking", "false"); settings->setDefault("player_transfer_distance", "0"); settings->setDefault("max_simultaneous_block_sends_per_client", "40"); settings->setDefault("time_send_interval", "5"); - settings->setDefault("default_game", "minetest"); + settings->setDefault("default_game", "default"); settings->setDefault("motd", ""); settings->setDefault("max_users", "15"); settings->setDefault("creative_mode", "false"); @@ -361,7 +403,7 @@ void set_default_settings() settings->setDefault("default_password", ""); settings->setDefault("default_privs", "interact, shout"); settings->setDefault("enable_pvp", "true"); - settings->setDefault("enable_mod_channels", "false"); + settings->setDefault("enable_mod_channels", "true"); settings->setDefault("disallow_empty_password", "false"); settings->setDefault("disable_anticheat", "false"); settings->setDefault("enable_rollback_recording", "false"); @@ -371,7 +413,7 @@ void set_default_settings() settings->setDefault("kick_msg_crash", "This server has experienced an internal error. You will now be disconnected."); settings->setDefault("ask_reconnect_on_crash", "false"); - settings->setDefault("chat_message_format", "<@name> @message"); + settings->setDefault("chat_message_format", "@name: @message"); settings->setDefault("profiler_print_interval", "0"); settings->setDefault("active_object_send_range_blocks", "8"); settings->setDefault("active_block_range", "4"); @@ -380,7 +422,7 @@ void set_default_settings() settings->setDefault("max_block_send_distance", "12"); settings->setDefault("block_send_optimize_distance", "4"); settings->setDefault("server_side_occlusion_culling", "true"); - settings->setDefault("csm_restriction_flags", "62"); + settings->setDefault("csm_restriction_flags", "60"); settings->setDefault("csm_restriction_noderange", "0"); settings->setDefault("max_clearobjects_extra_loaded_blocks", "4096"); settings->setDefault("time_speed", "72"); @@ -389,7 +431,7 @@ void set_default_settings() settings->setDefault("max_objects_per_block", "64"); settings->setDefault("server_map_save_interval", "5.3"); settings->setDefault("chat_message_max_size", "500"); - settings->setDefault("chat_message_limit_per_10sec", "8.0"); + settings->setDefault("chat_message_limit_per_10sec", "5.0"); settings->setDefault("chat_message_limit_trigger_kick", "50"); settings->setDefault("sqlite_synchronous", "2"); settings->setDefault("map_compression_level_disk", "-1"); @@ -402,13 +444,14 @@ void set_default_settings() settings->setDefault("nodetimer_interval", "0.2"); settings->setDefault("ignore_world_load_errors", "false"); settings->setDefault("remote_media", ""); - settings->setDefault("debug_log_level", "action"); + settings->setDefault("debug_log_level", "warning"); settings->setDefault("debug_log_size_max", "50"); settings->setDefault("chat_log_level", "error"); settings->setDefault("emergequeue_limit_total", "1024"); settings->setDefault("emergequeue_limit_diskonly", "128"); settings->setDefault("emergequeue_limit_generate", "128"); settings->setDefault("num_emerge_threads", "1"); + settings->setDefault("log_mod_memory_usage_on_load", "false"); settings->setDefault("secure.enable_security", "true"); settings->setDefault("secure.trusted_mods", ""); settings->setDefault("secure.http_mods", ""); @@ -416,7 +459,7 @@ void set_default_settings() // Physics settings->setDefault("movement_acceleration_default", "3"); settings->setDefault("movement_acceleration_air", "2"); - settings->setDefault("movement_acceleration_fast", "10"); + settings->setDefault("movement_acceleration_fast", "20"); settings->setDefault("movement_speed_walk", "4"); settings->setDefault("movement_speed_crouch", "1.35"); settings->setDefault("movement_speed_fast", "20"); @@ -424,7 +467,7 @@ void set_default_settings() settings->setDefault("movement_speed_jump", "6.5"); settings->setDefault("movement_liquid_fluidity", "1"); settings->setDefault("movement_liquid_fluidity_smooth", "0.5"); - settings->setDefault("movement_liquid_sink", "10"); + settings->setDefault("movement_liquid_sink", "20"); settings->setDefault("movement_gravity", "9.81"); // Liquids @@ -433,7 +476,7 @@ void set_default_settings() settings->setDefault("liquid_update", "1.0"); // Mapgen - settings->setDefault("mg_name", "v7"); + settings->setDefault("mg_name", "v7p"); settings->setDefault("water_level", "1"); settings->setDefault("mapgen_limit", "31007"); settings->setDefault("chunksize", "5"); @@ -453,60 +496,249 @@ void set_default_settings() settings->setDefault("screen_dpi", "72"); settings->setDefault("display_density_factor", "1"); + settings->setDefault("device_is_tablet", "false"); + // Altered settings for macOS -#if defined(__MACH__) && defined(__APPLE__) - settings->setDefault("keymap_sneak", "KEY_SHIFT"); - settings->setDefault("fps_max", "0"); +#if defined(__MACH__) && defined(__APPLE__) && !defined(__IOS__) + settings->setDefault("screen_w", "0"); + settings->setDefault("screen_h", "0"); + settings->setDefault("keymap_camera_mode", "KEY_KEY_C"); + settings->setDefault("vsync", "true"); + + int ScaleFactor = (int) [NSScreen mainScreen].backingScaleFactor; + settings->setDefault("screen_dpi", std::to_string(ScaleFactor * 72)); + if (ScaleFactor >= 2) { + settings->setDefault("hud_scaling", "1.5"); + } else { + settings->setDefault("font_size", std::to_string(TTF_DEFAULT_FONT_SIZE - 2)); + settings->setDefault("hud_scaling", "1.25"); + settings->setDefault("gui_scaling", "1.5"); + } + + // Shaders work but may reduce performance on iGPU + settings->setDefault("enable_shaders", "false"); #endif #ifdef HAVE_TOUCHSCREENGUI settings->setDefault("touchtarget", "true"); - settings->setDefault("touchscreen_threshold","20"); - settings->setDefault("fixed_virtual_joystick", "false"); + settings->setDefault("touchscreen_threshold", "20"); + settings->setDefault("touch_sensitivity", "0.2"); + settings->setDefault("fixed_virtual_joystick", "true"); settings->setDefault("virtual_joystick_triggers_aux1", "false"); + settings->setDefault("fast_move", "true"); #endif - // Altered settings for Android -#ifdef __ANDROID__ - settings->setDefault("screen_w", "0"); - settings->setDefault("screen_h", "0"); + + // Mobile Platform +#if defined(__ANDROID__) || defined(__IOS__) settings->setDefault("fullscreen", "true"); - settings->setDefault("smooth_lighting", "false"); - settings->setDefault("performance_tradeoffs", "true"); - settings->setDefault("max_simultaneous_block_sends_per_client", "10"); - settings->setDefault("emergequeue_limit_diskonly", "16"); - settings->setDefault("emergequeue_limit_generate", "16"); - settings->setDefault("max_block_generate_distance", "5"); - settings->setDefault("enable_3d_clouds", "false"); - settings->setDefault("fps_max", "30"); - settings->setDefault("fps_max_unfocused", "10"); - settings->setDefault("max_objects_per_block", "20"); - settings->setDefault("sqlite_synchronous", "1"); - settings->setDefault("map_compression_level_disk", "-1"); - settings->setDefault("map_compression_level_net", "-1"); - settings->setDefault("server_map_save_interval", "15"); - settings->setDefault("client_mapblock_limit", "1000"); - settings->setDefault("active_block_range", "2"); - settings->setDefault("viewing_range", "50"); - settings->setDefault("leaves_style", "simple"); - settings->setDefault("curl_verify_cert","false"); + settings->setDefault("emergequeue_limit_diskonly", "16"); + settings->setDefault("emergequeue_limit_generate", "16"); + settings->setDefault("curl_verify_cert", "false"); + settings->setDefault("max_objects_per_block", "16"); + settings->setDefault("doubletap_jump", "true"); + settings->setDefault("gui_scaling_filter_txr2img", "false"); + settings->setDefault("autosave_screensize", "false"); + settings->setDefault("recent_chat_messages", "6"); - // Apply settings according to screen size - float x_inches = (float) porting::getDisplaySize().X / - (160.f * porting::getDisplayDensity()); + // Set the optimal settings depending on the memory size [Android] | model [iOS] +#ifdef __ANDROID__ + float memoryMax = porting::getTotalSystemMemory(); - if (x_inches < 3.7f) { - settings->setDefault("hud_scaling", "0.6"); - settings->setDefault("font_size", "14"); - settings->setDefault("mono_font_size", "14"); - } else if (x_inches < 4.5f) { - settings->setDefault("hud_scaling", "0.7"); - settings->setDefault("font_size", "14"); - settings->setDefault("mono_font_size", "14"); - } else if (x_inches < 6.0f) { - settings->setDefault("hud_scaling", "0.85"); - settings->setDefault("font_size", "14"); - settings->setDefault("mono_font_size", "14"); + if (memoryMax < 2) { + // minimal settings for less than 2GB RAM +#elif __IOS__ + if (false) { + // obsolete +#endif + settings->setDefault("client_unload_unused_data_timeout", "60"); + settings->setDefault("client_mapblock_limit", "50"); + settings->setDefault("fps_max", "30"); + settings->setDefault("fps_max_unfocused", "10"); + settings->setDefault("viewing_range", "30"); + settings->setDefault("smooth_lighting", "false"); + settings->setDefault("enable_3d_clouds", "false"); + settings->setDefault("active_object_send_range_blocks", "1"); + settings->setDefault("active_block_range", "1"); + settings->setDefault("dedicated_server_step", "0.2"); + settings->setDefault("abm_interval", "3.0"); + settings->setDefault("chunksize", "3"); + settings->setDefault("max_block_generate_distance", "1"); + settings->setDefault("arm_inertia", "false"); +#ifdef __ANDROID__ + } else if (memoryMax >= 2 && memoryMax < 4) { + // low settings for 2-4GB RAM +#elif __IOS__ + } else if (!IOS_VERSION_AVAILABLE("13.0")) { + // low settings +#endif + settings->setDefault("client_unload_unused_data_timeout", "120"); + settings->setDefault("client_mapblock_limit", "200"); + settings->setDefault("fps_max", "35"); + settings->setDefault("fps_max_unfocused", "10"); + settings->setDefault("viewing_range", "40"); + settings->setDefault("smooth_lighting", "false"); + settings->setDefault("active_object_send_range_blocks", "1"); + settings->setDefault("active_block_range", "2"); + settings->setDefault("dedicated_server_step", "0.2"); + settings->setDefault("abm_interval", "2.0"); + settings->setDefault("chunksize", "3"); + settings->setDefault("max_block_generate_distance", "2"); + settings->setDefault("arm_inertia", "false"); +#ifdef __ANDROID__ + } else if (memoryMax >= 4 && memoryMax < 6) { + // medium settings for 4.1-6GB RAM +#elif __IOS__ + } else if (([SDVersion deviceVersion] == iPhone6S) || ([SDVersion deviceVersion] == iPhone6SPlus) || ([SDVersion deviceVersion] == iPhoneSE) || + ([SDVersion deviceVersion] == iPhone7) || ([SDVersion deviceVersion] == iPhone7Plus) || + ([SDVersion deviceVersion] == iPadMini4) || ([SDVersion deviceVersion] == iPadAir2) || ([SDVersion deviceVersion] == iPad5)) + { + // medium settings +#endif + settings->setDefault("client_unload_unused_data_timeout", "180"); + settings->setDefault("client_mapblock_limit", "300"); + settings->setDefault("fps_max", "35"); + settings->setDefault("viewing_range", "60"); + settings->setDefault("active_object_send_range_blocks", "2"); + settings->setDefault("active_block_range", "2"); + settings->setDefault("max_block_generate_distance", "3"); + } else { + // high settings + settings->setDefault("client_mapblock_limit", "500"); + settings->setDefault("viewing_range", "125"); + settings->setDefault("active_object_send_range_blocks", "4"); + settings->setDefault("max_block_generate_distance", "5"); + + // enable visual shader effects + settings->setDefault("enable_waving_water", "true"); + settings->setDefault("enable_waving_leaves", "true"); + settings->setDefault("enable_waving_plants", "true"); } - // Tablets >= 6.0 use non-Android defaults for these settings + + // Android Settings +#ifdef __ANDROID__ + // Switch to olges2 with shaders on powerful Android devices + if (memoryMax >= 6) { + settings->setDefault("video_driver", "ogles2"); + settings->setDefault("enable_shaders", "true"); + } else { + settings->setDefault("video_driver", "ogles1"); + settings->setDefault("enable_shaders", "false"); + } + + v2u32 window_size = RenderingEngine::getDisplaySize(); + if (window_size.X > 0) { + float x_inches = window_size.X / (160.f * RenderingEngine::getDisplayDensity()); + if (x_inches <= 3.7) { + // small 4" phones + g_settings->setDefault("hud_scaling", "0.55"); + g_settings->setDefault("touch_sensitivity", "0.3"); + } else if (x_inches > 3.7 && x_inches <= 4.5) { + // medium phones + g_settings->setDefault("hud_scaling", "0.6"); + g_settings->setDefault("selectionbox_width", "6"); + } else if (x_inches > 4.5 && x_inches <= 5.5) { + // large 6" phones + g_settings->setDefault("hud_scaling", "0.7"); + g_settings->setDefault("selectionbox_width", "6"); + } else if (x_inches > 5.5 && x_inches <= 6.5) { + // 7" tablets + g_settings->setDefault("hud_scaling", "0.9"); + g_settings->setDefault("selectionbox_width", "6"); + } else if (x_inches >= 7.0) { + settings->setDefault("device_is_tablet", "true"); + settings->setDefault("recent_chat_messages", "8"); + settings->setDefault("console_message_height", "0.4"); + } + + if (x_inches <= 4.5) { + settings->setDefault("font_size", std::to_string(TTF_DEFAULT_FONT_SIZE - 1)); + } else if (x_inches >= 7.0) { + settings->setDefault("font_size", std::to_string(TTF_DEFAULT_FONT_SIZE + 1)); + } + + // Settings for the Rounded or Cutout Screen + int RoundScreen = porting::getRoundScreen(); + if (RoundScreen > 0) + settings->setDefault("round_screen", std::to_string(RoundScreen)); + } +#endif // Android + + // iOS Settings +#ifdef __IOS__ + // Switch to olges2 with shaders in new iOS versions + if (IOS_VERSION_AVAILABLE("13.0")) { + settings->setDefault("video_driver", "ogles2"); + settings->setDefault("enable_shaders", "true"); + } else { + settings->setDefault("video_driver", "ogles1"); + settings->setDefault("enable_shaders", "false"); + } + + settings->setDefault("debug_log_level", "none"); + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + settings->setDefault("device_is_tablet", "true"); + settings->setDefault("recent_chat_messages", "8"); + settings->setDefault("console_message_height", "0.4"); + } + + // Set the size of the elements depending on the screen size + if SDVersion4Inch { + // 4" iPhone and iPod Touch + settings->setDefault("hud_scaling", "0.55"); + settings->setDefault("touch_sensitivity", "0.33"); + } else if SDVersion4and7Inch { + // 4.7" iPhone + settings->setDefault("hud_scaling", "0.6"); + settings->setDefault("touch_sensitivity", "0.27"); + } else if SDVersion5and5Inch { + // 5.5" iPhone Plus + settings->setDefault("hud_scaling", "0.65"); + settings->setDefault("touch_sensitivity", "0.3"); + } else if (SDVersion5and8Inch || SDVersion6and1Inch) { + // 5.8" and 6.1" iPhones + settings->setDefault("hud_scaling", "0.8"); + settings->setDefault("touch_sensitivity", "0.35"); + settings->setDefault("selectionbox_width", "6"); + } else if SDVersion6and5Inch { + // 6.5" iPhone + settings->setDefault("hud_scaling", "0.85"); + settings->setDefault("touch_sensitivity", "0.35"); + settings->setDefault("selectionbox_width", "6"); + } else if SDVersion7and9Inch { + // iPad mini + settings->setDefault("hud_scaling", "0.9"); + settings->setDefault("touch_sensitivity", "0.25"); + settings->setDefault("selectionbox_width", "6"); + } else if SDVersion8and3Inch { + settings->setDefault("touch_sensitivity", "0.25"); + settings->setDefault("selectionbox_width", "6"); + } else { + // iPad + settings->setDefault("touch_sensitivity", "0.3"); + settings->setDefault("selectionbox_width", "6"); + } + + if SDVersion4Inch { + settings->setDefault("font_size", std::to_string(TTF_DEFAULT_FONT_SIZE - 2)); + } else if (SDVersion4and7Inch || SDVersion5and5Inch) { + settings->setDefault("font_size", std::to_string(TTF_DEFAULT_FONT_SIZE - 1)); + } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && !SDVersion7and9Inch) { + settings->setDefault("font_size", std::to_string(TTF_DEFAULT_FONT_SIZE + 1)); + } + + // Settings for the Rounded Screen and Home Bar + if SDVersionRoundScreen { + int upwards = 25, round = 40; + if SDVersioniPhone12Series { + upwards = 20, round = 90; + } else if SDVersion8and3Inch { + upwards = 15, round = 20; + } + + settings->setDefault("hud_move_upwards", std::to_string(upwards)); + settings->setDefault("round_screen", std::to_string(round)); + } +#endif // iOS #endif } diff --git a/src/filesys.cpp b/src/filesys.cpp index 04ebd329a..cbf02db59 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -316,12 +316,13 @@ bool IsDirDelimiter(char c) bool RecursiveDelete(const std::string &path) { + infostream<<"Removing \""< paths; + paths.push_back(path); + fs::GetRecursiveSubPaths(path, paths, true, {}); + + // Go backwards to successfully delete the output of GetRecursiveSubPaths + for (int i = paths.size() - 1; i >= 0; i--) { + const std::string &p = paths[i]; + bool did = DeleteSingleFileOrEmptyDirectory(p); + if (!did) { + errorstream << "Failed to delete " << p << std::endl; + success = false; + } + } + + return success; +#endif } bool DeleteSingleFileOrEmptyDirectory(const std::string &path) @@ -387,7 +410,7 @@ std::string TempPath() configuration hardcodes mkstemp("/tmp/lua_XXXXXX"). */ -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__APPLE__) return porting::path_cache; #else return DIR_DELIM "tmp"; @@ -796,7 +819,8 @@ bool safeWriteToFile(const std::string &path, const std::string &content) } #ifndef SERVER -bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string &destination) +bool extractZipFile(io::IFileSystem *fs, const char *filename, + const std::string &destination, const char *password, std::string *errorMessage) { // Be careful here not to touch the global file hierarchy in Irrlicht // since this function needs to be thread-safe! @@ -814,6 +838,13 @@ bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string } irr_ptr opened_zip(zip_loader->createArchive(filename, false, false)); + if (opened_zip.get() == nullptr) { + if (errorMessage != nullptr) + *errorMessage = "failed to open zip file"; + return false; + } + + opened_zip->Password = core::stringc(password); const io::IFileList* files_in_zip = opened_zip->getFileList(); for (u32 i = 0; i < files_in_zip->getFileCount(); i++) { @@ -829,6 +860,14 @@ bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string irr_ptr toread(opened_zip->createAndOpenFile(i)); + if (toread.get() == nullptr) { + // Wrong password + fs->removeFileArchive(fs->getFileArchiveCount()-1); + if (errorMessage != nullptr) + *errorMessage = "invalid password"; + return false; + } + std::ofstream os(fullpath.c_str(), std::ios::binary); if (!os.good()) return false; diff --git a/src/filesys.h b/src/filesys.h index ccbf1cad2..232db8d05 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -138,7 +138,9 @@ const char *GetFilenameFromPath(const char *path); bool safeWriteToFile(const std::string &path, const std::string &content); #ifndef SERVER -bool extractZipFile(irr::io::IFileSystem *fs, const char *filename, const std::string &destination); +bool extractZipFile(irr::io::IFileSystem *fs, const char *filename, + const std::string &destination, const char *password = "", + std::string *errorMessage = nullptr); #endif bool ReadFile(const std::string &path, std::string &out); diff --git a/src/gettext.cpp b/src/gettext.cpp index be017f0de..7d24c6290 100644 --- a/src/gettext.cpp +++ b/src/gettext.cpp @@ -24,6 +24,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gettext.h" #include "util/string.h" #include "log.h" +#include "porting.h" + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include +#endif #if USE_GETTEXT && defined(_MSC_VER) #include @@ -131,6 +136,10 @@ void init_gettext(const char *path, const std::string &configured_language, setenv("LANG", configured_language.c_str(), 1); #endif +#if defined(__ANDROID__) || defined(__APPLE__) + setenv("LANG", configured_language.c_str(), 1); +#endif + // Reload locale with changed environment setlocale(LC_ALL, ""); #elif defined(_MSC_VER) @@ -207,6 +216,20 @@ void init_gettext(const char *path, const std::string &configured_language, } else { /* set current system default locale */ +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + SDL_Locale* locale = SDL_GetPreferredLocales(); + + if (locale) { + if (locale[0].language) { + char lang[3] = {0}; + strncpy(lang, locale[0].language, 2); + SDL_setenv("LANG", lang, 1); + SDL_setenv("LANGUAGE", lang, 1); + } + + SDL_free(locale); + } +#endif setlocale(LC_ALL, ""); } @@ -221,7 +244,7 @@ void init_gettext(const char *path, const std::string &configured_language, #endif #endif - std::string name = lowercase(PROJECT_NAME); + std::string name = "minetest";; infostream << "Gettext: domainname=\"" << name << "\" path=\"" << path << "\"" << std::endl; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 513b13e8e..ec9ecc38d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -19,7 +19,6 @@ set(gui_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/guiInventoryList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiItemImage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiScene.cpp ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h index 0b240b63e..9e613c269 100644 --- a/src/gui/StyleSpec.h +++ b/src/gui/StyleSpec.h @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/tile.h" // ITextureSource #include "client/fontengine.h" +#include "client/renderingengine.h" #include "debug.h" #include "irrlichttypes_extrabloated.h" #include "util/string.h" @@ -45,6 +46,7 @@ public: BGIMG_PRESSED, // Note: Deprecated property FGIMG, FGIMG_HOVERED, // Note: Deprecated property + FGIMG_MIDDLE, FGIMG_PRESSED, // Note: Deprecated property ALPHA, CONTENT_OFFSET, @@ -57,6 +59,12 @@ public: SOUND, SPACING, SIZE, + SCROLLBAR_BGIMG, + SCROLLBAR_THUMB_IMG, + SCROLLBAR_UP_IMG, + SCROLLBAR_DOWN_IMG, + SCROLLBAR_THUMB_TOP_IMG, + SCROLLBAR_THUMB_BOTTOM_IMG, NUM_PROPERTIES, NONE }; @@ -69,6 +77,32 @@ public: STATE_INVALID = 1 << 3, }; + // Used in guiConfirmRegistration.cpp, guiKeyChangeMenu.cpp and guiVolumeChange.h + static std::array getButtonStyle(const std::string texture_path = "", std::string color = "") { + std::array ret; + color = color != "" ? "_" + color : ""; + + const bool high_dpi = RenderingEngine::isHighDpi(); + const std::string x2 = high_dpi ? ".x2" : ""; + StyleSpec btn_spec; + btn_spec.set(BGIMG, texture_path + "gui/gui_button" + color + x2 + ".png"); + btn_spec.set(BGIMG_MIDDLE, high_dpi ? "48" : "32"); + btn_spec.set(BORDER, "false"); + btn_spec.set(PADDING, high_dpi ? "-30" : "-20"); + + ret[STATE_DEFAULT] = btn_spec; + + StyleSpec hovered_spec; + hovered_spec.set(BGIMG, texture_path + "gui/gui_button" + color + "_hovered" + x2 + ".png"); + ret[STATE_HOVERED] = hovered_spec; + + StyleSpec pressed_spec; + pressed_spec.set(BGIMG, texture_path + "gui/gui_button" + color + "_pressed" + x2 + ".png"); + ret[STATE_PRESSED] = pressed_spec; + + return ret; + } + private: std::array property_set{}; std::array properties; @@ -101,6 +135,8 @@ public: return FGIMG; } else if (name == "fgimg_hovered") { return FGIMG_HOVERED; + } else if (name == "fgimg_middle") { + return FGIMG_MIDDLE; } else if (name == "fgimg_pressed") { return FGIMG_PRESSED; } else if (name == "alpha") { @@ -125,6 +161,18 @@ public: return SPACING; } else if (name == "size") { return SIZE; + } else if (name == "scrollbar_bgimg") { + return SCROLLBAR_BGIMG; + } else if (name == "scrollbar_thumb_img") { + return SCROLLBAR_THUMB_IMG; + } else if (name == "scrollbar_up_img") { + return SCROLLBAR_UP_IMG; + } else if (name == "scrollbar_down_img") { + return SCROLLBAR_DOWN_IMG; + } else if (name == "scrollbar_thumb_top_img") { + return SCROLLBAR_THUMB_TOP_IMG; + } else if (name == "scrollbar_thumb_bottom_img") { + return SCROLLBAR_THUMB_BOTTOM_IMG; } else { return NONE; } @@ -176,9 +224,14 @@ public: { StyleSpec temp = styles[StyleSpec::STATE_DEFAULT]; temp.state_map = state; +#ifdef HAVE_TOUCHSCREENGUI + // always render pressed as hovered on touchscreen + if (state & STATE_PRESSED) + state = State(state | STATE_HOVERED); +#endif for (int i = StyleSpec::STATE_DEFAULT + 1; i <= state; i++) { if ((state & i) != 0) { - temp = temp | styles[i]; + temp |= styles[i]; } } diff --git a/src/gui/guiAnimatedImage.cpp b/src/gui/guiAnimatedImage.cpp index b1447c45f..890763e71 100644 --- a/src/gui/guiAnimatedImage.cpp +++ b/src/gui/guiAnimatedImage.cpp @@ -9,40 +9,37 @@ #include GUIAnimatedImage::GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, - s32 id, const core::rect &rectangle, const std::string &texture_name, - s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc) : - gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle), m_tsrc(tsrc) + s32 id, const core::rect &rectangle) : + gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle) { - m_texture = m_tsrc->getTexture(texture_name); - - m_frame_count = std::max(frame_count, 1); - m_frame_duration = std::max(frame_duration, 0); - - if (m_texture != nullptr) { - core::dimension2d size = m_texture->getOriginalSize(); - if (size.Height < (u64)m_frame_count) - m_frame_count = size.Height; - } else { - // No need to step an animation if we have nothing to draw - m_frame_count = 1; - } } void GUIAnimatedImage::draw() { - // Render the current frame - if (m_texture != nullptr) { - video::IVideoDriver *driver = Environment->getVideoDriver(); + if (m_texture == nullptr) + return; + video::IVideoDriver *driver = Environment->getVideoDriver(); + + core::dimension2d size = m_texture->getOriginalSize(); + + if ((u32)m_frame_count > size.Height) + m_frame_count = size.Height; + if (m_frame_idx >= m_frame_count) + m_frame_idx = m_frame_count - 1; + + size.Height /= m_frame_count; + + core::rect rect(core::position2d(0, size.Height * m_frame_idx), size); + core::rect *cliprect = NoClip ? nullptr : &AbsoluteClippingRect; + + if (m_middle.getArea() == 0) { const video::SColor color(255, 255, 255, 255); const video::SColor colors[] = {color, color, color, color}; - - core::dimension2d size = m_texture->getOriginalSize(); - size.Height /= m_frame_count; - - draw2DImageFilterScaled(driver, m_texture, AbsoluteRect, - core::rect(core::position2d(0, size.Height * m_frame_idx), size), - NoClip ? nullptr : &AbsoluteClippingRect, colors, true); + draw2DImageFilterScaled(driver, m_texture, AbsoluteRect, rect, cliprect, + colors, true); + } else { + draw2DImage9Slice(driver, m_texture, AbsoluteRect, rect, m_middle, cliprect); } // Step the animation @@ -55,7 +52,7 @@ void GUIAnimatedImage::draw() m_global_time = new_global_time; // Advance by the number of elapsed frames, looping if necessary - m_frame_idx += u32(m_frame_time / m_frame_duration); + m_frame_idx += (u32)(m_frame_time / m_frame_duration); m_frame_idx %= m_frame_count; // If 1 or more frames have elapsed, reset the frame time counter with @@ -63,11 +60,3 @@ void GUIAnimatedImage::draw() m_frame_time %= m_frame_duration; } } - - -void GUIAnimatedImage::setFrameIndex(s32 frame) -{ - s32 idx = std::max(frame, 0); - if (idx > 0 && idx < m_frame_count) - m_frame_idx = idx; -} diff --git a/src/gui/guiAnimatedImage.h b/src/gui/guiAnimatedImage.h index f8e6a506e..885aedece 100644 --- a/src/gui/guiAnimatedImage.h +++ b/src/gui/guiAnimatedImage.h @@ -1,6 +1,7 @@ #pragma once #include "irrlichttypes_extrabloated.h" +#include #include class ISimpleTextureSource; @@ -8,21 +9,33 @@ class ISimpleTextureSource; class GUIAnimatedImage : public gui::IGUIElement { public: GUIAnimatedImage(gui::IGUIEnvironment *env, gui::IGUIElement *parent, - s32 id, const core::rect &rectangle, const std::string &texture_name, - s32 frame_count, s32 frame_duration, ISimpleTextureSource *tsrc); + s32 id, const core::rect &rectangle); virtual void draw() override; - void setFrameIndex(s32 frame); + void setTexture(video::ITexture *texture) { m_texture = texture; }; + video::ITexture *getTexture() const { return m_texture; }; + + void setMiddleRect(const core::rect &middle) { m_middle = middle; }; + core::rect getMiddleRect() const { return m_middle; }; + + void setFrameDuration(u64 duration) { m_frame_duration = duration; }; + u64 getFrameDuration() const { return m_frame_duration; }; + + void setFrameCount(s32 count) { m_frame_count = std::max(count, 1); }; + s32 getFrameCount() const { return m_frame_count; }; + + void setFrameIndex(s32 frame) { m_frame_idx = std::max(frame, 0); }; s32 getFrameIndex() const { return m_frame_idx; }; private: - ISimpleTextureSource *m_tsrc; - video::ITexture *m_texture = nullptr; + u64 m_global_time = 0; s32 m_frame_idx = 0; s32 m_frame_count = 1; - u64 m_frame_duration = 1; + u64 m_frame_duration = 0; u64 m_frame_time = 0; + + core::rect m_middle; }; diff --git a/src/gui/guiBackgroundImage.cpp b/src/gui/guiBackgroundImage.cpp index 21c1e88cf..51adae767 100644 --- a/src/gui/guiBackgroundImage.cpp +++ b/src/gui/guiBackgroundImage.cpp @@ -48,21 +48,15 @@ void GUIBackgroundImage::draw() video::IVideoDriver *driver = Environment->getVideoDriver(); + core::rect srcrect(core::position2d(0, 0), + core::dimension2di(texture->getOriginalSize())); + if (m_middle.getArea() == 0) { const video::SColor color(255, 255, 255, 255); const video::SColor colors[] = {color, color, color, color}; - draw2DImageFilterScaled(driver, texture, rect, - core::rect(core::position2d(0, 0), - core::dimension2di(texture->getOriginalSize())), - nullptr, colors, true); + draw2DImageFilterScaled(driver, texture, rect, srcrect, nullptr, colors, true); } else { - core::rect middle = m_middle; - // `-x` is interpreted as `w - x` - if (middle.LowerRightCorner.X < 0) - middle.LowerRightCorner.X += texture->getOriginalSize().Width; - if (middle.LowerRightCorner.Y < 0) - middle.LowerRightCorner.Y += texture->getOriginalSize().Height; - draw2DImage9Slice(driver, texture, rect, middle); + draw2DImage9Slice(driver, texture, rect, srcrect, m_middle); } IGUIElement::draw(); diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp index ba95b81c3..a783d08c0 100644 --- a/src/gui/guiButton.cpp +++ b/src/gui/guiButton.cpp @@ -320,15 +320,9 @@ void GUIButton::draw() sourceRect, &AbsoluteClippingRect, image_colors, UseAlphaChannel); } else { - core::rect middle = BgMiddle; - // `-x` is interpreted as `w - x` - if (middle.LowerRightCorner.X < 0) - middle.LowerRightCorner.X += texture->getOriginalSize().Width; - if (middle.LowerRightCorner.Y < 0) - middle.LowerRightCorner.Y += texture->getOriginalSize().Height; draw2DImage9Slice(driver, texture, ScaleImage ? AbsoluteRect : core::rect(pos, sourceRect.getSize()), - middle, &AbsoluteClippingRect, image_colors); + sourceRect, BgMiddle, &AbsoluteClippingRect, image_colors); } // END PATCH } @@ -509,7 +503,12 @@ video::SColor GUIButton::getOverrideColor() const #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR > 8 video::SColor GUIButton::getActiveColor() const { - return video::SColor(0,0,0,0); // unused? + if (OverrideColorEnabled) + return OverrideColor; + IGUISkin* skin = Environment->getSkin(); + if (skin) + return skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT); + return OverrideColor; } #endif diff --git a/src/gui/guiButtonImage.cpp b/src/gui/guiButtonImage.cpp index da9edf78c..6de458613 100644 --- a/src/gui/guiButtonImage.cpp +++ b/src/gui/guiButtonImage.cpp @@ -32,15 +32,15 @@ using namespace gui; GUIButtonImage::GUIButtonImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent, s32 id, core::rect rectangle, ISimpleTextureSource *tsrc, bool noclip) - : GUIButton (environment, parent, id, rectangle, tsrc, noclip) + : GUIButton(environment, parent, id, rectangle, tsrc, noclip) { - m_image = Environment->addImage( - core::rect(0,0,rectangle.getWidth(),rectangle.getHeight()), this); - m_image->setScaleImage(isScalingImage()); + GUIButton::setScaleImage(true); + m_image = new GUIAnimatedImage(environment, this, id, rectangle); sendToBack(m_image); } -void GUIButtonImage::setForegroundImage(video::ITexture *image) +void GUIButtonImage::setForegroundImage(video::ITexture *image, + const core::rect &middle) { if (image == m_foreground_image) return; @@ -52,11 +52,12 @@ void GUIButtonImage::setForegroundImage(video::ITexture *image) m_foreground_image->drop(); m_foreground_image = image; - m_image->setImage(image); + m_image->setTexture(image); + m_image->setMiddleRect(middle); } //! Set element properties from a StyleSpec -void GUIButtonImage::setFromStyle(const StyleSpec& style) +void GUIButtonImage::setFromStyle(const StyleSpec &style) { GUIButton::setFromStyle(style); @@ -67,19 +68,13 @@ void GUIButtonImage::setFromStyle(const StyleSpec& style) getTextureSource()); setForegroundImage(guiScalingImageButton(driver, texture, - AbsoluteRect.getWidth(), AbsoluteRect.getHeight())); - setScaleImage(true); + AbsoluteRect.getWidth(), AbsoluteRect.getHeight()), + style.getRect(StyleSpec::FGIMG_MIDDLE, m_image->getMiddleRect())); } else { - setForegroundImage(nullptr); + setForegroundImage(); } } -void GUIButtonImage::setScaleImage(bool scaleImage) -{ - GUIButton::setScaleImage(scaleImage); - m_image->setScaleImage(scaleImage); -} - GUIButtonImage *GUIButtonImage::addButton(IGUIEnvironment *environment, const core::rect &rectangle, ISimpleTextureSource *tsrc, IGUIElement *parent, s32 id, const wchar_t *text, diff --git a/src/gui/guiButtonImage.h b/src/gui/guiButtonImage.h index a572fc801..e0f4a8f4c 100644 --- a/src/gui/guiButtonImage.h +++ b/src/gui/guiButtonImage.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiButton.h" #include "IGUIButton.h" +#include "guiAnimatedImage.h" using namespace irr; @@ -32,12 +33,11 @@ public: s32 id, core::rect rectangle, ISimpleTextureSource *tsrc, bool noclip = false); - void setForegroundImage(video::ITexture *image = nullptr); + void setForegroundImage(video::ITexture *image = nullptr, + const core::rect &middle = core::rect()); //! Set element properties from a StyleSpec - virtual void setFromStyle(const StyleSpec& style) override; - - virtual void setScaleImage(bool scaleImage=true) override; + virtual void setFromStyle(const StyleSpec &style) override; //! Do not drop returned handle static GUIButtonImage *addButton(gui::IGUIEnvironment *environment, @@ -47,5 +47,5 @@ public: private: video::ITexture *m_foreground_image = nullptr; - gui::IGUIImage *m_image; + GUIAnimatedImage *m_image; }; diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index f229273c9..31ce50c7e 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -31,7 +31,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "gettext.h" #include "irrlicht_changes/CGUITTFont.h" +#include #include +#include "touchscreengui.h" + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include +#endif inline u32 clamp_u8(s32 value) { @@ -43,6 +49,8 @@ inline bool isInCtrlKeys(const irr::EKEY_CODE& kc) return kc == KEY_LCONTROL || kc == KEY_RCONTROL || kc == KEY_CONTROL; } +GUIChatConsole* GUIChatConsole::m_chat_console = nullptr; + GUIChatConsole::GUIChatConsole( gui::IGUIEnvironment* env, gui::IGUIElement* parent, @@ -58,6 +66,8 @@ GUIChatConsole::GUIChatConsole( m_menumgr(menumgr), m_animate_time_old(porting::getTimeMs()) { + m_chat_console = this; + // load background settings s32 console_alpha = g_settings->getS32("console_alpha"); m_background_color.setAlpha(clamp_u8(console_alpha)); @@ -90,6 +100,8 @@ GUIChatConsole::GUIChatConsole( m_fontsize.X = MYMAX(m_fontsize.X, 1); m_fontsize.Y = MYMAX(m_fontsize.Y, 1); + createVScrollBar(); + // set default cursor options setCursor(true, true, 2.0, 0.1); @@ -100,6 +112,16 @@ GUIChatConsole::GUIChatConsole( GUIChatConsole::~GUIChatConsole() { + m_chat_console = nullptr; + + removeChild(m_vscrollbar); + delete m_vscrollbar; + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + if (porting::hasRealKeyboard() && SDL_IsTextInputActive()) + SDL_StopTextInput(); +#endif + if (m_font) m_font->drop(); } @@ -110,12 +132,25 @@ void GUIChatConsole::openConsole(f32 scale) m_open = true; m_desired_height_fraction = scale; + + if (g_settings->getU32("fps_max") < 60) { + m_desired_height_fraction *= m_screensize.Y; + m_height = m_desired_height_fraction; + } + m_desired_height = scale * m_screensize.Y; reformatConsole(); + updateVScrollBar(false, true); m_animate_time_old = porting::getTimeMs(); IGUIElement::setVisible(true); + m_vscrollbar->setVisible(true); Environment->setFocus(this); m_menumgr->createdMenu(this); + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + if (porting::hasRealKeyboard()) + SDL_StartTextInput(); +#endif } bool GUIChatConsole::isOpen() const @@ -133,13 +168,21 @@ void GUIChatConsole::closeConsole() m_open = false; Environment->removeFocus(this); m_menumgr->deletingMenu(this); -} -void GUIChatConsole::closeConsoleAtOnce() -{ - closeConsole(); - m_height = 0; - recalculateConsolePosition(); + if (g_settings->getU32("fps_max") < 60) { + m_height = 0; + recalculateConsolePosition(); + } + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + if (porting::hasRealKeyboard() && SDL_IsTextInputActive()) + SDL_StopTextInput(); +#endif + +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui && g_touchscreengui->isActive()) + g_touchscreengui->show(); +#endif } void GUIChatConsole::replaceAndAddToHistory(const std::wstring &line) @@ -179,6 +222,8 @@ void GUIChatConsole::draw() if(!IsVisible) return; + updateVScrollBar(); + video::IVideoDriver* driver = Environment->getVideoDriver(); // Check screen size @@ -192,6 +237,7 @@ void GUIChatConsole::draw() m_screensize = screensize; m_desired_height = m_desired_height_fraction * m_screensize.Y; reformatConsole(); + updateVScrollBar(true, false); } // Animation @@ -212,19 +258,29 @@ void GUIChatConsole::draw() void GUIChatConsole::reformatConsole() { - s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better) + s32 cols = (m_screensize.X - m_scrollbar_width) / m_fontsize.X - 2; // make room for a margin (looks better) s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt if (cols <= 0 || rows <= 0) cols = rows = 0; recalculateConsolePosition(); m_chat_backend->reformat(cols, rows); + + m_mark_begin.reset(); + m_mark_end.reset(); + m_cursor_press_pos.reset(); + m_history_marking = false; + m_prompt_marking = false; + m_long_press = false; } void GUIChatConsole::recalculateConsolePosition() { core::rect rect(0, 0, m_screensize.X, m_height); DesiredRect = rect; - recalculateAbsolutePosition(false); + recalculateAbsolutePosition(true); + + irr::core::rect scrollbarrect(m_screensize.X - m_scrollbar_width, 0, m_screensize.X, m_height); + m_vscrollbar->setRelativePosition(scrollbarrect); } void GUIChatConsole::animate(u32 msec) @@ -235,8 +291,10 @@ void GUIChatConsole::animate(u32 msec) // Set invisible if close animation finished (reset by openConsole) // This function (animate()) is never called once its visibility becomes false so do not // actually set visible to false before the inhibited period is over - if (!m_open && m_height == 0 && m_open_inhibited == 0) + if (!m_open && m_height == 0 && m_open_inhibited == 0) { + m_vscrollbar->setVisible(false); IGUIElement::setVisible(false); + } if (m_height != goal) { @@ -320,6 +378,57 @@ void GUIChatConsole::drawText() if (y + line_height < 0) continue; + s32 scroll_pos = buf.getScrollPos(); + ChatSelection real_mark_begin = m_mark_end > m_mark_begin ? m_mark_begin : m_mark_end; + ChatSelection real_mark_end = m_mark_end > m_mark_begin ? m_mark_end : m_mark_begin; + if (real_mark_begin != real_mark_end && + real_mark_begin.selection_type == ChatSelection::SELECTION_HISTORY && + real_mark_end.selection_type == ChatSelection::SELECTION_HISTORY && + (s32)row + scroll_pos >= real_mark_begin.row + real_mark_begin.scroll && + (s32)row + scroll_pos <= real_mark_end.row + real_mark_end.scroll) { + ChatFormattedFragment fragment_first = line.fragments[0]; + + if ((s32)row + scroll_pos == real_mark_begin.row + real_mark_begin.scroll && + real_mark_begin.fragment < line.fragments.size()) { + fragment_first = line.fragments[real_mark_begin.fragment]; + } + + ChatFormattedFragment fragment_last = line.fragments[line.fragments.size() - 1]; + + if ((s32)row + scroll_pos == real_mark_end.row + real_mark_end.scroll && + real_mark_end.fragment < line.fragments.size()) { + fragment_last = line.fragments[real_mark_end.fragment]; + } + + s32 x_begin = (fragment_first.column + 1) * m_fontsize.X; + s32 text_size = m_font->getDimension(fragment_last.text.c_str()).Width; + s32 x_end = (fragment_last.column + 1) * m_fontsize.X + text_size; + + if ((s32)row + scroll_pos == real_mark_begin.row + real_mark_begin.scroll) { + irr::core::stringw text = fragment_first.text.c_str(); + text = text.subString(0, real_mark_begin.character); + s32 text_size = m_font->getDimension(text.c_str()).Width; + x_begin = (fragment_first.column + 1) * m_fontsize.X + text_size; + + if (real_mark_begin.x_max) + x_begin = x_end; + } + + if ((s32)row + scroll_pos == real_mark_end.row + real_mark_end.scroll && + (real_mark_end.character < fragment_last.text.size()) && + !real_mark_end.x_max) { + irr::core::stringw text = fragment_last.text.c_str(); + text = text.subString(0, real_mark_end.character); + s32 text_size = m_font->getDimension(text.c_str()).Width; + x_end = (fragment_last.column + 1) * m_fontsize.X + text_size; + } + + core::rect destrect(x_begin, y, x_end, y + m_fontsize.Y); + video::IVideoDriver* driver = Environment->getVideoDriver(); + IGUISkin* skin = Environment->getSkin(); + driver->draw2DRectangle(skin->getColor(EGDC_HIGH_LIGHT), destrect, &AbsoluteClippingRect); + } + for (const ChatFormattedFragment &fragment : line.fragments) { s32 x = (fragment.column + 1) * m_fontsize.X; core::rect destrect( @@ -359,24 +468,62 @@ void GUIChatConsole::drawPrompt() ChatPrompt& prompt = m_chat_backend->getPrompt(); std::wstring prompt_text = prompt.getVisiblePortion(); + std::replace_if(prompt_text.begin(), prompt_text.end(), + [](wchar_t c) { return (c == L'\n' || c == L'\r'); }, L' '); - // FIXME Draw string at once, not character by character - // That will only work with the cursor once we have a monospace font - for (u32 i = 0; i < prompt_text.size(); ++i) - { - wchar_t ws[2] = {prompt_text[i], 0}; - s32 x = (1 + i) * m_fontsize.X; - core::rect destrect( - x, y, x + m_fontsize.X, y + m_fontsize.Y); - m_font->draw( - ws, - destrect, - video::SColor(255, 255, 255, 255), - false, - false, - &AbsoluteClippingRect); + ChatSelection real_mark_begin = m_mark_end > m_mark_begin ? m_mark_begin : m_mark_end; + ChatSelection real_mark_end = m_mark_end > m_mark_begin ? m_mark_end : m_mark_begin; + + if (real_mark_begin != real_mark_end && + real_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + real_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) { + std::wstring begin_text = L"]"; + int begin_text_size = m_font->getDimension(begin_text.c_str()).Width; + int text_pos = m_fontsize.X + begin_text_size; + + s32 x_begin = text_pos; + s32 text_size = m_font->getDimension(prompt_text.c_str()).Width; + s32 x_end = x_begin + text_size; + + int current_scroll = prompt.getViewPosition(); + if (real_mark_begin.character > 0) { + irr::core::stringw text = prompt_text.c_str(); + int scroll_offset = real_mark_begin.scroll - current_scroll; + int length = scroll_offset + real_mark_begin.character; + length = MYMIN(MYMAX(length, 0), prompt_text.size() - 1); + text = text.subString(1, length); + s32 text_size = m_font->getDimension(text.c_str()).Width; + x_begin = text_pos + text_size; + } + + if (real_mark_end.character < prompt_text.size() - 1) { + irr::core::stringw text = prompt_text.c_str(); + int scroll_offset = real_mark_end.scroll - current_scroll; + int length = scroll_offset + real_mark_end.character; + if (real_mark_end.x_max) + length++; + length = MYMIN(MYMAX(length, 0), prompt_text.size() - 1); + text = text.subString(1, length); + s32 text_size = m_font->getDimension(text.c_str()).Width; + x_end = text_pos + text_size; + } + + core::rect destrect(x_begin, y, x_end, y + m_fontsize.Y); + video::IVideoDriver* driver = Environment->getVideoDriver(); + IGUISkin* skin = Environment->getSkin(); + driver->draw2DRectangle(skin->getColor(EGDC_HIGH_LIGHT), destrect, &AbsoluteClippingRect); } + core::rect destrect( + m_fontsize.X, y, prompt_text.size() * m_fontsize.X, y + m_fontsize.Y); + m_font->draw( + prompt_text.c_str(), + destrect, + video::SColor(255, 255, 255, 255), + false, + false, + &AbsoluteClippingRect); + // Draw the cursor during on periods if ((m_cursor_blink & 0x8000) != 0) { @@ -385,7 +532,9 @@ void GUIChatConsole::drawPrompt() { s32 cursor_len = prompt.getCursorLength(); video::IVideoDriver* driver = Environment->getVideoDriver(); - s32 x = (1 + cursor_pos) * m_fontsize.X; + std::wstring text = prompt_text.substr(0, cursor_pos); + s32 x = m_font->getDimension(text.c_str()).Width + m_fontsize.X; + core::rect destrect( x, y + m_fontsize.Y * (1.0 - m_cursor_height), @@ -399,12 +548,302 @@ void GUIChatConsole::drawPrompt() &AbsoluteClippingRect); } } +} + +ChatSelection GUIChatConsole::getCursorPos(s32 x, s32 y) +{ + ChatSelection selection; + + if (m_font == NULL) + return selection; + + ChatBuffer& buf = m_chat_backend->getConsoleBuffer(); + selection.scroll = buf.getScrollPos(); + selection.selection_type = ChatSelection::SELECTION_HISTORY; + + s32 line_height = m_fontsize.Y; + s32 y_min = m_height - m_desired_height; + s32 y_max = buf.getRows() * line_height + y_min; + + if (y <= y_min) { + selection.row = 0; + } else if (y >= y_max) { + selection.row = buf.getRows() - 1; + } else { + for (u32 row = 0; row < buf.getRows(); row++) { + s32 y1 = row * line_height + m_height - m_desired_height; + s32 y2 = y1 + line_height; + + if (y1 + line_height < 0) + return selection; + + if (y >= y1 && y <= y2) { + selection.row = row; + break; + } + } + } + + ChatFormattedLine line = buf.getFormattedLine(selection.row); + selection.line_index = line.line_index; + int current_row = selection.row; + + while (!line.first) { + current_row--; + line = buf.getFormattedLine(current_row); + selection.line++; + } + + line = buf.getFormattedLine(selection.row); + + if (line.fragments.empty()) + return selection; + + const ChatFormattedFragment &fragment_first = line.fragments[0]; + const ChatFormattedFragment &fragment_last = line.fragments[line.fragments.size() - 1]; + s32 x_min = (fragment_first.column + 1) * m_fontsize.X; + s32 text_size = m_font->getDimension(fragment_last.text.c_str()).Width; + s32 x_max = (fragment_last.column + 1) * m_fontsize.X + text_size; + + if (x < x_min) { + x = x_min; + } else if (x > x_max) { + x = x_max; + selection.x_max = true; + } + + for (unsigned int i = 0; i < line.fragments.size(); i++) { + const ChatFormattedFragment &fragment = line.fragments[i]; + s32 fragment_x = (fragment.column + 1) * m_fontsize.X; + + if (x < fragment_x) + continue; + + if (i < line.fragments.size() - 1) { + const ChatFormattedFragment &fragment_next = line.fragments[i + 1]; + s32 fragment_next_x = (fragment_next.column + 1) * m_fontsize.X; + + if (x >= fragment_next_x) + continue; + } + + s32 index = m_font->getCharacterFromPos(fragment.text.c_str(), x - fragment_x); + + selection.fragment = i; + selection.character = index > -1 ? index : fragment.text.size() - 1; + return selection; + } + + return selection; +} + +ChatSelection GUIChatConsole::getPromptCursorPos(s32 x, s32 y) +{ + ChatSelection selection; + + if (m_font == NULL) + return selection; + + ChatPrompt& prompt = m_chat_backend->getPrompt(); + + selection.selection_type = ChatSelection::SELECTION_PROMPT; + selection.scroll = prompt.getViewPosition(); + + std::wstring prompt_text = prompt.getVisiblePortion(); + irr::core::stringw text = prompt_text.c_str(); + text = text.subString(1, prompt_text.size() - 1); + + std::wstring begin_text = L"]"; + int begin_text_size = m_font->getDimension(begin_text.c_str()).Width; + int text_pos = m_fontsize.X + begin_text_size; + int pos = m_font->getCharacterFromPos(text.c_str(), x - text_pos); + + if (pos == -1) { + selection.x_max = true; + selection.character = text.size() - 1; + } else { + selection.character = pos; + } + + return selection; +} + +ChatSelection GUIChatConsole::getCurrentPromptCursorPos() +{ + ChatSelection selection; + + if (m_font == NULL) + return selection; + + ChatPrompt& prompt = m_chat_backend->getPrompt(); + + selection.selection_type = ChatSelection::SELECTION_PROMPT; + selection.scroll = prompt.getViewPosition(); + selection.character = prompt.getVisibleCursorPosition() - 1; + + if ((unsigned int)selection.character > prompt.getLine().size() - selection.scroll - 1) { + selection.character--; + selection.x_max = true; + } + + return selection; +} + +irr::core::stringc GUIChatConsole::getSelectedText() +{ + if (m_font == NULL) + return ""; + + if (m_mark_begin == m_mark_end) + return ""; + + bool add_to_string = false; + irr::core::stringw text = ""; + + ChatSelection real_mark_begin = m_mark_end > m_mark_begin ? m_mark_begin : m_mark_end; + ChatSelection real_mark_end = m_mark_end > m_mark_begin ? m_mark_end : m_mark_begin; + + ChatBuffer& buf = m_chat_backend->getConsoleBuffer(); + + const ChatLine& first_line = buf.getLine(0); + int first_line_index = first_line.line_index; + int mark_begin_row_buf = real_mark_begin.line_index - first_line_index; + int mark_end_row_buf = real_mark_end.line_index - first_line_index; + + if (mark_begin_row_buf < 0 || mark_end_row_buf < 0) + return ""; + + for (int row = mark_begin_row_buf; row < mark_end_row_buf + 1; row++) { + const ChatLine& line = buf.getLine(row); + + std::vector formatted_lines; + buf.formatChatLine(line, buf.getColsCount(), formatted_lines); + + for (unsigned int i = 0; i < formatted_lines.size(); i++) { + const ChatFormattedLine &line = formatted_lines[i]; + + for (unsigned int j = 0; j < line.fragments.size(); j++) { + const ChatFormattedFragment &fragment = line.fragments[j]; + + for (unsigned int k = 0; k < fragment.text.size(); k++) { + if (!add_to_string && + row == mark_begin_row_buf && + i == real_mark_begin.line && + j == real_mark_begin.fragment && + k == real_mark_begin.character) { + add_to_string = true; + + if (real_mark_begin.x_max) + continue; + } + + if (add_to_string) { + if (row == mark_end_row_buf && + i == real_mark_end.line && + j == real_mark_end.fragment && + k == real_mark_end.character) { + if (real_mark_end.x_max) + text += fragment.text.c_str()[k]; + + irr::core::stringc text_c; + text_c = wide_to_utf8(text.c_str()).c_str(); + return text_c; + } + + text += fragment.text.c_str()[k]; + } + } + } + + if (row < mark_end_row_buf) { + text += L"\n"; + } + } + } + + irr::core::stringc text_c; + text_c = wide_to_utf8(text.c_str()).c_str(); + return text_c; +} + +irr::core::stringc GUIChatConsole::getPromptSelectedText() +{ + if (m_font == NULL) + return ""; + + if (m_mark_begin == m_mark_end) + return ""; + + ChatPrompt& prompt = m_chat_backend->getPrompt(); + + ChatSelection real_mark_begin = m_mark_end > m_mark_begin ? m_mark_begin : m_mark_end; + ChatSelection real_mark_end = m_mark_end > m_mark_begin ? m_mark_end : m_mark_begin; + + std::wstring prompt_text = prompt.getLine(); + + if (real_mark_end.scroll + real_mark_end.character > prompt_text.size()) + return ""; + + irr::core::stringw text = prompt_text.c_str(); + int begin = real_mark_begin.scroll + real_mark_begin.character; + int length = real_mark_end.scroll + real_mark_end.character - begin; + if (real_mark_end.x_max) + length++; + text = text.subString(begin, length); + + irr::core::stringc text_c; + text_c = wide_to_utf8(text.c_str()).c_str(); + return text_c; +} + +void GUIChatConsole::movePromptCursor(s32 x, s32 y) +{ + ChatSelection selection = getPromptCursorPos(x, y); + + int cursor_pos = selection.scroll + selection.character; + if (selection.x_max) + cursor_pos++; + + ChatPrompt& prompt = m_chat_backend->getPrompt(); + prompt.setCursorPos(cursor_pos); +} + +void GUIChatConsole::deletePromptSelection() +{ + if (m_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT || + m_mark_end.selection_type != ChatSelection::SELECTION_PROMPT || + m_mark_begin == m_mark_end) + return; + + ChatPrompt& prompt = m_chat_backend->getPrompt(); + int scroll_pos = prompt.getViewPosition(); + + ChatSelection real_mark_begin = m_mark_end > m_mark_begin ? m_mark_begin : m_mark_end; + ChatSelection real_mark_end = m_mark_end > m_mark_begin ? m_mark_end : m_mark_begin; + + int pos_begin = real_mark_begin.scroll + real_mark_begin.character; + int pos_end = real_mark_end.scroll + real_mark_end.character; + if (real_mark_end.x_max) + pos_end++; + + std::wstring prompt_text = prompt.getLine(); + std::wstring new_text; + new_text = prompt_text.substr(0, pos_begin); + new_text += prompt_text.substr(pos_end, prompt_text.size() - pos_end); + + prompt.replace(new_text); + + int cursor_pos = real_mark_begin.scroll + real_mark_begin.character; + prompt.setCursorPos(cursor_pos); + prompt.setViewPosition(scroll_pos); + + m_mark_begin.reset(); + m_mark_end.reset(); } bool GUIChatConsole::OnEvent(const SEvent& event) { - ChatPrompt &prompt = m_chat_backend->getPrompt(); if (event.EventType == EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown) @@ -432,8 +871,8 @@ bool GUIChatConsole::OnEvent(const SEvent& event) return true; } - if (event.KeyInput.Key == KEY_ESCAPE) { - closeConsoleAtOnce(); + if (event.KeyInput.Key == KEY_ESCAPE || event.KeyInput.Key == KEY_CANCEL) { + closeConsole(); m_close_on_enter = false; // inhibit open so the_game doesn't reopen immediately m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu" @@ -441,11 +880,17 @@ bool GUIChatConsole::OnEvent(const SEvent& event) } else if(event.KeyInput.Key == KEY_PRIOR) { + ChatBuffer& buf = m_chat_backend->getConsoleBuffer(); + s32 rows = -(s32)buf.getRows(); + m_vscrollbar->setPos(m_vscrollbar->getPos() + rows); m_chat_backend->scrollPageUp(); return true; } else if(event.KeyInput.Key == KEY_NEXT) { + ChatBuffer& buf = m_chat_backend->getConsoleBuffer(); + s32 rows = buf.getRows(); + m_vscrollbar->setPos(m_vscrollbar->getPos() + rows); m_chat_backend->scrollPageDown(); return true; } @@ -455,8 +900,10 @@ bool GUIChatConsole::OnEvent(const SEvent& event) std::wstring text = prompt.replace(L""); m_client->typeChatMessage(text); if (m_close_on_enter) { - closeConsoleAtOnce(); + closeConsole(); m_close_on_enter = false; + } else { + updateVScrollBar(true, true); } return true; } @@ -476,11 +923,11 @@ bool GUIChatConsole::OnEvent(const SEvent& event) } else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT) { + ChatSelection old_pos = getCurrentPromptCursorPos(); + // Left/right pressed // Move/select character/word to the left depending on control and shift keys - ChatPrompt::CursorOp op = event.KeyInput.Shift ? - ChatPrompt::CURSOROP_SELECT : - ChatPrompt::CURSOROP_MOVE; + ChatPrompt::CursorOp op = ChatPrompt::CURSOROP_MOVE; ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ? ChatPrompt::CURSOROP_DIR_LEFT : ChatPrompt::CURSOROP_DIR_RIGHT; @@ -488,30 +935,86 @@ bool GUIChatConsole::OnEvent(const SEvent& event) ChatPrompt::CURSOROP_SCOPE_WORD : ChatPrompt::CURSOROP_SCOPE_CHARACTER; prompt.cursorOperation(op, dir, scope); + + if (event.KeyInput.Shift) { + if (m_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT || + m_mark_end.selection_type != ChatSelection::SELECTION_PROMPT) { + m_mark_begin = old_pos; + } + m_mark_end = getCurrentPromptCursorPos(); + } else { + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) { + m_mark_begin.reset(); + m_mark_end.reset(); + } + } + return true; } else if(event.KeyInput.Key == KEY_HOME) { + ChatSelection old_pos = getCurrentPromptCursorPos(); + // Home pressed // move to beginning of line prompt.cursorOperation( ChatPrompt::CURSOROP_MOVE, ChatPrompt::CURSOROP_DIR_LEFT, ChatPrompt::CURSOROP_SCOPE_LINE); + + if (event.KeyInput.Shift) { + if (m_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT || + m_mark_end.selection_type != ChatSelection::SELECTION_PROMPT) { + m_mark_begin = old_pos; + } + m_mark_end = getCurrentPromptCursorPos(); + } else { + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) { + m_mark_begin.reset(); + m_mark_end.reset(); + } + } + return true; } else if(event.KeyInput.Key == KEY_END) { + ChatSelection old_pos = getCurrentPromptCursorPos(); + // End pressed // move to end of line prompt.cursorOperation( ChatPrompt::CURSOROP_MOVE, ChatPrompt::CURSOROP_DIR_RIGHT, ChatPrompt::CURSOROP_SCOPE_LINE); + + if (event.KeyInput.Shift) { + if (m_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT || + m_mark_end.selection_type != ChatSelection::SELECTION_PROMPT) { + m_mark_begin = old_pos; + } + m_mark_end = getCurrentPromptCursorPos(); + } else { + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) { + m_mark_begin.reset(); + m_mark_end.reset(); + } + } + return true; } else if(event.KeyInput.Key == KEY_BACK) { + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_begin != m_mark_end) { + deletePromptSelection(); + return true; + } + // Backspace or Ctrl-Backspace pressed // delete character / word to the left ChatPrompt::CursorOpScope scope = @@ -526,6 +1029,13 @@ bool GUIChatConsole::OnEvent(const SEvent& event) } else if(event.KeyInput.Key == KEY_DELETE) { + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_begin != m_mark_end) { + deletePromptSelection(); + return true; + } + // Delete or Ctrl-Delete pressed // delete character / word to the right ChatPrompt::CursorOpScope scope = @@ -540,16 +1050,44 @@ bool GUIChatConsole::OnEvent(const SEvent& event) } else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control) { - // Ctrl-A pressed - // Select all text - prompt.cursorOperation( - ChatPrompt::CURSOROP_SELECT, - ChatPrompt::CURSOROP_DIR_LEFT, // Ignored - ChatPrompt::CURSOROP_SCOPE_LINE); + if (prompt.getLine().size() > 0) { + ChatPrompt& prompt = m_chat_backend->getPrompt(); + + m_mark_begin.reset(); + m_mark_begin.selection_type = ChatSelection::SELECTION_PROMPT; + m_mark_begin.scroll = 0; + m_mark_begin.character = 0; + + m_mark_end.reset(); + m_mark_end.selection_type = ChatSelection::SELECTION_PROMPT; + m_mark_end.scroll = 0; + m_mark_end.character = prompt.getLine().size() - 1; + m_mark_end.x_max = true; + + prompt.cursorOperation( + ChatPrompt::CURSOROP_MOVE, + ChatPrompt::CURSOROP_DIR_RIGHT, + ChatPrompt::CURSOROP_SCOPE_LINE); + } + return true; } else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control) { + if (m_mark_begin != m_mark_end) { + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) { + irr::core::stringc text = getPromptSelectedText(); + Environment->getOSOperator()->copyToClipboard(text.c_str()); + return true; + } else if (m_mark_begin.selection_type == ChatSelection::SELECTION_HISTORY && + m_mark_end.selection_type == ChatSelection::SELECTION_HISTORY) { + irr::core::stringc text = getSelectedText(); + Environment->getOSOperator()->copyToClipboard(text.c_str()); + return true; + } + } + // Ctrl-C pressed // Copy text to clipboard if (prompt.getCursorLength() <= 0) @@ -561,6 +1099,12 @@ bool GUIChatConsole::OnEvent(const SEvent& event) } else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control) { + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_begin != m_mark_end) { + deletePromptSelection(); + } + // Ctrl-V pressed // paste text from clipboard if (prompt.getCursorLength() > 0) { @@ -579,6 +1123,15 @@ bool GUIChatConsole::OnEvent(const SEvent& event) } else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control) { + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_begin != m_mark_end) { + irr::core::stringc text = getPromptSelectedText(); + Environment->getOSOperator()->copyToClipboard(text.c_str()); + deletePromptSelection(); + return true; + } + // Ctrl-X pressed // Cut text to clipboard if (prompt.getCursorLength() <= 0) @@ -621,16 +1174,44 @@ bool GUIChatConsole::OnEvent(const SEvent& event) prompt.nickCompletion(names, backwards); return true; } else if (!iswcntrl(event.KeyInput.Char) && !event.KeyInput.Control) { + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_begin != m_mark_end) { + deletePromptSelection(); + } + prompt.input(event.KeyInput.Char); return true; } } +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + else if(event.EventType == EET_SDL_TEXT_EVENT) + { + if (event.SDLTextEvent.Type == ESDLET_TEXTINPUT) + { + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_begin != m_mark_end) { + deletePromptSelection(); + } + + std::wstring text = utf8_to_wide(event.SDLTextEvent.Text); + + for (u32 i = 0; i < text.size(); i++) + prompt.input(text[i]); + + } + + return true; + } +#endif else if(event.EventType == EET_MOUSE_INPUT_EVENT) { if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { s32 rows = myround(-3.0 * event.MouseInput.Wheel); m_chat_backend->scroll(rows); + m_vscrollbar->setPos(m_vscrollbar->getPos() + rows); } // Middle click or ctrl-click opens weblink, if enabled in config else if(m_cache_clickable_chat_weblinks && ( @@ -644,8 +1225,177 @@ bool GUIChatConsole::OnEvent(const SEvent& event) // Translate pixel position to font position middleClick(event.MouseInput.X / m_fontsize.X, event.MouseInput.Y / m_fontsize.Y); } + } else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + u32 row = m_chat_backend->getConsoleBuffer().getRows(); + s32 prompt_y = row * m_fontsize.Y + m_height - m_desired_height; + + if (event.MouseInput.Y >= prompt_y) { + m_prompt_marking = true; + if (event.MouseInput.Shift) { + if (m_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT || + m_mark_end.selection_type != ChatSelection::SELECTION_PROMPT) { + m_mark_begin = getCurrentPromptCursorPos(); + m_mark_end = getPromptCursorPos(event.MouseInput.X, event.MouseInput.Y); + } + } else { + m_mark_begin = getPromptCursorPos(event.MouseInput.X, event.MouseInput.Y); + m_mark_end = m_mark_begin; + } + movePromptCursor(event.MouseInput.X, event.MouseInput.Y); + } else { + m_history_marking = true; + m_mark_begin = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + m_mark_end = m_mark_begin; + } + } else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { + if (m_prompt_marking) { + m_mark_end = getPromptCursorPos(event.MouseInput.X, event.MouseInput.Y); + m_prompt_marking = false; + + if (!event.MouseInput.Shift) { + if (m_mark_begin == m_mark_end) { + m_mark_begin.reset(); + m_mark_end.reset(); + } + } + } else if (m_history_marking) { + m_mark_end = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + m_history_marking = false; + + if (m_mark_begin == m_mark_end) { + m_mark_begin.reset(); + m_mark_end.reset(); + } + } + } else if (event.MouseInput.Event == EMIE_MOUSE_MOVED) { + if (m_prompt_marking) { + m_mark_end = getPromptCursorPos(event.MouseInput.X, event.MouseInput.Y); + movePromptCursor(event.MouseInput.X, event.MouseInput.Y); + } else if (m_history_marking) { + m_mark_end = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + } } + + return true; } +#ifdef HAVE_TOUCHSCREENGUI + else if (event.EventType == EET_TOUCH_INPUT_EVENT) { + if (event.TouchInput.Event == irr::ETIE_PRESSED_DOWN) { + m_history_marking = false; + m_prompt_marking = false; + m_long_press = false; + + u32 row = m_chat_backend->getConsoleBuffer().getRows(); + s32 prompt_y = row * m_fontsize.Y + m_height - m_desired_height; + + ChatSelection real_mark_begin = m_mark_end > m_mark_begin ? m_mark_begin : m_mark_end; + ChatSelection real_mark_end = m_mark_end > m_mark_begin ? m_mark_end : m_mark_begin; + + if (event.TouchInput.Y >= prompt_y) { + + m_cursor_press_pos = getPromptCursorPos(event.TouchInput.X, event.TouchInput.Y); + + if (real_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT || + real_mark_end.selection_type != ChatSelection::SELECTION_PROMPT || + m_cursor_press_pos < real_mark_begin || + m_cursor_press_pos > real_mark_end) { + m_mark_begin = m_cursor_press_pos; + m_mark_end = m_cursor_press_pos; + m_prompt_marking = true; + } + } else { + m_cursor_press_pos = getCursorPos(event.TouchInput.X, event.TouchInput.Y); + + if (real_mark_begin.selection_type != ChatSelection::SELECTION_HISTORY || + real_mark_end.selection_type != ChatSelection::SELECTION_HISTORY || + m_cursor_press_pos < real_mark_begin || + m_cursor_press_pos > real_mark_end) { + m_mark_begin = m_cursor_press_pos; + m_mark_end = m_cursor_press_pos; + m_history_marking = true; + } + } + + } else if (event.TouchInput.Event == irr::ETIE_LEFT_UP) { + if (m_prompt_marking) { + ChatSelection cursor_pos = getPromptCursorPos(event.TouchInput.X, event.TouchInput.Y); + + if (!m_long_press && m_cursor_press_pos == cursor_pos) { + m_mark_begin.reset(); + m_mark_end.reset(); + } + + m_prompt_marking = false; + } else if (m_history_marking) { + ChatSelection cursor_pos = getCursorPos(event.TouchInput.X, event.TouchInput.Y); + + if (!m_long_press && m_cursor_press_pos == cursor_pos) { + m_mark_begin.reset(); + m_mark_end.reset(); + } + + m_history_marking = false; + } + + m_cursor_press_pos.reset(); + m_long_press = false; + + } else if (event.TouchInput.Event == irr::ETIE_MOVED) { + ChatSelection cursor_pos = getCursorPos(event.TouchInput.X, event.TouchInput.Y); + ChatSelection prompt_cursor_pos = getPromptCursorPos(event.TouchInput.X, event.TouchInput.Y); + + if (!m_prompt_marking && !m_long_press && + m_cursor_press_pos.selection_type == ChatSelection::SELECTION_PROMPT && + m_cursor_press_pos != prompt_cursor_pos) { + m_mark_begin = m_cursor_press_pos; + m_mark_end = m_cursor_press_pos; + m_prompt_marking = true; + } else if (!m_history_marking && !m_long_press && + m_cursor_press_pos.selection_type == ChatSelection::SELECTION_HISTORY && + m_cursor_press_pos != cursor_pos) { + m_mark_begin = m_cursor_press_pos; + m_mark_end = m_cursor_press_pos; + m_history_marking = true; + } + + if (m_prompt_marking) { + m_mark_end = prompt_cursor_pos; + } else if (m_history_marking) { + m_mark_end = cursor_pos; + } + + } else if (event.TouchInput.Event == irr::ETIE_PRESSED_LONG) { + if (!m_history_marking && ! m_prompt_marking) { + m_long_press = true; + if (m_mark_begin != m_mark_end) { + irr::core::stringc text; + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT && + m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) { + text = getPromptSelectedText(); + } else if (m_mark_begin.selection_type == ChatSelection::SELECTION_HISTORY && + m_mark_end.selection_type == ChatSelection::SELECTION_HISTORY) { + text = getSelectedText(); + } + Environment->getOSOperator()->copyToClipboard(text.c_str()); +#ifdef __ANDROID__ + SDL_AndroidShowToast( + "Copied to clipboard", 2, + -1, 0, 0); +#elif __IOS__ + porting::showToast("Copied to clipboard"); +#endif + } + } + } + + return true; + } +#endif + else if (event.EventType == EET_GUI_EVENT) { + if (event.GUIEvent.EventType == EGET_SCROLL_BAR_CHANGED) { + updateVScrollBar(); + } + } #if (IRRLICHT_VERSION_MT_REVISION >= 2) else if(event.EventType == EET_STRING_INPUT_EVENT) { @@ -661,6 +1411,7 @@ void GUIChatConsole::setVisible(bool visible) { m_open = visible; IGUIElement::setVisible(visible); + m_vscrollbar->setVisible(visible); if (!visible) { m_height = 0; recalculateConsolePosition(); @@ -726,3 +1477,223 @@ void GUIChatConsole::middleClick(s32 col, s32 row) m_chat_backend->addUnparsedMessage(utf8_to_wide(msg.str())); } } + +//! create a vertical scroll bar +void GUIChatConsole::createVScrollBar() +{ + IGUISkin *skin = 0; + if (Environment) + skin = Environment->getSkin(); + + m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16; + m_scrollbar_width *= 2; + + irr::core::rect scrollbarrect(m_screensize.X - m_scrollbar_width, + 0, m_screensize.X, m_height); + m_vscrollbar = new GUIScrollBar(Environment, getParent(), -1, + scrollbarrect, false, true); + + m_vscrollbar->setVisible(false); + m_vscrollbar->setMax(0); + m_vscrollbar->setPos(0); + m_vscrollbar->setPageSize(0); + m_vscrollbar->setSmallStep(1); + m_vscrollbar->setLargeStep(1); + m_vscrollbar->setArrowsVisible(GUIScrollBar::ArrowVisibility::SHOW); + + ITextureSource *tsrc = m_client->getTextureSource(); + m_vscrollbar->setTextures({ + tsrc->getTexture("gui/scrollbar_bg.png"), + tsrc->getTexture("gui/scrollbar_slider_middle.png"), + tsrc->getTexture("gui/scrollbar_up.png"), + tsrc->getTexture("gui/scrollbar_down.png"), + tsrc->getTexture("gui/scrollbar_slider_top.png"), + tsrc->getTexture("gui/scrollbar_slider_bottom.png"), + }); + + addChild(m_vscrollbar); +} + +void GUIChatConsole::updateVScrollBar(bool force_update, bool move_bottom) +{ + if (!m_vscrollbar) + return; + + ChatBuffer& buf = m_chat_backend->getConsoleBuffer(); + + if (m_bottom_scroll_pos != buf.getBottomScrollPos() || force_update) { + bool is_bottom = m_vscrollbar->getPos() == m_bottom_scroll_pos; + m_bottom_scroll_pos = buf.getBottomScrollPos(); + + if (buf.getBottomScrollPos() > 0) { + buf.scrollAbsolute(m_bottom_scroll_pos); + m_vscrollbar->setMax(m_bottom_scroll_pos); + if (is_bottom || move_bottom) + m_vscrollbar->setPos(m_bottom_scroll_pos); + } else { + m_vscrollbar->setMax(0); + m_vscrollbar->setPos(0); + } + } + + s32 page_size = (m_bottom_scroll_pos + buf.getRows() + 1) * m_fontsize.Y; + if (m_vscrollbar->getPageSize() != page_size) { + m_vscrollbar->setPageSize(page_size); + } + + if (buf.getDelFormatted() > 0) { + bool is_bottom = m_vscrollbar->getPos() == m_bottom_scroll_pos; + + if (!is_bottom && ! move_bottom) { + s32 pos = m_vscrollbar->getPos() - buf.getDelFormatted(); + + if (pos >= 0) + m_vscrollbar->setPos(pos); + } + + m_mark_begin.scroll -= buf.getDelFormatted(); + m_mark_end.scroll -= buf.getDelFormatted(); + buf.resetDelFormatted(); + } + + if (m_vscrollbar->getPos() != buf.getScrollPos()) { + if (buf.getScrollPos() >= 0) { + s32 deltaScrollY = m_vscrollbar->getPos() - buf.getScrollPos(); + m_chat_backend->scroll(deltaScrollY); + } + } + + if (IsVisible) { + if (m_vscrollbar->isVisible() && m_vscrollbar->getMax() == 0) + m_vscrollbar->setVisible(false); + else if (!m_vscrollbar->isVisible() && m_vscrollbar->getMax() > 0) + m_vscrollbar->setVisible(true); + } +} + +void GUIChatConsole::onLinesModified() +{ + if (m_mark_begin.selection_type == ChatSelection::SELECTION_HISTORY && + m_mark_end.selection_type == ChatSelection::SELECTION_HISTORY) { + + ChatBuffer& buf = m_chat_backend->getConsoleBuffer(); + const ChatLine& first_line = buf.getLine(0); + int first_line_index = first_line.line_index; + + if (m_mark_begin.line_index < first_line_index || + m_mark_end.line_index < first_line_index) { + m_mark_begin.reset(); + m_mark_end.reset(); + m_cursor_press_pos.reset(); + m_history_marking = false; + m_long_press = false; + } + } + + updateVScrollBar(true); +} + +void GUIChatConsole::onPromptModified() +{ + if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT) + m_mark_begin.reset(); + if (m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) + m_mark_end.reset(); + if (m_cursor_press_pos.selection_type == ChatSelection::SELECTION_PROMPT) + m_cursor_press_pos.reset(); + if (m_prompt_marking) { + m_prompt_marking = false; + m_long_press = false; + } +} + +bool GUIChatConsole::hasFocus() +{ + if (Environment->hasFocus(this)) + return true; + + if (Environment->hasFocus(m_vscrollbar)) + return true; + + const core::list &children = m_vscrollbar->getChildren(); + + for (gui::IGUIElement *it : children) { + if (Environment->hasFocus(it)) + return true; + } + + return false; +} + +bool GUIChatConsole::convertToMouseEvent( + SEvent &mouse_event, SEvent touch_event) const noexcept +{ +#ifdef HAVE_TOUCHSCREENGUI + mouse_event = {}; + mouse_event.EventType = EET_MOUSE_INPUT_EVENT; + mouse_event.MouseInput.X = touch_event.TouchInput.X; + mouse_event.MouseInput.Y = touch_event.TouchInput.Y; + switch (touch_event.TouchInput.Event) { + case ETIE_PRESSED_DOWN: + mouse_event.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; + mouse_event.MouseInput.ButtonStates = EMBSM_LEFT; + break; + case ETIE_MOVED: + mouse_event.MouseInput.Event = EMIE_MOUSE_MOVED; + mouse_event.MouseInput.ButtonStates = EMBSM_LEFT; + break; + case ETIE_LEFT_UP: + mouse_event.MouseInput.Event = EMIE_LMOUSE_LEFT_UP; + mouse_event.MouseInput.ButtonStates = 0; + break; + default: + return false; + } + + return true; +#else + + return false; +#endif +} + +bool GUIChatConsole::preprocessEvent(SEvent event) +{ + updateVScrollBar(); + +#ifdef HAVE_TOUCHSCREENGUI + if (event.EventType == irr::EET_TOUCH_INPUT_EVENT) { + const core::position2di p(event.TouchInput.X, event.TouchInput.Y); + + u32 row = m_chat_backend->getConsoleBuffer().getRows(); + s32 prompt_y = row * m_fontsize.Y + m_height - m_desired_height; + + if (m_vscrollbar->isPointInside(p) || !isPointInside(p)) { + SEvent mouse_event = {}; + bool success = convertToMouseEvent(mouse_event, event); + if (success) { + Environment->postEventFromUser(mouse_event); + } + } +#if defined(__ANDROID__) || defined(__IOS__) + else if (!porting::hasRealKeyboard() && + event.TouchInput.Y >= prompt_y && + event.TouchInput.Y <= m_height) { + if (event.TouchInput.Event == ETIE_PRESSED_DOWN && + !m_android_chat_open) { + ChatPrompt& prompt = m_chat_backend->getPrompt(); + porting::showInputDialog("", "", 2); + m_android_chat_open = true; + } + } +#endif + else { + OnEvent(event); + } + + return true; + } +#endif + + return false; +} diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h index 1d2f45047..cefaada22 100644 --- a/src/gui/guiChatConsole.h +++ b/src/gui/guiChatConsole.h @@ -23,6 +23,99 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "modalMenu.h" #include "chat.h" #include "config.h" +#include "guiScrollBar.h" + +struct ChatSelection +{ + enum SelectionType { + SELECTION_NONE, + SELECTION_HISTORY, + SELECTION_PROMPT + }; + + ChatSelection() : selection_type(SELECTION_NONE), scroll(0), row(0), + line_index(0), line(0), fragment(0), character(0), x_max(false) {}; + + void reset() { + selection_type = SELECTION_NONE; + scroll = 0; + row = 0; + line_index = 0; + line = 0; + fragment = 0; + character = 0; + x_max = false; + } + + bool operator== (const ChatSelection &other) const { + if (selection_type == SELECTION_HISTORY && + other.selection_type == SELECTION_HISTORY) { + return (row + scroll == other.row + other.scroll && + line_index == other.line_index && + line == other.line && + fragment == other.fragment && + character == other.character && + x_max == other.x_max); + + } else { + return (scroll + character == other.scroll + other.character && + x_max == other.x_max); + } + } + + bool operator< (const ChatSelection &other) const { + if (selection_type == SELECTION_HISTORY && + other.selection_type == SELECTION_HISTORY) { + if (row + scroll != other.row + other.scroll) + return (row + scroll < other.row + other.scroll); + if (line_index != other.line_index) + return (line_index < other.line_index); + if (line != other.line) + return (line < other.line); + if (fragment != other.fragment) + return (fragment < other.fragment); + if (character != other.character) + return (character < other.character); + if (x_max != other.x_max) + return (x_max < other.x_max); + + return false; + + } else { + if (scroll + character != other.scroll + other.character) + return (scroll + character < other.scroll + other.character); + if (x_max != other.x_max) + return (x_max < other.x_max); + + return false; + } + } + + bool operator> (const ChatSelection &other) { + return other < *this; + } + + bool operator<= (const ChatSelection &other) { + return !(*this > other); + } + + bool operator>= (const ChatSelection &other) { + return !(*this < other); + } + + bool operator!= (const ChatSelection &other) const { + return !this->operator==(other); + } + + SelectionType selection_type; + int scroll; + int row; + int line_index; + unsigned int line; + unsigned int fragment; + unsigned int character; + bool x_max; +}; class Client; @@ -50,8 +143,6 @@ public: // Close the console, equivalent to openConsole(0). // This doesn't close immediately but initiates an animation. void closeConsole(); - // Close the console immediately, without animation. - void closeConsoleAtOnce(); // Set whether to close the console after the user presses enter. void setCloseOnEnter(bool close) { m_close_on_enter = close; } @@ -74,6 +165,21 @@ public: virtual bool acceptsIME() { return true; } + bool hasFocus(); + + bool convertToMouseEvent( + SEvent &mouse_event, SEvent touch_event) const noexcept; + + bool preprocessEvent(SEvent event); + + bool getAndroidChatOpen() { return m_android_chat_open; } + void setAndroidChatOpen(bool value) { m_android_chat_open = value; } + + void onLinesModified(); + void onPromptModified(); + + static GUIChatConsole* getChatConsole() { return m_chat_console; } + private: void reformatConsole(); void recalculateConsolePosition(); @@ -87,7 +193,19 @@ private: // If clicked fragment has a web url, send it to the system default web browser void middleClick(s32 col, s32 row); + ChatSelection getCursorPos(s32 x, s32 y); + ChatSelection getPromptCursorPos(s32 x, s32 y); + ChatSelection getCurrentPromptCursorPos(); + irr::core::stringc getSelectedText(); + irr::core::stringc getPromptSelectedText(); + void movePromptCursor(s32 x, s32 y); + void deletePromptSelection(); + void createVScrollBar(); + void updateVScrollBar(bool force_update = false, bool move_bottom = false); + private: + static GUIChatConsole* m_chat_console; + ChatBackend* m_chat_backend; Client* m_client; IMenuManager* m_menumgr; @@ -134,4 +252,17 @@ private: bool m_cache_clickable_chat_weblinks; // Track if a ctrl key is currently held down bool m_is_ctrl_down; + + ChatSelection m_mark_begin; + ChatSelection m_mark_end; + bool m_history_marking = false; + bool m_prompt_marking = false; + bool m_long_press = false; + ChatSelection m_cursor_press_pos; + + u32 m_scrollbar_width = 0; + GUIScrollBar *m_vscrollbar = nullptr; + s32 m_bottom_scroll_pos = 0; + + bool m_android_chat_open = false; }; diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp index 3af528100..cee0ba81c 100644 --- a/src/gui/guiConfirmRegistration.cpp +++ b/src/gui/guiConfirmRegistration.cpp @@ -20,7 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiConfirmRegistration.h" #include "client/client.h" +#include "filesys.h" +#include "guiBackgroundImage.h" #include "guiButton.h" +#include "guiEditBoxWithScrollbar.h" #include #include #include @@ -32,6 +35,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/renderingengine.h" #endif +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + #include +#endif + #include "gettext.h" // Continuing from guiPasswordChange.cpp @@ -40,6 +47,8 @@ const int ID_confirm = 263; const int ID_intotext = 264; const int ID_cancel = 265; const int ID_message = 266; +const int ID_background = 267; +const int ID_confirmPasswordBg = 268; GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, Client *client, @@ -52,11 +61,21 @@ GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env, #ifdef HAVE_TOUCHSCREENGUI m_touchscreen_visible = false; #endif + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + if (porting::hasRealKeyboard()) + SDL_StartTextInput(); +#endif } GUIConfirmRegistration::~GUIConfirmRegistration() { removeChildren(); + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + if (porting::hasRealKeyboard() && SDL_IsTextInputActive()) + SDL_StopTextInput(); +#endif } void GUIConfirmRegistration::removeChildren() @@ -77,11 +96,13 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) /* Calculate new sizes and positions */ -#ifdef HAVE_TOUCHSCREENGUI - const float s = m_gui_scale * RenderingEngine::getDisplayDensity() / 2; + float s = MYMIN(screensize.X / 600.f, screensize.Y / 360.f); +#if HAVE_TOUCHSCREENGUI + s *= g_settings->getBool("device_is_tablet") ? 0.7f : 0.8f; #else - const float s = m_gui_scale; + s *= 0.5f; #endif + DesiredRect = core::rect( screensize.X / 2 - 600 * s / 2, screensize.Y / 2 - 360 * s / 2, @@ -98,11 +119,21 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) /* Add stuff */ - s32 ypos = 30 * s; + + // Background image + { + const std::string texture = "bg_common.png"; + const core::rect rect(0, 0, 0, 0); + const core::rect middle(40, 40, -40, -40); + new GUIBackgroundImage(Environment, this, ID_background, rect, + texture, middle, m_tsrc, true); + } + + s32 ypos = 20 * s; { core::rect rect2(0, 0, 540 * s, 180 * s); rect2 += topleft_client + v2s32(30 * s, ypos); - static const std::string info_text_template = strgettext( + const std::string info_text_template = strgettext( "You are about to join this server with the name \"%s\" for the " "first time.\n" "If you proceed, a new account using your credentials will be " @@ -122,40 +153,54 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize) e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER); } - ypos += 200 * s; - { - core::rect rect2(0, 0, 540 * s, 30 * s); - rect2 += topleft_client + v2s32(30 * s, ypos); - gui::IGUIEditBox *e = Environment->addEditBox(m_pass_confirm.c_str(), - rect2, true, this, ID_confirmPassword); - e->setPasswordBox(true); - Environment->setFocus(e); - } - - ypos += 50 * s; - { - core::rect rect2(0, 0, 230 * s, 35 * s); - rect2 = rect2 + v2s32(size.X / 2 - 220 * s, ypos); - text = wgettext("Register and Join"); - GUIButton::addButton(Environment, rect2, m_tsrc, this, ID_confirm, text); - delete[] text; - } - { - core::rect rect2(0, 0, 120 * s, 35 * s); - rect2 = rect2 + v2s32(size.X / 2 + 70 * s, ypos); - text = wgettext("Cancel"); - GUIButton::addButton(Environment, rect2, m_tsrc, this, ID_cancel, text); - delete[] text; - } + ypos += 140 * s; { core::rect rect2(0, 0, 500 * s, 40 * s); - rect2 += topleft_client + v2s32(30 * s, ypos + 40 * s); + rect2 += topleft_client + v2s32(30 * s, ypos + 45 * s); text = wgettext("Passwords do not match!"); IGUIElement *e = Environment->addStaticText( text, rect2, false, true, this, ID_message); e->setVisible(false); delete[] text; } + + ypos += 75 * s; + { + core::rect rect2(0, 0, 540 * s, 40 * s); + rect2 += topleft_client + v2s32(30 * s, ypos); + + core::rect bg_middle(10, 10, -10, -10); + new GUIBackgroundImage(Environment, this, ID_confirmPasswordBg, + rect2, "field_bg.png", bg_middle, m_tsrc, false); + + rect2.UpperLeftCorner.X += 5 * s; + rect2.LowerRightCorner.X -= 5 * s; + + gui::IGUIEditBox *e = Environment->addEditBox(m_pass_confirm.c_str(), + rect2, true, this, ID_confirmPassword); + e->setDrawBorder(false); + e->setDrawBackground(false); + e->setPasswordBox(true); + Environment->setFocus(e); + } + + ypos += 60 * s; + { + core::rect rect2(0, 0, 300 * s, 40 * s); + rect2 = rect2 + v2s32(size.X / 2 - 250 * s, ypos); + text = wgettext("Register and Join"); + GUIButton *e = GUIButton::addButton(Environment, rect2, m_tsrc, this, ID_confirm, text); + e->setStyles(StyleSpec::getButtonStyle("", "green")); + delete[] text; + } + { + core::rect rect2(0, 0, 140 * s, 40 * s); + rect2 = rect2 + v2s32(size.X / 2 + 110 * s, ypos); + text = wgettext("Cancel"); + GUIButton *e = GUIButton::addButton(Environment, rect2, m_tsrc, this, ID_cancel, text); + e->setStyles(StyleSpec::getButtonStyle()); + delete[] text; + } } void GUIConfirmRegistration::drawMenu() @@ -163,13 +208,9 @@ void GUIConfirmRegistration::drawMenu() gui::IGUISkin *skin = Environment->getSkin(); if (!skin) return; - video::IVideoDriver *driver = Environment->getVideoDriver(); - - video::SColor bgcolor(140, 0, 0, 0); - driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); gui::IGUIElement::draw(); -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) getAndroidUIInput(); #endif } @@ -257,10 +298,10 @@ bool GUIConfirmRegistration::OnEvent(const SEvent &event) return false; } -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) bool GUIConfirmRegistration::getAndroidUIInput() { - if (!hasAndroidUIInput() || m_jni_field_name != "password") + if (m_jni_field_name.empty() || m_jni_field_name != "password") return false; // still waiting diff --git a/src/gui/guiConfirmRegistration.h b/src/gui/guiConfirmRegistration.h index 6ca3bf260..31df018a3 100644 --- a/src/gui/guiConfirmRegistration.h +++ b/src/gui/guiConfirmRegistration.h @@ -51,13 +51,16 @@ public: bool processInput(); bool OnEvent(const SEvent &event); -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) bool getAndroidUIInput(); #endif private: std::wstring getLabelByID(s32 id) { return L""; } - std::string getNameByID(s32 id) { return "password"; } + std::string getNameByID(s32 id) + { + return id == 262 ? "password" : ""; // 262 is ID_confirmPassword + } Client *m_client = nullptr; const std::string &m_playername; @@ -65,4 +68,5 @@ private: bool *m_aborted = nullptr; std::wstring m_pass_confirm = L""; ISimpleTextureSource *m_tsrc; + video::SColor m_fullscreen_bgcolor; }; diff --git a/src/gui/guiEditBox.cpp b/src/gui/guiEditBox.cpp index 7398301b4..8a1b97e66 100644 --- a/src/gui/guiEditBox.cpp +++ b/src/gui/guiEditBox.cpp @@ -25,8 +25,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "IGUIFont.h" #include "porting.h" +#include "touchscreengui.h" #include "util/string.h" +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) +#include +#endif + GUIEditBox::~GUIEditBox() { if (m_override_font) @@ -178,6 +183,8 @@ void GUIEditBox::setTextMarkers(s32 begin, s32 end) if (begin != m_mark_begin || end != m_mark_end) { m_mark_begin = begin; m_mark_end = end; + m_real_mark_begin = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + m_real_mark_end = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); } } @@ -203,13 +210,29 @@ bool GUIEditBox::OnEvent(const SEvent &event) switch (event.EventType) { case EET_GUI_EVENT: - if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) { - if (event.GUIEvent.Caller == this) { - m_mouse_marking = false; - setTextMarkers(0, 0); - } + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) { +#ifdef HAVE_TOUCHSCREENGUI + if (!TouchScreenGUI::isActive()) +#endif + if (event.GUIEvent.Caller == this) { + m_mouse_marking = false; + setTextMarkers(0, 0); + } } break; +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + case EET_SDL_TEXT_EVENT: + if (event.SDLTextEvent.Type == irr::ESDLET_TEXTINPUT) { + core::stringw text = + utf8_to_stringw(event.SDLTextEvent.Text); + + for (size_t i = 0; i < text.size(); i++) + inputChar(text[i]); + + return true; + } + break; +#endif case EET_KEY_INPUT_EVENT: if (processKey(event)) return true; @@ -240,11 +263,21 @@ bool GUIEditBox::processKey(const SEvent &event) s32 new_mark_begin = m_mark_begin; s32 new_mark_end = m_mark_end; + // On Windows right alt simulates additional control press/release events. + // It causes unexpected bahavior, for example right alt + A would clear text + // in the edit box. At least for SDL we can easily check if alt key is + // pressed + bool altPressed = false; +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + SDL_Keymod keymod = SDL_GetModState(); + altPressed = keymod & KMOD_ALT; +#endif + // control shortcut handling - if (event.KeyInput.Control) { + if (event.KeyInput.Control && !altPressed) { // german backlash '\' entered with control + '?' - if (event.KeyInput.Char == '\\') { + if (m_writable && event.KeyInput.Char == '\\') { inputChar(event.KeyInput.Char); return true; } @@ -259,10 +292,14 @@ bool GUIEditBox::processKey(const SEvent &event) onKeyControlC(event); break; case KEY_KEY_X: - text_changed = onKeyControlX(event, new_mark_begin, new_mark_end); + if (m_writable) + text_changed = onKeyControlX( + event, new_mark_begin, new_mark_end); break; case KEY_KEY_V: - text_changed = onKeyControlV(event, new_mark_begin, new_mark_end); + if (m_writable) + text_changed = onKeyControlV( + event, new_mark_begin, new_mark_end); break; case KEY_HOME: // move/highlight to start of text @@ -288,6 +325,26 @@ bool GUIEditBox::processKey(const SEvent &event) new_mark_end = 0; } break; +#if defined(__ANDROID__) || defined(__IOS__) + case EET_TOUCH_INPUT_EVENT: + if (event.TouchInput.Event == irr::ETIE_PRESSED_LONG) { + if (!m_mouse_marking) { + m_long_press = true; + bool success = onKeyControlC(event); +#ifdef __ANDROID__ + if (success) + SDL_AndroidShowToast( + "Copied to clipboard", 2, + -1, 0, 0); +#elif __IOS__ + if (success) + porting::showToast("Copied to clipboard"); +#endif + } + return true; + } + break; +#endif default: return false; } @@ -336,13 +393,16 @@ bool GUIEditBox::processKey(const SEvent &event) m_blink_start_time = porting::getTimeMs(); } break; case KEY_RETURN: - if (m_multiline) { - inputChar(L'\n'); - } else { - calculateScrollPos(); - sendGuiEvent(EGET_EDITBOX_ENTER); + if (m_writable) { + if (m_multiline) { + inputChar(L'\n'); + } else { + calculateScrollPos(); + sendGuiEvent(EGET_EDITBOX_ENTER); + } + return true; } - return true; + break; case KEY_LEFT: if (event.KeyInput.Shift) { if (m_cursor_pos > 0) { @@ -388,11 +448,15 @@ bool GUIEditBox::processKey(const SEvent &event) } break; case KEY_BACK: - text_changed = onKeyBack(event, new_mark_begin, new_mark_end); + if (m_writable) + text_changed = onKeyBack( + event, new_mark_begin, new_mark_end); break; case KEY_DELETE: - text_changed = onKeyDelete(event, new_mark_begin, new_mark_end); + if (m_writable) + text_changed = onKeyDelete( + event, new_mark_begin, new_mark_end); break; case KEY_ESCAPE: @@ -426,8 +490,10 @@ bool GUIEditBox::processKey(const SEvent &event) return false; default: - inputChar(event.KeyInput.Char); - return true; + if (m_writable) { + inputChar(event.KeyInput.Char); + return true; + } } } @@ -450,8 +516,7 @@ bool GUIEditBox::onKeyUp(const SEvent &event, s32 &mark_begin, s32 &mark_end) // clang-format off if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) { s32 lineNo = getLineFromPos(m_cursor_pos); - s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : - (m_mark_begin > m_mark_end ? m_mark_begin : m_mark_end); + s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : m_real_mark_begin; if (lineNo > 0) { s32 cp = m_cursor_pos - m_broken_text_positions[lineNo]; if ((s32)m_broken_text[lineNo - 1].size() < cp) { @@ -482,8 +547,7 @@ bool GUIEditBox::onKeyDown(const SEvent &event, s32 &mark_begin, s32 &mark_end) // clang-format off if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) { s32 lineNo = getLineFromPos(m_cursor_pos); - s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : - (m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end); + s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : m_real_mark_begin; if (lineNo < (s32)m_broken_text.size() - 1) { s32 cp = m_cursor_pos - m_broken_text_positions[lineNo]; if ((s32)m_broken_text[lineNo + 1].size() < cp) { @@ -509,17 +573,16 @@ bool GUIEditBox::onKeyDown(const SEvent &event, s32 &mark_begin, s32 &mark_end) return false; } -void GUIEditBox::onKeyControlC(const SEvent &event) +bool GUIEditBox::onKeyControlC(const SEvent &event) { // copy to clipboard if (m_passwordbox || !m_operator || m_mark_begin == m_mark_end) - return; + return false; - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - - std::string s = stringw_to_utf8(Text.subString(realmbgn, realmend - realmbgn)); - m_operator->copyToClipboard(s.c_str()); + std::string s = stringw_to_utf8(Text.subString( + m_real_mark_begin, m_real_mark_end - m_real_mark_begin)); + m_operator->copyToClipboard(s.c_str()); + return true; } bool GUIEditBox::onKeyControlX(const SEvent &event, s32 &mark_begin, s32 &mark_end) @@ -533,18 +596,15 @@ bool GUIEditBox::onKeyControlX(const SEvent &event, s32 &mark_begin, s32 &mark_e if (m_passwordbox || !m_operator || m_mark_begin == m_mark_end) return false; - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - // Now remove from box if enabled if (isEnabled()) { // delete core::stringw s; - s = Text.subString(0, realmbgn); - s.append(Text.subString(realmend, Text.size() - realmend)); + s = Text.subString(0, m_real_mark_begin); + s.append(Text.subString(m_real_mark_end, Text.size() - m_real_mark_end)); Text = s; - m_cursor_pos = realmbgn; + m_cursor_pos = m_real_mark_begin; mark_begin = 0; mark_end = 0; return true; @@ -562,9 +622,6 @@ bool GUIEditBox::onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_e if (!m_operator) return false; - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - // add new character if (const c8 *p = m_operator->getTextFromClipboard()) { core::stringw inserted_text = utf8_to_stringw(p); @@ -582,13 +639,14 @@ bool GUIEditBox::onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_e } else { // replace text - core::stringw s = Text.subString(0, realmbgn); + core::stringw s = Text.subString(0, m_real_mark_begin); s.append(inserted_text); - s.append(Text.subString(realmend, Text.size() - realmend)); + s.append(Text.subString( + m_real_mark_end, Text.size() - m_real_mark_end)); if (!m_max || s.size() <= m_max) { Text = s; - m_cursor_pos = realmbgn + inserted_text.size(); + m_cursor_pos = m_real_mark_begin + inserted_text.size(); } } } @@ -607,16 +665,11 @@ bool GUIEditBox::onKeyBack(const SEvent &event, s32 &mark_begin, s32 &mark_end) if (m_mark_begin != m_mark_end) { // delete marked text - const s32 realmbgn = - m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = - m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - - s = Text.subString(0, realmbgn); - s.append(Text.subString(realmend, Text.size() - realmend)); + s = Text.subString(0, m_real_mark_begin); + s.append(Text.subString(m_real_mark_end, Text.size() - m_real_mark_end)); Text = s; - m_cursor_pos = realmbgn; + m_cursor_pos = m_real_mark_begin; } else { // delete text behind cursor if (m_cursor_pos > 0) @@ -645,16 +698,11 @@ bool GUIEditBox::onKeyDelete(const SEvent &event, s32 &mark_begin, s32 &mark_end if (m_mark_begin != m_mark_end) { // delete marked text - const s32 realmbgn = - m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = - m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - - s = Text.subString(0, realmbgn); - s.append(Text.subString(realmend, Text.size() - realmend)); + s = Text.subString(0, m_real_mark_begin); + s.append(Text.subString(m_real_mark_end, Text.size() - m_real_mark_end)); Text = s; - m_cursor_pos = realmbgn; + m_cursor_pos = m_real_mark_begin; } else { // delete text before cursor s = Text.subString(0, m_cursor_pos); @@ -690,14 +738,11 @@ void GUIEditBox::inputString(const core::stringw &str) core::stringw s; if (m_mark_begin != m_mark_end) { // replace marked text - s32 real_begin = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - s32 real_end = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - - s = Text.subString(0, real_begin); + s = Text.subString(0, m_real_mark_begin); s.append(str); - s.append(Text.subString(real_end, Text.size() - real_end)); + s.append(Text.subString(m_real_mark_end, Text.size() - m_real_mark_end)); Text = s; - m_cursor_pos = real_begin + len; + m_cursor_pos = m_real_mark_begin + 1; } else { // append string s = Text.subString(0, m_cursor_pos); @@ -720,56 +765,106 @@ void GUIEditBox::inputString(const core::stringw &str) bool GUIEditBox::processMouse(const SEvent &event) { switch (event.MouseInput.Event) { - case irr::EMIE_LMOUSE_LEFT_UP: + case irr::EMIE_LMOUSE_LEFT_UP: { + s32 cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); +#ifdef HAVE_TOUCHSCREENGUI + // Remove text markers for short tap in one place + if (TouchScreenGUI::isActive() && !m_long_press && + m_cursor_press_pos == cursor_pos && + Environment->hasFocus(this)) { + setTextMarkers(cursor_pos, cursor_pos); + } +#endif + m_cursor_press_pos = -1; + m_long_press = false; if (Environment->hasFocus(this)) { - m_cursor_pos = getCursorPos( - event.MouseInput.X, event.MouseInput.Y); - if (m_mouse_marking) { - setTextMarkers(m_mark_begin, m_cursor_pos); - } + m_cursor_pos = cursor_pos; m_mouse_marking = false; calculateScrollPos(); return true; } - break; + } break; case irr::EMIE_MOUSE_MOVED: { + s32 cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); +#ifdef HAVE_TOUCHSCREENGUI + // Start text marking when cursor was moved, so that user doesn't want + // to copy text. + if (TouchScreenGUI::isActive() && !m_long_press && !m_mouse_marking && + m_cursor_press_pos != -1 && + cursor_pos != m_cursor_press_pos && + Environment->hasFocus(this)) { + m_mouse_marking = true; + + int mark_length = m_real_mark_end - m_real_mark_begin; + + if (mark_length > 2 && + std::abs(m_cursor_press_pos - m_mark_begin) < 3) + setTextMarkers(m_mark_end, m_cursor_press_pos); + else if (mark_length > 2 && + std::abs(m_cursor_press_pos - m_mark_end) < 3) + setTextMarkers(m_mark_begin, m_cursor_press_pos); + else + setTextMarkers(m_cursor_press_pos, m_cursor_press_pos); + } +#endif if (m_mouse_marking) { - m_cursor_pos = getCursorPos( - event.MouseInput.X, event.MouseInput.Y); + m_cursor_pos = cursor_pos; setTextMarkers(m_mark_begin, m_cursor_pos); calculateScrollPos(); return true; } } break; - case EMIE_LMOUSE_PRESSED_DOWN: + case EMIE_LMOUSE_PRESSED_DOWN: { + m_long_press = false; if (!Environment->hasFocus(this)) { m_blink_start_time = porting::getTimeMs(); - m_mouse_marking = true; m_cursor_pos = getCursorPos( event.MouseInput.X, event.MouseInput.Y); - setTextMarkers(m_cursor_pos, m_cursor_pos); + m_cursor_press_pos = m_cursor_pos; + +#ifdef HAVE_TOUCHSCREENGUI + if (!TouchScreenGUI::isActive() || + m_cursor_pos < m_real_mark_begin || + m_cursor_pos > m_real_mark_end) { +#endif + m_mouse_marking = true; + setTextMarkers(m_cursor_pos, m_cursor_pos); +#ifdef HAVE_TOUCHSCREENGUI + } +#endif calculateScrollPos(); return true; } else { if (!AbsoluteClippingRect.isPointInside(core::position2d( event.MouseInput.X, event.MouseInput.Y))) { + m_cursor_press_pos = -1; return false; } else { // move cursor m_cursor_pos = getCursorPos( event.MouseInput.X, event.MouseInput.Y); + m_cursor_press_pos = m_cursor_pos; - s32 newMarkBegin = m_mark_begin; - if (!m_mouse_marking) - newMarkBegin = m_cursor_pos; +#ifdef HAVE_TOUCHSCREENGUI + if (!TouchScreenGUI::isActive() || + m_cursor_pos < m_real_mark_begin || + m_cursor_pos > m_real_mark_end) { +#endif + s32 newMarkBegin = m_mark_begin; + if (!m_mouse_marking) + newMarkBegin = m_cursor_pos; - m_mouse_marking = true; - setTextMarkers(newMarkBegin, m_cursor_pos); + m_mouse_marking = true; + setTextMarkers(newMarkBegin, m_cursor_pos); +#ifdef HAVE_TOUCHSCREENGUI + } +#endif calculateScrollPos(); return true; } } + } break; case EMIE_MOUSE_WHEEL: if (m_vscrollbar && m_vscrollbar->isVisible()) { s32 pos = m_vscrollbar->getPos(); diff --git a/src/gui/guiEditBox.h b/src/gui/guiEditBox.h index 17489281f..4c6a956b7 100644 --- a/src/gui/guiEditBox.h +++ b/src/gui/guiEditBox.h @@ -132,6 +132,13 @@ public: virtual bool acceptsIME() { return isEnabled() && m_writable; }; + //! Sets the scrollbar texture + void setScrollbarStyle(const StyleSpec &style, ISimpleTextureSource *tsrc) + { + if (m_vscrollbar != nullptr) + m_vscrollbar->setStyle(style, tsrc); + } + protected: virtual void breakText() = 0; @@ -179,6 +186,7 @@ protected: u32 m_blink_start_time = 0; s32 m_cursor_pos = 0; + s32 m_cursor_press_pos = 0; s32 m_hscroll_pos = 0; s32 m_vscroll_pos = 0; // scroll position in characters u32 m_max = 0; @@ -190,9 +198,12 @@ protected: bool m_writable; bool m_mouse_marking = false; + bool m_long_press = false; s32 m_mark_begin = 0; s32 m_mark_end = 0; + s32 m_real_mark_begin = 0; + s32 m_real_mark_end = 0; gui::IGUIFont *m_last_break_font = nullptr; IOSOperator *m_operator = nullptr; @@ -207,7 +218,7 @@ private: bool onKeyUp(const SEvent &event, s32 &mark_begin, s32 &mark_end); bool onKeyDown(const SEvent &event, s32 &mark_begin, s32 &mark_end); - void onKeyControlC(const SEvent &event); + bool onKeyControlC(const SEvent &event); bool onKeyControlX(const SEvent &event, s32 &mark_begin, s32 &mark_end); bool onKeyControlV(const SEvent &event, s32 &mark_begin, s32 &mark_end); bool onKeyBack(const SEvent &event, s32 &mark_begin, s32 &mark_end); diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp index 1b7f7832a..aa8d37f54 100644 --- a/src/gui/guiEditBoxWithScrollbar.cpp +++ b/src/gui/guiEditBoxWithScrollbar.cpp @@ -83,6 +83,7 @@ void GUIEditBoxWithScrollBar::draw() return; const bool focus = Environment->hasFocus(this); + const bool scollbar_focus = Environment->hasFocus(m_vscrollbar); IGUISkin* skin = Environment->getSkin(); if (!skin) @@ -94,6 +95,10 @@ void GUIEditBoxWithScrollBar::draw() default_bg_color = m_writable ? skin->getColor(EGDC_WINDOW) : video::SColor(0); bg_color = m_bg_color_used ? m_bg_color : default_bg_color; + if (IsEnabled && m_writable) { + bg_color = focus ? skin->getColor(EGDC_FOCUSED_EDITABLE) : skin->getColor(EGDC_EDITABLE); + } + if (!m_border && m_background) { skin->draw2DRectangle(this, bg_color, AbsoluteRect, &AbsoluteClippingRect); } @@ -134,10 +139,8 @@ void GUIEditBoxWithScrollBar::draw() // get mark position const bool ml = (!m_passwordbox && (m_word_wrap || m_multiline)); - const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; - const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - const s32 hline_start = ml ? getLineFromPos(realmbgn) : 0; - const s32 hline_count = ml ? getLineFromPos(realmend) - hline_start + 1 : 1; + const s32 hline_start = ml ? getLineFromPos(m_real_mark_begin) : 0; + const s32 hline_count = ml ? getLineFromPos(m_real_mark_end) - hline_start + 1 : 1; const s32 line_count = ml ? m_broken_text.size() : 1; // Save the override color information. @@ -187,26 +190,26 @@ void GUIEditBoxWithScrollBar::draw() false, true, &local_clip_rect); // draw mark and marked text - if (focus && m_mark_begin != m_mark_end && i >= hline_start && i < hline_start + hline_count) { + if ((focus || scollbar_focus) && m_mark_begin != m_mark_end && i >= hline_start && i < hline_start + hline_count) { s32 mbegin = 0, mend = 0; s32 lineStartPos = 0, lineEndPos = txt_line->size(); if (i == hline_start) { // highlight start is on this line - s = txt_line->subString(0, realmbgn - start_pos); + s = txt_line->subString(0, m_real_mark_begin - start_pos); mbegin = font->getDimension(s.c_str()).Width; // deal with kerning mbegin += font->getKerningWidth( - &((*txt_line)[realmbgn - start_pos]), - realmbgn - start_pos > 0 ? &((*txt_line)[realmbgn - start_pos - 1]) : 0); + &((*txt_line)[m_real_mark_begin - start_pos]), + m_real_mark_begin - start_pos > 0 ? &((*txt_line)[m_real_mark_begin - start_pos - 1]) : 0); - lineStartPos = realmbgn - start_pos; + lineStartPos = m_real_mark_begin - start_pos; } if (i == hline_start + hline_count - 1) { // highlight end is on this line - s2 = txt_line->subString(0, realmend - start_pos); + s2 = txt_line->subString(0, m_real_mark_end - start_pos); mend = font->getDimension(s2.c_str()).Width; lineEndPos = (s32)s2.size(); } else { diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp index 293080534..fb6a3bc66 100644 --- a/src/gui/guiEngine.cpp +++ b/src/gui/guiEngine.cpp @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/fontengine.h" #include "client/guiscalingfilter.h" #include "irrlicht_changes/static_text.h" +#include "translation.h" #if ENABLE_GLES #include "client/tile.h" @@ -47,7 +48,11 @@ with this program; if not, write to the Free Software Foundation, Inc., /******************************************************************************/ void TextDestGuiEngine::gotText(const StringMap &fields) { - m_engine->getScriptIface()->handleMainMenuButtons(fields); + try { + m_engine->getScriptIface()->handleMainMenuButtons(fields); + } catch (LuaError &e) { + m_engine->handleMainMenuLuaError(e.what()); + } } /******************************************************************************/ @@ -76,6 +81,9 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id) return NULL; #if ENABLE_GLES + if (hasNPotSupport()) + return m_driver->getTexture(name.c_str()); + video::ITexture *retval = m_driver->findTexture(name.c_str()); if (retval) return retval; @@ -84,10 +92,12 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id) if (!image) return NULL; - image = Align2Npot2(image, m_driver); - retval = m_driver->addTexture(name.c_str(), image); + // Verified by the profiler - it reduces memory usage! + video::IImage *newimage = Align2Npot2(image, m_driver); + retval = m_driver->addTexture(name.c_str(), newimage); + image = NULL; m_to_delete.insert(name); - image->drop(); + newimage->drop(); return retval; #else return m_driver->getTexture(name.c_str()); @@ -151,8 +161,8 @@ GUIEngine::GUIEngine(JoystickController *joystick, //create soundmanager MenuMusicFetcher soundfetcher; #if USE_SOUND - if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) - m_sound_manager = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher); + if (g_settings->getBool("enable_sound") && g_sound_manager_singleton) + m_sound_manager = createOpenALSoundManager(g_sound_manager_singleton, &soundfetcher); #endif if (!m_sound_manager) m_sound_manager = &dummySoundManager; @@ -191,6 +201,12 @@ GUIEngine::GUIEngine(JoystickController *joystick, infostream << "GUIEngine: Initializing Lua" << std::endl; + // Translate the error message before clearing g_client_translations + if (!m_data->script_data.errormessage.empty()) + m_data->script_data.errormessage = wide_to_utf8(translate_string( + utf8_to_wide(m_data->script_data.errormessage))); + + g_client_translations->clear(); m_script = new MainMenuScripting(this); try { @@ -204,8 +220,7 @@ GUIEngine::GUIEngine(JoystickController *joystick, run(); } catch (LuaError &e) { - errorstream << "Main menu error: " << e.what() << std::endl; - m_data->script_data.errormessage = e.what(); + handleMainMenuLuaError(e.what()); } m_menu->quitMenu(); @@ -213,6 +228,18 @@ GUIEngine::GUIEngine(JoystickController *joystick, m_menu = NULL; } +void GUIEngine::handleMainMenuLuaError(const char* errmsg) { + errorstream << "Main menu error: " << errmsg << std::endl; + m_data->script_data.errormessage = errmsg; +#ifdef __ANDROID__ + porting::handleError("Main menu error", errmsg); +#endif + + // Make the menu quit. Since an error message has been set this won't + // actually start the game. + m_startgame = true; +} + /******************************************************************************/ bool GUIEngine::loadMainMenuScript() { @@ -231,6 +258,9 @@ bool GUIEngine::loadMainMenuScript() } catch (const ModError &e) { errorstream << "GUIEngine: execution of menu script failed: " << e.what() << std::endl; +#ifdef __ANDROID__ + porting::handleError("Main menu load error", e.what()); +#endif } return false; @@ -269,6 +299,16 @@ void GUIEngine::run() } while (m_rendering_engine->run() && (!m_startgame) && (!m_kill)) { + IrrlichtDevice *device = m_rendering_engine->get_raw_device(); +#ifdef __IOS__ + if (device->isWindowMinimized()) +#else + if (!device->isWindowFocused()) +#endif + { + sleep_ms(50); + continue; + } const irr::core::dimension2d ¤t_screen_size = m_rendering_engine->get_video_driver()->getScreenSize(); @@ -306,7 +346,6 @@ void GUIEngine::run() driver->endScene(); - IrrlichtDevice *device = m_rendering_engine->get_raw_device(); u32 frametime_min = 1000 / (device->isWindowFocused() ? g_settings->getFloat("fps_max") : g_settings->getFloat("fps_max_unfocused")); @@ -317,7 +356,7 @@ void GUIEngine::run() m_script->step(); -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) m_menu->getAndroidUIInput(); #endif } @@ -499,19 +538,28 @@ void GUIEngine::drawHeader(video::IVideoDriver *driver) v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult, ((f32)texture->getOriginalSize().Height) * mult); +#if !defined(__ANDROID__) && !defined(__IOS__) // Don't draw the header if there isn't enough room s32 free_space = (((s32)screensize.Height)-320)/2; - if (free_space > splashsize.Y) { - core::rect splashrect(0, 0, splashsize.X, splashsize.Y); - splashrect += v2s32((screensize.Width/2)-(splashsize.X/2), - ((free_space/2)-splashsize.Y/2)+10); + if (free_space <= splashsize.Y) + return; + + core::rect splashrect(0, 0, splashsize.X, splashsize.Y); + splashrect += v2s32((screensize.Width/2)-(splashsize.X/2), + ((free_space/2)-splashsize.Y/2)); +#else + core::rect splashrect(0, 0, splashsize.X, splashsize.Y); + splashrect += v2s32((screensize.Width/2)-(splashsize.X/2), 0); + + if (g_settings->getBool("device_is_tablet")) + splashrect += v2s32(0, splashsize.Y/4); +#endif draw2DImageFilterScaled(driver, texture, splashrect, core::rect(core::position2d(0,0), core::dimension2di(texture->getOriginalSize())), NULL, NULL, true); - } } /******************************************************************************/ @@ -614,7 +662,7 @@ void GUIEngine::updateTopLeftTextSize() { core::rect rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()), g_fontengine->getTextHeight()); - rect += v2s32(4, 0); + rect += v2s32(5 + g_settings->getU16("round_screen"), 0); m_irr_toplefttext->remove(); m_irr_toplefttext = gui::StaticText::add(m_rendering_engine->get_gui_env(), diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h index 61f3afaf5..116c6549b 100644 --- a/src/gui/guiEngine.h +++ b/src/gui/guiEngine.h @@ -159,6 +159,8 @@ public: /** default destructor */ virtual ~GUIEngine(); + void handleMainMenuLuaError(const char* errmsg); + /** * return MainMenuScripting interface */ diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index 2e4f1c8c1..34181880f 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -67,6 +67,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiScrollContainer.h" #include "guiHyperText.h" #include "guiScene.h" +#include "touchscreengui.h" + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include +#endif #define MY_CHECKPOS(a,b) \ if (v_pos.size() != 2) { \ @@ -217,6 +222,10 @@ void GUIFormSpecMenu::setInitialFocus() if (it->getType() == gui::EGUIET_EDIT_BOX && it->getText()[0] == 0) { Environment->setFocus(it); +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + if (porting::hasRealKeyboard()) + SDL_StartTextInput(); +#endif return; } } @@ -225,6 +234,10 @@ void GUIFormSpecMenu::setInitialFocus() for (gui::IGUIElement *it : children) { if (it->getType() == gui::EGUIET_EDIT_BOX) { Environment->setFocus(it); +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + if (porting::hasRealKeyboard()) + SDL_StartTextInput(); +#endif return; } } @@ -534,10 +547,15 @@ void GUIFormSpecMenu::parseList(parserData *data, const std::string &element) pos.X + (geom.X - 1) * slot_spacing.X + slot_size.X, pos.Y + (geom.Y - 1) * slot_spacing.Y + slot_size.Y); + video::ITexture *bgimg = style.getTexture(StyleSpec::BGIMG, m_tsrc, nullptr); + video::ITexture *bgimg_hovered = style.getTexture(StyleSpec::BGIMG_HOVERED, m_tsrc, nullptr); + video::ITexture *bgimg_pressed = style.getTexture(StyleSpec::BGIMG_PRESSED, m_tsrc, nullptr); + core::rect bgimg_middle = style.getRect(StyleSpec::BGIMG_MIDDLE, core::rect()); + GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent, spec.fid, rect, m_invmgr, loc, listname, geom, start_i, - v2s32(slot_size.X, slot_size.Y), slot_spacing, this, - data->inventorylist_options, m_font); + v2s32(slot_size.X, slot_size.Y), slot_spacing, bgimg, bgimg_hovered, + bgimg_pressed, bgimg_middle, this, data->inventorylist_options, m_font); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); @@ -665,6 +683,10 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen std::vector v_geom = split(parts[1],','); std::string name = parts[3]; std::string value = parts[4]; + std::vector textures; + + if (parts.size() == 6) + textures = split(parts[5], ','); MY_CHECKPOS("scrollbar",0); MY_CHECKGEOM("scrollbar",1); @@ -720,6 +742,18 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen e->setPageSize(scrollbar_size * (max - min + 1) / data->scrollbar_options.thumb_size); + std::vector itextures; + + if (textures.empty()) { + // Fall back to the scrollbar textures specified in style[] + e->setStyle(style, m_tsrc); + } else { + for (u32 i = 0; i < textures.size(); ++i) + itextures.push_back(m_tsrc->getTexture(textures[i])); + + e->setTextures(itextures); + } + if (spec.fname == m_focused_element) { Environment->setFocus(e); } @@ -784,101 +818,99 @@ void GUIFormSpecMenu::parseScrollBarOptions(parserData* data, const std::string void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element) { std::vector parts; - if (!precheckElement("image", element, 2, 3, parts)) + if (!precheckElement("image", element, 2, 4, parts)) return; + size_t offset = parts.size() >= 3; + + std::vector v_pos = split(parts[0],','); + MY_CHECKPOS("image", 0); + + std::vector v_geom; if (parts.size() >= 3) { - std::vector v_pos = split(parts[0],','); - std::vector v_geom = split(parts[1],','); - std::string name = unescape_string(parts[2]); - - MY_CHECKPOS("image", 0); + v_geom = split(parts[1],','); MY_CHECKGEOM("image", 1); + } - v2s32 pos; - v2s32 geom; + std::string name = unescape_string(parts[1 + offset]); + video::ITexture *texture = m_tsrc->getTexture(name); - if (data->real_coordinates) { - pos = getRealCoordinateBasePos(v_pos); - geom = getRealCoordinateGeometry(v_geom); + v2s32 pos; + v2s32 geom; + + if (parts.size() < 3) { + if (texture != nullptr) { + core::dimension2du dim = texture->getOriginalSize(); + geom.X = dim.Width; + geom.Y = dim.Height; } else { - pos = getElementBasePos(&v_pos); + geom = v2s32(0); + } + } + + if (data->real_coordinates) { + pos = getRealCoordinateBasePos(v_pos); + if (parts.size() >= 3) + geom = getRealCoordinateGeometry(v_geom); + } else { + pos = getElementBasePos(&v_pos); + if (parts.size() >= 3) { geom.X = stof(v_geom[0]) * (float)imgsize.X; geom.Y = stof(v_geom[1]) * (float)imgsize.Y; } - - if (!data->explicit_size) - warningstream<<"invalid use of image without a size[] element"<getTexture(name); - if (!texture) { - errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" - << std::endl << "\t" << name << std::endl; - return; - } - - FieldSpec spec( - name, - L"", - L"", - 258 + m_fields.size(), - 1 - ); - core::rect rect(pos, pos + geom); - gui::IGUIImage *e = Environment->addImage(rect, data->current_parent, - spec.fid, 0, true); - e->setImage(texture); - e->setScaleImage(true); - auto style = getDefaultStyleForElement("image", spec.fname); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - m_fields.push_back(spec); - - // images should let events through - e->grab(); - m_clickthrough_elements.push_back(e); - return; } - // Else: 2 arguments in "parts" - - std::vector v_pos = split(parts[0],','); - std::string name = unescape_string(parts[1]); - - MY_CHECKPOS("image", 0); - - v2s32 pos = getElementBasePos(&v_pos); - if (!data->explicit_size) - warningstream<<"invalid use of image without a size[] element"<getTexture(name); - if (!texture) { - errorstream << "GUIFormSpecMenu::parseImage() Unable to load texture:" - << std::endl << "\t" << name << std::endl; - return; - } + warningstream << "Invalid use of image without a size[] element" << std::endl; FieldSpec spec( name, L"", L"", - 258 + m_fields.size() + 258 + m_fields.size(), + 1 ); - gui::IGUIImage *e = Environment->addImage(texture, pos, true, - data->current_parent, spec.fid, 0); + + core::rect rect = core::rect(pos, pos + geom); + + core::rect middle; + if (parts.size() >= 4) + parseMiddleRect(parts[3], &middle); + + // Temporary fix for issue #12581 in 5.6.0. + // Use legacy image when not rendering 9-slice image because GUIAnimatedImage + // uses NNAA filter which causes visual artifacts when image uses alpha blending. + + gui::IGUIElement *e; + if (middle.getArea() > 0) { + GUIAnimatedImage *image = new GUIAnimatedImage(Environment, data->current_parent, + spec.fid, rect); + + image->setTexture(texture); + image->setMiddleRect(middle); + e = image; + } + else { + gui::IGUIImage *image = Environment->addImage(rect, data->current_parent, spec.fid, nullptr, true); + image->setImage(texture); + image->setScaleImage(true); + image->grab(); // compensate for drop in addImage + e = image; + } + auto style = getDefaultStyleForElement("image", spec.fname); e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3)); - m_fields.push_back(spec); - // images should let events through - e->grab(); + // Animated images should let events through m_clickthrough_elements.push_back(e); + + m_fields.push_back(spec); } void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &element) { std::vector parts; - if (!precheckElement("animated_image", element, 6, 7, parts)) + if (!precheckElement("animated_image", element, 6, 8, parts)) return; std::vector v_pos = split(parts[0], ','); @@ -904,7 +936,8 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el } if (!data->explicit_size) - warningstream << "Invalid use of animated_image without a size[] element" << std::endl; + warningstream << "Invalid use of animated_image without a size[] element" + << std::endl; FieldSpec spec( name, @@ -917,9 +950,17 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el core::rect rect = core::rect(pos, pos + geom); - GUIAnimatedImage *e = new GUIAnimatedImage(Environment, data->current_parent, spec.fid, - rect, texture_name, frame_count, frame_duration, m_tsrc); + core::rect middle; + if (parts.size() >= 8) + parseMiddleRect(parts[7], &middle); + GUIAnimatedImage *e = new GUIAnimatedImage(Environment, data->current_parent, + spec.fid, rect); + + e->setTexture(m_tsrc->getTexture(texture_name)); + e->setMiddleRect(middle); + e->setFrameDuration(frame_duration); + e->setFrameCount(frame_count); if (parts.size() >= 7) e->setFrameIndex(stoi(parts[6]) - 1); @@ -1044,6 +1085,35 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element, m_fields.push_back(spec); } +bool GUIFormSpecMenu::parseMiddleRect(const std::string &value, core::rect *parsed_rect) +{ + core::rect rect; + std::vector v_rect = split(value, ','); + + if (v_rect.size() == 1) { + s32 x = stoi(v_rect[0]); + rect.UpperLeftCorner = core::vector2di(x, x); + rect.LowerRightCorner = core::vector2di(-x, -x); + } else if (v_rect.size() == 2) { + s32 x = stoi(v_rect[0]); + s32 y = stoi(v_rect[1]); + rect.UpperLeftCorner = core::vector2di(x, y); + rect.LowerRightCorner = core::vector2di(-x, -y); + // `-x` is interpreted as `w - x` + } else if (v_rect.size() == 4) { + rect.UpperLeftCorner = core::vector2di(stoi(v_rect[0]), stoi(v_rect[1])); + rect.LowerRightCorner = core::vector2di(stoi(v_rect[2]), stoi(v_rect[3])); + } else { + warningstream << "Invalid rectangle string format: \"" << value + << "\"" << std::endl; + return false; + } + + *parsed_rect = rect; + + return true; +} + void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element) { std::vector parts; @@ -1085,25 +1155,8 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme } core::rect middle; - if (parts.size() >= 5) { - std::vector v_middle = split(parts[4], ','); - if (v_middle.size() == 1) { - s32 x = stoi(v_middle[0]); - middle.UpperLeftCorner = core::vector2di(x, x); - middle.LowerRightCorner = core::vector2di(-x, -x); - } else if (v_middle.size() == 2) { - s32 x = stoi(v_middle[0]); - s32 y = stoi(v_middle[1]); - middle.UpperLeftCorner = core::vector2di(x, y); - middle.LowerRightCorner = core::vector2di(-x, -y); - // `-x` is interpreted as `w - x` - } else if (v_middle.size() == 4) { - middle.UpperLeftCorner = core::vector2di(stoi(v_middle[0]), stoi(v_middle[1])); - middle.LowerRightCorner = core::vector2di(stoi(v_middle[2]), stoi(v_middle[3])); - } else { - warningstream << "Invalid rectangle given to middle param of background[] element" << std::endl; - } - } + if (parts.size() >= 5) + parseMiddleRect(parts[4], &middle); if (!data->explicit_size && !clip) warningstream << "invalid use of unclipped background without a size[] element" << std::endl; @@ -1233,8 +1286,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element) e->setSelected(stoi(str_initial_selection)); auto style = getDefaultStyleForElement("table", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideFont(style.getFont()); + e->setStyle(style); m_tables.emplace_back(spec, e); m_fields.push_back(spec); @@ -1307,8 +1359,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element e->setSelected(stoi(str_initial_selection)); auto style = getDefaultStyleForElement("textlist", name); - e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); - e->setOverrideFont(style.getFont()); + e->setStyle(style); m_tables.emplace_back(spec, e); m_fields.push_back(spec); @@ -1414,6 +1465,10 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element std::vector v_geom = split(parts[1],','); std::string name = parts[2]; std::string label = parts[3]; + std::string default_val; + + if (parts.size() > 4) + default_val = parts[4]; MY_CHECKPOS("pwdfield",0); MY_CHECKGEOM("pwdfield",1); @@ -1437,19 +1492,23 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element core::rect rect = core::rect(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); + if (m_form_src && !default_val.empty()) + default_val = m_form_src->resolveText(default_val); + std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label))); + std::wstring wpassword = utf8_to_wide(unescape_string(default_val)); FieldSpec spec( name, wlabel, - L"", + wpassword, 258 + m_fields.size(), 0, ECI_IBEAM ); spec.send = true; - gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true, + gui::IGUIEditBox *e = Environment->addEditBox(wpassword.c_str(), rect, true, data->current_parent, spec.fid); if (spec.fname == m_focused_element) { @@ -1470,6 +1529,9 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); e->setDrawBorder(style.getBool(StyleSpec::BORDER, true)); e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF))); + if (style.get(StyleSpec::BGCOLOR, "") == "transparent") { + e->setDrawBackground(false); + } e->setOverrideFont(style.getFont()); irr::SEvent evt; @@ -1506,10 +1568,12 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, spec.flabel.swap(spec.fdefault); } + GUIEditBox *box = nullptr; gui::IGUIEditBox *e = nullptr; if (is_multiline) { - e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, Environment, - data->current_parent, spec.fid, rect, is_editable, true); + box = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, Environment, + data->current_parent, spec.fid, rect, is_editable, !is_editable); + e = box; } else if (is_editable) { e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, data->current_parent, spec.fid); @@ -1543,6 +1607,8 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec, e->setDrawBorder(border); e->setDrawBackground(border); e->setOverrideFont(style.getFont()); + if (box != nullptr) + box->setScrollbarStyle(style, m_tsrc); e->drop(); } @@ -1566,6 +1632,8 @@ void GUIFormSpecMenu::parseSimpleField(parserData *data, std::string label = parts[1]; std::string default_val = parts[2]; + bool is_dynamic = (name.length() > 0 && name[0] == 'D'); + core::rect rect; if (data->explicit_size) @@ -1596,6 +1664,8 @@ void GUIFormSpecMenu::parseSimpleField(parserData *data, ECI_IBEAM ); + spec.is_dynamic = is_dynamic; + createTextField(data, spec, rect, false); m_fields.push_back(spec); @@ -1612,6 +1682,8 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector& std::string label = parts[3]; std::string default_val = parts[4]; + bool is_dynamic = (name.length() > 0 && name[0] == 'D'); + MY_CHECKPOS(type,0); MY_CHECKGEOM(type,1); @@ -1660,6 +1732,8 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector& ECI_IBEAM ); + spec.is_dynamic = is_dynamic; + createTextField(data, spec, rect, type == "textarea"); // Note: Before 5.2.0 "parts.size() >= 6" resulted in a @@ -1733,7 +1807,7 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen spec.sound = style.get(StyleSpec::Property::SOUND, ""); GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, - data->current_parent, spec.fid, rect, m_client, m_tsrc); + data->current_parent, spec.fid, rect, m_client, m_tsrc, style); e->drop(); m_fields.push_back(spec); @@ -3247,10 +3321,34 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) double fitx_imgsize; double fity_imgsize; +#if defined(__ANDROID__) || defined(__IOS__) + v2f padded_screensize( + mydata.screensize.X, + mydata.screensize.Y + ); + + // Try and find a tabheader[] element in the formspec + for (unsigned int j = i; j < elements.size(); j++) { + // This could use split() but since we don't parse parameters + // here it's probably faster to use find(). + const size_t pos = elements[j].find('['); + if (pos == std::string::npos) + continue; + + // If we find a tabheader then decrease padded_screensize.Y and + // stop searching through the formspec. + const std::string element_type = trim(elements[j].substr(0, pos)); + if (element_type == "tabheader") { + padded_screensize.Y *= 0.9f; + break; + } + } +#else v2f padded_screensize( mydata.screensize.X * (1.0f - mydata.padding.X * 2.0f), mydata.screensize.Y * (1.0f - mydata.padding.Y * 2.0f) ); +#endif if (mydata.real_coordinates) { fitx_imgsize = padded_screensize.X / mydata.invsize.X; @@ -3271,6 +3369,10 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) // In Android, the preferred imgsize should be larger to accommodate the // smaller screensize. double prefer_imgsize = min_screen_dim / 10 * gui_scaling; + + // Try to fit 13 coordinates on large tablets. + if (g_settings->getBool("device_is_tablet")) + prefer_imgsize = min_screen_dim / 13 * gui_scaling; #else // Desktop computers have more space, so try to fit 15 coordinates. double prefer_imgsize = min_screen_dim / 15 * gui_scaling; @@ -3476,10 +3578,10 @@ void GUIFormSpecMenu::legacySortElements(core::list::Iterator fro } } -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) bool GUIFormSpecMenu::getAndroidUIInput() { - if (!hasAndroidUIInput()) + if (m_jni_field_name.empty()) return false; // still waiting @@ -3500,6 +3602,17 @@ bool GUIFormSpecMenu::getAndroidUIInput() std::string text = porting::getInputDialogValue(); ((gui::IGUIEditBox *)element)->setText(utf8_to_wide(text).c_str()); + + // Create event + gui::IGUIElement *focus = Environment->getFocus(); + if (focus) { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = focus; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = gui::EGET_EDITBOX_CHANGED; + element->OnEvent(e); + } } return false; } @@ -3631,9 +3744,12 @@ void GUIFormSpecMenu::drawMenu() } /* TODO find way to show tooltips on touchscreen */ -#ifndef HAVE_TOUCHSCREENGUI - m_pointer = RenderingEngine::get_raw_device()->getCursorControl()->getPosition(); +#ifdef HAVE_TOUCHSCREENGUI + if (!TouchScreenGUI::isActive()) #endif + { + m_pointer = RenderingEngine::get_raw_device()->getCursorControl()->getPosition(); + } /* Draw fields/buttons tooltips and update the mouse cursor @@ -3641,11 +3757,9 @@ void GUIFormSpecMenu::drawMenu() gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(m_pointer); -#ifndef HAVE_TOUCHSCREENGUI gui::ICursorControl *cursor_control = RenderingEngine::get_raw_device()-> getCursorControl(); gui::ECURSOR_ICON current_cursor_icon = cursor_control->getActiveIcon(); -#endif bool hovered_element_found = false; if (hovered != NULL) { @@ -3681,11 +3795,9 @@ void GUIFormSpecMenu::drawMenu() m_tooltips[field.fname].bgcolor); } -#ifndef HAVE_TOUCHSCREENGUI if (field.ftype != f_HyperText && // Handled directly in guiHyperText current_cursor_icon != field.fcursor_icon) cursor_control->setActiveIcon(field.fcursor_icon); -#endif hovered_element_found = true; @@ -3696,10 +3808,8 @@ void GUIFormSpecMenu::drawMenu() if (!hovered_element_found) { // no element is hovered -#ifndef HAVE_TOUCHSCREENGUI if (current_cursor_icon != ECI_NORMAL) cursor_control->setActiveIcon(ECI_NORMAL); -#endif } m_tooltip_element->draw(); @@ -3731,14 +3841,12 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, int tooltip_offset_x = m_btn_height; int tooltip_offset_y = m_btn_height; #ifdef HAVE_TOUCHSCREENGUI - tooltip_offset_x *= 3; - tooltip_offset_y = 0; - if (m_pointer.X > (s32)screenSize.X / 2) - tooltip_offset_x = -(tooltip_offset_x + tooltip_width); - - // Hide tooltip after ETIE_LEFT_UP - if (m_pointer.X == 0) - return; + if (TouchScreenGUI::isActive()) { + tooltip_offset_x *= 3; + tooltip_offset_y = 0; + if (m_pointer.X > (s32)screenSize.X / 2) + tooltip_offset_x = -(tooltip_offset_x + tooltip_width); + } #endif // Calculate and set the tooltip position @@ -4081,6 +4189,15 @@ enum ButtonEventType : u8 BET_OTHER }; +void GUIFormSpecMenu::clearSelection() +{ + m_selected_swap.clear(); + delete m_selected_item; + m_selected_item = nullptr; + m_selected_amount = 0; + m_selected_dragging = false; +} + bool GUIFormSpecMenu::OnEvent(const SEvent& event) { if (event.EventType==EET_KEY_INPUT_EVENT) { @@ -4268,7 +4385,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) count = MYMIN(s_count, 10); else if (button == BET_WHEEL_DOWN) count = 1; - else // left + else // left count = s_count; if (!event.MouseInput.Shift) { @@ -4290,8 +4407,14 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) move_amount = 1; else if (button == BET_MIDDLE) move_amount = MYMIN(m_selected_amount, 10); - else if (button == BET_LEFT) + else if (button == BET_LEFT) { +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui && g_touchscreengui->isActive() && s.listname == "craft") + move_amount = 1; + else +#endif move_amount = m_selected_amount; + } // else wheeldown if (identical) { @@ -4515,6 +4638,9 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) if (event.EventType == EET_GUI_EVENT) { if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED && isVisible()) { + // reset selection + clearSelection(); + // find the element that was clicked for (GUIFormSpecMenu::FieldSpec &s : m_fields) { if ((s.ftype == f_TabHeader) && @@ -4541,8 +4667,10 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) || (event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) || (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) { - s32 caller_id = event.GUIEvent.Caller->getID(); + // reset selection + clearSelection(); + s32 caller_id = event.GUIEvent.Caller->getID(); if (caller_id == 257) { if (m_allowclose) { acceptInput(quit_mode_accept); @@ -4651,14 +4779,19 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } - if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) { + if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED || + event.GUIEvent.EventType == gui::EGET_EDITBOX_CHANGED) { + // reset selection + clearSelection(); + int current_id = event.GUIEvent.Caller->getID(); if (current_id > 257) { - // find the element that was clicked + // find the element that was clicked or changed for (GUIFormSpecMenu::FieldSpec &s : m_fields) { // if it's a table, set the send field // so lua knows which table was changed - if ((s.ftype == f_Table) && (s.fid == current_id)) { + if ((s.ftype == f_Table && s.fid == current_id) || + (s.ftype == f_Unknown && s.is_dynamic && s.fid == current_id)) { s.send = true; acceptInput(); s.send=false; diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h index d4d23a912..c89c7c666 100644 --- a/src/gui/guiFormSpecMenu.h +++ b/src/gui/guiFormSpecMenu.h @@ -113,6 +113,7 @@ class GUIFormSpecMenu : public GUIModalMenu ftype(f_Unknown), is_exit(false), priority(priority), + is_dynamic(false), fcursor_icon(cursor_icon) { } @@ -126,6 +127,7 @@ class GUIFormSpecMenu : public GUIModalMenu bool is_exit; // Draw priority for formspec version < 3 int priority; + bool is_dynamic; core::rect rect; gui::ECURSOR_ICON fcursor_icon; std::string sound; @@ -257,6 +259,7 @@ public: void acceptInput(FormspecQuitMode quitmode=quit_mode_no); bool preprocessEvent(const SEvent& event); + void clearSelection(); bool OnEvent(const SEvent& event); bool doPause; bool pausesGame() { return doPause; } @@ -264,7 +267,7 @@ public: GUITable* getTable(const std::string &tablename); std::vector* getDropDownValues(const std::string &name); -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) bool getAndroidUIInput(); #endif @@ -456,6 +459,8 @@ private: void parseSetFocus(const std::string &element); void parseModel(parserData *data, const std::string &element); + bool parseMiddleRect(const std::string &value, core::rect *parsed_rect); + void tryClose(); void showTooltip(const std::wstring &text, const irr::video::SColor &color, diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp index 6298c14db..7ae3bd619 100644 --- a/src/gui/guiHyperText.cpp +++ b/src/gui/guiHyperText.cpp @@ -990,7 +990,7 @@ void TextDrawer::draw(const core::rect &clip_rect, //! constructor GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment, IGUIElement *parent, s32 id, const core::rect &rectangle, - Client *client, ISimpleTextureSource *tsrc) : + Client *client, ISimpleTextureSource *tsrc, const StyleSpec &style) : IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle), m_client(client), m_vscrollbar(nullptr), m_drawer(text, client, environment, tsrc), m_text_scrollpos(0, 0) @@ -1012,6 +1012,7 @@ GUIHyperText::GUIHyperText(const wchar_t *text, IGUIEnvironment *environment, m_vscrollbar = new GUIScrollBar(Environment, this, -1, rect, false, true); m_vscrollbar->setVisible(false); + m_vscrollbar->setStyle(style, tsrc); } //! destructor diff --git a/src/gui/guiHyperText.h b/src/gui/guiHyperText.h index df6ea3a8b..c5c3506a7 100644 --- a/src/gui/guiHyperText.h +++ b/src/gui/guiHyperText.h @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "StyleSpec.h" #include #include #include @@ -202,7 +203,7 @@ public: GUIHyperText(const wchar_t *text, gui::IGUIEnvironment *environment, gui::IGUIElement *parent, s32 id, const core::rect &rectangle, Client *client, - ISimpleTextureSource *tsrc); + ISimpleTextureSource *tsrc, const StyleSpec &style); //! destructor virtual ~GUIHyperText(); diff --git a/src/gui/guiInventoryList.cpp b/src/gui/guiInventoryList.cpp index 4b10b7cd4..a9c91d4f2 100644 --- a/src/gui/guiInventoryList.cpp +++ b/src/gui/guiInventoryList.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiInventoryList.h" #include "guiFormSpecMenu.h" +#include "client/guiscalingfilter.h" #include "client/hud.h" #include "client/client.h" @@ -33,6 +34,10 @@ GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env, const s32 start_item_i, const v2s32 &slot_size, const v2f32 &slot_spacing, + video::ITexture* slotbg_n_texture, + video::ITexture* slotbg_h_texture, + video::ITexture* slotbg_p_texture, + const core::rect &slotbg_middle, GUIFormSpecMenu *fs_menu, const Options &options, gui::IGUIFont *font) : @@ -44,10 +49,15 @@ GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env, m_start_item_i(start_item_i), m_slot_size(slot_size), m_slot_spacing(slot_spacing), + m_slotbg_n_texture(slotbg_n_texture), + m_slotbg_h_texture(slotbg_h_texture), + m_slotbg_p_texture(slotbg_p_texture), + m_slotbg_middle(slotbg_middle), m_fs_menu(fs_menu), m_options(options), m_font(font), m_hovered_i(-1), + m_pressed(false), m_already_warned(false) { } @@ -109,34 +119,56 @@ void GUIInventoryList::draw() (hovering ? IT_ROT_HOVERED : IT_ROT_NONE); // layer 0 - if (hovering) { - driver->draw2DRectangle(m_options.slotbg_h, rect, &AbsoluteClippingRect); - } else { - driver->draw2DRectangle(m_options.slotbg_n, rect, &AbsoluteClippingRect); - } + if (m_slotbg_n_texture) { + video::ITexture *texture = m_slotbg_n_texture; + if (hovering) { + if (m_pressed && m_slotbg_p_texture) + texture = m_slotbg_p_texture; + else if (m_slotbg_h_texture) + texture = m_slotbg_h_texture; + } - // Draw inv slot borders - if (m_options.slotborder) { - s32 x1 = rect.UpperLeftCorner.X; - s32 y1 = rect.UpperLeftCorner.Y; - s32 x2 = rect.LowerRightCorner.X; - s32 y2 = rect.LowerRightCorner.Y; - s32 border = 1; - core::rect clipping_rect = Parent ? Parent->getAbsoluteClippingRect() - : core::rect(); - core::rect *clipping_rect_ptr = Parent ? &clipping_rect : nullptr; - driver->draw2DRectangle(m_options.slotbordercolor, - core::rect(v2s32(x1 - border, y1 - border), - v2s32(x2 + border, y1)), clipping_rect_ptr); - driver->draw2DRectangle(m_options.slotbordercolor, - core::rect(v2s32(x1 - border, y2), - v2s32(x2 + border, y2 + border)), clipping_rect_ptr); - driver->draw2DRectangle(m_options.slotbordercolor, - core::rect(v2s32(x1 - border, y1), - v2s32(x1, y2)), clipping_rect_ptr); - driver->draw2DRectangle(m_options.slotbordercolor, - core::rect(v2s32(x2, y1), - v2s32(x2 + border, y2)), clipping_rect_ptr); + core::rect srcrect(core::position2d(0, 0), + core::dimension2di(texture->getOriginalSize())); + if (m_slotbg_middle.getArea() == 0) { + const video::SColor color(255, 255, 255, 255); + const video::SColor colors[] = {color, color, color, color}; + draw2DImageFilterScaled(driver, texture, rect, srcrect, + &AbsoluteClippingRect, colors, true); + } else { + draw2DImage9Slice(driver, texture, rect, srcrect, m_slotbg_middle, + &AbsoluteClippingRect); + } + } else { + if (hovering) { + driver->draw2DRectangle(m_options.slotbg_h, rect, &AbsoluteClippingRect); + } else { + driver->draw2DRectangle(m_options.slotbg_n, rect, &AbsoluteClippingRect); + } + + // Draw inv slot borders + if (m_options.slotborder) { + s32 x1 = rect.UpperLeftCorner.X; + s32 y1 = rect.UpperLeftCorner.Y; + s32 x2 = rect.LowerRightCorner.X; + s32 y2 = rect.LowerRightCorner.Y; + s32 border = 1; + core::rect clipping_rect = Parent ? Parent->getAbsoluteClippingRect() + : core::rect(); + core::rect *clipping_rect_ptr = Parent ? &clipping_rect : nullptr; + driver->draw2DRectangle(m_options.slotbordercolor, + core::rect(v2s32(x1 - border, y1 - border), + v2s32(x2 + border, y1)), clipping_rect_ptr); + driver->draw2DRectangle(m_options.slotbordercolor, + core::rect(v2s32(x1 - border, y2), + v2s32(x2 + border, y2 + border)), clipping_rect_ptr); + driver->draw2DRectangle(m_options.slotbordercolor, + core::rect(v2s32(x1 - border, y1), + v2s32(x1, y2)), clipping_rect_ptr); + driver->draw2DRectangle(m_options.slotbordercolor, + core::rect(v2s32(x2, y1), + v2s32(x2 + border, y2)), clipping_rect_ptr); + } } // layer 1 @@ -173,8 +205,15 @@ bool GUIInventoryList::OnEvent(const SEvent &event) m_hovered_i = getItemIndexAtPos(v2s32(event.MouseInput.X, event.MouseInput.Y)); - if (m_hovered_i != -1) + if (m_hovered_i != -1) { + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) + m_pressed = true; + else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) + m_pressed = false; return IGUIElement::OnEvent(event); + } + + m_pressed = false; // no item slot at pos of mouse event => allow clicking through // find the element that would be hovered if this inventorylist was invisible diff --git a/src/gui/guiInventoryList.h b/src/gui/guiInventoryList.h index 9d4f3ef51..d576730a5 100644 --- a/src/gui/guiInventoryList.h +++ b/src/gui/guiInventoryList.h @@ -69,6 +69,10 @@ public: const s32 start_item_i, const v2s32 &slot_size, const v2f32 &slot_spacing, + video::ITexture* slotbg_n_texture, + video::ITexture* slotbg_h_texture, + video::ITexture* slotbg_p_texture, + const core::rect &slotbg_middle, GUIFormSpecMenu *fs_menu, const Options &options, gui::IGUIFont *font); @@ -117,6 +121,12 @@ private: // specifies how large the space between slots is (space between is spacing-size) const v2f32 m_slot_spacing; + // Slot textures + video::ITexture* m_slotbg_n_texture; + video::ITexture* m_slotbg_h_texture; + video::ITexture* m_slotbg_p_texture; + core::rect m_slotbg_middle; + // the GUIFormSpecMenu can have an item selected and co. GUIFormSpecMenu *m_fs_menu; @@ -128,6 +138,9 @@ private: // the index of the hovered item; -1 if no item is hovered s32 m_hovered_i; + // Whether the hovered item is being pressed + bool m_pressed; + // we do not want to write a warning on every draw bool m_already_warned; }; diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index 825fa2ba9..d21b57527 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -21,6 +21,7 @@ #include "guiKeyChangeMenu.h" #include "debug.h" +#include "guiBackgroundImage.h" #include "guiButton.h" #include "serialization.h" #include @@ -29,10 +30,14 @@ #include #include #include +#include "filesys.h" +#include "porting.h" #include "settings.h" +#include "StyleSpec.h" #include #include "mainmenumanager.h" // for g_gamecallback +#include "client/renderingengine.h" #define KMaxButtonPerColumns 12 @@ -41,6 +46,7 @@ extern MainGameCallback *g_gamecallback; enum { GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR, + GUI_ID_BACKGROUND_IMG, // buttons GUI_ID_KEY_FORWARD_BUTTON, GUI_ID_KEY_BACKWARD_BUTTON, @@ -84,11 +90,21 @@ enum GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, - ISimpleTextureSource *tsrc) : + ISimpleTextureSource *tsrc, bool main_menu) : GUIModalMenu(env, parent, id, menumgr), - m_tsrc(tsrc) + m_tsrc(tsrc), + m_main_menu(main_menu) { init_keys(); + + if (m_main_menu) return; + v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color"); + m_fullscreen_bgcolor = video::SColor( + (u8) MYMIN(MYMAX(g_settings->getS32("formspec_fullscreen_bg_opacity"), 0), 255), + MYMIN(MYMAX(myround(formspec_bgcolor.X), 0), 255), + MYMIN(MYMAX(myround(formspec_bgcolor.Y), 0), 255), + MYMIN(MYMAX(myround(formspec_bgcolor.Z), 0), 255) + ); } GUIKeyChangeMenu::~GUIKeyChangeMenu() @@ -120,7 +136,20 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { removeChildren(); - const float s = m_gui_scale; + float s = MYMIN(screensize.X / 835.f, screensize.Y / 430.f); +#if HAVE_TOUCHSCREENGUI + s *= g_settings->getBool("device_is_tablet") ? 0.8f : 0.9f; +#else + s *= 0.75f; +#endif + + // Make sure the GUI will fit on the screen + // The change keys GUI is 835x430 pixels (with a scaling of 1) + if (835 * s > screensize.X) + s = screensize.X / 835.f; + if (430 * s > screensize.Y) + s = screensize.Y / 430.f; + DesiredRect = core::rect( screensize.X / 2 - 835 * s / 2, screensize.Y / 2 - 430 * s / 2, @@ -132,11 +161,29 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) v2s32 size = DesiredRect.getSize(); v2s32 topleft(0, 0); + std::string texture_path = ""; + if (m_main_menu) + texture_path = porting::path_share + DIR_DELIM "textures" DIR_DELIM + "base" DIR_DELIM "pack" DIR_DELIM; + + // Background image { - core::rect rect(0, 0, 600 * s, 40 * s); - rect += topleft + v2s32(25 * s, 3 * s); + const std::string texture = texture_path + "bg_common.png"; + const core::rect rect(0, 0, 0, 0); + const core::rect middle(40, 40, -40, -40); + new GUIBackgroundImage(Environment, this, GUI_ID_BACKGROUND_IMG, rect, + texture, middle, m_tsrc, true); + } + + { + core::rect rect(0, 0, 795 * s, 50 * s); + rect += topleft + v2s32(25 * s, 10 * s); //gui::IGUIStaticText *t = - const wchar_t *text = wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)"); +#if !defined(__ANDROID__) && !defined(__IOS__) + const wchar_t *text = wgettext("Keybindings. (If this menu screws up, remove stuff from multicraft.conf)"); +#else + const wchar_t *text = wgettext("Change Keys"); +#endif Environment->addStaticText(text, rect, false, true, this, -1); delete[] text; @@ -174,7 +221,7 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { s32 option_x = offset.X; s32 option_y = offset.Y + 5 * s; - u32 option_w = 180 * s; + u32 option_w = 300 * s; { core::rect rect(0, 0, option_w, 30 * s); rect += topleft + v2s32(option_x, option_y); @@ -189,7 +236,7 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { s32 option_x = offset.X; s32 option_y = offset.Y + 5 * s; - u32 option_w = 280 * s; + u32 option_w = 300 * s; { core::rect rect(0, 0, option_w, 30 * s); rect += topleft + v2s32(option_x, option_y); @@ -204,7 +251,7 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { s32 option_x = offset.X; s32 option_y = offset.Y + 5 * s; - u32 option_w = 280; + u32 option_w = 300 * s; { core::rect rect(0, 0, option_w, 30 * s); rect += topleft + v2s32(option_x, option_y); @@ -216,18 +263,23 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) offset += v2s32(0, 25); } + const std::array styles = + StyleSpec::getButtonStyle(texture_path); + { - core::rect rect(0, 0, 100 * s, 30 * s); - rect += topleft + v2s32(size.X / 2 - 105 * s, size.Y - 40 * s); - const wchar_t *text = wgettext("Save"); - GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_BACK_BUTTON, text); + core::rect rect(0, 0, 150 * s, 35 * s); + rect += topleft + v2s32(size.X / 2 - 165 * s, size.Y - 50 * s); + const wchar_t *text = wgettext("Save"); + GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_BACK_BUTTON, text); + e->setStyles(styles); delete[] text; } { - core::rect rect(0, 0, 100 * s, 30 * s); - rect += topleft + v2s32(size.X / 2 + 5 * s, size.Y - 40 * s); + core::rect rect(0, 0, 150 * s, 35 * s); + rect += topleft + v2s32(size.X / 2 + 15 * s, size.Y - 50 * s); const wchar_t *text = wgettext("Cancel"); - GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_ABORT_BUTTON, text); + GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_ABORT_BUTTON, text); + e->setStyles(styles); delete[] text; } } @@ -237,10 +289,13 @@ void GUIKeyChangeMenu::drawMenu() gui::IGUISkin* skin = Environment->getSkin(); if (!skin) return; - video::IVideoDriver* driver = Environment->getVideoDriver(); - video::SColor bgcolor(140, 0, 0, 0); - driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); + if (!m_main_menu) { + video::IVideoDriver* driver = Environment->getVideoDriver(); + v2u32 screenSize = driver->getScreenSize(); + core::rect allbg(0, 0, screenSize.X, screenSize.Y); + driver->draw2DRectangle(m_fullscreen_bgcolor, allbg, &allbg); + } gui::IGUIElement::draw(); } @@ -277,6 +332,9 @@ bool GUIKeyChangeMenu::acceptInput() g_gamecallback->signalKeyConfigChange(); + if (!g_settings_path.empty()) + g_settings->updateConfigFile(g_settings_path.c_str()); + return true; } @@ -296,7 +354,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) if (event.EventType == EET_KEY_INPUT_EVENT && active_key && event.KeyInput.PressedDown) { - bool prefer_character = shift_down; + bool prefer_character = shift_down && event.KeyInput.Char != 0; KeyPress kp(event.KeyInput, prefer_character); if (event.KeyInput.Key == irr::KEY_DELETE) @@ -355,6 +413,13 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) && event.KeyInput.Key == irr::KEY_ESCAPE) { quitMenu(); return true; + } else if (event.EventType == EET_KEY_INPUT_EVENT && + !event.KeyInput.PressedDown) { + if (shift_down && !event.KeyInput.Shift) { + shift_down = false; + active_key = nullptr; + return true; + } } else if (event.EventType == EET_GUI_EVENT) { if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) diff --git a/src/gui/guiKeyChangeMenu.h b/src/gui/guiKeyChangeMenu.h index 8c6bc5e8b..5ca99c63c 100644 --- a/src/gui/guiKeyChangeMenu.h +++ b/src/gui/guiKeyChangeMenu.h @@ -43,7 +43,8 @@ class GUIKeyChangeMenu : public GUIModalMenu { public: GUIKeyChangeMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id, - IMenuManager *menumgr, ISimpleTextureSource *tsrc); + IMenuManager *menumgr, ISimpleTextureSource *tsrc, + bool main_menu = false); ~GUIKeyChangeMenu(); void removeChildren(); @@ -77,4 +78,7 @@ private: gui::IGUIStaticText *key_used_text = nullptr; std::vector key_settings; ISimpleTextureSource *m_tsrc; + bool m_main_menu = false; + + video::SColor m_fullscreen_bgcolor; }; diff --git a/src/gui/guiScrollBar.cpp b/src/gui/guiScrollBar.cpp index c6a03f3e4..77090619c 100644 --- a/src/gui/guiScrollBar.cpp +++ b/src/gui/guiScrollBar.cpp @@ -12,16 +12,18 @@ the arrow buttons where there is insufficient space. #include "guiScrollBar.h" #include -#include +#include GUIScrollBar::GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id, core::rect rectangle, bool horizontal, bool auto_scale) : IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle), - up_button(nullptr), down_button(nullptr), is_dragging(false), - is_horizontal(horizontal), is_auto_scaling(auto_scale), - dragged_by_slider(false), tray_clicked(false), scroll_pos(0), - draw_center(0), thumb_size(0), min_pos(0), max_pos(100), small_step(10), - large_step(50), drag_offset(0), page_size(100), border_size(0) + up_button(nullptr), down_button(nullptr), bg_image(nullptr), + slider_image(nullptr), slider_top_image(nullptr), + slider_bottom_image(nullptr), is_dragging(false), is_horizontal(horizontal), + is_auto_scaling(auto_scale), dragged_by_slider(false), + tray_clicked(false), scroll_pos(0), draw_center(0), thumb_size(0), + min_pos(0), max_pos(100), small_step(10), large_step(50), + drag_offset(0), page_size(100), border_size(0) { refreshControls(); setNotClipped(false); @@ -193,6 +195,14 @@ bool GUIScrollBar::OnEvent(const SEvent &event) return IGUIElement::OnEvent(event); } +gui::IGUIImage* GUIScrollBar::addImage(const core::rect &rect, video::ITexture *texture) +{ + gui::IGUIImage *e = Environment->addImage(rect, this); + e->setImage(texture); + e->setScaleImage(true); + return e; +} + void GUIScrollBar::draw() { if (!IsVisible) @@ -208,10 +218,26 @@ void GUIScrollBar::draw() refreshControls(); slider_rect = AbsoluteRect; - skin->draw2DRectangle(this, skin->getColor(EGDC_SCROLLBAR), slider_rect, - &AbsoluteClippingRect); - if (core::isnotzero(range())) { + if (m_textures.size() >= 1) { + s32 w = RelativeRect.getWidth(); + s32 h = RelativeRect.getHeight(); + core::rect rect{0, w, w, h - w}; + + if (is_horizontal) + rect = {h, 0, w - h, h}; + + if (!bg_image) + bg_image = addImage(rect, m_textures[0]); + else + bg_image->setRelativePosition(rect); + } else { + skin->draw2DRectangle(this, skin->getColor(EGDC_SCROLLBAR), + slider_rect, &AbsoluteClippingRect); + } + + // Always show scrollbar thumb + //if (core::isnotzero(range())) { if (is_horizontal) { slider_rect.UpperLeftCorner.X = AbsoluteRect.UpperLeftCorner.X + draw_center - thumb_size / 2; @@ -223,8 +249,42 @@ void GUIScrollBar::draw() slider_rect.LowerRightCorner.Y = slider_rect.UpperLeftCorner.Y + thumb_size; } - skin->draw3DButtonPaneStandard(this, slider_rect, &AbsoluteClippingRect); - } + + if (m_textures.size() >= 2) { + s32 w = slider_rect.getWidth(); + s32 h = slider_rect.getHeight(); + core::rect rect{0, draw_center - (h / 2), w, draw_center + h - (h / 2)}; + + if (is_horizontal) + rect = {draw_center - (w / 2), 0, draw_center + w - (w / 2), h}; + + if (!slider_image) + slider_image = addImage(rect, m_textures[1]); + else + slider_image->setRelativePosition(rect); + + // Add top and bottom images if required + // TODO: Horizontal scrollbars + if (m_textures.size() >= 6 && !is_horizontal) { + core::rect top_rect = rect; + top_rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + w / 2; + if (!slider_top_image) + slider_top_image = addImage(top_rect, m_textures[4]); + else + slider_top_image->setRelativePosition(top_rect); + + core::rect bottom_rect = rect; + bottom_rect.UpperLeftCorner.Y = rect.LowerRightCorner.Y - w / 2; + if (!slider_bottom_image) + slider_bottom_image = addImage(bottom_rect, m_textures[5]); + else + slider_bottom_image->setRelativePosition(bottom_rect); + } + } else { + skin->draw3DButtonPaneStandard(this, slider_rect, &AbsoluteClippingRect); + } + //} + IGUIElement::draw(); } @@ -327,6 +387,34 @@ s32 GUIScrollBar::getPos() const return scroll_pos; } +void GUIScrollBar::setTextures(const std::vector &textures) +{ + m_textures = textures; + refreshControls(); +}; + +void GUIScrollBar::setStyle(const StyleSpec &style, ISimpleTextureSource *tsrc) +{ + if (style.isNotDefault(StyleSpec::SCROLLBAR_BGIMG) && + style.isNotDefault(StyleSpec::SCROLLBAR_THUMB_IMG) && + style.isNotDefault(StyleSpec::SCROLLBAR_UP_IMG) && + style.isNotDefault(StyleSpec::SCROLLBAR_DOWN_IMG)) { + arrow_visibility = ArrowVisibility::SHOW; + std::vector textures = { + style.getTexture(StyleSpec::SCROLLBAR_BGIMG, tsrc), + style.getTexture(StyleSpec::SCROLLBAR_THUMB_IMG, tsrc), + style.getTexture(StyleSpec::SCROLLBAR_UP_IMG, tsrc), + style.getTexture(StyleSpec::SCROLLBAR_DOWN_IMG, tsrc) + }; + if (style.isNotDefault(StyleSpec::SCROLLBAR_THUMB_TOP_IMG) && + style.isNotDefault(StyleSpec::SCROLLBAR_THUMB_BOTTOM_IMG)) { + textures.push_back(style.getTexture(StyleSpec::SCROLLBAR_THUMB_TOP_IMG, tsrc)); + textures.push_back(style.getTexture(StyleSpec::SCROLLBAR_THUMB_BOTTOM_IMG, tsrc)); + } + setTextures(textures); + } +} + void GUIScrollBar::refreshControls() { IGUISkin *skin = Environment->getSkin(); @@ -342,13 +430,21 @@ void GUIScrollBar::refreshControls() if (is_horizontal) { s32 h = RelativeRect.getHeight(); border_size = RelativeRect.getWidth() < h * 4 ? 0 : h; + if (!up_button) { up_button = Environment->addButton( core::rect(0, 0, h, h), this); up_button->setSubElement(true); up_button->setTabStop(false); } - if (sprites) { + + if (m_textures.size() >= 3) { + up_button->setImage(m_textures[2]); + up_button->setScaleImage(true); + up_button->setDrawBorder(false); + up_button->setUseAlphaChannel(true); + up_button->setSpriteBank(nullptr); + } else if (sprites) { up_button->setSpriteBank(sprites); up_button->setSprite(EGBS_BUTTON_UP, s32(skin->getIcon(EGDI_CURSOR_LEFT)), @@ -360,6 +456,7 @@ void GUIScrollBar::refreshControls() up_button->setRelativePosition(core::rect(0, 0, h, h)); up_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT); + if (!down_button) { down_button = Environment->addButton( core::rect(RelativeRect.getWidth() - h, 0, @@ -368,7 +465,14 @@ void GUIScrollBar::refreshControls() down_button->setSubElement(true); down_button->setTabStop(false); } - if (sprites) { + + if (m_textures.size() >= 4) { + down_button->setImage(m_textures[3]); + down_button->setScaleImage(true); + down_button->setDrawBorder(false); + down_button->setUseAlphaChannel(true); + down_button->setSpriteBank(nullptr); + } else if (sprites) { down_button->setSpriteBank(sprites); down_button->setSprite(EGBS_BUTTON_UP, s32(skin->getIcon(EGDI_CURSOR_RIGHT)), @@ -377,6 +481,7 @@ void GUIScrollBar::refreshControls() s32(skin->getIcon(EGDI_CURSOR_RIGHT)), current_icon_color); } + down_button->setRelativePosition( core::rect(RelativeRect.getWidth() - h, 0, RelativeRect.getWidth(), h)); @@ -384,14 +489,23 @@ void GUIScrollBar::refreshControls() EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT); } else { s32 w = RelativeRect.getWidth(); - border_size = RelativeRect.getHeight() < w * 4 ? 0 : w; + s32 h = RelativeRect.getHeight(); + border_size = h < w * 4 ? 0 : w; + if (!up_button) { up_button = Environment->addButton( core::rect(0, 0, w, w), this); up_button->setSubElement(true); up_button->setTabStop(false); } - if (sprites) { + + if (m_textures.size() >= 3) { + up_button->setImage(m_textures[2]); + up_button->setScaleImage(true); + up_button->setDrawBorder(false); + up_button->setUseAlphaChannel(true); + up_button->setSpriteBank(nullptr); + } else if (sprites) { up_button->setSpriteBank(sprites); up_button->setSprite(EGBS_BUTTON_UP, s32(skin->getIcon(EGDI_CURSOR_UP)), @@ -403,15 +517,21 @@ void GUIScrollBar::refreshControls() up_button->setRelativePosition(core::rect(0, 0, w, w)); up_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT); + if (!down_button) { down_button = Environment->addButton( - core::rect(0, RelativeRect.getHeight() - w, - w, RelativeRect.getHeight()), - this); + core::rect(0, 0, w, w), this); down_button->setSubElement(true); down_button->setTabStop(false); } - if (sprites) { + + if (m_textures.size() >= 4) { + down_button->setImage(m_textures[3]); + down_button->setScaleImage(true); + down_button->setDrawBorder(false); + down_button->setUseAlphaChannel(true); + down_button->setSpriteBank(nullptr); + } else if (sprites) { down_button->setSpriteBank(sprites); down_button->setSprite(EGBS_BUTTON_UP, s32(skin->getIcon(EGDI_CURSOR_DOWN)), diff --git a/src/gui/guiScrollBar.h b/src/gui/guiScrollBar.h index d18f8e875..6736dd51d 100644 --- a/src/gui/guiScrollBar.h +++ b/src/gui/guiScrollBar.h @@ -12,7 +12,10 @@ the arrow buttons where there is insufficient space. #pragma once +#include "guiAnimatedImage.h" #include "irrlichttypes_extrabloated.h" +#include "StyleSpec.h" +#include using namespace irr; using namespace gui; @@ -39,6 +42,7 @@ public: s32 getLargeStep() const { return large_step; } s32 getSmallStep() const { return small_step; } s32 getPos() const; + s32 getPageSize() const { return page_size; } void setMax(const s32 &max); void setMin(const s32 &min); @@ -47,14 +51,21 @@ public: void setPos(const s32 &pos); void setPageSize(const s32 &size); void setArrowsVisible(ArrowVisibility visible); + void setTextures(const std::vector &textures); + void setStyle(const StyleSpec &style, ISimpleTextureSource *tsrc); private: void refreshControls(); s32 getPosFromMousePos(const core::position2di &p) const; f32 range() const { return f32(max_pos - min_pos); } + gui::IGUIImage *addImage(const core::rect &rect, video::ITexture *texture); IGUIButton *up_button; IGUIButton *down_button; + gui::IGUIImage *bg_image; + gui::IGUIImage *slider_image; + gui::IGUIImage *slider_top_image; + gui::IGUIImage *slider_bottom_image; ArrowVisibility arrow_visibility = DEFAULT; bool is_dragging; bool is_horizontal; @@ -72,6 +83,9 @@ private: s32 page_size; s32 border_size; + std::vector m_textures; + core::rect m_texture_middle; + core::rect slider_rect; video::SColor current_icon_color; }; diff --git a/src/gui/guiSkin.cpp b/src/gui/guiSkin.cpp index ca692f6cb..668fcd270 100644 --- a/src/gui/guiSkin.cpp +++ b/src/gui/guiSkin.cpp @@ -29,7 +29,7 @@ GUISkin::GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver) { Colors[EGDC_3D_DARK_SHADOW] = video::SColor(101,50,50,50); Colors[EGDC_3D_SHADOW] = video::SColor(101,130,130,130); - Colors[EGDC_3D_FACE] = video::SColor(220,100,100,100); + Colors[EGDC_3D_FACE] = video::SColor(101,210,210,210); Colors[EGDC_3D_HIGH_LIGHT] = video::SColor(101,255,255,255); Colors[EGDC_3D_LIGHT] = video::SColor(101,210,210,210); Colors[EGDC_ACTIVE_BORDER] = video::SColor(101,16,14,115); @@ -1003,6 +1003,16 @@ void GUISkin::drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon, colors = Colors; bool gray = element && !element->isEnabled(); +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 9 + if (icon == EGDI_CHECK_BOX_CHECKED) { + // Scale checkbox check specifically + int radius = getSize(EGDS_CHECK_BOX_WIDTH) * .38; + core::recti rect{position.X - radius, position.Y - radius, position.X + radius, position.Y + radius}; + SpriteBank->draw2DSprite(Icons[icon], rect, 0, + &colors[gray ? EGDC_GRAY_WINDOW_SYMBOL : EGDC_WINDOW_SYMBOL], currenttime - starttime, loop); + return; + } +#endif SpriteBank->draw2DSprite(Icons[icon], position, clip, colors[gray? EGDC_GRAY_WINDOW_SYMBOL : EGDC_WINDOW_SYMBOL], starttime, currenttime, loop, true); } diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp index 1a1f1623f..cdd548ce1 100644 --- a/src/gui/guiTable.cpp +++ b/src/gui/guiTable.cpp @@ -77,14 +77,12 @@ GUITable::GUITable(gui::IGUIEnvironment *env, setTabStop(true); setTabOrder(-1); updateAbsolutePosition(); -#ifdef HAVE_TOUCHSCREENGUI - float density = 1; // dp scaling is applied by the skin -#else float density = RenderingEngine::getDisplayDensity(); -#endif + float gui_scaling = g_settings->getFloat("gui_scaling"); + scale = density * gui_scaling; core::rect relative_rect = m_scrollbar->getRelativePosition(); - s32 width = (relative_rect.getWidth() / (2.0 / 3.0)) * density * - g_settings->getFloat("gui_scaling"); + s32 width = (relative_rect.getWidth() / (2.0 / 3.0)) * + gui_scaling; m_scrollbar->setRelativePosition(core::rect( relative_rect.LowerRightCorner.X-width,relative_rect.UpperLeftCorner.Y, relative_rect.LowerRightCorner.X,relative_rect.LowerRightCorner.Y @@ -387,7 +385,7 @@ void GUITable::setTable(const TableOptions &options, image = m_images[row->content_index]; // Get content width and update xmax - row->content_width = image ? image->getOriginalSize().Width : 0; + row->content_width = image ? image->getOriginalSize().Width * scale : 0; row->content_width = MYMAX(row->content_width, width); s32 row_xmax = row->x + padding + row->content_width; xmax = MYMAX(xmax, row_xmax); @@ -759,17 +757,21 @@ void GUITable::drawCell(const Cell *cell, video::SColor color, core::rect source_rect( core::position2d(0, 0), image->getOriginalSize()); - s32 imgh = source_rect.LowerRightCorner.Y; + s32 imgh = source_rect.LowerRightCorner.Y * scale; s32 rowh = row_rect.getHeight(); if (imgh < rowh) dest_pos.Y += (rowh - imgh) / 2; else source_rect.LowerRightCorner.Y = rowh; - video::SColor color(255, 255, 255, 255); + video::SColor colors[] = {color, color, color, color}; - driver->draw2DImage(image, dest_pos, source_rect, - &client_clip, color, true); + core::rect image_pos(dest_pos.X, dest_pos.Y, + dest_pos.X + (image->getOriginalSize().Width * scale), + dest_pos.Y + (image->getOriginalSize().Height * scale)); + + draw2DImageFilterScaled(driver, image, image_pos, source_rect, + &client_clip, colors, true); } } } diff --git a/src/gui/guiTable.h b/src/gui/guiTable.h index a4b69fbf9..35cca4cb8 100644 --- a/src/gui/guiTable.h +++ b/src/gui/guiTable.h @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "guiScrollBar.h" +#include "StyleSpec.h" class ISimpleTextureSource; @@ -147,6 +148,14 @@ public: /* Irrlicht event handler */ virtual bool OnEvent(const SEvent &event); + /* Set scrollbar texture */ + void setStyle(const StyleSpec &style) + { + setNotClipped(style.getBool(StyleSpec::NOCLIP, false)); + setOverrideFont(style.getFont()); + m_scrollbar->setStyle(style, m_tsrc); + } + protected: enum ColumnType { COLUMN_TYPE_TEXT, @@ -207,6 +216,8 @@ protected: gui::IGUIFont *m_font = nullptr; GUIScrollBar *m_scrollbar = nullptr; + float scale; + // Allocated strings and images std::vector m_strings; std::vector m_images; diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp index 61ab758a1..4d13b2539 100644 --- a/src/gui/guiVolumeChange.cpp +++ b/src/gui/guiVolumeChange.cpp @@ -19,18 +19,22 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "guiVolumeChange.h" #include "debug.h" +#include "guiBackgroundImage.h" #include "guiButton.h" +#include "guiScrollBar.h" #include "serialization.h" #include #include #include -#include #include #include #include "settings.h" +#include "StyleSpec.h" #include "gettext.h" +#include "client/renderingengine.h" +const int ID_backgroundImage = 262; const int ID_soundText = 263; const int ID_soundExitButton = 264; const int ID_soundSlider = 265; @@ -43,6 +47,13 @@ GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env, GUIModalMenu(env, parent, id, menumgr), m_tsrc(tsrc) { + v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color"); + m_fullscreen_bgcolor = video::SColor( + (u8) MYMIN(MYMAX(g_settings->getS32("formspec_fullscreen_bg_opacity"), 0), 255), + MYMIN(MYMAX(myround(formspec_bgcolor.X), 0), 255), + MYMIN(MYMAX(myround(formspec_bgcolor.Y), 0), 255), + MYMIN(MYMAX(myround(formspec_bgcolor.Z), 0), 255) + ); } GUIVolumeChange::~GUIVolumeChange() @@ -52,6 +63,9 @@ GUIVolumeChange::~GUIVolumeChange() void GUIVolumeChange::removeChildren() { + if (gui::IGUIElement *e = getElementFromId(ID_backgroundImage)) + e->remove(); + if (gui::IGUIElement *e = getElementFromId(ID_soundText)) e->remove(); @@ -74,12 +88,18 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) /* Calculate new sizes and positions */ - const float s = m_gui_scale; + float s = MYMIN(screensize.X / 380.f, screensize.Y / 180.f); +#if HAVE_TOUCHSCREENGUI + s *= g_settings->getBool("device_is_tablet") ? 0.4f : 0.5f; +#else + s *= 0.35f; +#endif + DesiredRect = core::rect( screensize.X / 2 - 380 * s / 2, - screensize.Y / 2 - 200 * s / 2, + screensize.Y / 2 - 180 * s / 2, screensize.X / 2 + 380 * s / 2, - screensize.Y / 2 + 200 * s / 2 + screensize.Y / 2 + 180 * s / 2 // 200 ); recalculateAbsolutePosition(false); @@ -90,8 +110,15 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) Add stuff */ { - core::rect rect(0, 0, 160 * s, 20 * s); - rect = rect + v2s32(size.X / 2 - 80 * s, size.Y / 2 - 70 * s); + const std::string texture = "bg_common.png"; + const core::rect rect(0, 0, 0, 0); + const core::rect middle(40, 40, -40, -40); + new GUIBackgroundImage(Environment, this, ID_backgroundImage, rect, + texture, middle, m_tsrc, true); + } + { + core::rect rect(0, 0, 320 * s, 25 * s); + rect = rect + v2s32(30 * s, size.Y / 2 - 35 * s); // 55 wchar_t text[100]; const wchar_t *str = wgettext("Sound Volume: %d%%"); @@ -103,28 +130,38 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize) true, this, ID_soundText); } { - core::rect rect(0, 0, 80 * s, 30 * s); - rect = rect + v2s32(size.X / 2 - 80 * s / 2, size.Y / 2 + 55 * s); + core::rect rect(0, 0, 80 * s, 35 * s); + rect = rect + v2s32(size.X / 2 - 40 * s, size.Y / 2 + 35 * s); // 45 const wchar_t *text = wgettext("Exit"); - GUIButton::addButton(Environment, rect, m_tsrc, this, ID_soundExitButton, text); + GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc, this, + ID_soundExitButton, text); delete[] text; + + e->setStyles(StyleSpec::getButtonStyle()); } { - core::rect rect(0, 0, 300 * s, 20 * s); - rect = rect + v2s32(size.X / 2 - 150 * s, size.Y / 2); - gui::IGUIScrollBar *e = Environment->addScrollBar(true, - rect, this, ID_soundSlider); + core::rect rect(0, 0, 320 * s, 30 * s); + rect = rect + v2s32(size.X / 2 - 160 * s, size.Y / 2 - 12 * s); // 30 + GUIScrollBar *e = new GUIScrollBar(Environment, this, + ID_soundSlider, rect, true, false); e->setMax(100); e->setPos(volume); + e->setArrowsVisible(GUIScrollBar::ArrowVisibility::SHOW); + e->setTextures({ + m_tsrc->getTexture("gui/scrollbar_horiz_bg.png"), + m_tsrc->getTexture("gui/scrollbar_slider.png"), + m_tsrc->getTexture("gui/scrollbar_minus.png"), + m_tsrc->getTexture("gui/scrollbar_plus.png"), + }); } - { - core::rect rect(0, 0, 160 * s, 20 * s); - rect = rect + v2s32(size.X / 2 - 80 * s, size.Y / 2 - 35 * s); + /*{ + core::rect rect(0, 0, 150 * s, 25 * s); + rect = rect + v2s32(30 * s, size.Y / 2 + 5 * s); const wchar_t *text = wgettext("Muted"); Environment->addCheckBox(g_settings->getBool("mute_sound"), rect, this, ID_soundMuteButton, text); delete[] text; - } + }*/ } void GUIVolumeChange::drawMenu() @@ -133,25 +170,35 @@ void GUIVolumeChange::drawMenu() if (!skin) return; video::IVideoDriver* driver = Environment->getVideoDriver(); - video::SColor bgcolor(140, 0, 0, 0); - driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); + + v2u32 screenSize = driver->getScreenSize(); + core::rect allbg(0, 0, screenSize.X, screenSize.Y); + driver->draw2DRectangle(m_fullscreen_bgcolor, allbg, &allbg); + gui::IGUIElement::draw(); } +void GUIVolumeChange::saveSettingsAndQuit() +{ + if (!g_settings_path.empty()) + g_settings->updateConfigFile(g_settings_path.c_str()); + quitMenu(); +} + bool GUIVolumeChange::OnEvent(const SEvent& event) { if (event.EventType == EET_KEY_INPUT_EVENT) { if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) { - quitMenu(); + saveSettingsAndQuit(); return true; } if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) { - quitMenu(); + saveSettingsAndQuit(); return true; } } else if (event.EventType == EET_GUI_EVENT) { - if (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) { + /*if (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) { gui::IGUIElement *e = getElementFromId(ID_soundMuteButton); if (e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) { g_settings->setBool("mute_sound", ((gui::IGUICheckBox*)e)->isChecked()); @@ -159,11 +206,11 @@ bool GUIVolumeChange::OnEvent(const SEvent& event) Environment->setFocus(this); return true; - } + }*/ if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) { if (event.GUIEvent.Caller->getID() == ID_soundExitButton) { - quitMenu(); + saveSettingsAndQuit(); return true; } Environment->setFocus(this); @@ -180,9 +227,13 @@ bool GUIVolumeChange::OnEvent(const SEvent& event) } if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) { if (event.GUIEvent.Caller->getID() == ID_soundSlider) { - s32 pos = ((gui::IGUIScrollBar*)event.GUIEvent.Caller)->getPos(); + s32 pos = ((GUIScrollBar*)event.GUIEvent.Caller)->getPos(); g_settings->setFloat("sound_volume", (float) pos / 100); + // unmute sound when changing the volume + if (g_settings->getBool("mute_sound")) + g_settings->setBool("mute_sound", false); + gui::IGUIElement *e = getElementFromId(ID_soundText); wchar_t text[100]; const wchar_t *str = wgettext("Sound Volume: %d%%"); diff --git a/src/gui/guiVolumeChange.h b/src/gui/guiVolumeChange.h index 466e17f9d..fcb7a4ccf 100644 --- a/src/gui/guiVolumeChange.h +++ b/src/gui/guiVolumeChange.h @@ -50,5 +50,8 @@ protected: std::string getNameByID(s32 id) { return ""; } private: + void saveSettingsAndQuit(); + ISimpleTextureSource *m_tsrc; + video::SColor m_fullscreen_bgcolor; }; diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index 038940e96..c4de78cd2 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -32,7 +32,8 @@ public: virtual void exitToOS() = 0; virtual void keyConfig() = 0; virtual void disconnect() = 0; - virtual void changePassword() = 0; + virtual void changePassword(const std::string &old_pw = "", + const std::string &new_pw = "", const std::string &confirm_pw = "") = 0; virtual void changeVolume() = 0; virtual void signalKeyConfigChange() = 0; @@ -119,9 +120,13 @@ public: disconnect_requested = true; } - virtual void changePassword() + virtual void changePassword(const std::string &old_pw = "", + const std::string &new_pw = "", const std::string &confirm_pw = "") { changepassword_requested = true; + old_pw_tmp = old_pw; + new_pw_tmp = new_pw; + confirm_pw_tmp = confirm_pw; } virtual void changeVolume() @@ -142,6 +147,10 @@ public: bool disconnect_requested = false; bool changepassword_requested = false; + std::string old_pw_tmp; + std::string new_pw_tmp; + std::string confirm_pw_tmp; + bool changevolume_requested = false; bool keyconfig_requested = false; bool shutdown_requested = false; diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 56f1b7340..6cc9bc936 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -20,6 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "modalMenu.h" +#include "client/guiscalingfilter.h" +#include "client/joystick_controller.h" +#include "client/renderingengine.h" +#include "client/tile.h" +#include "filesys.h" #include "gettext.h" #include "porting.h" #include "settings.h" @@ -29,13 +34,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/renderingengine.h" #endif +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include +#endif + // clang-format off GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, bool remap_dbl_click) : IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, core::rect(0, 0, 100, 100)), -#ifdef __ANDROID__ - m_jni_field_name(""), +#if defined(__ANDROID__) || defined(__IOS__) + m_jni_field_name(), #endif m_menumgr(menumgr), m_remap_dbl_click(remap_dbl_click) @@ -59,6 +68,10 @@ GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, GUIModalMenu::~GUIModalMenu() { +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + if (porting::hasRealKeyboard() && SDL_IsTextInputActive()) + SDL_StopTextInput(); +#endif m_menumgr->deletingMenu(this); } @@ -85,8 +98,54 @@ void GUIModalMenu::draw() } drawMenu(); + +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + if (SDLGameController::isActive() && SDLGameController::isCursorVisible()) + drawCursor(); +#endif } +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) +void GUIModalMenu::drawCursor() +{ + video::IVideoDriver *driver = Environment->getVideoDriver(); + irr::IrrlichtDevice *device = RenderingEngine::get_raw_device(); + v2s32 pointer = device->getCursorControl()->getPosition(); + + v3f crosshair_color = g_settings->getV3F("crosshair_color"); + u32 cross_r = rangelim(myround(crosshair_color.X), 0, 255); + u32 cross_g = rangelim(myround(crosshair_color.Y), 0, 255); + u32 cross_b = rangelim(myround(crosshair_color.Z), 0, 255); + u32 cross_a = rangelim(g_settings->getS32("crosshair_alpha"), 0, 255); + video::SColor crosshair_argb = video::SColor(cross_a, cross_r, cross_g, cross_b); + + const int cursor_line_size = 16; + float hud_scaling = g_settings->getFloat("hud_scaling"); + float scale_factor = hud_scaling * RenderingEngine::getDisplayDensity(); + int cursor_size = (int)(cursor_line_size * scale_factor); + + std::string sprite_path = porting::path_share + DIR_DELIM + "textures" + + DIR_DELIM + "base" + DIR_DELIM + "pack" + DIR_DELIM + + "cursor.png"; + video::ITexture *cursor = driver->getTexture(sprite_path.c_str()); + + if (cursor) { + core::rect rect(pointer.X - cursor_size, pointer.Y - cursor_size, + pointer.X + cursor_size, pointer.Y + cursor_size); + video::SColor crosshair_color[] = {crosshair_argb, crosshair_argb, + crosshair_argb, crosshair_argb}; + draw2DImageFilterScaled(driver, cursor, rect, + core::rect({0, 0}, cursor->getOriginalSize()), + nullptr, crosshair_color, true); + } else { + driver->draw2DLine(pointer - v2s32(cursor_size, 0), + pointer + v2s32(cursor_size, 0), crosshair_argb); + driver->draw2DLine(pointer - v2s32(0, cursor_size), + pointer + v2s32(0, cursor_size), crosshair_argb); + } +} +#endif + /* This should be called when the menu wants to quit. @@ -101,9 +160,19 @@ void GUIModalMenu::quitMenu() // This removes Environment's grab on us Environment->removeFocus(this); m_menumgr->deletingMenu(this); + this->grab(); this->remove(); + +#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 9 + // Force update hovered elements, so that GUI Environment drops previously + // grabbed elements that are not hovered anymore + Environment->forceUpdateHoveredElement(); +#endif + + this->drop(); + #ifdef HAVE_TOUCHSCREENGUI - if (g_touchscreengui && m_touchscreen_visible) + if (g_touchscreengui && g_touchscreengui->isActive() && m_touchscreen_visible) g_touchscreengui->show(); #endif } @@ -186,10 +255,10 @@ static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent) #ifdef HAVE_TOUCHSCREENGUI -bool GUIModalMenu::simulateMouseEvent( - gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event) +bool GUIModalMenu::convertToMouseEvent( + SEvent &mouse_event, ETOUCH_INPUT_EVENT touch_event) const noexcept { - SEvent mouse_event{}; // value-initialized, not unitialized + mouse_event = {}; mouse_event.EventType = EET_MOUSE_INPUT_EVENT; mouse_event.MouseInput.X = m_pointer.X; mouse_event.MouseInput.Y = m_pointer.Y; @@ -209,11 +278,7 @@ bool GUIModalMenu::simulateMouseEvent( default: return false; } - if (preprocessEvent(mouse_event)) - return true; - if (!target) - return false; - return target->OnEvent(mouse_event); + return true; } void GUIModalMenu::enter(gui::IGUIElement *hovered) @@ -246,8 +311,26 @@ void GUIModalMenu::leave() bool GUIModalMenu::preprocessEvent(const SEvent &event) { -#ifdef __ANDROID__ // clang-format off +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + // Enable text input events when edit box is focused + if (event.EventType == EET_GUI_EVENT) { + if (event.GUIEvent.EventType == irr::gui::EGET_ELEMENT_FOCUSED && + event.GUIEvent.Caller && + event.GUIEvent.Caller->getType() == irr::gui::EGUIET_EDIT_BOX) { + if (porting::hasRealKeyboard()) + SDL_StartTextInput(); + } + else if (event.GUIEvent.EventType == irr::gui::EGET_ELEMENT_FOCUS_LOST && + event.GUIEvent.Caller && + event.GUIEvent.Caller->getType() == irr::gui::EGUIET_EDIT_BOX) { + if (porting::hasRealKeyboard() && SDL_IsTextInputActive()) + SDL_StopTextInput(); + } + } +#endif + +#if defined(__ANDROID__) || defined(__IOS__) // display software keyboard when clicking edit boxes if (event.EventType == EET_MOUSE_INPUT_EVENT && event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { @@ -261,17 +344,10 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) std::string field_name = getNameByID(hovered->getID()); // read-only field - if (field_name.empty()) + if (field_name.empty() || porting::hasRealKeyboard()) return retval; m_jni_field_name = field_name; - /*~ Imperative, as in "Enter/type in text". - Don't forget the space. */ - std::string message = gettext("Enter "); - std::string label = wide_to_utf8(getLabelByID(hovered->getID())); - if (label.empty()) - label = "text"; - message += strgettext(label) + ":"; // single line text input int type = 2; @@ -284,7 +360,7 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) if (((gui::IGUIEditBox *)hovered)->isPasswordBox()) type = 3; - porting::showInputDialog(gettext("OK"), "", + porting::showInputDialog(wide_to_utf8(getLabelByID(hovered->getID())), wide_to_utf8(((gui::IGUIEditBox *)hovered)->getText()), type); return retval; } @@ -301,7 +377,7 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) if (event.TouchInput.Event == ETIE_PRESSED_DOWN || event.TouchInput.Event == ETIE_MOVED) m_pointer = v2s32(event.TouchInput.X, event.TouchInput.Y); if (event.TouchInput.Event == ETIE_PRESSED_DOWN) - m_down_pos = m_pointer; + m_old_pointer = m_pointer; gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(core::position2d(m_pointer)); if (event.TouchInput.Event == ETIE_PRESSED_DOWN) Environment->setFocus(hovered); @@ -310,11 +386,25 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) enter(hovered); } gui::IGUIElement *focused = Environment->getFocus(); - bool ret = simulateMouseEvent(focused, event.TouchInput.Event); - if (!ret && m_hovered != focused) - ret = simulateMouseEvent(m_hovered.get(), event.TouchInput.Event); - if (event.TouchInput.Event == ETIE_LEFT_UP) +#if defined(__ANDROID__) || defined(__IOS__) + if (event.TouchInput.Event == ETIE_PRESSED_LONG) { + if (focused->getType() == irr::gui::EGUIET_EDIT_BOX) + focused->OnEvent(event); + return true; + } +#endif + SEvent mouse_event; + if (!convertToMouseEvent(mouse_event, event.TouchInput.Event)) + return false; + bool ret = preprocessEvent(mouse_event); + if (!ret && focused) + ret = focused->OnEvent(mouse_event); + if (!ret && m_hovered && m_hovered != focused) + ret = m_hovered->OnEvent(mouse_event); + if (event.TouchInput.Event == ETIE_LEFT_UP) { + m_pointer = v2s32(0, 0); leave(); + } return ret; } case 2: { @@ -355,24 +445,3 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) } return false; } - -#ifdef __ANDROID__ -bool GUIModalMenu::hasAndroidUIInput() -{ - // no dialog shown - if (m_jni_field_name.empty()) - return false; - - // still waiting - if (porting::getInputDialogState() == -1) - return true; - - // no value abort dialog processing - if (porting::getInputDialogState() != 0) { - m_jni_field_name.clear(); - return false; - } - - return true; -} -#endif diff --git a/src/gui/modalMenu.h b/src/gui/modalMenu.h index 87dbe41f8..a08e164ff 100644 --- a/src/gui/modalMenu.h +++ b/src/gui/modalMenu.h @@ -46,6 +46,9 @@ public: void allowFocusRemoval(bool allow); bool canTakeFocus(gui::IGUIElement *e); void draw(); +#if defined(_IRR_COMPILE_WITH_SDL_DEVICE_) + void drawCursor(); +#endif void quitMenu(); void removeChildren(); @@ -54,9 +57,8 @@ public: virtual bool preprocessEvent(const SEvent &event); virtual bool OnEvent(const SEvent &event) { return false; }; virtual bool pausesGame() { return false; } // Used for pause menu -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) virtual bool getAndroidUIInput() { return false; } - bool hasAndroidUIInput(); #endif protected: @@ -74,7 +76,7 @@ protected: v2s32 m_old_pointer; // Mouse position after previous mouse event v2u32 m_screensize_old; float m_gui_scale; -#ifdef __ANDROID__ +#if defined(__ANDROID__) || defined(__IOS__) std::string m_jni_field_name; #endif #ifdef HAVE_TOUCHSCREENGUI @@ -105,7 +107,7 @@ private: #ifdef HAVE_TOUCHSCREENGUI irr_ptr m_hovered; - bool simulateMouseEvent(gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event); + bool convertToMouseEvent(SEvent &mouse_event, ETOUCH_INPUT_EVENT touch_event) const noexcept; void enter(gui::IGUIElement *element); void leave(); #endif diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp index 7d960ce40..781489a6b 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchscreengui.cpp @@ -35,14 +35,21 @@ with this program; if not, write to the Free Software Foundation, Inc., using namespace irr::core; -const char **button_imagenames = (const char *[]) { - "jump_btn.png", - "down.png", - "zoom.png", - "aux1_btn.png" +const char *button_imagenames[] = { + "jump_btn.png", + "drop_btn.png", + "down_btn.png", + //"zoom.png", + "aux_btn.png", + "inventory_btn.png", + "escape_btn.png", + "minimap_btn.png", + "rangeview_btn.png", + "camera_btn.png", + "chat_btn.png" }; -const char **joystick_imagenames = (const char *[]) { +const char *joystick_imagenames[] = { "joystick_off.png", "joystick_bg.png", "joystick_center.png" @@ -76,13 +83,13 @@ static irr::EKEY_CODE id2keycode(touch_gui_button_id id) case crunch_id: key = "sneak"; break; - case zoom_id: - key = "zoom"; - break; + /*case zoom_id: + key = "zoom"; + break;*/ case aux1_id: key = "aux1"; break; - case fly_id: + /*case fly_id: key = "freemove"; break; case noclip_id: @@ -96,7 +103,7 @@ static irr::EKEY_CODE id2keycode(touch_gui_button_id id) break; case toggle_chat_id: key = "toggle_chat"; - break; + break;*/ case minimap_id: key = "minimap"; break; @@ -109,6 +116,8 @@ static irr::EKEY_CODE id2keycode(touch_gui_button_id id) case range_id: key = "rangeselect"; break; + case escape_id: + return irr::KEY_ESCAPE; default: break; } @@ -118,10 +127,10 @@ static irr::EKEY_CODE id2keycode(touch_gui_button_id id) TouchScreenGUI *g_touchscreengui; -static void load_button_texture(button_info *btn, const char *path, +static void load_button_texture(const button_info *btn, const char *path, const rect &button_rect, ISimpleTextureSource *tsrc, video::IVideoDriver *driver) { - unsigned int tid; + u32 tid; video::ITexture *texture = guiScalingImageButton(driver, tsrc->getTexture(path, &tid), button_rect.getWidth(), button_rect.getHeight()); @@ -151,7 +160,7 @@ AutoHideButtonBar::AutoHideButtonBar(IrrlichtDevice *device, } void AutoHideButtonBar::init(ISimpleTextureSource *tsrc, - const char *starter_img, int button_id, const v2s32 &UpperLeft, + const char *starter_img, s32 button_id, const v2s32 &UpperLeft, const v2s32 &LowerRight, autohide_button_bar_dir dir, float timeout) { m_texturesource = tsrc; @@ -197,7 +206,7 @@ void AutoHideButtonBar::addButton(touch_gui_button_id button_id, << std::endl; return; } - int button_size = 0; + s32 button_size = 0; if ((m_dir == AHBB_Dir_Top_Bottom) || (m_dir == AHBB_Dir_Bottom_Top)) button_size = m_lower_right.X - m_upper_left.X; @@ -207,8 +216,8 @@ void AutoHideButtonBar::addButton(touch_gui_button_id button_id, irr::core::rect current_button; if ((m_dir == AHBB_Dir_Right_Left) || (m_dir == AHBB_Dir_Left_Right)) { - int x_start = 0; - int x_end = 0; + s32 x_start = 0; + s32 x_end = 0; if (m_dir == AHBB_Dir_Left_Right) { x_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size()) @@ -240,7 +249,7 @@ void AutoHideButtonBar::addButton(touch_gui_button_id button_id, m_lower_right.Y, y_end); } - auto *btn = new button_info(); + std::shared_ptr btn(new button_info); btn->guibutton = m_guienv->addButton(current_button, nullptr, button_id, caption, nullptr); btn->guibutton->grab(); @@ -251,22 +260,22 @@ void AutoHideButtonBar::addButton(touch_gui_button_id button_id, btn->immediate_release = true; btn->ids.clear(); - load_button_texture(btn, btn_image, current_button, m_texturesource, + load_button_texture(btn.get(), btn_image, current_button, m_texturesource, m_driver); m_buttons.push_back(btn); } -void AutoHideButtonBar::addToggleButton(touch_gui_button_id button_id, +/*void AutoHideButtonBar::addToggleButton(touch_gui_button_id button_id, const wchar_t *caption, const char *btn_image_1, const char *btn_image_2) { addButton(button_id, caption, btn_image_1); - button_info *btn = m_buttons.back(); + std::shared_ptr btn = m_buttons.back(); btn->togglable = 1; btn->textures.push_back(btn_image_1); btn->textures.push_back(btn_image_2); -} +}*/ bool AutoHideButtonBar::isButton(const SEvent &event) { @@ -283,15 +292,13 @@ bool AutoHideButtonBar::isButton(const SEvent &event) if (m_active) { // check for all buttons in vector - auto iter = m_buttons.begin(); - - while (iter != m_buttons.end()) { - if ((*iter)->guibutton == element) { + for (const auto &button : m_buttons) { + if (button->guibutton == element) { auto *translated = new SEvent(); memset(translated, 0, sizeof(SEvent)); translated->EventType = irr::EET_KEY_INPUT_EVENT; - translated->KeyInput.Key = (*iter)->keycode; + translated->KeyInput.Key = button->keycode; translated->KeyInput.Control = false; translated->KeyInput.Shift = false; translated->KeyInput.Char = 0; @@ -306,25 +313,24 @@ bool AutoHideButtonBar::isButton(const SEvent &event) delete translated; - (*iter)->ids.push_back(event.TouchInput.ID); + button->ids.push_back(event.TouchInput.ID); m_timeout = 0; - if ((*iter)->togglable == 1) { - (*iter)->togglable = 2; - load_button_texture(*iter, (*iter)->textures[1], - (*iter)->guibutton->getRelativePosition(), + if (button->togglable == 1) { + button->togglable = 2; + load_button_texture(button.get(), button->textures[1], + button->guibutton->getRelativePosition(), m_texturesource, m_driver); - } else if ((*iter)->togglable == 2) { - (*iter)->togglable = 1; - load_button_texture(*iter, (*iter)->textures[0], - (*iter)->guibutton->getRelativePosition(), + } else if (button->togglable == 2) { + button->togglable = 1; + load_button_texture(button.get(), button->textures[0], + button->guibutton->getRelativePosition(), m_texturesource, m_driver); } return true; } - ++iter; } } else { // check for starter button only @@ -409,6 +415,8 @@ void AutoHideButtonBar::show() } } +bool TouchScreenGUI::m_active = true; + TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver): m_device(device), m_guienv(device->getGUIEnvironment()), @@ -419,14 +427,14 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver) for (auto &button : m_buttons) { button.guibutton = nullptr; button.repeatcounter = -1; - button.repeatdelay = BUTTON_REPEAT_DELAY; } m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold"); + m_touch_sensitivity = rangelim(g_settings->getFloat("touch_sensitivity"), 0.1, 1.0); m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick"); m_joystick_triggers_aux1 = g_settings->getBool("virtual_joystick_triggers_aux1"); m_screensize = m_device->getVideoDriver()->getScreenSize(); - button_size = MYMIN(m_screensize.Y / 4.5f, + button_size = std::min(m_screensize.Y / 4.5f, RenderingEngine::getDisplayDensity() * g_settings->getFloat("hud_scaling") * 65.0f); } @@ -436,6 +444,7 @@ void TouchScreenGUI::initButton(touch_gui_button_id id, const rect &button_ { button_info *btn = &m_buttons[id]; btn->guibutton = m_guienv->addButton(button_rect, nullptr, id, caption.c_str()); + btn->guibutton->setVisible(m_visible); btn->guibutton->grab(); btn->repeatcounter = -1; btn->repeatdelay = repeat_delay; @@ -447,21 +456,141 @@ void TouchScreenGUI::initButton(touch_gui_button_id id, const rect &button_ m_texturesource, m_device->getVideoDriver()); } -button_info *TouchScreenGUI::initJoystickButton(touch_gui_button_id id, - const rect &button_rect, int texture_id, bool visible) +std::shared_ptr TouchScreenGUI::initJoystickButton(touch_gui_button_id id, + const rect &button_rect, s32 texture_id, bool visible) { - auto *btn = new button_info(); + std::shared_ptr btn(new button_info); btn->guibutton = m_guienv->addButton(button_rect, nullptr, id, L"O"); - btn->guibutton->setVisible(visible); + btn->guibutton->setVisible(visible && m_visible); btn->guibutton->grab(); btn->ids.clear(); - load_button_texture(btn, joystick_imagenames[texture_id], + load_button_texture(btn.get(), joystick_imagenames[texture_id], button_rect, m_texturesource, m_device->getVideoDriver()); return btn; } +rect TouchScreenGUI::getButtonRect(touch_gui_button_id id) +{ + switch (id) { + case joystick_off_id: + if (m_fixed_joystick) { + return rect(button_size / 2, + m_screensize.Y - button_size * 4.5, + button_size * 4.5, + m_screensize.Y - button_size / 2); + } else { + return rect(button_size / 2, + m_screensize.Y - button_size * 3.5, + button_size * 3.5, + m_screensize.Y - button_size / 2); + } + case joystick_bg_id: + return rect(button_size / 2, + m_screensize.Y - button_size * 4.5, + button_size * 4.5, + m_screensize.Y - button_size / 2); + case joystick_center_id: + return rect(0, 0, button_size * 1.5, button_size * 1.5); + case jump_id: + return rect(m_screensize.X - button_size * 3.37, + m_screensize.Y - button_size * 2.75, + m_screensize.X - button_size * 1.87, + m_screensize.Y - button_size * 1.25); + case drop_id: + return rect(m_screensize.X - button_size, + m_screensize.Y / 2 - button_size * 1.5, + m_screensize.X, + m_screensize.Y / 2 - button_size / 2); + case crunch_id: + return rect(m_screensize.X - button_size * 3.38, + m_screensize.Y - button_size * 0.75, + m_screensize.X - button_size * 1.7, + m_screensize.Y); + case inventory_id: + return rect(m_screensize.X - button_size * 1.7, + m_screensize.Y - button_size * 1.5, + m_screensize.X, + m_screensize.Y); + //case zoom_id: + // return rect(m_screensize.X - (1.25 * button_size), + // m_screensize.Y - (4 * button_size), + // m_screensize.X - (0.25 * button_size), + // m_screensize.Y - (3 * button_size)); + case aux1_id: + return rect(m_screensize.X - button_size * 1.8, + m_screensize.Y - button_size * 4, + m_screensize.X - button_size * 0.3, + m_screensize.Y - button_size * 2.5); + case escape_id: + return rect(m_screensize.X / 2 - button_size * 2, + 0, + m_screensize.X / 2 - button_size, + button_size); + case minimap_id: + return rect(m_screensize.X / 2 - button_size, + 0, + m_screensize.X / 2, + button_size); + case range_id: + return rect(m_screensize.X / 2, + 0, + m_screensize.X / 2 + button_size, + button_size); + case camera_id: + return rect(m_screensize.X / 2 + button_size, + 0, + m_screensize.X / 2 + button_size * 2, + button_size); + case chat_id: + return rect(m_screensize.X - button_size * 1.25, + 0, + m_screensize.X, + button_size); + default: + return rect(0, 0, 0, 0); + } +} + +void TouchScreenGUI::updateButtons() +{ + v2u32 screensize = m_device->getVideoDriver()->getScreenSize(); + + if (screensize != m_screensize) { + m_screensize = screensize; + button_size = std::min(m_screensize.Y / 4.5f, + RenderingEngine::getDisplayDensity() * + g_settings->getFloat("hud_scaling") * 65.0f); + + for (auto &button : m_buttons) { + if (button.guibutton) { + s32 id = button.guibutton->getID(); + rect rect = getButtonRect((touch_gui_button_id)id); + button.guibutton->setRelativePosition(rect); + } + } + + if (m_joystick_btn_off->guibutton) { + s32 id = m_joystick_btn_off->guibutton->getID(); + rect rect = getButtonRect((touch_gui_button_id)id); + m_joystick_btn_off->guibutton->setRelativePosition(rect); + } + + if (m_joystick_btn_bg->guibutton) { + s32 id = m_joystick_btn_bg->guibutton->getID(); + rect rect = getButtonRect((touch_gui_button_id)id); + m_joystick_btn_bg->guibutton->setRelativePosition(rect); + } + + if (m_joystick_btn_center->guibutton) { + s32 id = m_joystick_btn_center->guibutton->getID(); + rect rect = getButtonRect((touch_gui_button_id)id); + m_joystick_btn_center->guibutton->setRelativePosition(rect); + } + } +} + void TouchScreenGUI::init(ISimpleTextureSource *tsrc) { assert(tsrc); @@ -474,51 +603,32 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc) */ if (m_fixed_joystick) { m_joystick_btn_off = initJoystickButton(joystick_off_id, - rect(button_size, - m_screensize.Y - button_size * 4, - button_size * 4, - m_screensize.Y - button_size), 0); + getButtonRect(joystick_off_id), 0); } else { m_joystick_btn_off = initJoystickButton(joystick_off_id, - rect(button_size, - m_screensize.Y - button_size * 3, - button_size * 3, - m_screensize.Y - button_size), 0); + getButtonRect(joystick_off_id), 0); } m_joystick_btn_bg = initJoystickButton(joystick_bg_id, - rect(button_size, - m_screensize.Y - button_size * 4, - button_size * 4, - m_screensize.Y - button_size), - 1, false); + getButtonRect(joystick_bg_id), 1, false); m_joystick_btn_center = initJoystickButton(joystick_center_id, - rect(0, 0, button_size, button_size), 2, false); + getButtonRect(joystick_center_id), 2, false); // init jump button - initButton(jump_id, - rect(m_screensize.X - (1.75 * button_size), - m_screensize.Y - button_size, - m_screensize.X - (0.25 * button_size), - m_screensize.Y), - L"x", false); + initButton(jump_id, getButtonRect(jump_id), L"x", false); + + // init drop button + initButton(drop_id, getButtonRect(drop_id), L"drop", false); // init crunch button - initButton(crunch_id, - rect(m_screensize.X - (3.25 * button_size), - m_screensize.Y - button_size, - m_screensize.X - (1.75 * button_size), - m_screensize.Y), - L"H", false); + initButton(crunch_id, getButtonRect(crunch_id), L"H", false); + + // init inventory button + initButton(inventory_id, getButtonRect(inventory_id), L"inv", false); // init zoom button - initButton(zoom_id, - rect(m_screensize.X - (1.25 * button_size), - m_screensize.Y - (4 * button_size), - m_screensize.X - (0.25 * button_size), - m_screensize.Y - (3 * button_size)), - L"z", false); + // initButton(zoom_id, getButtonRect(zoom_id), L"z", false); // init aux1 button if (!m_joystick_triggers_aux1) @@ -529,7 +639,7 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc) m_screensize.Y - (1.5 * button_size)), L"spc1", false); - m_settingsbar.init(m_texturesource, "gear_icon.png", settings_starter_id, + /*m_settingsbar.init(m_texturesource, "gear_icon.png", settings_starter_id, v2s32(m_screensize.X - (1.25 * button_size), m_screensize.Y - ((SETTINGS_BAR_Y_OFFSET + 1.0) * button_size) + (0.5 * button_size)), @@ -562,29 +672,39 @@ void TouchScreenGUI::init(ISimpleTextureSource *tsrc) m_rarecontrolsbar.addButton(chat_id, L"Chat", "chat_btn.png"); m_rarecontrolsbar.addButton(inventory_id, L"inv", "inventory_btn.png"); - m_rarecontrolsbar.addButton(drop_id, L"drop", "drop_btn.png"); + m_rarecontrolsbar.addButton(drop_id, L"drop", "drop_btn.png");*/ + + // init pause button [1] + initButton(escape_id, getButtonRect(escape_id), L"Exit", false); + + // init minimap button [2] + initButton(minimap_id, getButtonRect(minimap_id), L"minimap", false); + + // init rangeselect button [3] + initButton(range_id, getButtonRect(range_id), L"rangeview", false); + + // init camera button [4] + initButton(camera_id, getButtonRect(camera_id), L"camera", false); + + // init chat button + initButton(chat_id, getButtonRect(chat_id), L"Chat", false); + + m_buttons_initialized = true; } touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y) { - IGUIElement *rootguielement = m_guienv->getRootGUIElement(); - - if (rootguielement != nullptr) { - gui::IGUIElement *element = - rootguielement->getElementFromPoint(core::position2d(x, y)); - - if (element) - for (unsigned int i = 0; i < after_last_element_id; i++) - if (element == m_buttons[i].guibutton) - return (touch_gui_button_id) i; - } - - return after_last_element_id; + for (u32 i = 0; i < after_last_element_id; i++) { + if (!m_buttons[i].guibutton) + continue; + + if (m_buttons[i].guibutton->isPointInside(core::position2d(x, y))) + return (touch_gui_button_id) i; } touch_gui_button_id TouchScreenGUI::getButtonID(size_t eventID) { - for (unsigned int i = 0; i < after_last_element_id; i++) { + for (u32 i = 0; i < after_last_element_id; i++) { button_info *btn = &m_buttons[i]; auto id = std::find(btn->ids.begin(), btn->ids.end(), eventID); @@ -651,12 +771,11 @@ void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button, assert(pos != btn->ids.end()); btn->ids.erase(pos); - if (!btn->ids.empty()) - return; - - translated->KeyInput.PressedDown = false; - btn->repeatcounter = -1; - m_receiver->OnEvent(*translated); + if (btn->ids.empty()) { + translated->KeyInput.PressedDown = false; + btn->repeatcounter = -1; + m_receiver->OnEvent(*translated); + } } delete translated; } @@ -686,10 +805,25 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id) translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP; m_receiver->OnEvent(*translated); delete translated; - } else { - // do double tap detection - doubleTapDetection(); - } + } else if (!m_move_has_really_moved) { + auto *translated = new SEvent; + memset(translated, 0, sizeof(SEvent)); + translated->EventType = EET_MOUSE_INPUT_EVENT; + translated->MouseInput.X = m_move_downlocation.X; + translated->MouseInput.Y = m_move_downlocation.Y; + translated->MouseInput.Shift = false; + translated->MouseInput.Control = false; + translated->MouseInput.ButtonStates = 0; + translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP; + m_receiver->OnEvent(*translated); + delete translated; + quickTapDetection(); + m_shootline = m_device + ->getSceneManager() + ->getSceneCollisionManager() + ->getRayFromScreenCoordinates( + v2s32(m_move_downlocation.X, m_move_downlocation.Y)); + } } // handle joystick @@ -697,11 +831,12 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id) m_has_joystick_id = false; // reset joystick - for (unsigned int i = 0; i < 4; i++) + for (u32 i = 0; i < 4; i++) m_joystick_status[i] = false; applyJoystickStatus(); - m_joystick_btn_off->guibutton->setVisible(true); + if (m_visible) + m_joystick_btn_off->guibutton->setVisible(true); m_joystick_btn_bg->guibutton->setVisible(false); m_joystick_btn_center->guibutton->setVisible(false); } else { @@ -719,8 +854,71 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id) } } +void TouchScreenGUI::moveJoystick(const SEvent &event, float dx, float dy) { + m_joystick_has_really_moved = true; + double distance = sqrt(dx * dx + dy * dy); + + // angle in degrees + double angle = acos(dx / distance) * 180 / M_PI; + if (dy < 0) + angle *= -1; + // rotate to make comparing easier + angle = fmod(angle + 180 + 22.5, 360); + + // reset state before applying + for (bool & joystick_status : m_joystick_status) + joystick_status = false; + + if (distance <= m_touchscreen_threshold) { + // do nothing + } else if (angle < 45) + m_joystick_status[j_left] = true; + else if (angle < 90) { + m_joystick_status[j_forward] = true; + m_joystick_status[j_left] = true; + } else if (angle < 135) + m_joystick_status[j_forward] = true; + else if (angle < 180) { + m_joystick_status[j_forward] = true; + m_joystick_status[j_right] = true; + } else if (angle < 225) + m_joystick_status[j_right] = true; + else if (angle < 270) { + m_joystick_status[j_backward] = true; + m_joystick_status[j_right] = true; + } else if (angle < 315) + m_joystick_status[j_backward] = true; + else if (angle <= 360) { + m_joystick_status[j_backward] = true; + m_joystick_status[j_left] = true; + } + + if (distance > button_size * 1.5) { + m_joystick_status[j_special1] = true; + // move joystick "button" + s32 ndx = button_size * dx / distance * 1.5f - button_size / 2.0f * 1.5f; + s32 ndy = button_size * dy / distance * 1.5f - button_size / 2.0f * 1.5f; + if (m_fixed_joystick) { + m_joystick_btn_center->guibutton->setRelativePosition(v2s32( + button_size * 5 / 2 + ndx, + m_screensize.Y - button_size * 5 / 2 + ndy)); + } else { + m_joystick_btn_center->guibutton->setRelativePosition(v2s32( + m_pointerpos[event.TouchInput.ID].X + ndx, + m_pointerpos[event.TouchInput.ID].Y + ndy)); + } + } else { + m_joystick_btn_center->guibutton->setRelativePosition(v2s32( + event.TouchInput.X - button_size / 2.0f * 1.5f, + event.TouchInput.Y - button_size / 2.0f * 1.5f)); + } +} + void TouchScreenGUI::translateEvent(const SEvent &event) { + if (!m_buttons_initialized) + return; + if (!m_visible) { infostream << "TouchScreenGUI::translateEvent got event but not visible!" @@ -751,22 +949,22 @@ void TouchScreenGUI::translateEvent(const SEvent &event) // handle button events if (button != after_last_element_id) { handleButtonEvent(button, eventID, true); - m_settingsbar.deactivate(); - m_rarecontrolsbar.deactivate(); + //m_settingsbar.deactivate(); + //m_rarecontrolsbar.deactivate(); } else if (isHUDButton(event)) { - m_settingsbar.deactivate(); - m_rarecontrolsbar.deactivate(); + //m_settingsbar.deactivate(); + //m_rarecontrolsbar.deactivate(); // already handled in isHUDButton() } else if (m_settingsbar.isButton(event)) { - m_rarecontrolsbar.deactivate(); + //m_rarecontrolsbar.deactivate(); // already handled in isSettingsBarButton() } else if (m_rarecontrolsbar.isButton(event)) { - m_settingsbar.deactivate(); + //m_settingsbar.deactivate(); // already handled in isSettingsBarButton() } else { // handle non button events - m_settingsbar.deactivate(); - m_rarecontrolsbar.deactivate(); + //m_settingsbar.deactivate(); + //m_rarecontrolsbar.deactivate(); s32 dxj = event.TouchInput.X - button_size * 5.0f / 2.0f; s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5.0f / 2.0f; @@ -774,7 +972,11 @@ void TouchScreenGUI::translateEvent(const SEvent &event) /* Select joystick when left 1/3 of screen dragged or * when joystick tapped (fixed joystick position) */ - if ((m_fixed_joystick && dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5) || + bool inside_joystick = + m_fixed_joystick + ? dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5 + : event.TouchInput.X < m_screensize.X / 3.0f; + if (inside_joystick) { (!m_fixed_joystick && event.TouchInput.X < m_screensize.X / 3.0f)) { // If we don't already have a starting point for joystick make this the one. if (!m_has_joystick_id) { @@ -787,14 +989,16 @@ void TouchScreenGUI::translateEvent(const SEvent &event) m_joystick_btn_center->guibutton->setVisible(true); // If it's a fixed joystick, don't move the joystick "button". - if (!m_fixed_joystick) + if (m_fixed_joystick) { + moveJoystick(event, dxj, dyj); + } else { m_joystick_btn_bg->guibutton->setRelativePosition(v2s32( - event.TouchInput.X - button_size * 3.0f / 2.0f, - event.TouchInput.Y - button_size * 3.0f / 2.0f)); - - m_joystick_btn_center->guibutton->setRelativePosition(v2s32( - event.TouchInput.X - button_size / 2.0f, - event.TouchInput.Y - button_size / 2.0f)); + event.TouchInput.X - button_size * 3.0f / 1.5f, + event.TouchInput.Y - button_size * 3.0f / 1.5f)); + m_joystick_btn_center->guibutton->setRelativePosition(v2s32( + event.TouchInput.X - button_size / 2.0f * 1.5f, + event.TouchInput.Y - button_size / 2.0f * 1.5f)); + } } } else { // If we don't already have a moving point make this the moving one. @@ -810,14 +1014,11 @@ void TouchScreenGUI::translateEvent(const SEvent &event) } m_pointerpos[event.TouchInput.ID] = v2s32(event.TouchInput.X, event.TouchInput.Y); - } - else if (event.TouchInput.Event == ETIE_LEFT_UP) { + } else if (event.TouchInput.Event == ETIE_LEFT_UP) { verbosestream << "Up event for pointerid: " << event.TouchInput.ID << std::endl; handleReleaseEvent(event.TouchInput.ID); - } else { - assert(event.TouchInput.Event == ETIE_MOVED); - + } else if (event.TouchInput.Event == ETIE_MOVED) { if (m_pointerpos[event.TouchInput.ID] == v2s32(event.TouchInput.X, event.TouchInput.Y)) return; @@ -842,11 +1043,8 @@ void TouchScreenGUI::translateEvent(const SEvent &event) s32 dx = X - m_pointerpos[event.TouchInput.ID].X; s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y; - // adapt to similar behaviour as pc screen - double d = g_settings->getFloat("mouse_sensitivity") * 3.0f; - - m_camera_yaw_change -= dx * d; - m_camera_pitch = MYMIN(MYMAX(m_camera_pitch + (dy * d), -180), 180); + m_camera_yaw_change -= dx * m_touch_sensitivity; + m_camera_pitch = MYMIN(MYMAX(m_camera_pitch + (dy * m_touch_sensitivity), -180), 180); // update shootline m_shootline = m_device @@ -876,73 +1074,16 @@ void TouchScreenGUI::translateEvent(const SEvent &event) dy = Y - m_screensize.Y + button_size * 5 / 2; } - double distance_sq = dx * dx + dy * dy; - s32 dxj = event.TouchInput.X - button_size * 5.0f / 2.0f; s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5.0f / 2.0f; bool inside_joystick = (dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5); if (m_joystick_has_really_moved || inside_joystick || (!m_fixed_joystick && - distance_sq > m_touchscreen_threshold * m_touchscreen_threshold)) { - m_joystick_has_really_moved = true; - double distance = sqrt(distance_sq); - - // angle in degrees - double angle = acos(dx / distance) * 180 / M_PI; - if (dy < 0) - angle *= -1; - // rotate to make comparing easier - angle = fmod(angle + 180 + 22.5, 360); - - // reset state before applying - for (bool & joystick_status : m_joystick_status) - joystick_status = false; - - if (distance <= m_touchscreen_threshold) { - // do nothing - } else if (angle < 45) - m_joystick_status[j_left] = true; - else if (angle < 90) { - m_joystick_status[j_forward] = true; - m_joystick_status[j_left] = true; - } else if (angle < 135) - m_joystick_status[j_forward] = true; - else if (angle < 180) { - m_joystick_status[j_forward] = true; - m_joystick_status[j_right] = true; - } else if (angle < 225) - m_joystick_status[j_right] = true; - else if (angle < 270) { - m_joystick_status[j_backward] = true; - m_joystick_status[j_right] = true; - } else if (angle < 315) - m_joystick_status[j_backward] = true; - else if (angle <= 360) { - m_joystick_status[j_backward] = true; - m_joystick_status[j_left] = true; - } - - if (distance > button_size) { - m_joystick_status[j_aux1] = true; - // move joystick "button" - s32 ndx = button_size * dx / distance - button_size / 2.0f; - s32 ndy = button_size * dy / distance - button_size / 2.0f; - if (m_fixed_joystick) { - m_joystick_btn_center->guibutton->setRelativePosition(v2s32( - button_size * 5 / 2 + ndx, - m_screensize.Y - button_size * 5 / 2 + ndy)); - } else { - m_joystick_btn_center->guibutton->setRelativePosition(v2s32( - m_pointerpos[event.TouchInput.ID].X + ndx, - m_pointerpos[event.TouchInput.ID].Y + ndy)); - } - } else { - m_joystick_btn_center->guibutton->setRelativePosition( - v2s32(X - button_size / 2, Y - button_size / 2)); - } - } - } + dx * dx + dy * dy > m_touchscreen_threshold * m_touchscreen_threshold)) { + moveJoystick(event, dx, dy); + } + } if (!m_has_move_id && !m_has_joystick_id) handleChangedButton(event); @@ -951,14 +1092,14 @@ void TouchScreenGUI::translateEvent(const SEvent &event) void TouchScreenGUI::handleChangedButton(const SEvent &event) { - for (unsigned int i = 0; i < after_last_element_id; i++) { + for (u32 i = 0; i < after_last_element_id; i++) { if (m_buttons[i].ids.empty()) continue; for (auto iter = m_buttons[i].ids.begin(); iter != m_buttons[i].ids.end(); ++iter) { if (event.TouchInput.ID == *iter) { - int current_button_id = + s32 current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y); if (current_button_id == i) @@ -976,7 +1117,7 @@ void TouchScreenGUI::handleChangedButton(const SEvent &event) } } - int current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y); + s32 current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y); if (current_button_id == after_last_element_id) return; @@ -988,27 +1129,17 @@ void TouchScreenGUI::handleChangedButton(const SEvent &event) event.TouchInput.ID, true); } -bool TouchScreenGUI::doubleTapDetection() +// Punch or left click +bool TouchScreenGUI::quickTapDetection() { m_key_events[0].down_time = m_key_events[1].down_time; m_key_events[0].x = m_key_events[1].x; m_key_events[0].y = m_key_events[1].y; - m_key_events[1].down_time = m_move_downtime; - m_key_events[1].x = m_move_downlocation.X; - m_key_events[1].y = m_move_downlocation.Y; - u64 delta = porting::getDeltaMs(m_key_events[0].down_time, porting::getTimeMs()); - if (delta > 400) - return false; - - double distance = sqrt( - (m_key_events[0].x - m_key_events[1].x) * - (m_key_events[0].x - m_key_events[1].x) + - (m_key_events[0].y - m_key_events[1].y) * - (m_key_events[0].y - m_key_events[1].y)); - - if (distance > (20 + m_touchscreen_threshold)) - return false; + // ignore the occasional touch + u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs()); + if (delta < 50) + return false; auto *translated = new SEvent(); memset(translated, 0, sizeof(SEvent)); @@ -1039,7 +1170,7 @@ bool TouchScreenGUI::doubleTapDetection() void TouchScreenGUI::applyJoystickStatus() { - for (unsigned int i = 0; i < 5; i++) { + for (u32 i = 0; i < 5; i++) { if (i == 4 && !m_joystick_triggers_aux1) continue; @@ -1065,6 +1196,9 @@ TouchScreenGUI::~TouchScreenGUI() } } + if (!m_buttons_initialized) + return; + if (m_joystick_btn_off->guibutton) { m_joystick_btn_off->guibutton->drop(); m_joystick_btn_off->guibutton = nullptr; @@ -1083,15 +1217,13 @@ TouchScreenGUI::~TouchScreenGUI() void TouchScreenGUI::step(float dtime) { + updateButtons(); + // simulate keyboard repeats for (auto &button : m_buttons) { if (!button.ids.empty()) { button.repeatcounter += dtime; - // in case we're moving around digging does not happen - if (m_has_move_id) - m_move_has_really_moved = true; - if (button.repeatcounter < button.repeatdelay) continue; @@ -1109,7 +1241,7 @@ void TouchScreenGUI::step(float dtime) } // joystick - for (unsigned int i = 0; i < 4; i++) { + for (u32 i = 0; i < 4; i++) { if (m_joystick_status[i]) { applyJoystickStatus(); break; @@ -1145,8 +1277,8 @@ void TouchScreenGUI::step(float dtime) } } - m_settingsbar.step(dtime); - m_rarecontrolsbar.step(dtime); + //m_settingsbar.step(dtime); + //m_rarecontrolsbar.step(dtime); } void TouchScreenGUI::resetHud() @@ -1154,7 +1286,7 @@ void TouchScreenGUI::resetHud() m_hud_rects.clear(); } -void TouchScreenGUI::registerHudItem(int index, const rect &rect) +void TouchScreenGUI::registerHudItem(s32 index, const rect &rect) { m_hud_rects[index] = rect; } @@ -1167,7 +1299,7 @@ void TouchScreenGUI::Toggle(bool visible) button.guibutton->setVisible(visible); } - if (m_joystick_btn_off->guibutton) + if (m_joystick_btn_off != nullptr && m_joystick_btn_off->guibutton) m_joystick_btn_off->guibutton->setVisible(visible); // clear all active buttons @@ -1175,11 +1307,11 @@ void TouchScreenGUI::Toggle(bool visible) while (!m_known_ids.empty()) handleReleaseEvent(m_known_ids.begin()->id); - m_settingsbar.hide(); - m_rarecontrolsbar.hide(); + //m_settingsbar.hide(); + //m_rarecontrolsbar.hide(); } else { - m_settingsbar.show(); - m_rarecontrolsbar.show(); + //m_settingsbar.show(); + //m_rarecontrolsbar.show(); } } @@ -1198,3 +1330,11 @@ void TouchScreenGUI::show() Toggle(true); } + +void TouchScreenGUI::handleReleaseAll() +{ + while (!m_known_ids.empty()) + handleReleaseEvent(m_known_ids.back().id); + for (auto &button : m_buttons) + button.ids.clear(); // should do nothing +} diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h index b7a9eea8e..d32db4c8d 100644 --- a/src/gui/touchscreengui.h +++ b/src/gui/touchscreengui.h @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#include #include #include "client/tile.h" @@ -37,23 +38,24 @@ using namespace irr::gui; typedef enum { jump_id = 0, - crunch_id, - zoom_id, - aux1_id, - after_last_element_id, - settings_starter_id, - rare_controls_starter_id, - fly_id, - noclip_id, - fast_id, - debug_id, - camera_id, - range_id, - minimap_id, - toggle_chat_id, - chat_id, - inventory_id, drop_id, + crunch_id, + //zoom_id, + aux1_id, + inventory_id, + escape_id, + minimap_id, + range_id, + camera_id, + chat_id, + after_last_element_id, + // settings_starter_id, + // rare_controls_starter_id, + // fly_id, + // noclip_id, + // fast_id, + // debug_id, + // toggle_chat_id, forward_id, backward_id, left_id, @@ -81,15 +83,12 @@ typedef enum } autohide_button_bar_dir; #define MIN_DIG_TIME_MS 500 -#define BUTTON_REPEAT_DELAY 0.2f +#define BUTTON_REPEAT_DELAY 1.0f #define SETTINGS_BAR_Y_OFFSET 5 #define RARE_CONTROLS_BAR_Y_OFFSET 5 -// Very slow button repeat frequency -#define SLOW_BUTTON_REPEAT 1.0f - -extern const char **button_imagenames; -extern const char **joystick_imagenames; +extern const char *button_imagenames[]; +extern const char *joystick_imagenames[]; struct button_info { @@ -101,7 +100,7 @@ struct button_info bool immediate_release; // 0: false, 1: (true) first texture, 2: (true) second texture - int togglable = 0; + s32 togglable = 0; std::vector textures; }; @@ -110,7 +109,7 @@ class AutoHideButtonBar public: AutoHideButtonBar(IrrlichtDevice *device, IEventReceiver *receiver); - void init(ISimpleTextureSource *tsrc, const char *starter_img, int button_id, + void init(ISimpleTextureSource *tsrc, const char *starter_img, s32 button_id, const v2s32 &UpperLeft, const v2s32 &LowerRight, autohide_button_bar_dir dir, float timeout); @@ -121,8 +120,8 @@ public: const char *btn_image); // add toggle button to be shown - void addToggleButton(touch_gui_button_id id, const wchar_t *caption, - const char *btn_image_1, const char *btn_image_2); + /*void addToggleButton(touch_gui_button_id id, const wchar_t *caption, + const char *btn_image_1, const char *btn_image_2);*/ // detect settings bar button events bool isButton(const SEvent &event); @@ -145,7 +144,7 @@ private: IGUIEnvironment *m_guienv; IEventReceiver *m_receiver; button_info m_starter; - std::vector m_buttons; + std::vector> m_buttons; v2s32 m_upper_left; v2s32 m_lower_right; @@ -153,6 +152,7 @@ private: // show settings bar bool m_active = false; + // is the gui visible bool m_visible = true; // settings bar timeout @@ -179,7 +179,12 @@ public: return res; } - double getPitch() { return m_camera_pitch; } + double getPitchChange() + { + double res = m_camera_pitch; + m_camera_pitch = 0; + return res; + } /* * Returns a line which describes what the player is pointing at. @@ -193,12 +198,21 @@ public: void step(float dtime); void resetHud(); - void registerHudItem(int index, const rect &rect); + void registerHudItem(s32 index, const rect &rect); void Toggle(bool visible); void hide(); void show(); + // handle all buttons + void handleReleaseAll(); + + // returns true if device is active + static bool isActive() { return m_active; } + + // set device active state + static void setActive(bool active) { m_active = active; } + private: IrrlichtDevice *m_device; IGUIEnvironment *m_guienv; @@ -207,9 +221,11 @@ private: v2u32 m_screensize; s32 button_size; double m_touchscreen_threshold; + double m_touch_sensitivity; std::map> m_hud_rects; std::map m_hud_ids; bool m_visible; // is the gui visible + bool m_buttons_initialized = false; // value in degree double m_camera_yaw_change = 0.0; @@ -217,7 +233,7 @@ private: // forward, backward, left, right touch_gui_button_id m_joystick_names[5] = { - forward_id, backward_id, left_id, right_id, aux1_id}; + forward_id, backward_id, left_id, right_id, /*aux1_id*/}; bool m_joystick_status[5] = {false, false, false, false, false}; /* @@ -240,9 +256,9 @@ private: bool m_joystick_has_really_moved = false; bool m_fixed_joystick = false; bool m_joystick_triggers_aux1 = false; - button_info *m_joystick_btn_off = nullptr; - button_info *m_joystick_btn_bg = nullptr; - button_info *m_joystick_btn_center = nullptr; + std::shared_ptr m_joystick_btn_off = nullptr; + std::shared_ptr m_joystick_btn_bg = nullptr; + std::shared_ptr m_joystick_btn_center = nullptr; button_info m_buttons[after_last_element_id]; @@ -261,15 +277,21 @@ private: float repeat_delay = BUTTON_REPEAT_DELAY); // initialize a joystick button - button_info *initJoystickButton(touch_gui_button_id id, - const rect &button_rect, int texture_id, + std::shared_ptr initJoystickButton(touch_gui_button_id id, + const rect &button_rect, s32 texture_id, bool visible = true); + rect getButtonRect(touch_gui_button_id id); + + void updateButtons(); + + void moveJoystick(const SEvent &event, float dx, float dy); + struct id_status { size_t id; - int X; - int Y; + s32 X; + s32 Y; }; // vector to store known ids and their initial touch positions @@ -281,8 +303,8 @@ private: // handle pressed hud buttons bool isHUDButton(const SEvent &event); - // handle double taps - bool doubleTapDetection(); + // handle quick taps + bool quickTapDetection(); // handle release event void handleReleaseEvent(size_t evt_id); @@ -290,7 +312,7 @@ private: // apply joystick status void applyJoystickStatus(); - // double-click detection variables + // long-click detection variables struct key_event { u64 down_time; @@ -301,7 +323,7 @@ private: // array for saving last known position of a pointer std::map m_pointerpos; - // array for double tap detection + // array for long-click detection key_event m_key_events[2]; // settings bar @@ -309,6 +331,9 @@ private: // rare controls bar AutoHideButtonBar m_rarecontrolsbar; + + // device active state + static bool m_active; }; extern TouchScreenGUI *g_touchscreengui; diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 9e6816ba8..895cfb2d6 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -391,7 +391,7 @@ const HTTPFetchResult * HTTPFetchOngoing::complete(CURLcode res) } if (res != CURLE_OK) { - errorstream << "HTTPFetch for " << request.url << " failed (" + warningstream << "HTTPFetch for " << request.url << " failed (" << curl_easy_strerror(res) << ")" << std::endl; } else if (result.response_code >= 400) { errorstream << "HTTPFetch for " << request.url diff --git a/src/hud.h b/src/hud.h index 5c0bd9948..904dd7d75 100644 --- a/src/hud.h +++ b/src/hud.h @@ -53,7 +53,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_PARAM_HOTBAR_SELECTED_IMAGE 3 #define HUD_HOTBAR_ITEMCOUNT_DEFAULT 8 +#if !defined(__ANDROID__) && !defined(__IOS__) #define HUD_HOTBAR_ITEMCOUNT_MAX 32 +#else +#define HUD_HOTBAR_ITEMCOUNT_MAX 9 +#endif #define HOTBAR_IMAGE_SIZE 48 diff --git a/src/irrlicht_changes/static_text.cpp b/src/irrlicht_changes/static_text.cpp index baf0ea626..324754d67 100644 --- a/src/irrlicht_changes/static_text.cpp +++ b/src/irrlicht_changes/static_text.cpp @@ -239,13 +239,6 @@ video::SColor StaticText::getOverrideColor() const return ColoredText.getDefaultColor(); } -#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR > 8 -video::SColor StaticText::getActiveColor() const -{ - return getOverrideColor(); -} -#endif - //! Sets if the static text should use the overide color or the //! color in the gui skin. void StaticText::enableOverrideColor(bool enable) @@ -253,6 +246,10 @@ void StaticText::enableOverrideColor(bool enable) // TODO } +video::SColor StaticText::getActiveColor() const +{ + return ColoredText.getDefaultColor(); +} bool StaticText::isOverrideColorEnabled() const { diff --git a/src/irrlicht_changes/static_text.h b/src/irrlicht_changes/static_text.h index 74ef62008..33e8cf54c 100644 --- a/src/irrlicht_changes/static_text.h +++ b/src/irrlicht_changes/static_text.h @@ -135,10 +135,7 @@ namespace gui //! Gets the override color virtual video::SColor getOverrideColor() const; - #if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR > 8 - //! Gets the currently used text color - virtual video::SColor getActiveColor() const; - #endif + video::SColor getActiveColor() const; // IrrLicht 1.9; doesn't hurt in 1.8 //! Sets if the static text should use the overide color or the //! color in the gui skin. diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 5b8be3029..20f04aeac 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -126,13 +126,18 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const { // protocol_version >= 37 u8 version = 6; + + // protocol_version < 37 + if (protocol_version < 37) + version = 3; + writeU8(os, version); writeU8(os, type); os << serializeString16(name); os << serializeString16(description); os << serializeString16(inventory_image); os << serializeString16(wield_image); - writeV3F32(os, wield_scale); + writeV3F(os, wield_scale, protocol_version); writeS16(os, stack_max); writeU8(os, usable); writeU8(os, liquids_pointable); @@ -153,13 +158,25 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const os << serializeString16(node_placement_prediction); - // Version from ContentFeatures::serialize to keep in sync - sound_place.serialize(os, CONTENTFEATURES_VERSION); - sound_place_failed.serialize(os, CONTENTFEATURES_VERSION); + if (version == 3) { + os << serializeString16(sound_place.name); + writeF1000(os, sound_place.gain); + writeF1000(os, range); + os << serializeString16(sound_place_failed.name); + writeF1000(os, sound_place_failed.gain); + } else { + // Version from ContentFeatures::serialize to keep in sync + sound_place.serialize(os, CONTENTFEATURES_VERSION); + sound_place_failed.serialize(os, CONTENTFEATURES_VERSION); + writeF32(os, range); + } - writeF32(os, range); os << serializeString16(palette_image); writeARGB8(os, color); + + if (version == 3) + return; + os << serializeString16(inventory_overlay); os << serializeString16(wield_overlay); diff --git a/src/light.cpp b/src/light.cpp index 40f61764a..dcde81167 100644 --- a/src/light.cpp +++ b/src/light.cpp @@ -74,7 +74,7 @@ void set_light_table(float gamma) params.gamma = rangelim(gamma, 0.33f, 3.0f); // Boundary values should be fixed - light_LUT[0] = 0; + light_LUT[0] = 20; light_LUT[LIGHT_SUN] = 255; for (size_t i = 1; i < LIGHT_SUN; i++) { diff --git a/src/log.cpp b/src/log.cpp index 1d3141c0d..14a28af9d 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -334,6 +334,33 @@ void StreamLogOutput::logRaw(LogLevel lev, const std::string &line) { bool colored_message = (Logger::color_mode == LOG_COLOR_ALWAYS) || (Logger::color_mode == LOG_COLOR_AUTO && is_tty); +#if defined(__MACH__) && defined(__APPLE__) + if (colored_message) { + switch (lev) { + case LL_ERROR: + // error is red + m_stream << "📕 "; + break; + case LL_WARNING: + // warning is yellow + m_stream << "📙 "; + break; + case LL_INFO: + // info is a green + m_stream << "📗 "; + break; + case LL_VERBOSE: + // verbose is blue + m_stream << "📘 "; + break; + default: + // action is white + m_stream << "📔 "; + } + } + + m_stream << line << std::endl; +#else if (colored_message) { switch (lev) { case LL_ERROR: @@ -364,6 +391,7 @@ void StreamLogOutput::logRaw(LogLevel lev, const std::string &line) // reset to white color m_stream << "\033[0m"; } +#endif } void LogOutputBuffer::updateLogLevel() diff --git a/src/main.cpp b/src/main.cpp index 268a94c00..b87cef560 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/clientlauncher.h" #include "gui/guiEngine.h" #include "gui/mainmenumanager.h" +#include "translation.h" #endif #ifdef HAVE_TOUCHSCREENGUI #include "gui/touchscreengui.h" @@ -118,13 +119,21 @@ FileLogOutput file_log_output; static OptionList allowed_options; +#if defined(__ANDROID__) || defined(__IOS__) +int real_main(int argc, char *argv[]) +#else int main(int argc, char *argv[]) +#endif { int retval; debug_set_exception_handler(); g_logger.registerThread("Main"); +#ifdef NDEBUG g_logger.addOutputMaxLevel(&stderr_output, LL_ACTION); +#else + g_logger.addOutputMaxLevel(&stderr_output, LL_INFO); +#endif Settings cmd_args; bool cmd_args_ok = get_cmdline_opts(argc, argv, &cmd_args); @@ -151,10 +160,8 @@ int main(int argc, char *argv[]) #ifdef __ANDROID__ porting::initAndroid(); - porting::initializePathsAndroid(); -#else - porting::initializePaths(); #endif + porting::initializePaths(); if (!create_userdata_path()) { errorstream << "Cannot create user data directory" << std::endl; @@ -192,7 +199,6 @@ int main(int argc, char *argv[]) if (g_settings->getBool("enable_console")) porting::attachOrCreateConsole(); -#ifndef __ANDROID__ // Run unit tests if (cmd_args.getFlag("run-unittests")) { #if BUILD_UNITTESTS @@ -204,7 +210,6 @@ int main(int argc, char *argv[]) return 1; #endif } -#endif GameStartData game_params; #ifdef SERVER @@ -242,6 +247,14 @@ int main(int argc, char *argv[]) return retval; } +#ifdef WIN32 +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) +{ + return main(__argc, __argv); +} +#endif + /***************************************************************************** * Startup / Init @@ -468,13 +481,9 @@ static bool setup_log_params(const Settings &cmd_args) static bool create_userdata_path() { bool success; - -#ifdef __ANDROID__ - if (!fs::PathExists(porting::path_user)) { - success = fs::CreateDir(porting::path_user); - } else { +#if defined(__ANDROID__) || defined(__IOS__) + if (fs::PathExists(porting::path_user)) success = true; - } #else // Create user data directory success = fs::CreateDir(porting::path_user); @@ -483,6 +492,15 @@ static bool create_userdata_path() return success; } +#if !defined(_MSC_VER) && !defined(SERVER) +static void language_setting_changed(const std::string &name, void *userdata) +{ + init_gettext(porting::path_locale.c_str(), + g_settings->get("language"), 0, nullptr); + g_client_translations->clear(); +} +#endif + static bool init_common(const Settings &cmd_args, int argc, char *argv[]) { startup_message(); @@ -510,6 +528,9 @@ static bool init_common(const Settings &cmd_args, int argc, char *argv[]) init_gettext(porting::path_locale.c_str(), g_settings->get("language"), argc, argv); +#if !defined(_MSC_VER) && !defined(SERVER) + g_settings->registerChangedCallback("language", language_setting_changed, nullptr); +#endif return true; } @@ -548,16 +569,16 @@ static bool read_config_file(const Settings &cmd_args) g_settings_path = cmd_args.get("config"); } else { std::vector filenames; - filenames.push_back(porting::path_user + DIR_DELIM + "minetest.conf"); + filenames.push_back(porting::path_user + DIR_DELIM + "multicraft.conf"); // Legacy configuration file location filenames.push_back(porting::path_user + - DIR_DELIM + ".." + DIR_DELIM + "minetest.conf"); + DIR_DELIM + ".." + DIR_DELIM + "multicraft.conf"); #if RUN_IN_PLACE // Try also from a lower level (to aid having the same configuration // for many RUN_IN_PLACE installs) filenames.push_back(porting::path_user + - DIR_DELIM + ".." + DIR_DELIM + ".." + DIR_DELIM + "minetest.conf"); + DIR_DELIM + ".." + DIR_DELIM + ".." + DIR_DELIM + "multicraft.conf"); #endif for (const std::string &filename : filenames) { @@ -812,7 +833,7 @@ static bool determine_subgame(GameParams *game_params) gamespec = findSubgame(g_settings->get("default_game")); infostream << "Using default gameid [" << gamespec.id << "]" << std::endl; if (!gamespec.isValid()) { - errorstream << "Game specified in default_game [" + warningstream << "Game specified in default_game [" << g_settings->get("default_game") << "] is invalid." << std::endl; return false; @@ -905,7 +926,7 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & if (!name_ok) { if (admin_nick.empty()) { errorstream << "No name given for admin. " - << "Please check your minetest.conf that it " + << "Please check your multicraft.conf that it " << "contains a 'name = ' to your main admin account." << std::endl; } else { diff --git a/src/map.cpp b/src/map.cpp index 0e70afcb9..dfd4009ca 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -43,7 +43,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" #include "database/database.h" #include "database/database-dummy.h" +#if USE_SQLITE #include "database/database-sqlite3.h" +#endif #include "script/scripting_server.h" #include #include @@ -165,33 +167,49 @@ MapNode Map::getNode(v3s16 p, bool *is_valid_position) return node; } -// throws InvalidPositionException if not found -void Map::setNode(v3s16 p, MapNode & n) +static void set_node_in_block(MapBlock *block, v3s16 relpos, MapNode n) { - v3s16 blockpos = getNodeBlockPos(p); - MapBlock *block = getBlockNoCreate(blockpos); - v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; // Never allow placing CONTENT_IGNORE, it causes problems if(n.getContent() == CONTENT_IGNORE){ + const NodeDefManager *nodedef = block->getParent()->getNodeDefManager(); + v3s16 blockpos = block->getPos(); + v3s16 p = blockpos * MAP_BLOCKSIZE + relpos; bool temp_bool; - errorstream<<"Map::setNode(): Not allowing to place CONTENT_IGNORE" + errorstream<<"Not allowing to place CONTENT_IGNORE" <<" while trying to replace \"" - <get(block->getNodeNoCheck(relpos, &temp_bool)).name + <get(block->getNodeNoCheck(relpos, &temp_bool)).name <<"\" at "<setNodeNoCheck(relpos, n); } +// throws InvalidPositionException if not found +void Map::setNode(v3s16 p, MapNode & n) +{ + v3s16 blockpos = getNodeBlockPos(p); + MapBlock *block = getBlockNoCreate(blockpos); + v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; + set_node_in_block(block, relpos, n); +} + void Map::addNodeAndUpdate(v3s16 p, MapNode n, std::map &modified_blocks, bool remove_metadata) { +#if USE_SQLITE // Collect old node for rollback RollbackNode rollback_oldnode(this, p, m_gamedef); +#endif + + v3s16 blockpos = getNodeBlockPos(p); + MapBlock *block = getBlockNoCreate(blockpos); + if (block->isDummy()) + throw InvalidPositionException(); + v3s16 relpos = p - blockpos * MAP_BLOCKSIZE; // This is needed for updating the lighting - MapNode oldnode = getNode(p); + MapNode oldnode = block->getNodeUnsafe(relpos); // Remove node metadata if (remove_metadata) { @@ -199,20 +217,32 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, } // Set the node on the map - // Ignore light (because calling voxalgo::update_lighting_nodes) - n.setLight(LIGHTBANK_DAY, 0, m_nodedef); - n.setLight(LIGHTBANK_NIGHT, 0, m_nodedef); - setNode(p, n); + const ContentFeatures &cf = m_nodedef->get(n); + const ContentFeatures &oldcf = m_nodedef->get(oldnode); + if (cf.lightingEquivalent(oldcf)) { + // No light update needed, just copy over the old light. + n.setLight(LIGHTBANK_DAY, oldnode.getLightRaw(LIGHTBANK_DAY, oldcf), cf); + n.setLight(LIGHTBANK_NIGHT, oldnode.getLightRaw(LIGHTBANK_NIGHT, oldcf), cf); + set_node_in_block(block, relpos, n); - // Update lighting - std::vector > oldnodes; - oldnodes.emplace_back(p, oldnode); - voxalgo::update_lighting_nodes(this, oldnodes, modified_blocks); + modified_blocks[blockpos] = block; + } else { + // Ignore light (because calling voxalgo::update_lighting_nodes) + n.setLight(LIGHTBANK_DAY, 0, cf); + n.setLight(LIGHTBANK_NIGHT, 0, cf); + set_node_in_block(block, relpos, n); - for (auto &modified_block : modified_blocks) { - modified_block.second->expireDayNightDiff(); + // Update lighting + std::vector > 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 USE_SQLITE // Report for rollback if(m_gamedef->rollback()) { @@ -221,6 +251,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n, action.setSetNode(p, rollback_oldnode, rollback_newnode); m_gamedef->rollback()->reportAction(action); } +#endif /* Add neighboring liquid nodes and this node to transform queue. @@ -766,6 +797,7 @@ void Map::transformLiquids(std::map &modified_blocks, n0.setLight(LIGHTBANK_DAY, 0, m_nodedef); n0.setLight(LIGHTBANK_NIGHT, 0, m_nodedef); +#if USE_SQLITE // Find out whether there is a suspect for this action std::string suspect; if (m_gamedef->rollback()) @@ -783,7 +815,9 @@ void Map::transformLiquids(std::map &modified_blocks, RollbackAction action; action.setSetNode(p0, rollback_oldnode, rollback_newnode); m_gamedef->rollback()->reportAction(action); - } else { + } else +#endif + { // Set node setNode(p0, n0); } @@ -1194,8 +1228,13 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, Settings conf; bool succeeded = conf.readConfigFile(conf_path.c_str()); if (!succeeded || !conf.exists("backend")) { +#if !defined(__ANDROID__) && !defined(__APPLE__) // fall back to sqlite3 conf.set("backend", "sqlite3"); +#else + // fall back to leveldb + conf.set("backend", "leveldb"); +#endif } std::string backend = conf.get("backend"); dbase = createDatabase(backend, savedir, conf); @@ -1661,8 +1700,10 @@ MapDatabase *ServerMap::createDatabase( const std::string &savedir, Settings &conf) { +#if USE_SQLITE if (name == "sqlite3") return new MapDatabaseSQLite3(savedir); +#endif if (name == "dummy") return new Database_Dummy(); #if USE_LEVELDB @@ -1681,7 +1722,7 @@ MapDatabase *ServerMap::createDatabase( } #endif - throw BaseException(std::string("Database backend ") + name + " not supported."); + throw ModError(std::string("Database backend ") + name + " not supported."); } void ServerMap::beginSave() diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 6f5ab80ca..183005239 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -330,7 +330,7 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, } } -void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level) +void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int compression_level, std::string formspec_prepend) { if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapBlock format not supported"); @@ -399,10 +399,10 @@ void MapBlock::serialize(std::ostream &os_compressed, u8 version, bool disk, int Node metadata */ if (version >= 29) { - m_node_metadata.serialize(os, version, disk); + m_node_metadata.serialize(os, version, disk, false, false, formspec_prepend); } else { // use os_raw from above to avoid allocating another stream object - m_node_metadata.serialize(os_raw, version, disk); + m_node_metadata.serialize(os_raw, version, disk, false, false, formspec_prepend); // prior to 29 node data was compressed individually compress(os_raw.str(), os, version, compression_level); } diff --git a/src/mapblock.h b/src/mapblock.h index b8821585d..0ac904720 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -459,7 +459,8 @@ public: // These don't write or read version by itself // Set disk to true for on-disk format, false for over-the-network format // Precondition: version >= SER_FMT_VER_LOWEST_WRITE - void serialize(std::ostream &result, u8 version, bool disk, int compression_level); + void serialize(std::ostream &result, u8 version, bool disk, int compression_level, + std::string formspec_prepend=""); // If disk == true: In addition to doing other things, will add // unknown blocks from id-name mapping to wndef void deSerialize(std::istream &is, u8 version, bool disk); diff --git a/src/mapgen/CMakeLists.txt b/src/mapgen/CMakeLists.txt index e74bd85db..b8cf57e0d 100644 --- a/src/mapgen/CMakeLists.txt +++ b/src/mapgen/CMakeLists.txt @@ -9,6 +9,7 @@ set(mapgen_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v5.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v6.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v7.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v7p.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_valleys.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mg_biome.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mg_decoration.cpp diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp index 0b93616c8..efe4b233c 100644 --- a/src/mapgen/mapgen.cpp +++ b/src/mapgen/mapgen.cpp @@ -47,6 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen_v5.h" #include "mapgen_v6.h" #include "mapgen_v7.h" +#include "mapgen_v7p.h" #include "mapgen_valleys.h" #include "mapgen_singlenode.h" #include "cavegen.h" @@ -89,6 +90,7 @@ struct MapgenDesc { // Of the remaining, v5 last due to age, v7 first due to being the default. // The order of 'enum MapgenType' in mapgen.h must match this order. static MapgenDesc g_reg_mapgens[] = { + {"v7p", true}, {"v7", true}, {"valleys", true}, {"carpathian", true}, @@ -96,7 +98,7 @@ static MapgenDesc g_reg_mapgens[] = { {"flat", true}, {"fractal", true}, {"singlenode", true}, - {"v6", true}, + {"v6", false}, }; STATIC_ASSERT( @@ -174,6 +176,8 @@ Mapgen *Mapgen::createMapgen(MapgenType mgtype, MapgenParams *params, return new MapgenV6((MapgenV6Params *)params, emerge); case MAPGEN_V7: return new MapgenV7((MapgenV7Params *)params, emerge); + case MAPGEN_V7P: + return new MapgenV7P((MapgenV7PParams *)params, emerge); case MAPGEN_VALLEYS: return new MapgenValleys((MapgenValleysParams *)params, emerge); default: @@ -199,6 +203,8 @@ MapgenParams *Mapgen::createMapgenParams(MapgenType mgtype) return new MapgenV6Params; case MAPGEN_V7: return new MapgenV7Params; + case MAPGEN_V7P: + return new MapgenV7PParams; case MAPGEN_VALLEYS: return new MapgenValleysParams; default: diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h index 959dfaff0..dcf1804f2 100644 --- a/src/mapgen/mapgen.h +++ b/src/mapgen/mapgen.h @@ -26,8 +26,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" #include "util/container.h" -#define MAPGEN_DEFAULT MAPGEN_V7 -#define MAPGEN_DEFAULT_NAME "v7" +#define MAPGEN_DEFAULT MAPGEN_V7P +#define MAPGEN_DEFAULT_NAME "v7p" /////////////////// Mapgen flags #define MG_CAVES 0x02 @@ -102,6 +102,7 @@ private: // Order must match the order of 'static MapgenDesc g_reg_mapgens[]' in mapgen.cpp enum MapgenType { + MAPGEN_V7P, MAPGEN_V7, MAPGEN_VALLEYS, MAPGEN_CARPATHIAN, diff --git a/src/mapgen/mapgen_v7p.cpp b/src/mapgen/mapgen_v7p.cpp new file mode 100644 index 000000000..0508ad637 --- /dev/null +++ b/src/mapgen/mapgen_v7p.cpp @@ -0,0 +1,435 @@ +/* +Minetest +Copyright (C) 2013-2016 kwolekr, Ryan Kwolek +Copyright (C) 2014-2017 paramat + +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 3.0 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 "mapgen.h" +#include "voxel.h" +#include "noise.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "nodedef.h" +#include "voxelalgorithms.h" +//#include "profiler.h" // For TimeTaker +#include "settings.h" // For g_settings +#include "emerge.h" +#include "dungeongen.h" +#include "cavegen.h" +#include "mg_biome.h" +#include "mg_ore.h" +#include "mg_decoration.h" +#include "mapgen_v7p.h" + + +FlagDesc flagdesc_mapgen_v7p[] = { + {"mountains", MGV7P_MOUNTAINS}, + {"ridges", MGV7P_RIDGES}, + {NULL, 0} +}; + + +//////////////////////////////////////////////////////////////////////////////// + + +MapgenV7P::MapgenV7P(MapgenV7PParams *params, EmergeParams *emerge) + : MapgenBasic(MAPGEN_V7P, params, emerge) +{ + spflags = params->spflags; + + // Average of mgv6 small caves count + small_caves_count = 6 * csize.X * csize.Z * MAP_BLOCKSIZE / 50000; + + // Highest level of bedrock + bedrock_level = water_level - 64; + + // 2D noise + noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Z); + noise_terrain_alt = new Noise(¶ms->np_terrain_alt, seed, csize.X, csize.Z); + noise_terrain_persist = new Noise(¶ms->np_terrain_persist, seed, csize.X, csize.Z); + noise_height_select = new Noise(¶ms->np_height_select, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + + if (spflags & MGV7P_MOUNTAINS) { + noise_mount_height = new Noise(¶ms->np_mount_height, seed, csize.X, csize.Z); + noise_mountain = new Noise(¶ms->np_mountain, seed, csize.X, csize.Z); + } + + if (spflags & MGV7P_RIDGES) { + noise_ridge_uwater = new Noise(¶ms->np_ridge_uwater, seed, csize.X, csize.Z); + noise_ridge = new Noise(¶ms->np_ridge, seed, csize.X, csize.Z); + } + + // Resolve additional nodes + c_bedrock = ndef->getId("mapgen_bedrock"); +} + + +MapgenV7P::~MapgenV7P() +{ + delete noise_terrain_base; + delete noise_terrain_alt; + delete noise_terrain_persist; + delete noise_height_select; + delete noise_filler_depth; + + if (spflags & MGV7P_MOUNTAINS) { + delete noise_mount_height; + delete noise_mountain; + } + + if (spflags & MGV7P_RIDGES) { + delete noise_ridge_uwater; + delete noise_ridge; + } +} + + +MapgenV7PParams::MapgenV7PParams() +{ + spflags = MGV7P_MOUNTAINS | MGV7P_RIDGES; + + np_terrain_base = NoiseParams(4, 35, v3f(600, 600, 600), 82341, 5, 0.6, 2.0); + np_terrain_alt = NoiseParams(4, 25, v3f(600, 600, 600), 5934, 5, 0.6, 2.0); + np_terrain_persist = NoiseParams(0.6, 0.1, v3f(2000, 2000, 2000), 539, 3, 0.6, 2.0); + np_height_select = NoiseParams(-8, 16, v3f(500, 500, 500), 4213, 6, 0.7, 2.0); + np_filler_depth = NoiseParams(0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0); + np_mount_height = NoiseParams(128, 56, v3f(1000, 1000, 1000), 72449, 3, 0.6, 2.0); + np_ridge_uwater = NoiseParams(0, 1, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0); + np_mountain = NoiseParams(-0.6, 1, v3f(250, 250, 250), 5333, 5, 0.63, 2.0); + np_ridge = NoiseParams(0, 1, v3f(100, 100, 100), 6467, 4, 0.75, 2.0); +} + + +void MapgenV7PParams::readParams(const Settings *settings) +{ + settings->getFlagStrNoEx("mgv7p_spflags", spflags, flagdesc_mapgen_v7p); + + settings->getNoiseParams("mgv7p_np_terrain_base", np_terrain_base); + settings->getNoiseParams("mgv7p_np_terrain_alt", np_terrain_alt); + settings->getNoiseParams("mgv7p_np_terrain_persist", np_terrain_persist); + settings->getNoiseParams("mgv7p_np_height_select", np_height_select); + settings->getNoiseParams("mgv7p_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgv7p_np_mount_height", np_mount_height); + settings->getNoiseParams("mgv7p_np_ridge_uwater", np_ridge_uwater); + settings->getNoiseParams("mgv7p_np_mountain", np_mountain); + settings->getNoiseParams("mgv7p_np_ridge", np_ridge); +} + + +void MapgenV7PParams::writeParams(Settings *settings) const +{ + settings->setFlagStr("mgv7p_spflags", spflags, flagdesc_mapgen_v7p); + + settings->setNoiseParams("mgv7p_np_terrain_base", np_terrain_base); + settings->setNoiseParams("mgv7p_np_terrain_alt", np_terrain_alt); + settings->setNoiseParams("mgv7p_np_terrain_persist", np_terrain_persist); + settings->setNoiseParams("mgv7p_np_height_select", np_height_select); + settings->setNoiseParams("mgv7p_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgv7p_np_mount_height", np_mount_height); + settings->setNoiseParams("mgv7p_np_ridge_uwater", np_ridge_uwater); + settings->setNoiseParams("mgv7p_np_mountain", np_mountain); + settings->setNoiseParams("mgv7p_np_ridge", np_ridge); +} + + +void MapgenV7PParams::setDefaultSettings(Settings *settings) +{ + settings->setDefault("mgv7p_spflags", flagdesc_mapgen_v7p, + MGV7P_MOUNTAINS | MGV7P_RIDGES); +} + + +/////////////////////////////////////////////////////////////////////////////// + + +int MapgenV7P::getSpawnLevelAtPoint(v2s16 p) +{ + // If enabled, first check if inside a river + if (spflags & MGV7P_RIDGES) { + float width = 0.2; + float uwatern = + NoisePerlin2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * 2; + if (fabs(uwatern) <= width) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + } + + // Base/mountain terrain calculation + s16 y = baseTerrainLevelAtPoint(p.X, p.Y); + if (spflags & MGV7P_MOUNTAINS) + y = MYMAX(mountainLevelAtPoint(p.X, p.Y), y); + + if (y <= water_level || y > water_level + 16) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + else + return y + 2; // +2 because surface is at y and due to biome 'dust' +} + + +void MapgenV7P::makeChunk(BlockMakeData *data) +{ + // Pre-conditions + assert(data->vmanip); + assert(data->nodedef); + + this->generating = true; + this->vm = data->vmanip; + this->ndef = data->nodedef; + + v3s16 blockpos_min = data->blockpos_min; + v3s16 blockpos_max = data->blockpos_max; + node_min = blockpos_min * MAP_BLOCKSIZE; + node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1); + full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE; + full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1); + + blockseed = getBlockSeed2(full_node_min, seed); + + if (node_max.Y <= bedrock_level) { + // Only generate bedrock + generateBedrock(); + } else { + // Generate base and mountain terrain + s16 stone_surface_max_y = generateTerrain(); + + // Generate rivers + if (spflags & MGV7P_RIDGES) + generateRidgeTerrain(); + + // Create heightmap + updateHeightmap(node_min, node_max); + + // Init biome generator, place biome-specific nodes, and build biomemap + if (flags & MG_BIOMES) { + biomegen->calcBiomeNoise(node_min); + generateBiomes(); + } + + // Generate mgv6 caves but not deep into bedrock + if (flags & MG_CAVES) + generateCaves(stone_surface_max_y, water_level); + + // Generate dungeons + if (flags & MG_DUNGEONS) + generateDungeons(stone_surface_max_y); + + // Generate the registered decorations + if (flags & MG_DECORATIONS) + m_emerge->decomgr->placeAllDecos( + this, blockseed, node_min, node_max); + + // Generate the registered ores + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + + // Sprinkle some dust on top after everything else was generated + dustTopNodes(); + + // Update liquids + updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); + } + + // Calculate lighting + if (flags & MG_LIGHT) + calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), + full_node_min, full_node_max, true); + + this->generating = false; +} + + +float MapgenV7P::baseTerrainLevelAtPoint(s16 x, s16 z) +{ + float hselect = NoisePerlin2D(&noise_height_select->np, x, z, seed); + hselect = rangelim(hselect, 0.0, 1.0); + + float persist = NoisePerlin2D(&noise_terrain_persist->np, x, z, seed); + + noise_terrain_base->np.persist = persist; + float height_base = NoisePerlin2D(&noise_terrain_base->np, x, z, seed); + + noise_terrain_alt->np.persist = persist; + float height_alt = NoisePerlin2D(&noise_terrain_alt->np, x, z, seed); + + if (height_alt > height_base) + return height_alt; + + return (height_base * hselect) + (height_alt * (1.0 - hselect)); +} + + +float MapgenV7P::baseTerrainLevelFromMap(int index) +{ + float hselect = rangelim(noise_height_select->result[index], 0.0, 1.0); + float height_base = noise_terrain_base->result[index]; + float height_alt = noise_terrain_alt->result[index]; + + if (height_alt > height_base) + return height_alt; + + return (height_base * hselect) + (height_alt * (1.0 - hselect)); +} + + +float MapgenV7P::mountainLevelAtPoint(s16 x, s16 z) +{ + float mnt_h_n = NoisePerlin2D(&noise_mount_height->np, x, z, seed); + float mnt_n = NoisePerlin2D(&noise_mountain->np, x, z, seed); + + return mnt_n * mnt_h_n; +} + + +float MapgenV7P::mountainLevelFromMap(int idx_xz) +{ + float mounthn = noise_mount_height->result[idx_xz]; + float mountn = noise_mountain->result[idx_xz]; + + return mountn * mounthn; +} + + +void MapgenV7P::generateBedrock() +{ + MapNode n_bedrock(c_bedrock); + + for (s16 z = node_min.Z; z <= node_max.Z; z++) + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + u32 vi = vm->m_area.index(node_min.X, y, z); + + for (s16 x = node_min.X; x <= node_max.X; x++, vi++) { + if (vm->m_data[vi].getContent() == CONTENT_IGNORE) + vm->m_data[vi] = n_bedrock; + } + } +} + + +int MapgenV7P::generateTerrain() +{ + MapNode n_stone(c_stone); + MapNode n_bedrock(c_bedrock); + MapNode n_water(c_water_source); + MapNode n_air(CONTENT_AIR); + + //// Calculate noise for terrain generation + noise_terrain_persist->perlinMap2D(node_min.X, node_min.Z); + float *persistmap = noise_terrain_persist->result; + + noise_terrain_base ->perlinMap2D(node_min.X, node_min.Z, persistmap); + noise_terrain_alt ->perlinMap2D(node_min.X, node_min.Z, persistmap); + noise_height_select->perlinMap2D(node_min.X, node_min.Z); + + if (spflags & MGV7P_MOUNTAINS) { + noise_mount_height->perlinMap2D(node_min.X, node_min.Z); + noise_mountain ->perlinMap2D(node_min.X, node_min.Z); + } + + //// Place nodes + const v3s16 &em = vm->m_area.getExtent(); + s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; + u32 index2d = 0; + + for (s16 z = node_min.Z; z <= node_max.Z; z++) + for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) { + s16 surface_y = baseTerrainLevelFromMap(index2d); + if (spflags & MGV7P_MOUNTAINS) + surface_y = MYMAX(mountainLevelFromMap(index2d), surface_y); + + if (surface_y > stone_surface_max_y) + stone_surface_max_y = surface_y; + + u32 vi = vm->m_area.index(x, node_min.Y - 1, z); + + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + if (vm->m_data[vi].getContent() == CONTENT_IGNORE) { + if (y <= surface_y) { + if (y <= bedrock_level) + vm->m_data[vi] = n_bedrock; // Bedrock + else + vm->m_data[vi] = n_stone; // Base and mountain terrain + } else if (y <= water_level) { + vm->m_data[vi] = n_water; // Water + } else { + vm->m_data[vi] = n_air; // Air + } + } + vm->m_area.add_y(em, vi, 1); + } + } + + return stone_surface_max_y; +} + + +void MapgenV7P::generateRidgeTerrain() +{ + if (node_max.Y < water_level - 16) + return; + + noise_ridge->perlinMap2D(node_min.X, node_min.Z); + noise_ridge_uwater->perlinMap2D(node_min.X, node_min.Z); + + MapNode n_water(c_water_source); + MapNode n_air(CONTENT_AIR); + + float width = 0.2; + + for (s16 z = node_min.Z; z <= node_max.Z; z++) + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + u32 vi = vm->m_area.index(node_min.X, y, z); + + for (s16 x = node_min.X; x <= node_max.X; x++, vi++) { + int index2d = (z - node_min.Z) * csize.X + (x - node_min.X); + + float uwatern = noise_ridge_uwater->result[index2d] * 2; + if (fabs(uwatern) > width) + continue; + + float altitude = y - water_level; + float height_mod = (altitude + 17) / 2.5; + float width_mod = width - fabs(uwatern); + float nridge = noise_ridge->result[index2d] * MYMAX(altitude, 0) / 7.0; + + if (nridge + width_mod * height_mod < 0.6) + continue; + + vm->m_data[vi] = (y > water_level) ? n_air : n_water; + } + } +} + + +void MapgenV7P::generateCaves(s16 max_stone_y, s16 large_cave_depth) +{ + if (max_stone_y < node_min.Y) + return; + + PseudoRandom ps(blockseed + 21343); + PseudoRandom ps2(blockseed + 1032); + + u32 large_caves_count = (node_max.Y <= large_cave_depth) ? ps.range(0, 2) : 0; + + for (u32 i = 0; i < small_caves_count + large_caves_count; i++) { + CavesV6 cave(ndef, &gennotify, water_level, c_water_source, c_lava_source); + + bool large_cave = (i >= small_caves_count); + cave.makeCave(vm, node_min, node_max, &ps, &ps2, + large_cave, max_stone_y, heightmap); + } +} diff --git a/src/mapgen/mapgen_v7p.h b/src/mapgen/mapgen_v7p.h new file mode 100644 index 000000000..d0f01a0f0 --- /dev/null +++ b/src/mapgen/mapgen_v7p.h @@ -0,0 +1,92 @@ +/* +Minetest +Copyright (C) 2013-2016 kwolekr, Ryan Kwolek +Copyright (C) 2014-2017 paramat + +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 3.0 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. +*/ + +#ifndef MAPGEN_V7P_HEADER +#define MAPGEN_V7P_HEADER + +#include "mapgen.h" + +//////////// Mapgen V7P flags +#define MGV7P_MOUNTAINS 0x01 +#define MGV7P_RIDGES 0x02 + +class BiomeManager; + +extern FlagDesc flagdesc_mapgen_v7p[]; + + +struct MapgenV7PParams : public MapgenParams +{ + NoiseParams np_terrain_base; + NoiseParams np_terrain_alt; + NoiseParams np_terrain_persist; + NoiseParams np_height_select; + NoiseParams np_filler_depth; + NoiseParams np_mount_height; + NoiseParams np_ridge_uwater; + NoiseParams np_mountain; + NoiseParams np_ridge; + + MapgenV7PParams(); + ~MapgenV7PParams() {} + + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; + void setDefaultSettings(Settings *settings); +}; + +class MapgenV7P : public MapgenBasic +{ +public: + MapgenV7P(MapgenV7PParams *params, EmergeParams *emerge); + ~MapgenV7P(); + + virtual MapgenType getType() const { return MAPGEN_V7P; } + + virtual void makeChunk(BlockMakeData *data); + int getSpawnLevelAtPoint(v2s16 p); + + float baseTerrainLevelAtPoint(s16 x, s16 z); + float baseTerrainLevelFromMap(int index); + float mountainLevelAtPoint(s16 x, s16 z); + float mountainLevelFromMap(int idx_xz); + + void generateBedrock(); + int generateTerrain(); + void generateRidgeTerrain(); + virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth); + +private: + Noise *noise_terrain_base; + Noise *noise_terrain_alt; + Noise *noise_terrain_persist; + Noise *noise_height_select; + Noise *noise_mount_height; + Noise *noise_ridge_uwater; + Noise *noise_mountain; + Noise *noise_ridge; + + content_t c_bedrock; + + u32 small_caves_count; + s16 bedrock_level; +}; + +#endif diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp index e25ae6334..8be04452d 100644 --- a/src/mapgen/mg_schematic.cpp +++ b/src/mapgen/mg_schematic.cpp @@ -121,7 +121,7 @@ void Schematic::resolveNodeNames() } -void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place) +void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place, ServerMap *map) { assert(schemdata && slice_probs); sanity_check(m_ndef != NULL); @@ -197,6 +197,10 @@ void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_pla if (rot) vm->m_data[vi].rotateAlongYAxis(m_ndef, rot); + + // Wipe metadata if a map object was specified + if (map != nullptr && (force_place || force_place_node)) + map->removeNodeMetadata(pos); } } y_map++; @@ -265,7 +269,7 @@ void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags, MMVManip vm(map); vm.initialEmerge(bp1, bp2); - blitToVManip(&vm, p, rot, force_place); + blitToVManip(&vm, p, rot, force_place, map); voxalgo::blit_back_with_light(map, &vm, &modified_blocks); @@ -484,20 +488,14 @@ bool Schematic::serializeToLua(std::ostream *os, bool use_comments, } -bool Schematic::loadSchematicFromFile(const std::string &filename, - const NodeDefManager *ndef, StringMap *replace_names) +bool Schematic::loadSchematicFromStream(std::istream *is, + const std::string &filename, const NodeDefManager *ndef, + StringMap *replace_names) { - std::ifstream is(filename.c_str(), std::ios_base::binary); - if (!is.good()) { - errorstream << __FUNCTION__ << ": unable to open file '" - << filename << "'" << std::endl; - return false; - } - if (!m_ndef) m_ndef = ndef; - if (!deserializeFromMts(&is)) + if (!deserializeFromMts(is)) return false; name = filename; @@ -517,6 +515,19 @@ bool Schematic::loadSchematicFromFile(const std::string &filename, } +bool Schematic::loadSchematicFromFile(const std::string &filename, + const NodeDefManager *ndef, StringMap *replace_names) +{ + std::ifstream is(filename.c_str(), std::ios_base::binary); + if (!is.good()) { + errorstream << __FUNCTION__ << ": unable to open file '" + << filename << "'" << std::endl; + return false; + } + return loadSchematicFromStream(&is, filename, ndef, replace_names); +} + + bool Schematic::saveSchematicToFile(const std::string &filename, const NodeDefManager *ndef) { diff --git a/src/mapgen/mg_schematic.h b/src/mapgen/mg_schematic.h index 621495eaa..835f1a2bc 100644 --- a/src/mapgen/mg_schematic.h +++ b/src/mapgen/mg_schematic.h @@ -100,6 +100,8 @@ public: virtual void resolveNodeNames(); + bool loadSchematicFromStream(std::istream *is, const std::string &filename, + const NodeDefManager *ndef, StringMap *replace_names = NULL); bool loadSchematicFromFile(const std::string &filename, const NodeDefManager *ndef, StringMap *replace_names = NULL); bool saveSchematicToFile(const std::string &filename, @@ -110,7 +112,7 @@ public: bool serializeToMts(std::ostream *os) const; bool serializeToLua(std::ostream *os, bool use_comments, u32 indent_spaces) const; - void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place); + void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place, ServerMap *map = nullptr); bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place); void placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place); diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 2a63e2684..faccd22e5 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/strfnd.h" #include "client/clientevent.h" #include "client/sound.h" +#include "client/tile.h" #include "network/clientopcodes.h" #include "network/connection.h" #include "script/scripting_client.h" @@ -183,7 +184,7 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) m_access_denied_reason = "Unknown"; if (pkt->getCommand() != TOCLIENT_ACCESS_DENIED) { - // 13/03/15 Legacy code from 0.4.12 and lesser but is still used + // Legacy code from 0.4.12 and older but is still used // in some places of the server code if (pkt->getSize() >= 2) { std::wstring wide_reason; @@ -196,14 +197,14 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) if (pkt->getSize() < 1) return; - u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA; + u8 denyCode; *pkt >> denyCode; + if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN || denyCode == SERVER_ACCESSDENIED_CRASH) { *pkt >> m_access_denied_reason; - if (m_access_denied_reason.empty()) { + if (m_access_denied_reason.empty()) m_access_denied_reason = accessDeniedStrings[denyCode]; - } u8 reconnect; *pkt >> reconnect; m_access_denied_reconnect = reconnect & 1; @@ -220,9 +221,8 @@ void Client::handleCommand_AccessDenied(NetworkPacket* pkt) // Until then (which may be never), this is outside // of the defined protocol. *pkt >> m_access_denied_reason; - if (m_access_denied_reason.empty()) { + if (m_access_denied_reason.empty()) m_access_denied_reason = "Unknown"; - } } } @@ -671,6 +671,8 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) m_media_downloader->addFile(name, sha1_raw); } + bool disable_texture_packs = false; + { std::string str; *pkt >> str; @@ -683,8 +685,13 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) m_media_downloader->addRemoteServer(baseurl); } } + + if (pkt->getRemainingBytes() >= 1) + *pkt >> disable_texture_packs; } + setDisableTexturePacks(disable_texture_packs); + m_media_downloader->step(this); } diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 263496863..7a5ccf8ef 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -734,6 +734,7 @@ void Channel::UpdateTimers(float dtime) current_packet_successful = 0; } +#if !(defined(__ANDROID__) && defined(__aarch64__)) /* dynamic window size */ float successful_to_lost_ratio = 0.0f; bool done = false; @@ -762,6 +763,7 @@ void Channel::UpdateTimers(float dtime) setWindowSize(m_window_size - 50); } } +#endif } if (bpm_counter > 10.0f) { @@ -960,8 +962,10 @@ void Peer::Drop() UDPPeer::UDPPeer(u16 a_id, Address a_address, Connection* connection) : Peer(a_address,a_id,connection) { +#if !(defined(__ANDROID__) && defined(__aarch64__)) for (Channel &channel : channels) channel.setWindowSize(START_RELIABLE_WINDOW_SIZE); +#endif } bool UDPPeer::getAddress(MTProtocols type,Address& toset) diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp index c113d2c56..f7016b2fa 100644 --- a/src/network/connectionthreads.cpp +++ b/src/network/connectionthreads.cpp @@ -750,9 +750,9 @@ void ConnectionSendThread::sendPackets(float dtime) if (!peer) continue; if (peer->m_increment_packets_remaining == 0) { - LOG(warningstream << m_connection->getDesc() - << " Packet quota used up for peer_id=" << peerId - << ", was " << peer_packet_quota << " pkts" << std::endl); + //LOG(warningstream << m_connection->getDesc() + // << " Packet quota used up for peer_id=" << peerId + // << ", was " << peer_packet_quota << " pkts" << std::endl); } } } diff --git a/src/network/connectionthreads.h b/src/network/connectionthreads.h index 95423f503..7a1777c3f 100644 --- a/src/network/connectionthreads.h +++ b/src/network/connectionthreads.h @@ -20,8 +20,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "IrrCompileConfig.h" + #include +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include "threading/sdl_thread.h" +#else #include "threading/thread.h" +#endif #include "connection.h" namespace con diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp index 3a1566f48..78151df94 100644 --- a/src/network/networkpacket.cpp +++ b/src/network/networkpacket.cpp @@ -23,6 +23,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/serialize.h" #include "networkprotocol.h" +NetworkPacket::NetworkPacket(u16 command, u32 datasize, session_t peer_id, + u16 protocol_version): +m_datasize(datasize), m_command(command), m_peer_id(peer_id), + m_protocol_version(protocol_version) +{ + if (m_protocol_version == 0) + m_protocol_version = 37; + m_data.resize(m_datasize); +} + NetworkPacket::NetworkPacket(u16 command, u32 datasize, session_t peer_id): m_datasize(datasize), m_command(command), m_peer_id(peer_id) { @@ -318,7 +328,7 @@ NetworkPacket& NetworkPacket::operator<<(float src) { checkDataSize(4); - writeF32(&m_data[m_read_offset], src); + writeF(&m_data[m_read_offset], src, m_protocol_version); m_read_offset += 4; return *this; @@ -403,7 +413,7 @@ NetworkPacket& NetworkPacket::operator>>(float& dst) { checkReadOffset(m_read_offset, 4); - dst = readF32(&m_data[m_read_offset]); + dst = readF(&m_data[m_read_offset], m_protocol_version); m_read_offset += 4; return *this; @@ -413,7 +423,7 @@ NetworkPacket& NetworkPacket::operator>>(v2f& dst) { checkReadOffset(m_read_offset, 8); - dst = readV2F32(&m_data[m_read_offset]); + dst = readV2F(&m_data[m_read_offset], m_protocol_version); m_read_offset += 8; return *this; @@ -423,7 +433,7 @@ NetworkPacket& NetworkPacket::operator>>(v3f& dst) { checkReadOffset(m_read_offset, 12); - dst = readV3F32(&m_data[m_read_offset]); + dst = readV3F(&m_data[m_read_offset], m_protocol_version); m_read_offset += 12; return *this; diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h index 5bc8c5612..e2fcb321b 100644 --- a/src/network/networkpacket.h +++ b/src/network/networkpacket.h @@ -28,6 +28,7 @@ class NetworkPacket { public: + NetworkPacket(u16 command, u32 datasize, session_t peer_id, u16 protocol_version); NetworkPacket(u16 command, u32 datasize, session_t peer_id); NetworkPacket(u16 command, u32 datasize); NetworkPacket() = default; @@ -43,6 +44,7 @@ public: u16 getCommand() { return m_command; } u32 getRemainingBytes() const { return m_datasize - m_read_offset; } const char *getRemainingString() { return getString(m_read_offset); } + u16 getProtocolVersion() const { return m_protocol_version; } // Returns a c-string without copying. // A better name for this would be getRawString() @@ -118,6 +120,11 @@ public: // ^ this comment has been here for 4 years Buffer oldForgePacket(); + inline void setProtocolVersion(const u16 protocol_version) + { + m_protocol_version = protocol_version; + } + private: void checkReadOffset(u32 from_offset, u32 field_size); @@ -134,4 +141,5 @@ private: u32 m_read_offset = 0; u16 m_command = 0; session_t m_peer_id = 0; + u16 m_protocol_version = 37; }; diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index adc9bee08..6e93e5410 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -213,7 +213,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION) // Server's supported network protocol range -#define SERVER_PROTOCOL_VERSION_MIN 37 +#define SERVER_PROTOCOL_VERSION_MIN 25 +#define SERVER_PROTOCOL_VERSION_MIN_NOCOMPAT 37 #define SERVER_PROTOCOL_VERSION_MAX LATEST_PROTOCOL_VERSION // Client's supported network protocol range @@ -228,7 +229,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define PASSWORD_SIZE 28 // Maximum password length. Allows for // base64-encoded SHA-1 (27+\0). -// See also: Formspec Version History in doc/lua_api.txt +// See also formspec [Version History] in doc/lua_api.txt #define FORMSPEC_API_VERSION 5 #define TEXTURENAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-" @@ -1001,7 +1002,7 @@ enum AuthMechanism AUTH_MECHANISM_FIRST_SRP = 1 << 2, }; -enum AccessDeniedCode { +enum AccessDeniedCode : u8 { SERVER_ACCESSDENIED_WRONG_PASSWORD, SERVER_ACCESSDENIED_UNEXPECTED_DATA, SERVER_ACCESSDENIED_SINGLEPLAYER, @@ -1024,18 +1025,18 @@ enum NetProtoCompressionMode { const static std::string accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = { "Invalid password", - "Your client sent something the server didn't expect. Try reconnecting or updating your client", + "Your client sent something the server didn't expect. Try reconnecting or updating your client.", "The server is running in simple singleplayer mode. You cannot connect.", - "Your client's version is not supported.\nPlease contact server administrator.", - "Player name contains disallowed characters.", - "Player name not allowed.", - "Too many users.", + "Your client's version is not supported.\nPlease contact the server administrator.", + "Player name contains disallowed characters", + "Player name not allowed", + "Too many users", "Empty passwords are disallowed. Set a password and try again.", "Another client is connected with this name. If your client closed unexpectedly, try again in a minute.", - "Server authentication failed. This is likely a server error.", + "Internal server error", "", - "Server shutting down.", - "This server has experienced an internal error. You will now be disconnected." + "Server shutting down", + "The server has experienced an internal error. You will now be disconnected." }; enum PlayerListModifer : u8 diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 2e0044f4f..8309d0ca5 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -176,7 +176,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_ACTIVE_OBJECT_MESSAGES", 0, true }, // 0x32 (may be sent as unrel over channel 1 too) { "TOCLIENT_HP", 0, true }, // 0x33 { "TOCLIENT_MOVE_PLAYER", 0, true }, // 0x34 - { "TOCLIENT_ACCESS_DENIED_LEGACY", 0, true }, // 0x35 + null_command_factory, // 0x35 { "TOCLIENT_FOV", 0, true }, // 0x36 { "TOCLIENT_DEATHSCREEN", 0, true }, // 0x37 { "TOCLIENT_MEDIA", 2, true }, // 0x38 diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 756a685e9..3c1f12f3a 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -143,9 +143,10 @@ void Server::handleCommand_Init(NetworkPacket* pkt) client->net_proto_version = net_proto_version; + const u16 server_proto_ver_min = g_settings->getBool("enable_protocol_compat") ? SERVER_PROTOCOL_VERSION_MIN : SERVER_PROTOCOL_VERSION_MIN_NOCOMPAT; if ((g_settings->getBool("strict_protocol_version_checking") && net_proto_version != LATEST_PROTOCOL_VERSION) || - net_proto_version < SERVER_PROTOCOL_VERSION_MIN || + net_proto_version < server_proto_ver_min || net_proto_version > SERVER_PROTOCOL_VERSION_MAX) { actionstream << "Server: A mismatched client tried to connect from " << addr_s << " proto_max=" << (int)max_net_proto_version << std::endl; @@ -189,8 +190,8 @@ void Server::handleCommand_Init(NetworkPacket* pkt) std::string legacyPlayerNameCasing = playerName; - if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) { - actionstream << "Server: Player with the name \"singleplayer\" tried " + if (!isSingleplayer() && strcasecmp(playername, "Player") == 0) { + actionstream << "Server: Player with the name \"Player\" tried " "to connect from " << addr_s << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_NAME); return; @@ -227,7 +228,7 @@ void Server::handleCommand_Init(NetworkPacket* pkt) Compose auth methods for answer */ std::string encpwd; // encrypted Password field for the user - bool has_auth = m_script->getAuth(playername, &encpwd, NULL); + bool has_auth = m_script->getAuth(playername, &encpwd, nullptr); u32 auth_mechs = 0; client->chosen_mech = AUTH_MECHANISM_NONE; @@ -335,7 +336,7 @@ void Server::handleCommand_Init2(NetworkPacket* pkt) sendDetachedInventories(peer_id, false); // Send player movement settings - SendMovement(peer_id); + SendMovement(peer_id, protocol_version); // Send time of day u16 time = m_env->getTimeOfDay(); @@ -466,7 +467,7 @@ void Server::handleCommand_GotBlocks(NetworkPacket* pkt) void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, NetworkPacket *pkt) { - if (pkt->getRemainingBytes() < 12 + 12 + 4 + 4 + 4 + 1 + 1) + if (pkt->getRemainingBytes() < 12 + 12 + 4 + 4) return; v3s32 ps, ss; @@ -485,10 +486,14 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, f32 fov = 0; u8 wanted_range = 0; - *pkt >> keyPressed; - *pkt >> f32fov; - fov = (f32)f32fov / 80.0f; - *pkt >> wanted_range; + if (pkt->getRemainingBytes() >= 4) + *pkt >> keyPressed; + if (pkt->getRemainingBytes() >= 1) { + *pkt >> f32fov; + fov = (f32)f32fov / 80.0f; + if (pkt->getRemainingBytes() >= 1) + *pkt >> wanted_range; + } v3f position((f32)ps.X / 100.0f, (f32)ps.Y / 100.0f, (f32)ps.Z / 100.0f); v3f speed((f32)ss.X / 100.0f, (f32)ss.Y / 100.0f, (f32)ss.Z / 100.0f); @@ -782,9 +787,6 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt) void Server::handleCommand_Damage(NetworkPacket* pkt) { - u16 damage; - - *pkt >> damage; session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); @@ -797,6 +799,17 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) return; } + u16 damage; + + // Minetest 0.4 uses 8-bit integers for damage. + if (player->protocol_version >= 37) { + *pkt >> damage; + } else { + u8 raw_damage; + *pkt >> raw_damage; + damage = raw_damage; + } + PlayerSAO *playersao = player->getPlayerSAO(); if (playersao == NULL) { errorstream << @@ -852,15 +865,6 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt) *pkt >> item; - if (item >= player->getHotbarItemcount()) { - actionstream << "Player: " << player->getName() - << " tried to access item=" << item - << " out of hotbar_itemcount=" - << player->getHotbarItemcount() - << "; ignoring." << std::endl; - return; - } - playersao->getPlayer()->setWieldIndex(item); } @@ -983,16 +987,6 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) v3f player_pos = playersao->getLastGoodPosition(); // Update wielded item - - if (item_i >= player->getHotbarItemcount()) { - actionstream << "Player: " << player->getName() - << " tried to access item=" << item_i - << " out of hotbar_itemcount=" - << player->getHotbarItemcount() - << "; ignoring." << std::endl; - return; - } - playersao->getPlayer()->setWieldIndex(item_i); // Get pointed to object (NULL if not POINTEDTYPE_OBJECT) @@ -1348,21 +1342,26 @@ void Server::handleCommand_RemovedSounds(NetworkPacket* pkt) } } -void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) +static bool pkt_read_formspec_fields(NetworkPacket *pkt, StringMap &fields) { - v3s16 p; - std::string formname; - u16 num; + u16 field_count; + *pkt >> field_count; - *pkt >> p >> formname >> num; - - StringMap fields; - for (u16 k = 0; k < num; k++) { + u64 length = 0; + for (u16 k = 0; k < field_count; k++) { std::string fieldname; *pkt >> fieldname; fields[fieldname] = pkt->readLongString(); - } + length += fieldname.size(); + length += fields[fieldname].size(); + } + // 640K ought to be enough for anyone + return length < 640 * 1024; +} + +void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) +{ session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); @@ -1383,15 +1382,30 @@ void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) return; } + v3s16 p; + std::string formname; + StringMap fields; + + *pkt >> p >> formname; + + if (!pkt_read_formspec_fields(pkt, fields)) { + warningstream << "Too large formspec fields! Ignoring for pos=" + << PP(p) << ", player=" << player->getName() << std::endl; + return; + } + +#if USE_SQLITE // If something goes wrong, this player is to blame RollbackScopeActor rollback_scope(m_rollback, std::string("player:")+player->getName()); // Check the target node for rollback data; leave others unnoticed RollbackNode rn_old(&m_env->getMap(), p, this); +#endif m_script->node_on_receive_fields(p, formname, fields, playersao); +#if USE_SQLITE // Report rollback data RollbackNode rn_new(&m_env->getMap(), p, this); if (rollback() && rn_new != rn_old) { @@ -1399,22 +1413,11 @@ void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) action.setSetNode(p, rn_old, rn_new); rollback()->reportAction(action); } +#endif } void Server::handleCommand_InventoryFields(NetworkPacket* pkt) { - std::string client_formspec_name; - u16 num; - - *pkt >> client_formspec_name >> num; - - StringMap fields; - for (u16 k = 0; k < num; k++) { - std::string fieldname; - *pkt >> fieldname; - fields[fieldname] = pkt->readLongString(); - } - session_t peer_id = pkt->getPeerId(); RemotePlayer *player = m_env->getPlayer(peer_id); @@ -1435,6 +1438,17 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt) return; } + std::string client_formspec_name; + StringMap fields; + + *pkt >> client_formspec_name; + + if (!pkt_read_formspec_fields(pkt, fields)) { + warningstream << "Too large formspec fields! Ignoring for formname=\"" + << client_formspec_name << "\", player=" << player->getName() << std::endl; + return; + } + if (client_formspec_name.empty()) { // pass through inventory submits m_script->on_playerReceiveFields(playersao, client_formspec_name, fields); return; @@ -1470,11 +1484,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); + const std::string playername = client->getName(); - std::string playername = client->getName(); - - std::string salt; - std::string verification_key; + std::string salt, verification_key; std::string addr_s = getPeerAddress(peer_id).serializeString(); u8 is_empty; @@ -1484,6 +1496,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) verbosestream << "Server: Got TOSERVER_FIRST_SRP from " << addr_s << ", with is_empty=" << (is_empty == 1) << std::endl; + const bool empty_disallowed = !isSingleplayer() && is_empty == 1 && + g_settings->getBool("disallow_empty_password"); + // Either this packet is sent because the user is new or to change the password if (cstate == CS_HelloSent) { if (!client->isMechAllowed(AUTH_MECHANISM_FIRST_SRP)) { @@ -1494,9 +1509,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) return; } - if (!isSingleplayer() && - g_settings->getBool("disallow_empty_password") && - is_empty == 1) { + if (empty_disallowed) { actionstream << "Server: " << playername << " supplied empty password from " << addr_s << std::endl; DenyAccess(peer_id, SERVER_ACCESSDENIED_EMPTY_PASSWORD); @@ -1504,8 +1517,19 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) } std::string initial_ver_key; - initial_ver_key = encode_srp_verifier(verification_key, salt); + + // It is possible for multiple connections to get this far with the same + // player name. In the end only one player with a given name will be emerged + // (see Server::StateTwoClientInit) but we still have to be careful here. + if (m_script->getAuth(playername, nullptr, nullptr)) { + // Another client beat us to it + actionstream << "Server: Client from " << addr_s + << " tried to register " << playername << " a second time." + << std::endl; + DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED); + return; + } m_script->createAuth(playername, initial_ver_key); m_script->on_authplayer(playername, addr_s, true); @@ -1518,6 +1542,15 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt) return; } m_clients.event(peer_id, CSE_SudoLeave); + + if (empty_disallowed) { + actionstream << "Server: " << playername + << " supplied empty password" << std::endl; + SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM, + L"Changing to an empty password is not allowed.")); + return; + } + std::string pw_db_field = encode_srp_verifier(verification_key, salt); bool success = m_script->setPassword(playername, pw_db_field); if (success) { @@ -1539,8 +1572,6 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); - bool wantSudo = (cstate == CS_Active); - if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { actionstream << "Server: got SRP _A packet in wrong state " << cstate << " from " << getPeerAddress(peer_id).serializeString() << @@ -1548,6 +1579,8 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) return; } + const bool wantSudo = (cstate == CS_Active); + if (client->chosen_mech != AUTH_MECHANISM_NONE) { actionstream << "Server: got SRP _A packet, while auth is already " "going on with mech " << client->chosen_mech << " from " << @@ -1594,8 +1627,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) client->chosen_mech = chosen; - std::string salt; - std::string verifier; + std::string salt, verifier; if (based_on == 0) { @@ -1627,6 +1659,7 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) << std::endl; if (wantSudo) { DenySudoAccess(peer_id); + client->resetChosenMech(); return; } @@ -1644,10 +1677,10 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) session_t peer_id = pkt->getPeerId(); RemoteClient *client = getClient(peer_id, CS_Invalid); ClientState cstate = client->getState(); - std::string addr_s = getPeerAddress(pkt->getPeerId()).serializeString(); - std::string playername = client->getName(); + const std::string addr_s = client->getAddress().serializeString(); + const std::string playername = client->getName(); - bool wantSudo = (cstate == CS_Active); + const bool wantSudo = (cstate == CS_Active); verbosestream << "Server: Received TOSERVER_SRP_BYTES_M." << std::endl; @@ -1693,6 +1726,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) << " tried to change their password, but supplied wrong" << " (SRP) password for authentication." << std::endl; DenySudoAccess(peer_id); + client->resetChosenMech(); return; } @@ -1706,8 +1740,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) if (client->create_player_on_auth_success) { m_script->createAuth(playername, client->enc_pwd); - std::string checkpwd; // not used, but needed for passing something - if (!m_script->getAuth(playername, &checkpwd, NULL)) { + if (!m_script->getAuth(playername, nullptr, nullptr)) { errorstream << "Server: " << playername << " cannot be authenticated (auth handler does not work?)" << std::endl; diff --git a/src/network/socket.cpp b/src/network/socket.cpp index c7d0f995b..bff040349 100644 --- a/src/network/socket.cpp +++ b/src/network/socket.cpp @@ -125,6 +125,11 @@ bool UDPSocket::init(bool ipv6, bool noExceptions) reinterpret_cast(&value), sizeof(value)); } +#ifdef __IOS__ + int value = 1; + setsockopt(m_handle, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(value)); +#endif + return true; } diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 8f16df1a9..abd62dc6d 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -74,7 +74,13 @@ void NodeBox::reset() void NodeBox::serialize(std::ostream &os, u16 protocol_version) const { // Protocol >= 36 - const u8 version = 6; + u8 version; + if (protocol_version >= 36) + version = 6; + else if (protocol_version >= 27) + version = 3; + else + version = 2; writeU8(os, version); switch (type) { @@ -84,28 +90,38 @@ void NodeBox::serialize(std::ostream &os, u16 protocol_version) const writeU16(os, fixed.size()); for (const aabb3f &nodebox : fixed) { - writeV3F32(os, nodebox.MinEdge); - writeV3F32(os, nodebox.MaxEdge); + writeV3F(os, nodebox.MinEdge, protocol_version); + writeV3F(os, nodebox.MaxEdge, protocol_version); } break; case NODEBOX_WALLMOUNTED: writeU8(os, type); - writeV3F32(os, wall_top.MinEdge); - writeV3F32(os, wall_top.MaxEdge); - writeV3F32(os, wall_bottom.MinEdge); - writeV3F32(os, wall_bottom.MaxEdge); - writeV3F32(os, wall_side.MinEdge); - writeV3F32(os, wall_side.MaxEdge); + writeV3F(os, wall_top.MinEdge, protocol_version); + writeV3F(os, wall_top.MaxEdge, protocol_version); + writeV3F(os, wall_bottom.MinEdge, protocol_version); + writeV3F(os, wall_bottom.MaxEdge, protocol_version); + writeV3F(os, wall_side.MinEdge, protocol_version); + writeV3F(os, wall_side.MaxEdge, protocol_version); break; case NODEBOX_CONNECTED: + if (version <= 2) { + // send old clients nodes that can't be walked through + // to prevent abuse + writeU8(os, NODEBOX_FIXED); + + writeU16(os, 1); + writeV3F1000(os, v3f(-BS/2, -BS/2, -BS/2)); + writeV3F1000(os, v3f(BS/2, BS/2, BS/2)); + break; + } writeU8(os, type); #define WRITEBOX(box) \ writeU16(os, (box).size()); \ for (const aabb3f &i: (box)) { \ - writeV3F32(os, i.MinEdge); \ - writeV3F32(os, i.MaxEdge); \ + writeV3F(os, i.MinEdge, protocol_version); \ + writeV3F(os, i.MaxEdge, protocol_version); \ }; WRITEBOX(fixed); @@ -115,14 +131,17 @@ void NodeBox::serialize(std::ostream &os, u16 protocol_version) const WRITEBOX(connect_left); WRITEBOX(connect_back); WRITEBOX(connect_right); - WRITEBOX(disconnected_top); - WRITEBOX(disconnected_bottom); - WRITEBOX(disconnected_front); - WRITEBOX(disconnected_left); - WRITEBOX(disconnected_back); - WRITEBOX(disconnected_right); - WRITEBOX(disconnected); - WRITEBOX(disconnected_sides); + + if (version > 5) { + WRITEBOX(disconnected_top); + WRITEBOX(disconnected_bottom); + WRITEBOX(disconnected_front); + WRITEBOX(disconnected_left); + WRITEBOX(disconnected_back); + WRITEBOX(disconnected_right); + WRITEBOX(disconnected); + WRITEBOX(disconnected_sides); + } break; default: writeU8(os, type); @@ -203,8 +222,18 @@ void NodeBox::deSerialize(std::istream &is) void TileDef::serialize(std::ostream &os, u16 protocol_version) const { - // protocol_version >= 36 - u8 version = 6; + u8 version; + if (protocol_version >= 37) + version = 6; + else if (protocol_version >= 30) + version = 4; + else if (protocol_version >= 29) + version = 3; + else if (protocol_version >= 26) + version = 2; + else + version = 1; + writeU8(os, version); if (protocol_version > 39) { @@ -219,6 +248,25 @@ void TileDef::serialize(std::ostream &os, u16 protocol_version) const os << serializeString16(name); } animation.serialize(os, version); + + // Compatibility with Minetest 0.4. + if (version < 6) { + writeU8(os, backface_culling); + if (version < 2) + return; + writeU8(os, tileable_horizontal); + writeU8(os, tileable_vertical); + if (version < 3) + return; + writeU8(os, has_color); + if (has_color) { + writeU8(os, color.getRed()); + writeU8(os, color.getGreen()); + writeU8(os, color.getBlue()); + } + return; + } + bool has_scale = scale > 0; u16 flags = 0; if (backface_culling) @@ -444,7 +492,14 @@ u8 ContentFeatures::getAlphaForLegacy() const void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const { - const u8 version = CONTENTFEATURES_VERSION; + if (protocol_version < 31) { + serializeOld(os, protocol_version); + return; + } + + u8 version = CONTENTFEATURES_VERSION; + if (protocol_version < 37) + version = 10; writeU8(os, version); // general @@ -460,7 +515,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const // visual writeU8(os, drawtype); os << serializeString16(mesh); - writeF32(os, visual_scale); + writeF(os, visual_scale, protocol_version); writeU8(os, 6); for (const TileDef &td : tiledef) td.serialize(os, protocol_version); @@ -683,8 +738,6 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, if (!tile.world_aligned) layer->scale = 1; - layer->flags_texture = tsrc->getShaderFlagsTexture(layer->normal_texture ? true : false); - // Material flags layer->material_flags = 0; if (backface_culling) @@ -732,9 +785,6 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer, layer->texture->getOriginalSize(), i); frame.texture = tsrc->getTextureForMesh(os.str(), &frame.texture_id); - if (layer->normal_texture) - frame.normal_texture = tsrc->getNormalTexture(os.str()); - frame.flags_texture = layer->flags_texture; (*layer->frames)[i] = frame; } } @@ -1026,6 +1076,105 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc } #endif +//// Serialization of old ContentFeatures formats +void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const +{ + u8 compatible_param_type_2 = param_type_2; + if ((protocol_version < 28) + && (compatible_param_type_2 == CPT2_MESHOPTIONS)) + compatible_param_type_2 = CPT2_NONE; + else if (protocol_version < 30) { + if (compatible_param_type_2 == CPT2_COLOR) + compatible_param_type_2 = CPT2_NONE; + else if (compatible_param_type_2 == CPT2_COLORED_FACEDIR) + compatible_param_type_2 = CPT2_FACEDIR; + else if (compatible_param_type_2 == CPT2_COLORED_WALLMOUNTED) + compatible_param_type_2 = CPT2_WALLMOUNTED; + } + + float compatible_visual_scale = visual_scale; + if (protocol_version < 30 && drawtype == NDT_PLANTLIKE) + compatible_visual_scale = sqrt(visual_scale); + + TileDef compatible_tiles[6]; + for (u8 i = 0; i < 6; i++) { + compatible_tiles[i] = tiledef[i]; + if (tiledef_overlay[i].name != "") { + std::stringstream s; + s << "(" << tiledef[i].name << ")^(" << tiledef_overlay[i].name + << ")"; + compatible_tiles[i].name = s.str(); + } + } + + // Protocol >= 24 + if (protocol_version < 31) { + const u8 version = protocol_version < 27 ? 7 : 8; + writeU8(os, version); + + os << serializeString16(name); + writeU16(os, groups.size()); + for (ItemGroupList::const_iterator i = groups.begin(); + i != groups.end(); ++i) { + os << serializeString16(i->first); + writeS16(os, i->second); + } + writeU8(os, drawtype); + writeF1000(os, compatible_visual_scale); + writeU8(os, 6); + for (u32 i = 0; i < 6; i++) + compatible_tiles[i].serialize(os, protocol_version); + writeU8(os, CF_SPECIAL_COUNT); + for (u32 i = 0; i < CF_SPECIAL_COUNT; i++) + tiledef_special[i].serialize(os, protocol_version); + writeU8(os, alpha); + writeU8(os, post_effect_color.getAlpha()); + writeU8(os, post_effect_color.getRed()); + writeU8(os, post_effect_color.getGreen()); + writeU8(os, post_effect_color.getBlue()); + writeU8(os, param_type); + writeU8(os, compatible_param_type_2); + writeU8(os, is_ground_content); + writeU8(os, light_propagates); + writeU8(os, sunlight_propagates); + writeU8(os, walkable); + writeU8(os, pointable); + writeU8(os, diggable); + writeU8(os, climbable); + writeU8(os, buildable_to); + os << serializeString16(""); // legacy: used to be metadata_name + writeU8(os, liquid_type); + os << serializeString16(liquid_alternative_flowing); + os << serializeString16(liquid_alternative_source); + writeU8(os, liquid_viscosity); + writeU8(os, liquid_renewable); + writeU8(os, light_source); + writeU32(os, damage_per_second); + node_box.serialize(os, protocol_version); + selection_box.serialize(os, protocol_version); + writeU8(os, legacy_facedir_simple); + writeU8(os, legacy_wallmounted); + sound_footstep.serialize(os, version); + sound_dig.serialize(os, version); + sound_dug.serialize(os, version); + writeU8(os, rightclickable); + writeU8(os, drowning); + writeU8(os, leveled); + writeU8(os, liquid_range); + writeU8(os, waving); + os << serializeString16(mesh); + collision_box.serialize(os, protocol_version); + writeU8(os, floodable); + writeU16(os, connects_to_ids.size()); + for (u16 connects_to_id : connects_to_ids) + writeU16(os, connects_to_id); + writeU8(os, connect_sides); + } else { + throw SerializationError("ContentFeatures::serialize(): " + "Unsupported version requested"); + } +} + /* NodeDefManager */ diff --git a/src/nodedef.h b/src/nodedef.h index 25d495c21..8c64c0959 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -432,6 +432,7 @@ struct ContentFeatures void reset(); void serialize(std::ostream &os, u16 protocol_version) const; void deSerialize(std::istream &is); + void serializeOld(std::ostream &os, u16 protocol_version) const; /* Some handy methods @@ -478,6 +479,12 @@ struct ContentFeatures return (liquid_alternative_flowing_id == f.liquid_alternative_flowing_id); } + bool lightingEquivalent(const ContentFeatures &other) const { + return light_propagates == other.light_propagates + && sunlight_propagates == other.sunlight_propagates + && light_source == other.light_source; + } + int getGroup(const std::string &group) const { return itemgroup_get(groups, group); diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index 210dc80d3..9f707eb49 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -40,7 +40,8 @@ NodeMetadata::~NodeMetadata() delete m_inventory; } -void NodeMetadata::serialize(std::ostream &os, u8 version, bool disk) const +void NodeMetadata::serialize(std::ostream &os, u8 version, bool disk, + std::string formspec_prepend) const { int num_vars = disk ? m_stringvars.size() : countNonPrivate(); writeU32(os, num_vars); @@ -50,7 +51,10 @@ void NodeMetadata::serialize(std::ostream &os, u8 version, bool disk) const continue; os << serializeString16(sv.first); - os << serializeString32(sv.second); + if (!formspec_prepend.empty() && sv.first == "formspec") + os << serializeString32(insert_formspec_prepend(sv.second, formspec_prepend)); + else + os << serializeString32(sv.second); if (version >= 2) writeU8(os, (priv) ? 1 : 0); } @@ -113,7 +117,7 @@ int NodeMetadata::countNonPrivate() const */ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, - bool absolute_pos, bool include_empty) const + bool absolute_pos, bool include_empty, std::string formspec_prepend) const { /* Version 0 is a placeholder for "nothing to see here; go away." @@ -146,7 +150,7 @@ void NodeMetadataList::serialize(std::ostream &os, u8 blockver, bool disk, u16 p16 = (p.Z * MAP_BLOCKSIZE + p.Y) * MAP_BLOCKSIZE + p.X; writeU16(os, p16); } - data->serialize(os, version, disk); + data->serialize(os, version, disk, formspec_prepend); } } diff --git a/src/nodemetadata.h b/src/nodemetadata.h index 364e51aad..89f3e5fcf 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -40,7 +40,8 @@ public: NodeMetadata(IItemDefManager *item_def_mgr); ~NodeMetadata(); - void serialize(std::ostream &os, u8 version, bool disk=true) const; + void serialize(std::ostream &os, u8 version, bool disk=true, + std::string formspec_prepend="") const; void deSerialize(std::istream &is, u8 version); void clear(); @@ -82,7 +83,8 @@ public: ~NodeMetadataList(); void serialize(std::ostream &os, u8 blockver, bool disk = true, - bool absolute_pos = false, bool include_empty = false) const; + bool absolute_pos = false, bool include_empty = false, + std::string formspec_prepend = "") const; void deSerialize(std::istream &is, IItemDefManager *item_def_mgr, bool absolute_pos = false); diff --git a/src/noise.cpp b/src/noise.cpp index 2f4de6855..25de5bf40 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -37,15 +37,6 @@ #define NOISE_MAGIC_Z 52591 #define NOISE_MAGIC_SEED 1013 -typedef float (*Interp2dFxn)( - float v00, float v10, float v01, float v11, - float x, float y); - -typedef float (*Interp3dFxn)( - float v000, float v100, float v010, float v110, - float v001, float v101, float v011, float v111, - float x, float y, float z); - FlagDesc flagdesc_noiseparams[] = { {"defaults", NOISE_FLAG_DEFAULTS}, {"eased", NOISE_FLAG_EASED}, @@ -197,47 +188,34 @@ inline float linearInterpolation(float v0, float v1, float t) inline float biLinearInterpolation( float v00, float v10, float v01, float v11, - float x, float y) -{ - float tx = easeCurve(x); - float ty = easeCurve(y); - float u = linearInterpolation(v00, v10, tx); - float v = linearInterpolation(v01, v11, tx); - return linearInterpolation(u, v, ty); -} - - -inline float biLinearInterpolationNoEase( - float v00, float v10, - float v01, float v11, - float x, float y) + float x, float y, + bool eased) { + // Inlining will optimize this branch out when possible + if (eased) { + x = easeCurve(x); + y = easeCurve(y); + } float u = linearInterpolation(v00, v10, x); float v = linearInterpolation(v01, v11, x); return linearInterpolation(u, v, y); } -float triLinearInterpolation( +inline float triLinearInterpolation( float v000, float v100, float v010, float v110, float v001, float v101, float v011, float v111, - float x, float y, float z) + float x, float y, float z, + bool eased) { - float tx = easeCurve(x); - float ty = easeCurve(y); - float tz = easeCurve(z); - float u = biLinearInterpolationNoEase(v000, v100, v010, v110, tx, ty); - float v = biLinearInterpolationNoEase(v001, v101, v011, v111, tx, ty); - return linearInterpolation(u, v, tz); -} - -float triLinearInterpolationNoEase( - float v000, float v100, float v010, float v110, - float v001, float v101, float v011, float v111, - float x, float y, float z) -{ - float u = biLinearInterpolationNoEase(v000, v100, v010, v110, x, y); - float v = biLinearInterpolationNoEase(v001, v101, v011, v111, x, y); + // Inlining will optimize this branch out when possible + if (eased) { + x = easeCurve(x); + y = easeCurve(y); + z = easeCurve(z); + } + float u = biLinearInterpolation(v000, v100, v010, v110, x, y, false); + float v = biLinearInterpolation(v001, v101, v011, v111, x, y, false); return linearInterpolation(u, v, z); } @@ -255,10 +233,7 @@ float noise2d_gradient(float x, float y, s32 seed, bool eased) float v01 = noise2d(x0, y0+1, seed); float v11 = noise2d(x0+1, y0+1, seed); // Interpolate - if (eased) - return biLinearInterpolation(v00, v10, v01, v11, xl, yl); - - return biLinearInterpolationNoEase(v00, v10, v01, v11, xl, yl); + return biLinearInterpolation(v00, v10, v01, v11, xl, yl, eased); } @@ -282,17 +257,11 @@ float noise3d_gradient(float x, float y, float z, s32 seed, bool eased) float v011 = noise3d(x0, y0 + 1, z0 + 1, seed); float v111 = noise3d(x0 + 1, y0 + 1, z0 + 1, seed); // Interpolate - if (eased) { - return triLinearInterpolation( - v000, v100, v010, v110, - v001, v101, v011, v111, - xl, yl, zl); - } - - return triLinearInterpolationNoEase( + return triLinearInterpolation( v000, v100, v010, v110, v001, v101, v011, v111, - xl, yl, zl); + xl, yl, zl, + eased); } @@ -517,9 +486,6 @@ void Noise::gradientMap2D( s32 x0, y0; bool eased = np.flags & (NOISE_FLAG_DEFAULTS | NOISE_FLAG_EASED); - Interp2dFxn interpolate = eased ? - biLinearInterpolation : biLinearInterpolationNoEase; - x0 = std::floor(x); y0 = std::floor(y); u = x - (float)x0; @@ -546,7 +512,8 @@ void Noise::gradientMap2D( u = orig_u; noisex = 0; for (i = 0; i != sx; i++) { - gradient_buf[index++] = interpolate(v00, v10, v01, v11, u, v); + gradient_buf[index++] = + biLinearInterpolation(v00, v10, v01, v11, u, v, eased); u += step_x; if (u >= 1.0) { @@ -582,8 +549,7 @@ void Noise::gradientMap3D( u32 nlx, nly, nlz; s32 x0, y0, z0; - Interp3dFxn interpolate = (np.flags & NOISE_FLAG_EASED) ? - triLinearInterpolation : triLinearInterpolationNoEase; + bool eased = np.flags & NOISE_FLAG_EASED; x0 = std::floor(x); y0 = std::floor(y); @@ -624,10 +590,11 @@ void Noise::gradientMap3D( u = orig_u; noisex = 0; for (i = 0; i != sx; i++) { - gradient_buf[index++] = interpolate( + gradient_buf[index++] = triLinearInterpolation( v000, v100, v010, v110, v001, v101, v011, v111, - u, v, w); + u, v, w, + eased); u += step_x; if (u >= 1.0) { diff --git a/src/object_properties.cpp b/src/object_properties.cpp index 562c7baf1..52bc32053 100644 --- a/src/object_properties.cpp +++ b/src/object_properties.cpp @@ -116,44 +116,90 @@ bool ObjectProperties::validate() return ret; } -void ObjectProperties::serialize(std::ostream &os) const +void ObjectProperties::serialize(std::ostream &os, u16 protocol_version) const { - writeU8(os, 4); // PROTOCOL_VERSION >= 37 + if (protocol_version > 36) + writeU8(os, 4); // PROTOCOL_VERSION >= 37 + else + writeU8(os, 1); writeU16(os, hp_max); writeU8(os, physical); - writeF32(os, 0.f); // Removed property (weight) - writeV3F32(os, collisionbox.MinEdge); - writeV3F32(os, collisionbox.MaxEdge); - writeV3F32(os, selectionbox.MinEdge); - writeV3F32(os, selectionbox.MaxEdge); - writeU8(os, pointable); - os << serializeString16(visual); - writeV3F32(os, visual_size); - writeU16(os, textures.size()); - for (const std::string &texture : textures) { - os << serializeString16(texture); + writeF(os, 0.f, protocol_version); // Removed property (weight) + if (protocol_version > 36) { + writeV3F32(os, collisionbox.MinEdge); + writeV3F32(os, collisionbox.MaxEdge); + writeV3F32(os, selectionbox.MinEdge); + writeV3F32(os, selectionbox.MaxEdge); + writeU8(os, pointable); + } else if (pointable) { + writeV3F1000(os, selectionbox.MinEdge); + writeV3F1000(os, selectionbox.MaxEdge); + } else { + // A hack to emulate unpointable objects + for (u8 i = 0; i < 6; i++) + writeF1000(os, 0); } + + // The "wielditem" type isn't exactly the same as "item", however this + // is the most similar compatible option + if (visual == "item" && protocol_version < 37) + os << serializeString16("wielditem"); + else + os << serializeString16(visual); + + if (protocol_version > 36) { + writeV3F32(os, visual_size); + } else { + writeF1000(os, visual_size.X); + writeF1000(os, visual_size.Y); + } + + // MT 0.4.15 and below don't have the wield_item property and expect + // wield_item to be in textures[0]. + if (protocol_version < 37 && (visual == "item" || visual == "wielditem") && + !wield_item.empty()) { + writeU16(os, 1); + // MT 0.4.15 and below only expect the item name, if anything else + // (such as an item count or metadata) is sent then older clients will + // show the object as an unknown item. + const size_t pos = wield_item.find(' '); + if (pos == std::string::npos) + os << serializeString16(wield_item); + else + os << serializeString16(wield_item.substr(0, pos)); + } else { + writeU16(os, textures.size()); + for (const std::string &texture : textures) { + os << serializeString16(texture); + } + } + writeV2S16(os, spritediv); writeV2S16(os, initial_sprite_basepos); writeU8(os, is_visible); writeU8(os, makes_footstep_sound); - writeF32(os, automatic_rotate); + writeF(os, automatic_rotate, protocol_version); os << serializeString16(mesh); writeU16(os, colors.size()); for (video::SColor color : colors) { writeARGB8(os, color); } writeU8(os, collideWithObjects); - writeF32(os, stepheight); + writeF(os, stepheight, protocol_version); writeU8(os, automatic_face_movement_dir); - writeF32(os, automatic_face_movement_dir_offset); + writeF(os, automatic_face_movement_dir_offset, protocol_version); writeU8(os, backface_culling); os << serializeString16(nametag); writeARGB8(os, nametag_color); - writeF32(os, automatic_face_movement_max_rotation_per_sec); + writeF(os, automatic_face_movement_max_rotation_per_sec, protocol_version); os << serializeString16(infotext); os << serializeString16(wield_item); writeS8(os, glow); + + // Everything after this can use writeF32(). + if (protocol_version < 37) + return; + writeU16(os, breath_max); writeF32(os, eye_height); writeF32(os, zoom_fov); diff --git a/src/object_properties.h b/src/object_properties.h index b9a175510..6045cf8ec 100644 --- a/src/object_properties.h +++ b/src/object_properties.h @@ -40,7 +40,7 @@ struct ObjectProperties std::string mesh = ""; v3f visual_size = v3f(1, 1, 1); std::vector textures; - std::string damage_texture_modifier = "^[brighten"; + std::string damage_texture_modifier = "^[colorize:#FF000085"; std::vector colors; v2s16 spritediv = v2s16(1, 1); v2s16 initial_sprite_basepos; @@ -70,6 +70,6 @@ struct ObjectProperties std::string dump(); // check limits of some important properties (strings) that'd cause exceptions later on bool validate(); - void serialize(std::ostream &os) const; + void serialize(std::ostream &os, u16 protocol_version) const; void deSerialize(std::istream &is); }; diff --git a/src/porting.cpp b/src/porting.cpp index 22b584707..a80764478 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -530,26 +530,7 @@ bool setSystemPaths() #endif -void migrateCachePath() -{ - const std::string local_cache_path = path_user + DIR_DELIM + "cache"; - - // Delete tmp folder if it exists (it only ever contained - // a temporary ogg file, which is no longer used). - if (fs::PathExists(local_cache_path + DIR_DELIM + "tmp")) - fs::RecursiveDelete(local_cache_path + DIR_DELIM + "tmp"); - - // Bail if migration impossible - if (path_cache == local_cache_path || !fs::PathExists(local_cache_path) - || fs::PathExists(path_cache)) { - return; - } - if (!fs::Rename(local_cache_path, path_cache)) { - errorstream << "Failed to migrate local cache path " - "to system path!" << std::endl; - } -} - +#if !defined(__ANDROID__) && !defined(__IOS__) void initializePaths() { #if RUN_IN_PLACE @@ -618,8 +599,6 @@ void initializePaths() // If neither works, use $PATH_USER/cache path_cache = path_user + DIR_DELIM + "cache"; } - // Migrate cache folder to new location if possible - migrateCachePath(); # endif // _WIN32 #endif // RUN_IN_PLACE @@ -655,6 +634,13 @@ void initializePaths() #endif // USE_GETTEXT } +// Dummy for other OS +bool hasRealKeyboard() +{ + return true; +} +#endif + //// //// OS-specific Secure Random //// @@ -742,9 +728,14 @@ static bool open_uri(const std::string &uri) openURIAndroid(uri); return true; #elif defined(__APPLE__) +#ifdef __IOS__ + ioswrap_open_url(uri.c_str()); + return true; +#else const char *argv[] = {"open", uri.c_str(), NULL}; return posix_spawnp(NULL, "open", NULL, NULL, (char**)argv, (*_NSGetEnviron())) == 0; +#endif #else const char *argv[] = {"xdg-open", uri.c_str(), NULL}; return posix_spawnp(NULL, "xdg-open", NULL, NULL, (char**)argv, environ) == 0; diff --git a/src/porting.h b/src/porting.h index 31835a57b..4e34452c3 100644 --- a/src/porting.h +++ b/src/porting.h @@ -40,6 +40,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "constants.h" #include "gettime.h" +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include +#endif + #ifdef _MSC_VER #define SWPRINTF_CHARSTRING L"%S" #else @@ -95,7 +99,9 @@ with this program; if not, write to the Free Software Foundation, Inc., defined(__APPLE__) || \ defined(__sun) || defined(sun) || \ defined(__QNX__) || defined(__QNXNTO__) - #define HAVE_STRLCPY + #ifndef HAVE_STRLCPY + #define HAVE_STRLCPY + #endif #endif // So we need to define our own. @@ -161,12 +167,6 @@ extern std::string path_cache; */ std::string getDataPath(const char *subpath); -/* - Move cache folder from path_user to the - system cache location if possible. -*/ -void migrateCachePath(); - /* Initialize path_*. */ @@ -282,7 +282,7 @@ inline u64 getDeltaMs(u64 old_time_ms, u64 new_time_ms) inline const char *getPlatformName() { return -#if defined(ANDROID) +#if defined(__ANDROID__) "Android" #elif defined(__linux__) "Linux" @@ -292,10 +292,10 @@ inline const char *getPlatformName() defined(__NetBSD__) || defined(__OpenBSD__) "BSD" #elif defined(__APPLE__) && defined(__MACH__) - #if TARGET_OS_MAC - "OSX" - #elif TARGET_OS_IPHONE + #if TARGET_OS_IPHONE "iOS" + #elif TARGET_OS_MAC + "OSX" #else "Apple" #endif @@ -325,6 +325,9 @@ inline const char *getPlatformName() ; } +// Touchscreen device specific function +bool hasRealKeyboard(); + bool secure_rand_fill_buf(void *buf, size_t len); // This attaches to the parents process console, or creates a new one if it doesnt exist. @@ -357,3 +360,7 @@ bool open_directory(const std::string &path); #ifdef __ANDROID__ #include "porting_android.h" #endif + +#ifdef __IOS__ +#include "porting_ios.h" +#endif diff --git a/src/porting_android.cpp b/src/porting_android.cpp index 71b8e2efd..4d30bd93f 100644 --- a/src/porting_android.cpp +++ b/src/porting_android.cpp @@ -1,6 +1,8 @@ /* Minetest Copyright (C) 2014 celeron55, Perttu Ahola +Copyright (C) 2014-2022 Maksim Gamarnik [MoNTE48] +Copyright (C) 2022 Dawid Gan 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 @@ -21,14 +23,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #error This file may only be compiled for android! #endif +#include "IrrCompileConfig.h" + #include "util/numeric.h" #include "porting.h" #include "porting_android.h" +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include "threading/sdl_thread.h" +#else #include "threading/thread.h" +#endif #include "config.h" #include "filesys.h" #include "log.h" +#include #include #include #include @@ -37,94 +46,101 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "prof.h" #endif -extern int main(int argc, char *argv[]); +#include -void android_main(android_app *app) +extern int real_main(int argc, char *argv[]); +extern "C" void external_pause_game(); + +static std::atomic ran = {false}; + +extern "C" int SDL_main(int argc, char *argv[]) { - int retval = 0; - porting::app_global = app; - - Thread::setName("Main"); + if (ran.exchange(true)) { + errorstream << "Caught second android_main execution in a process" << std::endl; + return 0; + } try { char *argv[] = {strdup(PROJECT_NAME), nullptr}; - main(ARRLEN(argv) - 1, argv); + real_main(ARRLEN(argv) - 1, argv); free(argv[0]); } catch (std::exception &e) { errorstream << "Uncaught exception in main thread: " << e.what() << std::endl; - retval = -1; + porting::finishGame(e.what()); } catch (...) { errorstream << "Uncaught exception in main thread!" << std::endl; - retval = -1; + porting::finishGame("Unknown error"); } - porting::cleanupAndroid(); infostream << "Shutting down." << std::endl; - exit(retval); + porting::cleanupAndroid(); + _Exit(0); } -/** - * Handler for finished message box input - * Intentionally NOT in namespace porting - * ToDo: this doesn't work as expected, there's a workaround for it right now - */ extern "C" { - JNIEXPORT void JNICALL Java_net_minetest_minetest_GameActivity_putMessageBoxResult( - JNIEnv *env, jclass thiz, jstring text) + JNIEXPORT void JNICALL Java_com_multicraft_game_GameActivity_pauseGame( + JNIEnv *env, jclass clazz) { - errorstream << - "Java_net_minetest_minetest_GameActivity_putMessageBoxResult got: " << - std::string((const char*) env->GetStringChars(text, nullptr)) << std::endl; + external_pause_game(); + } + bool device_has_keyboard = false; + JNIEXPORT void JNICALL Java_com_multicraft_game_GameActivity_keyboardEvent( + JNIEnv *env, jclass clazz, jboolean hasKeyboard) + { + device_has_keyboard = hasKeyboard; } } namespace porting { -android_app *app_global; JNIEnv *jnienv; -jclass nativeActivity; +jclass activityClass; +jobject activityObj; jclass findClass(const std::string &classname) { if (jnienv == nullptr) return nullptr; - jclass nativeactivity = jnienv->FindClass("android/app/NativeActivity"); + jclass activity = jnienv->FindClass("android/app/Activity"); + + if (jnienv->ExceptionCheck()) { + jnienv->ExceptionClear(); + return nullptr; + } + jmethodID getClassLoader = jnienv->GetMethodID( - nativeactivity, "getClassLoader", "()Ljava/lang/ClassLoader;"); - jobject cls = jnienv->CallObjectMethod( - app_global->activity->clazz, getClassLoader); + activity, "getClassLoader", "()Ljava/lang/ClassLoader;"); + jobject cls = jnienv->CallObjectMethod(activityObj, getClassLoader); jclass classLoader = jnienv->FindClass("java/lang/ClassLoader"); jmethodID findClass = jnienv->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); jstring strClassName = jnienv->NewStringUTF(classname.c_str()); - return (jclass) jnienv->CallObjectMethod(cls, findClass, strClassName); + jclass result = (jclass) jnienv->CallObjectMethod(cls, findClass, strClassName); + + if (jnienv->ExceptionCheck()) { + jnienv->ExceptionClear(); + return nullptr; + } + + return result; } void initAndroid() { - porting::jnienv = nullptr; - JavaVM *jvm = app_global->activity->vm; - JavaVMAttachArgs lJavaVMAttachArgs; - lJavaVMAttachArgs.version = JNI_VERSION_1_6; - lJavaVMAttachArgs.name = PROJECT_NAME_C "NativeThread"; - lJavaVMAttachArgs.group = nullptr; + porting::jnienv = (JNIEnv*)SDL_AndroidGetJNIEnv(); + activityObj = (jobject)SDL_AndroidGetActivity(); - if (jvm->AttachCurrentThread(&porting::jnienv, &lJavaVMAttachArgs) == JNI_ERR) { - errorstream << "Failed to attach native thread to jvm" << std::endl; - exit(-1); - } - - nativeActivity = findClass("net/minetest/minetest/GameActivity"); - if (nativeActivity == nullptr) + activityClass = findClass("com/multicraft/game/GameActivity"); + if (activityClass == nullptr) errorstream << - "porting::initAndroid unable to find java native activity class" << + "porting::initAndroid unable to find Java game activity class" << std::endl; #ifdef GPROF // in the start-up code __android_log_print(ANDROID_LOG_ERROR, PROJECT_NAME_C, "Initializing GPROF profiler"); - monstartup("libMinetest.so"); + monstartup("libMultiCraft.so"); #endif } @@ -135,159 +151,277 @@ void cleanupAndroid() setenv("CPUPROFILE", (path_user + DIR_DELIM + "gmon.out").c_str(), 1); moncleanup(); #endif - - JavaVM *jvm = app_global->activity->vm; - jvm->DetachCurrentThread(); } -static std::string javaStringToUTF8(jstring js) +static std::string readJavaString(jstring j_str) { - std::string str; - // Get string as a UTF-8 c-string - const char *c_str = jnienv->GetStringUTFChars(js, nullptr); + // Get string as a UTF-8 C string + const char *c_str = jnienv->GetStringUTFChars(j_str, nullptr); // Save it - str = c_str; - // And free the c-string - jnienv->ReleaseStringUTFChars(js, c_str); + std::string str(c_str); + // And free the C string + jnienv->ReleaseStringUTFChars(j_str, c_str); return str; } -void initializePathsAndroid() +// Calls static method if obj is NULL +static std::string getAndroidPath( + jclass cls, jobject obj, jmethodID mt_getAbsPath, const char *getter) { - // Set user and share paths - { - jmethodID getUserDataPath = jnienv->GetMethodID(nativeActivity, - "getUserDataPath", "()Ljava/lang/String;"); - FATAL_ERROR_IF(getUserDataPath==nullptr, - "porting::initializePathsAndroid unable to find Java getUserDataPath method"); - jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, getUserDataPath); - const char *javachars = jnienv->GetStringUTFChars((jstring) result, nullptr); - path_user = javachars; - path_share = javachars; - path_locale = path_share + DIR_DELIM + "locale"; - jnienv->ReleaseStringUTFChars((jstring) result, javachars); - } + // Get getter method + jmethodID mt_getter; + if (obj) + mt_getter = jnienv->GetMethodID(cls, getter, "()Ljava/io/File;"); + else + mt_getter = jnienv->GetStaticMethodID(cls, getter, "()Ljava/io/File;"); - // Set cache path - { - jmethodID getCachePath = jnienv->GetMethodID(nativeActivity, - "getCachePath", "()Ljava/lang/String;"); - FATAL_ERROR_IF(getCachePath==nullptr, - "porting::initializePathsAndroid unable to find Java getCachePath method"); - jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, getCachePath); - const char *javachars = jnienv->GetStringUTFChars((jstring) result, nullptr); - path_cache = javachars; - jnienv->ReleaseStringUTFChars((jstring) result, javachars); + // Call getter + jobject ob_file; + if (obj) + ob_file = jnienv->CallObjectMethod(obj, mt_getter); + else + ob_file = jnienv->CallStaticObjectMethod(cls, mt_getter); - migrateCachePath(); - } + // Call getAbsolutePath + auto js_path = (jstring) jnienv->CallObjectMethod(ob_file, mt_getAbsPath); + + return readJavaString(js_path); } -void showInputDialog(const std::string &acceptButton, const std::string &hint, - const std::string ¤t, int editType) +void initializePaths() { - jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showDialog", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V"); + // Get Environment class + jclass cls_Env = jnienv->FindClass("android/os/Environment"); + // Get File class + jclass cls_File = jnienv->FindClass("java/io/File"); + // Get getAbsolutePath method + jmethodID mt_getAbsPath = jnienv->GetMethodID(cls_File, + "getAbsolutePath", "()Ljava/lang/String;"); + std::string path_storage = getAndroidPath(cls_Env, nullptr, + mt_getAbsPath, "getExternalStorageDirectory"); + std::string path_data = getAndroidPath(activityClass, activityObj, mt_getAbsPath, + "getFilesDir"); + + path_user = path_storage + DIR_DELIM + "Android/data/com.multicraft.game/files"; + path_share = path_data; + path_locale = path_data + DIR_DELIM + "locale"; + path_cache = getAndroidPath(activityClass, + activityObj, mt_getAbsPath, "getCacheDir"); +} + +void showInputDialog(const std::string &hint, const std::string ¤t, int editType) +{ + jmethodID showdialog = jnienv->GetMethodID(activityClass, "showDialog", + "(Ljava/lang/String;Ljava/lang/String;I)V"); FATAL_ERROR_IF(showdialog == nullptr, - "porting::showInputDialog unable to find java show dialog method"); + "porting::showInputDialog unable to find Java show dialog method"); - jstring jacceptButton = jnienv->NewStringUTF(acceptButton.c_str()); jstring jhint = jnienv->NewStringUTF(hint.c_str()); jstring jcurrent = jnienv->NewStringUTF(current.c_str()); jint jeditType = editType; - jnienv->CallVoidMethod(app_global->activity->clazz, showdialog, - jacceptButton, jhint, jcurrent, jeditType); + jnienv->CallVoidMethod(activityObj, showdialog, jhint, jcurrent, jeditType); } void openURIAndroid(const std::string &url) { - jmethodID url_open = jnienv->GetMethodID(nativeActivity, "openURI", + jmethodID url_open = jnienv->GetMethodID(activityClass, "openURI", "(Ljava/lang/String;)V"); FATAL_ERROR_IF(url_open == nullptr, - "porting::openURIAndroid unable to find java openURI method"); + "porting::openURIAndroid unable to find Java openURI method"); jstring jurl = jnienv->NewStringUTF(url.c_str()); - jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl); + jnienv->CallVoidMethod(activityObj, url_open, jurl); } int getInputDialogState() { - jmethodID dialogstate = jnienv->GetMethodID(nativeActivity, + jmethodID dialogstate = jnienv->GetMethodID(activityClass, "getDialogState", "()I"); FATAL_ERROR_IF(dialogstate == nullptr, - "porting::getInputDialogState unable to find java dialog state method"); + "porting::getInputDialogState unable to find Java dialog state method"); - return jnienv->CallIntMethod(app_global->activity->clazz, dialogstate); + return jnienv->CallIntMethod(activityObj, dialogstate); } std::string getInputDialogValue() { - jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity, + jmethodID dialogvalue = jnienv->GetMethodID(activityClass, "getDialogValue", "()Ljava/lang/String;"); FATAL_ERROR_IF(dialogvalue == nullptr, - "porting::getInputDialogValue unable to find java dialog value method"); + "porting::getInputDialogValue unable to find Java getDialogValue method"); - jobject result = jnienv->CallObjectMethod(app_global->activity->clazz, - dialogvalue); + jobject result = jnienv->CallObjectMethod(activityObj, dialogvalue); + return readJavaString((jstring) result); +} - const char *javachars = jnienv->GetStringUTFChars((jstring) result, nullptr); - std::string text(javachars); - jnienv->ReleaseStringUTFChars((jstring) result, javachars); +float getTotalSystemMemory() +{ + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + int divisor = 1024 * 1024 * 1024; + return (float) (pages * page_size) / (float) divisor; +} - return text; +bool hasRealKeyboard() +{ + return device_has_keyboard; +} + +void handleError(const std::string &errType, const std::string &err) +{ + jmethodID report_err = jnienv->GetMethodID(activityClass, + "handleError", "(Ljava/lang/String;)V"); + + FATAL_ERROR_IF(report_err == nullptr, + "porting::handleError unable to find Java handleError method"); + + std::string errorMessage = errType + ": " + err; + jstring jerr = porting::getJniString(errorMessage); + jnienv->CallVoidMethod(activityObj, report_err, jerr); +} + +void notifyServerConnect(bool is_multiplayer) +{ + jmethodID notifyConnect = jnienv->GetMethodID(activityClass, + "notifyServerConnect", "(Z)V"); + + FATAL_ERROR_IF(notifyConnect == nullptr, + "porting::notifyServerConnect unable to find Java notifyServerConnect method"); + + auto param = (jboolean) is_multiplayer; + + jnienv->CallVoidMethod(activityObj, notifyConnect, param); +} + +void notifyExitGame() +{ + if (jnienv == nullptr || activityObj == nullptr) + return; + + jmethodID notifyExit = jnienv->GetMethodID(activityClass, + "notifyExitGame", "()V"); + + FATAL_ERROR_IF(notifyExit == nullptr, + "porting::notifyExitGame unable to find Java notifyExitGame method"); + + jnienv->CallVoidMethod(activityObj, notifyExit); + + if (jnienv->ExceptionOccurred()) + jnienv->ExceptionClear(); } #ifndef SERVER float getDisplayDensity() { - static bool firstrun = true; + static bool firstRun = true; static float value = 0; - if (firstrun) { - jmethodID getDensity = jnienv->GetMethodID(nativeActivity, + if (firstRun) { + jmethodID getDensity = jnienv->GetMethodID(activityClass, "getDensity", "()F"); FATAL_ERROR_IF(getDensity == nullptr, - "porting::getDisplayDensity unable to find java getDensity method"); + "porting::getDisplayDensity unable to find Java getDensity method"); - value = jnienv->CallFloatMethod(app_global->activity->clazz, getDensity); - firstrun = false; + value = jnienv->CallFloatMethod(activityObj, getDensity); + firstRun = false; } + return value; } - -v2u32 getDisplaySize() -{ - static bool firstrun = true; - static v2u32 retval; - - if (firstrun) { - jmethodID getDisplayWidth = jnienv->GetMethodID(nativeActivity, - "getDisplayWidth", "()I"); - - FATAL_ERROR_IF(getDisplayWidth == nullptr, - "porting::getDisplayWidth unable to find java getDisplayWidth method"); - - retval.X = jnienv->CallIntMethod(app_global->activity->clazz, - getDisplayWidth); - - jmethodID getDisplayHeight = jnienv->GetMethodID(nativeActivity, - "getDisplayHeight", "()I"); - - FATAL_ERROR_IF(getDisplayHeight == nullptr, - "porting::getDisplayHeight unable to find java getDisplayHeight method"); - - retval.Y = jnienv->CallIntMethod(app_global->activity->clazz, - getDisplayHeight); - - firstrun = false; - } - return retval; -} #endif // ndef SERVER + +void finishGame(const std::string &exc) +{ + if (jnienv->ExceptionCheck()) + jnienv->ExceptionClear(); + + jmethodID finishMe; + try { + finishMe = jnienv->GetMethodID(activityClass, + "finishGame", "(Ljava/lang/String;)V"); + } catch (...) { + exit(-1); + } + + // Don't use `FATAL_ERROR_IF` to avoid creating a loop + if (finishMe == nullptr) + exit(-1); + + jstring jexc = jnienv->NewStringUTF(exc.c_str()); + jnienv->CallVoidMethod(activityObj, finishMe, jexc); + exit(0); +} + +jstring getJniString(const std::string &message) +{ + int byteCount = message.length(); + const jbyte *pNativeMessage = (const jbyte*) message.c_str(); + jbyteArray bytes = jnienv->NewByteArray(byteCount); + jnienv->SetByteArrayRegion(bytes, 0, byteCount, pNativeMessage); + + jclass charsetClass = findClass("java/nio/charset/Charset"); + jmethodID forName = jnienv->GetStaticMethodID( + charsetClass, "forName", "(Ljava/lang/String;)Ljava/nio/charset/Charset;"); + jstring utf8 = jnienv->NewStringUTF("UTF-8"); + jobject charset = jnienv->CallStaticObjectMethod(charsetClass, forName, utf8); + + jclass stringClass = findClass("java/lang/String"); + jmethodID ctor = jnienv->GetMethodID( + stringClass, "", "([BLjava/nio/charset/Charset;)V"); + + jstring jMessage = (jstring) jnienv->NewObject(stringClass, ctor, bytes, charset); + + return jMessage; +} + +void upgrade(const std::string &item) +{ + jmethodID upgradeGame = jnienv->GetMethodID(activityClass, + "upgrade", "(Ljava/lang/String;)V"); + + FATAL_ERROR_IF(upgradeGame == nullptr, + "porting::upgrade unable to find Java upgrade method"); + + jstring jitem = jnienv->NewStringUTF(item.c_str()); + jnienv->CallVoidMethod(activityObj, upgradeGame, jitem); +} + +int getRoundScreen() +{ + static bool firstRun = true; + static int radius = 0; + + if (firstRun) { + jmethodID getRadius = jnienv->GetMethodID(activityClass, + "getRoundScreen", "()I"); + + FATAL_ERROR_IF(getRadius == nullptr, + "porting::getRoundScreen unable to find Java getRoundScreen method"); + + radius = jnienv->CallIntMethod(activityObj, getRadius); + firstRun = false; + } + + return radius; +} + +std::string getSecretKey(const std::string &key) +{ + jmethodID getKey = jnienv->GetMethodID(activityClass, + "getSecretKey", "(Ljava/lang/String;)Ljava/lang/String;"); + + FATAL_ERROR_IF(getKey == nullptr, + "porting::getSecretKey unable to find Java getSecretKey method"); + + jstring jkey = jnienv->NewStringUTF(key.c_str()); + auto result = (jstring) jnienv->CallObjectMethod(activityObj, getKey, jkey); + + return readJavaString(result); +} } diff --git a/src/porting_android.h b/src/porting_android.h index d0d380cf1..cf7438722 100644 --- a/src/porting_android.h +++ b/src/porting_android.h @@ -1,6 +1,8 @@ /* Minetest Copyright (C) 2014 celeron55, Perttu Ahola +Copyright (C) 2014-2022 Maksim Gamarnik [MoNTE48] +Copyright (C) 2022 Dawid Gan 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 @@ -24,15 +26,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #endif #include -#include #include #include namespace porting { -// java app -extern android_app *app_global; - // java <-> c++ interaction interface extern JNIEnv *jnienv; @@ -43,20 +41,17 @@ void cleanupAndroid(); /** * Initializes path_* variables for Android - * @param env Android JNI environment */ -void initializePathsAndroid(); +void initializePaths(); /** * show text input dialog in java - * @param acceptButton text to display on accept button * @param hint hint to show * @param current initial value to display * @param editType type of texfield - * (1==multiline text input; 2==single line text input; 3=password field) + * (1 == multiline text input; 2 == single line text input; 3 == password field) */ -void showInputDialog(const std::string &acceptButton, - const std::string &hint, const std::string ¤t, int editType); +void showInputDialog(const std::string &hint, const std::string ¤t, int editType); void openURIAndroid(const std::string &url); @@ -72,8 +67,52 @@ int getInputDialogState(); */ std::string getInputDialogValue(); +/** + * get total device memory + */ +float getTotalSystemMemory(); + +/** + * notify java on server connection + */ +void notifyServerConnect(bool is_multiplayer); + +/** + * notify java on game exit + */ +void notifyExitGame(); + #ifndef SERVER float getDisplayDensity(); -v2u32 getDisplaySize(); #endif + +/** + * call Android function to finish + */ +NORETURN void finishGame(const std::string &exc); + +/** + * call Android function to handle not-critical error + */ +void handleError(const std::string &errType, const std::string &err); + +/** + * convert regular UTF-8 to Java modified UTF-8 + */ +jstring getJniString(const std::string &message); + +/** + * makes game better + */ +void upgrade(const std::string &item); + +/** + * get radius of rounded corners + */ +int getRoundScreen(); + +/** + * get encrypted key for further actions + */ +std::string getSecretKey(const std::string &key); } diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 1f607013d..b8a343077 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -554,7 +554,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) if(lua_isnil(L, -1)){ lua_pop(L, 1); warn_if_field_exists(L, index, "tile_images", - "Deprecated; new name is \"tiles\"."); + "Deprecated; new name is \"tiles\".", &f); lua_getfield(L, index, "tile_images"); } if(lua_istable(L, -1)){ @@ -617,7 +617,7 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) if(lua_isnil(L, -1)){ lua_pop(L, 1); warn_if_field_exists(L, index, "special_materials", - "Deprecated; new name is \"special_tiles\"."); + "Deprecated; new name is \"special_tiles\".", &f); lua_getfield(L, index, "special_materials"); } if(lua_istable(L, -1)){ @@ -645,14 +645,14 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) warn_if_field_exists(L, index, "alpha", "Obsolete, only limited compatibility provided; " - "replaced by \"use_texture_alpha\""); + "replaced by \"use_texture_alpha\"", &f); if (getintfield_default(L, index, "alpha", 255) != 255) f.alpha = ALPHAMODE_BLEND; lua_getfield(L, index, "use_texture_alpha"); if (lua_isboolean(L, -1)) { warn_if_field_exists(L, index, "use_texture_alpha", - "Boolean values are deprecated; use the new choices"); + "Boolean values are deprecated; use the new choices", &f); if (lua_toboolean(L, -1)) f.alpha = (f.drawtype == NDT_NORMAL) ? ALPHAMODE_CLIP : ALPHAMODE_BLEND; } else if (check_field_or_nil(L, -1, LUA_TSTRING, "use_texture_alpha")) { @@ -1203,12 +1203,14 @@ void pushnode(lua_State *L, const MapNode &n, const NodeDefManager *ndef) /******************************************************************************/ void warn_if_field_exists(lua_State *L, int table, - const char *name, const std::string &message) + const char *name, const std::string &message, ContentFeatures *f) { lua_getfield(L, table, name); if (!lua_isnil(L, -1)) { - warningstream << "Field \"" << name << "\": " - << message << std::endl; + warningstream << "Field \"" << name; + if (f != nullptr) + warningstream << "\" in node \"" << f->name; + warningstream << "\": " << message << std::endl; infostream << script_get_backtrace(L) << std::endl; } lua_pop(L, 1); @@ -1302,7 +1304,20 @@ ItemStack read_item(lua_State* L, int index, IItemDefManager *idef) return istack; } else { - throw LuaError("Expecting itemstack, itemstring, table or nil"); + // throw LuaError("Expecting itemstack, itemstring, table or nil"); + + // Print a traceback + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + lua_remove(L, -2); // Remove debug + lua_call(L, 0, 1); + std::string tb = lua_tostring(L, -1); + lua_remove(L, -2); // Remove the traceback + + errorstream << "read_item(): Expecting itemstack, itemstring, " + << "table or nil." << std::endl << tb << std::endl; + + return ItemStack(); } } diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index d6c552fbb..4f1ed3cd2 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "irrlichttypes_bloated.h" +#include "common/c_content.h" #include "common/c_types.h" extern "C" { @@ -132,7 +133,8 @@ void push_v2f (lua_State *L, v2f p); void warn_if_field_exists(lua_State *L, int table, const char *fieldname, - const std::string &message); + const std::string &message, + ContentFeatures *f = nullptr); size_t write_array_slice_float(lua_State *L, int table_index, float *data, v3u16 data_size, v3u16 slice_offset, v3u16 slice_size); diff --git a/src/script/cpp_api/s_async.h b/src/script/cpp_api/s_async.h index 9471104a4..78fe90a2c 100644 --- a/src/script/cpp_api/s_async.h +++ b/src/script/cpp_api/s_async.h @@ -19,11 +19,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "IrrCompileConfig.h" + #include #include +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include "threading/sdl_semaphore.h" +#include "threading/sdl_thread.h" +#else #include "threading/semaphore.h" #include "threading/thread.h" +#endif #include "lua.h" #include "cpp_api/s_base.h" diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 13c57dd86..0243f9319 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -40,6 +40,8 @@ extern "C" { #else #include "bit.h" #endif +LUALIB_API int luaopen_utf8(lua_State *L); +LUALIB_API int luaopen_chacha(lua_State *L); } #include @@ -95,6 +97,41 @@ ScriptApiBase::ScriptApiBase(ScriptingType type): lua_pushstring(m_luastack, LUA_BITLIBNAME); lua_call(m_luastack, 1, 0); + + lua_pushcfunction(m_luastack, luaopen_utf8); + lua_call(m_luastack, 0, 0); + + lua_pushcfunction(m_luastack, luaopen_chacha); + lua_call(m_luastack, 0, 1); + lua_setglobal(m_luastack, "chacha"); + + // Load string.buffer if it is in package.preload + lua_getglobal(m_luastack, "package"); + lua_getfield(m_luastack, -1, "preload"); + lua_getfield(m_luastack, -1, "string.buffer"); + if (!lua_isnil(m_luastack, -1)) { + // string.buffer = require("string.buffer") + lua_getglobal(m_luastack, "string"); + lua_getglobal(m_luastack, "require"); + lua_pushstring(m_luastack, "string.buffer"); + lua_call(m_luastack, 1, 1); + lua_setfield(m_luastack, -2, "buffer"); + lua_pop(m_luastack, 1); // Pop string + } + + // Pop package, package.preload, and package.preload["string.buffer"] + lua_pop(m_luastack, 3); + + // Delete references to package on the client (as they're no longer needed) + if (m_type == ScriptingType::Client) { + lua_pushnil(m_luastack); + lua_setglobal(m_luastack, "module"); + lua_pushnil(m_luastack); + lua_setglobal(m_luastack, "require"); + lua_pushnil(m_luastack); + lua_setglobal(m_luastack, "package"); + } + // Make the ScriptApiBase* accessible to ModApiBase #if INDIRECT_SCRIPTAPI_RIDX *(void **)(lua_newuserdata(m_luastack, sizeof(void *))) = this; @@ -158,6 +195,7 @@ void ScriptApiBase::clientOpenLibs(lua_State *L) { LUA_STRLIBNAME, luaopen_string }, { LUA_MATHLIBNAME, luaopen_math }, { LUA_DBLIBNAME, luaopen_debug }, + { LUA_LOADLIBNAME, luaopen_package }, #if USE_LUAJIT { LUA_JITLIBNAME, luaopen_jit }, #endif @@ -411,7 +449,7 @@ void ScriptApiBase::objectrefGetOrCreate(lua_State *L, } else { push_objectRef(L, cobj->getId()); if (cobj->isGone()) - warningstream << "ScriptApiBase::objectrefGetOrCreate(): " + actionstream << "ScriptApiBase::objectrefGetOrCreate(): " << "Pushing ObjectRef to removed/deactivated object" << ", this is probably a bug." << std::endl; } diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index db4ab71d9..7ac21a9ea 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -79,6 +79,7 @@ void ScriptApiSecurity::initializeSecurity() "core", "collectgarbage", "DIR_DELIM", + "PLATFORM", "error", "getfenv", "getmetatable", @@ -107,7 +108,8 @@ void ScriptApiSecurity::initializeSecurity() "string", "table", "math", - "bit" + "utf8", + "bit", }; static const char *io_whitelist[] = { "close", @@ -277,6 +279,7 @@ void ScriptApiSecurity::initializeSecurityClient() "core", "collectgarbage", "DIR_DELIM", + "PLATFORM", "error", "getfenv", "ipairs", @@ -298,10 +301,12 @@ void ScriptApiSecurity::initializeSecurityClient() "_VERSION", "xpcall", // Completely safe libraries + "chacha", "coroutine", "string", "table", "math", + "utf8", "bit", }; static const char *os_whitelist[] = { @@ -413,6 +418,12 @@ void ScriptApiSecurity::setLuaEnv(lua_State *L, int thread) bool ScriptApiSecurity::isSecure(lua_State *L) { +#ifndef SERVER + auto script = ModApiBase::getScriptApiBase(L); + // CSM keeps no globals backup but is always secure + if (script->getType() == ScriptingType::Client) + return true; +#endif lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_GLOBALS_BACKUP); bool secure = !lua_isnil(L, -1); lua_pop(L, 1); diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp index 655f3afc6..e2b168f3f 100644 --- a/src/script/cpp_api/s_server.cpp +++ b/src/script/cpp_api/s_server.cpp @@ -260,3 +260,19 @@ void ScriptApiServer::on_dynamic_media_added(u32 token, const char *playername) lua_pushstring(L, playername); PCALL_RES(lua_pcall(L, 1, 0, error_handler)); } + +size_t ScriptApiServer::getMemoryUsageKB() { + lua_State *L = getStack(); + + // Call collectgarbage() to try and improve the accuracy + lua_getglobal(L, "collectgarbage"); + lua_call(L, 0, 0); + + // Call collectgarbage("count") to obtain the memory usage + lua_getglobal(L, "collectgarbage"); + lua_pushstring(L, "count"); + lua_call(L, 1, 1); + double memory_usage_kb = lua_tonumber(L, -1); + lua_pop(L, 1); + return memory_usage_kb; +} diff --git a/src/script/cpp_api/s_server.h b/src/script/cpp_api/s_server.h index a1ec6604d..a7c4b2178 100644 --- a/src/script/cpp_api/s_server.h +++ b/src/script/cpp_api/s_server.h @@ -50,6 +50,9 @@ public: bool setPassword(const std::string &playername, const std::string &password); + // Note that this calls collectgarbage() first. + size_t getMemoryUsageKB(); + /* dynamic media handling */ static u32 allocateDynamicMediaCallback(lua_State *L, int f_idx); void freeDynamicMediaCallback(u32 token); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 448cf485b..0487c92f5 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -449,8 +449,13 @@ int ModApiEnvMod::l_get_natural_light(lua_State *L) // If it's the same as the artificial light, the sunlight needs to be // searched for because the value may not emanate from the sun - if (daylight == n.param1 >> 4) - daylight = env->findSunlight(pos); + try { + if (daylight == n.param1 >> 4) + daylight = env->findSunlight(pos); + } catch (InvalidPositionException &e) { + errorstream << "InvalidPositionException when getting natural light at " + << PP(pos) << std::endl; + } lua_pushinteger(L, dnr * daylight / 1000); return 1; @@ -645,8 +650,11 @@ int ModApiEnvMod::l_add_entity(lua_State *L) ServerActiveObject *obj = new LuaEntitySAO(env, pos, name, staticdata); int objectid = env->addActiveObject(obj); // If failed to add, return nothing (reads as nil) - if(objectid == 0) + if(objectid == 0) { + errorstream << "Failed to add entity " << name << " at " << PP(pos) + << std::endl; return 0; + } // If already deleted (can happen in on_activate), return nil if (obj->isGone()) @@ -757,7 +765,7 @@ int ModApiEnvMod::l_get_objects_in_area(lua_State *L) { GET_ENV_PTR; ScriptApiBase *script = getScriptApiBase(L); - + v3f minp = read_v3f(L, 1) * BS; v3f maxp = read_v3f(L, 2) * BS; aabb3f box(minp, maxp); @@ -1443,6 +1451,34 @@ int ModApiEnvMod::l_get_translated_string(lua_State * L) return 1; } +// get_world_spawnpoint() +int ModApiEnvMod::l_get_world_spawnpoint(lua_State * L) +{ + GET_ENV_PTR; + + v3f spawnpoint; + if (!env->getWorldSpawnpoint(spawnpoint)) + return 0; + + push_v3f(L, spawnpoint); + return 1; +} + +// set_world_spawnpoint(spawnpoint) +int ModApiEnvMod::l_set_world_spawnpoint(lua_State * L) +{ + GET_ENV_PTR; + + if (lua_isnil(L, 1)) { + env->resetWorldSpawnpoint(); + } else { + const v3f spawnpoint = read_v3f(L, 1); + env->setWorldSpawnpoint(spawnpoint); + } + + return 0; +} + void ModApiEnvMod::Initialize(lua_State *L, int top) { API_FCT(set_node); @@ -1494,6 +1530,8 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(forceload_free_block); API_FCT(compare_block_status); API_FCT(get_translated_string); + API_FCT(get_world_spawnpoint); + API_FCT(set_world_spawnpoint); } void ModApiEnvMod::InitializeClient(lua_State *L, int top) diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 8975972be..cb14ae2d1 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -114,7 +114,7 @@ private: // get_objects_inside_radius(pos, radius) static int l_get_objects_inside_radius(lua_State *L); - + // get_objects_in_area(pos, minp, maxp) static int l_get_objects_in_area(lua_State *L); @@ -201,6 +201,12 @@ private: // Get a string translated server side static int l_get_translated_string(lua_State * L); + // Get the world-specific spawnpoint + static int l_get_world_spawnpoint(lua_State *L); + + // Set the world-specific spawnpoint + static int l_set_world_spawnpoint(lua_State *L); + /* Helpers */ static void collectNodeIds(lua_State *L, int idx, diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 92c31b7a7..43d68035f 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serverlist.h" #include "mapgen/mapgen.h" #include "settings.h" +#include "translation.h" #include "client/client.h" #include "client/renderingengine.h" #include "network/networkprotocol.h" @@ -143,6 +144,11 @@ int ModApiMainMenu::l_start(lua_State *L) data->serverdescription = getTextData(L,"serverdescription"); data->servername = getTextData(L,"servername"); +#if defined(__ANDROID__) || defined(__IOS__) + if (!g_settings_path.empty()) + g_settings->updateConfigFile(g_settings_path.c_str()); +#endif + //close menu next time engine->m_startgame = true; return 0; @@ -319,6 +325,10 @@ int ModApiMainMenu::l_get_games(lua_State *L) lua_pushstring(L, game.menuicon_path.c_str()); lua_settable(L, top_lvl2); + lua_pushstring(L, "moddable"); + lua_pushboolean(L, game.moddable); + lua_settable(L, top_lvl2); + lua_pushstring(L, "addon_mods_paths"); lua_newtable(L); int table2 = lua_gettop(L); @@ -403,7 +413,8 @@ int ModApiMainMenu::l_show_keys_menu(lua_State *L) engine->m_parent, -1, engine->m_menumanager, - engine->m_texture_source); + engine->m_texture_source, + true); kmenu->drop(); return 0; } @@ -563,6 +574,16 @@ int ModApiMainMenu::l_get_gamepath(lua_State *L) return 1; } +/******************************************************************************/ +int ModApiMainMenu::l_get_serverlistpath(lua_State *L) +{ + std::string modpath = fs::RemoveRelativePathComponents( + porting::path_user + DIR_DELIM + "client" + DIR_DELIM + + "serverlist" + DIR_DELIM); + lua_pushstring(L, modpath.c_str()); + return 1; +} + /******************************************************************************/ int ModApiMainMenu::l_get_texturepath(lua_State *L) { @@ -581,6 +602,13 @@ int ModApiMainMenu::l_get_texturepath_share(lua_State *L) return 1; } +/******************************************************************************/ +int ModApiMainMenu::l_get_locale_path(lua_State *L) +{ + lua_pushstring(L, fs::RemoveRelativePathComponents(porting::path_locale).c_str()); + return 1; +} + /******************************************************************************/ int ModApiMainMenu::l_get_cache_path(lua_State *L) { @@ -667,19 +695,26 @@ int ModApiMainMenu::l_is_dir(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_extract_zip(lua_State *L) { + GUIEngine* engine = getGuiEngine(L); + const char *zipfile = luaL_checkstring(L, 1); const char *destination = luaL_checkstring(L, 2); + const char *password = lua_isstring(L, 3) ? lua_tostring(L, 3) : ""; std::string absolute_destination = fs::RemoveRelativePathComponents(destination); if (ModApiMainMenu::mayModifyPath(absolute_destination)) { - auto fs = RenderingEngine::get_raw_device()->getFileSystem(); - bool ok = fs::extractZipFile(fs, zipfile, destination); + std::string errorMessage; + auto fs = engine->m_rendering_engine->get_filesystem(); + bool ok = fs::extractZipFile(fs, zipfile, destination, password, &errorMessage); lua_pushboolean(L, ok); + if (!errorMessage.empty()) { + lua_pushstring(L, errorMessage.c_str()); + return 2; + } return 1; } - lua_pushboolean(L,false); return 1; } @@ -717,6 +752,10 @@ bool ModApiMainMenu::mayModifyPath(std::string path) if (fs::PathStartsWith(path, fs::RemoveRelativePathComponents(porting::path_cache))) return true; + std::string path_share = fs::RemoveRelativePathComponents(porting::path_share); + if (fs::PathStartsWith(path, path_share + DIR_DELIM "builtin")) + return true; + return false; } @@ -806,30 +845,6 @@ int ModApiMainMenu::l_gettext(lua_State *L) return 1; } -/******************************************************************************/ -int ModApiMainMenu::l_get_screen_info(lua_State *L) -{ - lua_newtable(L); - int top = lua_gettop(L); - lua_pushstring(L,"density"); - lua_pushnumber(L,RenderingEngine::getDisplayDensity()); - lua_settable(L, top); - - const v2u32 &window_size = RenderingEngine::getWindowSize(); - lua_pushstring(L,"window_width"); - lua_pushnumber(L, window_size.X); - lua_settable(L, top); - - lua_pushstring(L,"window_height"); - lua_pushnumber(L, window_size.Y); - lua_settable(L, top); - - lua_pushstring(L, "render_info"); - lua_pushstring(L, wide_to_utf8(RenderingEngine::get_video_driver()->getName()).c_str()); - lua_settable(L, top); - return 1; -} - /******************************************************************************/ int ModApiMainMenu::l_get_min_supp_proto(lua_State *L) { @@ -880,6 +895,31 @@ int ModApiMainMenu::l_do_async_callback(lua_State *L) return 1; } +/******************************************************************************/ +int ModApiMainMenu::l_sleep_ms(lua_State *L) +{ + int delay = luaL_checkinteger(L, 1); + sleep_ms(delay); + return 0; +} + +/******************************************************************************/ +int ModApiMainMenu::l_load_translation(lua_State *L) +{ + const std::string tr_data = luaL_checkstring(L, 1); + g_client_translations->loadTranslation(tr_data); + return 0; +} + +/******************************************************************************/ +int ModApiMainMenu::l_get_translated_string(lua_State * L) +{ + std::string string = luaL_checkstring(L, 1); + string = wide_to_utf8(translate_string(utf8_to_wide(string), g_client_translations)); + lua_pushstring(L, string.c_str()); + return 1; +} + /******************************************************************************/ void ModApiMainMenu::Initialize(lua_State *L, int top) { @@ -904,8 +944,10 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_modpaths); API_FCT(get_clientmodpath); API_FCT(get_gamepath); + API_FCT(get_serverlistpath); API_FCT(get_texturepath); API_FCT(get_texturepath_share); + API_FCT(get_locale_path); API_FCT(get_cache_path); API_FCT(get_temp_path); API_FCT(create_dir); @@ -919,12 +961,13 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(download_file); API_FCT(gettext); API_FCT(get_video_drivers); - API_FCT(get_screen_info); API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); API_FCT(open_url); API_FCT(open_dir); API_FCT(do_async_callback); + API_FCT(load_translation); + API_FCT(get_translated_string); } /******************************************************************************/ @@ -938,6 +981,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(get_modpaths); API_FCT(get_clientmodpath); API_FCT(get_gamepath); + API_FCT(get_serverlistpath); API_FCT(get_texturepath); API_FCT(get_texturepath_share); API_FCT(get_cache_path); @@ -952,4 +996,5 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top) API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); API_FCT(gettext); + API_FCT(sleep_ms); } diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 400fa5a93..ad6c2c8c9 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -102,8 +102,6 @@ private: static int l_set_formspec_prepend(lua_State *L); - static int l_get_screen_info(lua_State *L); - //filesystem static int l_get_mainmenu_path(lua_State *L); @@ -118,10 +116,14 @@ private: static int l_get_gamepath(lua_State *L); + static int l_get_serverlistpath(lua_State *L); + static int l_get_texturepath(lua_State *L); static int l_get_texturepath_share(lua_State *L); + static int l_get_locale_path(lua_State *L); + static int l_get_cache_path(lua_State *L); static int l_get_temp_path(lua_State *L); @@ -156,6 +158,13 @@ private: // async static int l_do_async_callback(lua_State *L); + // MultiCraft + static int l_load_translation(lua_State *L); + + static int l_sleep_ms(lua_State *L); + + static int l_get_translated_string(lua_State *L); + public: /** diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index aa9b6b741..7b59ea243 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -177,6 +177,29 @@ Schematic *load_schematic(lua_State *L, int index, const NodeDefManager *ndef, schem = SchematicManager::create(SCHEMATIC_NORMAL); std::string filepath = lua_tostring(L, index); + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "cached_mts_files"); + lua_remove(L, -2); // Remove core + if (lua_istable(L, -1)) { + lua_getfield(L, -1, filepath.c_str()); + lua_remove(L, -2); // Remove cached_mts_files + if (lua_isstring(L, -1)) { + size_t len = 0; + const char *raw = lua_tolstring(L, -1, &len); + const std::string data = std::string(raw, len); + std::istringstream is(data); + lua_pop(L, 1); // Remove the schematic + if (!schem->loadSchematicFromStream(&is, filepath, ndef, + replace_names)) { + delete schem; + return NULL; + } + return schem; + } + } + lua_pop(L, 1); + if (!fs::IsPathAbsolute(filepath)) filepath = ModApiBase::getCurrentModPath(L) + DIR_DELIM + filepath; diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 07c99f9fc..ceaa728b9 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -242,7 +242,6 @@ int ModApiServer::l_get_player_information(lua_State *L) lua_pushstring(L, info.lang_code.c_str()); lua_settable(L, table); -#ifndef NDEBUG lua_pushstring(L,"serialization_version"); lua_pushnumber(L, info.ser_vers); lua_settable(L, table); @@ -263,10 +262,17 @@ int ModApiServer::l_get_player_information(lua_State *L) lua_pushstring(L, info.vers_string.c_str()); lua_settable(L, table); + lua_pushstring(L,"platform"); + lua_pushstring(L, info.platform.c_str()); + lua_settable(L, table); + + lua_pushstring(L,"sysinfo"); + lua_pushstring(L, info.sysinfo.c_str()); + lua_settable(L, table); + lua_pushstring(L,"state"); lua_pushstring(L, ClientInterface::state2Name(info.state).c_str()); lua_settable(L, table); -#endif return 1; } @@ -325,12 +331,15 @@ int ModApiServer::l_disconnect_player(lua_State *L) else message.append("Disconnected."); - RemotePlayer *player = dynamic_cast(getEnv(L))->getPlayer(name); - if (player == NULL) { + Server *server = getServer(L); + + RemotePlayer *player = server->getEnv().getPlayer(name); + if (!player) { lua_pushboolean(L, false); // No such player return 1; } - getServer(L)->DenyAccess_Legacy(player->getPeerId(), utf8_to_wide(message)); + + server->DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, message); lua_pushboolean(L, true); return 1; } @@ -506,6 +515,18 @@ int ModApiServer::l_dynamic_add_media(lua_State *L) return 1; } +// static_add_media(filepath, filename) +int ModApiServer::l_static_add_media(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + const std::string filename = luaL_checkstring(L, 1); + const std::string filepath = luaL_checkstring(L, 2); + + Server *server = getServer(L); + server->addMediaFile(filename, filepath); + return 0; +} + // is_singleplayer() int ModApiServer::l_is_singleplayer(lua_State *L) { @@ -547,6 +568,7 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(sound_stop); API_FCT(sound_fade); API_FCT(dynamic_add_media); + API_FCT(static_add_media); API_FCT(get_player_information); API_FCT(get_player_privs); diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index 511871ee0..a19c792d2 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -76,6 +76,9 @@ private: // dynamic_add_media(filepath) static int l_dynamic_add_media(lua_State *L); + // static_add_media(filename, filepath) + static int l_static_add_media(lua_State *L); + // get_player_privs(name, text) static int l_get_player_privs(lua_State *L); diff --git a/src/script/lua_api/l_storage.cpp b/src/script/lua_api/l_storage.cpp index 04e93f169..33fedcf42 100644 --- a/src/script/lua_api/l_storage.cpp +++ b/src/script/lua_api/l_storage.cpp @@ -25,7 +25,9 @@ with this program; if not, write to the Free Software Foundation, Inc., int ModApiStorage::l_get_mod_storage(lua_State *L) { - lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_CURRENT_MOD_NAME); + lua_getglobal(L, "core"); + lua_getfield(L, -1, "get_current_modname"); + lua_call(L, 0, 1); if (!lua_isstring(L, -1)) { return 0; } diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index d884b8889..ea68d7b6e 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -43,6 +43,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/png.h" #include +#ifndef SERVER +#include "client/renderingengine.h" +#endif + // log([level,] text) // Writes a line to the logger. // The one-argument version logs to LL_NONE. @@ -110,8 +114,12 @@ int ModApiUtil::l_parse_json(lua_State *L) size_t jlen = strlen(jsonstr); if (jlen > 100) { errorstream << "Data (" << jlen +#ifdef NDEBUG + << " bytes) not printed." << std::endl; +#else << " bytes) printed to warningstream." << std::endl; warningstream << "data: \"" << jsonstr << "\"" << std::endl; +#endif } else { errorstream << "data: \"" << jsonstr << "\"" << std::endl; } @@ -469,6 +477,8 @@ int ModApiUtil::l_get_version(lua_State *L) lua_setfield(L, table, "hash"); } + lua_pushboolean(L, DEVELOPMENT_BUILD); + lua_setfield(L, table, "is_dev"); return 1; } @@ -577,6 +587,66 @@ int ModApiUtil::l_set_last_run_mod(lua_State *L) return 0; } +#ifndef SERVER +int ModApiUtil::l_upgrade(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; +#ifdef __ANDROID__ + const std::string item_name = luaL_checkstring(L, 1); + porting::upgrade(item_name); + lua_pushboolean(L, true); +#else + // Not implemented on non-Android platforms + lua_pushnil(L); +#endif + + return 1; +} + +int ModApiUtil::l_get_secret_key(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; +#if defined(__ANDROID__) || defined(__IOS__) + const std::string secret_name = luaL_checkstring(L, 1); + const std::string res = porting::getSecretKey(secret_name); + lua_pushlstring(L, res.c_str(), res.size()); +#else + // Not implemented on desktop platforms + lua_pushstring(L, ""); +#endif + + return 1; +} + +int ModApiUtil::l_get_screen_info(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + lua_newtable(L); + int top = lua_gettop(L); + lua_pushstring(L,"density"); + lua_pushnumber(L,RenderingEngine::getDisplayDensity()); + lua_settable(L, top); + + lua_pushstring(L,"display_width"); + lua_pushnumber(L,RenderingEngine::getDisplaySize().X); + lua_settable(L, top); + + lua_pushstring(L,"display_height"); + lua_pushnumber(L,RenderingEngine::getDisplaySize().Y); + lua_settable(L, top); + + const v2u32 &window_size = RenderingEngine::getWindowSize(); + lua_pushstring(L,"window_width"); + lua_pushnumber(L, window_size.X); + lua_settable(L, top); + + lua_pushstring(L,"window_height"); + lua_pushnumber(L, window_size.Y); + lua_settable(L, top); + return 1; +} +#endif + void ModApiUtil::Initialize(lua_State *L, int top) { API_FCT(log); @@ -628,6 +698,7 @@ void ModApiUtil::Initialize(lua_State *L, int top) void ModApiUtil::InitializeClient(lua_State *L, int top) { +#ifndef SERVER API_FCT(log); API_FCT(get_us_time); @@ -647,6 +718,14 @@ void ModApiUtil::InitializeClient(lua_State *L, int top) API_FCT(sha1); API_FCT(colorspec_to_colorstring); API_FCT(colorspec_to_bytes); + + API_FCT(get_screen_info); + + LuaSettings::create(L, g_settings, g_settings_path); + lua_setfield(L, top, "settings"); +#else + FATAL_ERROR("InitializeClient called from server"); +#endif } void ModApiUtil::InitializeAsync(lua_State *L, int top) @@ -686,3 +765,14 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top) LuaSettings::create(L, g_settings, g_settings_path); lua_setfield(L, top, "settings"); } + +void ModApiUtil::InitializeMainMenu(lua_State *L, int top) { + Initialize(L, top); +#ifndef SERVER + API_FCT(upgrade); + API_FCT(get_secret_key); + API_FCT(get_screen_info); +#else + FATAL_ERROR("InitializeMainMenu called from server"); +#endif +} diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index 9f0863776..0903a66d4 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -125,10 +125,21 @@ private: // set_last_run_mod(modname) static int l_set_last_run_mod(lua_State *L); +#ifndef SERVER + // upgrade(string) + static int l_upgrade(lua_State *L); + + // get_secret_key(string) + static int l_get_secret_key(lua_State *L); + + static int l_get_screen_info(lua_State *L); +#endif + public: static void Initialize(lua_State *L, int top); static void InitializeAsync(lua_State *L, int top); static void InitializeClient(lua_State *L, int top); + static void InitializeMainMenu(lua_State *L, int top); static void InitializeAsync(AsyncEngine &engine); }; diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index bcc820def..a55928788 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_nodemeta.h" #include "lua_api/l_localplayer.h" #include "lua_api/l_camera.h" +#include "lua_api/l_settings.h" ClientScripting::ClientScripting(Client *client): ScriptApiBase(ScriptingType::Client) @@ -73,6 +74,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top) LuaLocalPlayer::Register(L); LuaCamera::Register(L); ModChannelRef::Register(L); + LuaSettings::Register(L); ModApiUtil::InitializeClient(L, top); ModApiClient::Initialize(L, top); diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index 7d93c3cc5..be005849e 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_base.h" #include "lua_api/l_http.h" #include "lua_api/l_mainmenu.h" +#include "lua_api/l_noise.h" #include "lua_api/l_sound.h" #include "lua_api/l_util.h" #include "lua_api/l_settings.h" @@ -65,7 +66,7 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top) // Initialize mod API modules ModApiMainMenu::Initialize(L, top); - ModApiUtil::Initialize(L, top); + ModApiUtil::InitializeMainMenu(L, top); ModApiSound::Initialize(L, top); ModApiHttp::Initialize(L, top); @@ -82,6 +83,7 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top) /******************************************************************************/ void MainMenuScripting::registerLuaClasses(lua_State *L, int top) { + LuaSecureRandom::Register(L); LuaSettings::Register(L); } diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp index ced4248e3..dd71f95bd 100644 --- a/src/script/scripting_server.cpp +++ b/src/script/scripting_server.cpp @@ -118,7 +118,9 @@ void ServerScripting::InitializeModApi(lua_State *L, int top) ModApiItemMod::Initialize(L, top); ModApiMapgen::Initialize(L, top); ModApiParticles::Initialize(L, top); +#if USE_SQLITE ModApiRollback::Initialize(L, top); +#endif ModApiServer::Initialize(L, top); ModApiUtil::Initialize(L, top); ModApiHttp::Initialize(L, top); diff --git a/src/server.cpp b/src/server.cpp index 29db0a3f2..ce8db9bf1 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -51,7 +51,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "modchannels.h" #include "serverlist.h" #include "util/string.h" +#if USE_SQLITE #include "rollback.h" +#endif #include "util/serialize.h" #include "util/thread.h" #include "defaultsettings.h" @@ -355,7 +357,9 @@ Server::~Server() // Delete things in the reverse order of creation delete m_emerge; delete m_env; +#if USE_SQLITE delete m_rollback; +#endif delete m_mod_storage_database; delete m_banmanager; delete m_itemdef; @@ -432,6 +436,17 @@ void Server::init() m_modmgr->loadMods(m_script); + // m_compat_player_models is used to prevent constant re-parsing of the + // setting + std::string player_models = g_settings->get("compat_player_model"); + player_models.erase(std::remove_if(player_models.begin(), + player_models.end(), static_cast(&std::isspace)), + player_models.end()); + if (player_models.empty() || isSingleplayer()) + FATAL_ERROR_IF(!m_compat_player_models.empty(), "Compat player models list not empty"); + else + m_compat_player_models = str_split(player_models, ','); + // Read Textures and calculate sha1 sums fillMediaCache(); @@ -472,10 +487,12 @@ void Server::init() // Initialize mapgens m_emerge->initMapgens(servermap->getMapgenParams()); +#if USE_SQLITE if (g_settings->getBool("enable_rollback_recording")) { // Create rollback manager m_rollback = new RollbackManager(m_path_world, this); } +#endif // Give environment reference to scripting api m_script->initializeEnvironment(m_env); @@ -483,7 +500,13 @@ void Server::init() // Register us to receive map edit events servermap->addEventReceiver(this); - m_env->loadMeta(); + try { + m_env->loadMeta(); + } catch (SerializationError &e) { + warningstream << "Environment metadata is corrupted: " << e.what() << std::endl; + warningstream << "Loading the default instead" << std::endl; + m_env->loadDefaultMeta(); + } // Those settings can be overwritten in world.mt, they are // intended to be cached after environment loading. @@ -510,14 +533,6 @@ void Server::start() // Start thread m_thread->start(); - // ASCII art for the win! - std::cerr - << " __. __. __. " << std::endl - << " _____ |__| ____ _____ / |_ _____ _____ / |_ " << std::endl - << " / \\| |/ \\ / __ \\ _\\/ __ \\/ __> _\\" << std::endl - << "| Y Y \\ | | \\ ___/| | | ___/\\___ \\| | " << std::endl - << "|__|_| / |___| /\\______> | \\______>_____/| | " << std::endl - << " \\/ \\/ \\/ \\/ \\/ " << std::endl; actionstream << "World at [" << m_path_world << "]" << std::endl; actionstream << "Server for gameid=\"" << m_gamespec.id << "\" listening on "; @@ -697,7 +712,7 @@ void Server::AsyncRunStep(bool initial_step) // send masterserver announce { float &counter = m_masterserver_timer; - if (!isSingleplayer() && (!counter || counter >= 300.0) && + if (!isSingleplayer() && (!counter || counter >= 60.0) && g_settings->getBool("server_announce")) { ServerList::sendAnnounce(counter ? ServerList::AA_UPDATE : ServerList::AA_START, @@ -828,7 +843,11 @@ void Server::AsyncRunStep(bool initial_step) // u16 id // std::string data buffer.append(idbuf, sizeof(idbuf)); - buffer.append(serializeString16(aom.datastring)); + if (client->net_proto_version >= 37 || + aom.legacystring.empty()) + buffer.append(serializeString16(aom.datastring)); + else + buffer.append(serializeString16(aom.legacystring)); } } /* @@ -1036,8 +1055,7 @@ void Server::Receive() } catch (const ClientStateError &e) { errorstream << "ProcessData: peer=" << peer_id << " what()=" << e.what() << std::endl; - DenyAccess_Legacy(peer_id, L"Your client sent something server didn't expect." - L"Try reconnecting or updating your client"); + DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA); } catch (const con::PeerNotFoundException &e) { // Do nothing } catch (const con::NoIncomingDataException &e) { @@ -1070,15 +1088,13 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) if (player && player->getPeerId() != PEER_ID_INEXISTENT) { actionstream << "Server: Failed to emerge player \"" << playername << "\" (player allocated to an another client)" << std::endl; - DenyAccess_Legacy(peer_id, L"Another client is connected with this " - L"name. If your client closed unexpectedly, try again in " - L"a minute."); + DenyAccess(peer_id, SERVER_ACCESSDENIED_ALREADY_CONNECTED); } else { errorstream << "Server: " << playername << ": Failed to emerge player" << std::endl; - DenyAccess_Legacy(peer_id, L"Could not allocate player."); + DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL); } - return NULL; + return nullptr; } /* @@ -1142,18 +1158,16 @@ void Server::ProcessData(NetworkPacket *pkt) Address address = getPeerAddress(peer_id); std::string addr_s = address.serializeString(); - if(m_banmanager->isIpBanned(addr_s)) { + // FIXME: Isn't it a bit excessive to check this for every packet? + if (m_banmanager->isIpBanned(addr_s)) { std::string ban_name = m_banmanager->getBanName(addr_s); infostream << "Server: A banned client tried to connect from " - << addr_s << "; banned name was " - << ban_name << std::endl; - // This actually doesn't seem to transfer to the client - DenyAccess_Legacy(peer_id, L"Your ip is banned. Banned name was " - + utf8_to_wide(ban_name)); + << addr_s << "; banned name was " << ban_name << std::endl; + DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, + "Your IP is banned. Banned name was " + ban_name); return; } - } - catch(con::PeerNotFoundException &e) { + } catch (con::PeerNotFoundException &e) { /* * no peer for this packet found * most common reason is peer timeout, e.g. peer didn't @@ -1204,6 +1218,9 @@ void Server::ProcessData(NetworkPacket *pkt) return; } + RemoteClient *client = getClient(peer_id); + if (client) + pkt->setProtocolVersion(client->net_proto_version); handleCommand(pkt); } catch (SendFailedException &e) { errorstream << "Server::ProcessData(): SendFailedException: " @@ -1211,7 +1228,7 @@ void Server::ProcessData(NetworkPacket *pkt) << std::endl; } catch (PacketError &e) { actionstream << "Server::ProcessData(): PacketError: " - << "what=" << e.what() + << "what=" << e.what() << ", command=" << pkt->getCommand() // TODO: REMOVE COMMAND= << std::endl; } } @@ -1285,6 +1302,8 @@ bool Server::getClientInfo(session_t peer_id, ClientInfo &ret) ret.minor = client->getMinor(); ret.patch = client->getPatch(); ret.vers_string = client->getFullVer(); + ret.platform = client->getPlatform(); + ret.sysinfo = client->getSysInfo(); ret.lang_code = client->getLangCode(); @@ -1344,11 +1363,11 @@ void Server::Send(session_t peer_id, NetworkPacket *pkt) clientCommandFactoryTable[pkt->getCommand()].reliable); } -void Server::SendMovement(session_t peer_id) +void Server::SendMovement(session_t peer_id, u16 protocol_version) { std::ostringstream os(std::ios_base::binary); - NetworkPacket pkt(TOCLIENT_MOVEMENT, 12 * sizeof(float), peer_id); + NetworkPacket pkt(TOCLIENT_MOVEMENT, 12 * sizeof(float), peer_id, protocol_version); pkt << g_settings->getFloat("movement_acceleration_default"); pkt << g_settings->getFloat("movement_acceleration_air"); @@ -1386,7 +1405,13 @@ void Server::SendPlayerHP(PlayerSAO *playersao) void Server::SendHP(session_t peer_id, u16 hp) { NetworkPacket pkt(TOCLIENT_HP, 1, peer_id); - pkt << hp; + // Minetest 0.4 uses 8-bit integers for HPP. + if (m_clients.getProtocolVersion(peer_id) >= 37) { + pkt << hp; + } else { + u8 raw_hp = hp & 0xFF; + pkt << raw_hp; + } Send(&pkt); } @@ -1412,13 +1437,6 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason, Send(&pkt); } -void Server::SendAccessDenied_Legacy(session_t peer_id,const std::wstring &reason) -{ - NetworkPacket pkt(TOCLIENT_ACCESS_DENIED_LEGACY, 0, peer_id); - pkt << reason; - Send(&pkt); -} - void Server::SendDeathscreen(session_t peer_id, bool set_camera_point_target, v3f camera_point_target) { @@ -1505,6 +1523,9 @@ void Server::SendInventory(PlayerSAO *sao, bool incremental) void Server::SendChatMessage(session_t peer_id, const ChatMessage &message) { + NetworkPacket legacypkt(TOCLIENT_CHAT_MESSAGE_OLD, 0, peer_id); + legacypkt << message.message; + NetworkPacket pkt(TOCLIENT_CHAT_MESSAGE, 0, peer_id); u8 version = 1; u8 type = message.type; @@ -1516,9 +1537,12 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message) if (!player) return; - Send(&pkt); + if (player->protocol_version < 35) + Send(&legacypkt); + else + Send(&pkt); } else { - m_clients.sendToAll(&pkt); + m_clients.sendToAllCompat(&pkt, &legacypkt, 35); } } @@ -1536,7 +1560,11 @@ void Server::SendShowFormspecMessage(session_t peer_id, const std::string &forms pkt.putLongString(""); } else { m_formspec_state_data[peer_id] = formname; - pkt.putLongString(formspec); + RemotePlayer *player = m_env->getPlayer(peer_id); + if (player && player->protocol_version < 37) + pkt.putLongString(insert_formspec_prepend(formspec, player->formspec_prepend)); + else + pkt.putLongString(formspec); } pkt << formname; @@ -1574,7 +1602,7 @@ void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version, } assert(protocol_version != 0); - NetworkPacket pkt(TOCLIENT_SPAWN_PARTICLE, 0, peer_id); + NetworkPacket pkt(TOCLIENT_SPAWN_PARTICLE, 0, peer_id, protocol_version); { // NetworkPacket and iostreams are incompatible... @@ -1621,7 +1649,7 @@ void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version, } assert(protocol_version != 0); - NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id); + NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id, protocol_version); pkt << p.amount << p.time << p.minpos << p.maxpos << p.minvel << p.maxvel << p.minacc << p.maxacc << p.minexptime << p.maxexptime @@ -1656,7 +1684,7 @@ void Server::SendDeleteParticleSpawner(session_t peer_id, u32 id) void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form) { - NetworkPacket pkt(TOCLIENT_HUDADD, 0 , peer_id); + NetworkPacket pkt(TOCLIENT_HUDADD, 0 , peer_id, m_clients.getProtocolVersion(peer_id)); pkt << id << (u8) form->type << form->pos << form->name << form->scale << form->text << form->number << form->item << form->dir @@ -1675,7 +1703,7 @@ void Server::SendHUDRemove(session_t peer_id, u32 id) void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void *value) { - NetworkPacket pkt(TOCLIENT_HUDCHANGE, 0, peer_id); + NetworkPacket pkt(TOCLIENT_HUDCHANGE, 0, peer_id, m_clients.getProtocolVersion(peer_id)); pkt << id << (u8) stat; switch (stat) { @@ -1784,7 +1812,8 @@ void Server::SendSetStars(session_t peer_id, const StarParams ¶ms) void Server::SendCloudParams(session_t peer_id, const CloudParams ¶ms) { - NetworkPacket pkt(TOCLIENT_CLOUD_PARAMS, 0, peer_id); + NetworkPacket pkt(TOCLIENT_CLOUD_PARAMS, 0, peer_id, + m_clients.getProtocolVersion(peer_id)); pkt << params.density << params.color_bright << params.color_ambient << params.height << params.thickness << params.speed; Send(&pkt); @@ -1803,15 +1832,20 @@ void Server::SendOverrideDayNightRatio(session_t peer_id, bool do_override, void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) { - NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id); - pkt << time << time_speed; + if (peer_id != PEER_ID_INEXISTENT) { + NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id, + m_clients.getProtocolVersion(peer_id)); + pkt << time << time_speed; - if (peer_id == PEER_ID_INEXISTENT) { - m_clients.sendToAll(&pkt); - } - else { Send(&pkt); + return; } + + NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id, 37); + NetworkPacket legacypkt(TOCLIENT_TIME_OF_DAY, 0, peer_id, 32); + pkt << time << time_speed; + legacypkt << time << time_speed; + m_clients.sendToAllCompat(&pkt, &legacypkt, 37); } void Server::SendPlayerBreath(PlayerSAO *sao) @@ -1832,7 +1866,7 @@ void Server::SendMovePlayer(session_t peer_id) // Send attachment updates instantly to the client prior updating position sao->sendOutdatedData(); - NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, peer_id); + NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, peer_id, player->protocol_version); pkt << sao->getBasePosition() << sao->getLookPitch() << sao->getRotation().Y; { @@ -1861,7 +1895,7 @@ void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames f32 animation_speed) { NetworkPacket pkt(TOCLIENT_LOCAL_PLAYER_ANIMATIONS, 0, - peer_id); + peer_id, m_clients.getProtocolVersion(peer_id)); pkt << animation_frames[0] << animation_frames[1] << animation_frames[2] << animation_frames[3] << animation_speed; @@ -1871,7 +1905,7 @@ void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames void Server::SendEyeOffset(session_t peer_id, v3f first, v3f third) { - NetworkPacket pkt(TOCLIENT_EYE_OFFSET, 0, peer_id); + NetworkPacket pkt(TOCLIENT_EYE_OFFSET, 0, peer_id, m_clients.getProtocolVersion(peer_id)); pkt << first << third; Send(&pkt); } @@ -1904,7 +1938,11 @@ void Server::SendPlayerInventoryFormspec(session_t peer_id) return; NetworkPacket pkt(TOCLIENT_INVENTORY_FORMSPEC, 0, peer_id); - pkt.putLongString(player->inventory_formspec); + if (player->protocol_version < 37) + pkt.putLongString(insert_formspec_prepend(player->inventory_formspec, + player->formspec_prepend)); + else + pkt.putLongString(player->inventory_formspec); Send(&pkt); } @@ -1915,6 +1953,10 @@ void Server::SendPlayerFormspecPrepend(session_t peer_id) assert(player); if (player->getPeerId() == PEER_ID_INEXISTENT) return; + if (player->protocol_version < 37) { + SendPlayerInventoryFormspec(peer_id); + return; + } NetworkPacket pkt(TOCLIENT_FORMSPEC_PREPEND, 0, peer_id); pkt << player->formspec_prepend; @@ -2035,6 +2077,10 @@ void Server::SendActiveObjectMessages(session_t peer_id, const std::string &data void Server::SendCSMRestrictionFlags(session_t peer_id) { + const u16 protocol_version = m_clients.getProtocolVersion(peer_id); + if (protocol_version < 35 && protocol_version != 0) + return; + NetworkPacket pkt(TOCLIENT_CSM_RESTRICTION_FLAGS, sizeof(m_csm_restriction_flags) + sizeof(m_csm_restriction_noderange), peer_id); pkt << m_csm_restriction_flags << m_csm_restriction_noderange; @@ -2126,17 +2172,29 @@ s32 Server::playSound(const SimpleSoundSpec &spec, float gain = params.gain * spec.gain; NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0); + NetworkPacket legacypkt(TOCLIENT_PLAY_SOUND, 0, PEER_ID_INEXISTENT, 32); pkt << id << spec.name << gain << (u8) params.type << pos << params.object << params.loop << params.fade << params.pitch << ephemeral; + legacypkt << id << spec.name << gain + << (u8) params.type << pos << params.object + << params.loop << params.fade; bool as_reliable = !ephemeral; + bool play_sound = gain > 0; for (const u16 dst_client : dst_clients) { + const u16 protocol_version = m_clients.getProtocolVersion(dst_client); + if (!play_sound && protocol_version < 32) + continue; if (psound) psound->clients.insert(dst_client); - m_clients.send(dst_client, 0, &pkt, as_reliable); + + if (protocol_version >= 37) + m_clients.send(dst_client, 0, &pkt, as_reliable); + else + m_clients.send(dst_client, 0, &legacypkt, as_reliable); } return id; } @@ -2297,6 +2355,13 @@ void Server::sendMetadataChanged(const std::list &meta_updates, float far if (!client) continue; + if (client->net_proto_version < 37) { + for (const v3s16 &pos : meta_updates) { + client->SetBlockNotSent(getNodeBlockPos(pos)); + } + continue; + } + ServerActiveObject *player = m_env->getActiveObject(i); v3f player_pos = player ? player->getBasePosition() : v3f(); @@ -2343,7 +2408,13 @@ void Server::SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, */ thread_local const int net_compression_level = rangelim(g_settings->getS16("map_compression_level_net"), -1, 9); std::ostringstream os(std::ios_base::binary); - block->serialize(os, ver, false, net_compression_level); + + RemotePlayer *player = m_env->getPlayer(peer_id); + if (player && player->protocol_version < 37) + block->serialize(os, ver, false, net_compression_level, + player->formspec_prepend); + else + block->serialize(os, ver, false, net_compression_level); block->serializeNetworkSpecific(os); std::string s = os.str(); @@ -2437,6 +2508,76 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos) return true; } +// Hacks because I don't want to make duplicate read/write functions for little +// endian numbers. +u32 readU32_le(std::istream &is) { + char buf[4] = {0}; + is.read(buf, sizeof(buf)); + std::reverse(buf, buf + sizeof(buf)); + return readU32((u8 *)buf); +} + +v3f readV3F32_le(std::istream &is) { + char buf[12] = {0}; + is.read(buf, sizeof(buf)); + std::reverse(buf, buf + 4); + std::reverse(buf + 4, buf + 8); + std::reverse(buf + 8, buf + 12); + return readV3F32((u8 *)buf); +} + +void writeV3F32_le(std::ostream &os, v3f pos) { + char buf[12]; + writeV3F32((u8 *)buf, pos); + std::reverse(buf, buf + 4); + std::reverse(buf + 4, buf + 8); + std::reverse(buf + 8, buf + 12); + os.write(buf, sizeof(buf)); +} + +// Converts MT 5+ player models into MT 0.4 compatible models +std::string makeCompatPlayerModel(std::string b3d) { + std::stringstream ss(b3d); + + // ss.read(4) != "BB3D" + const u32 header = readU32_le(ss); + if (header != 0x44334242) { + warningstream << "Invalid B3D header in player model: " << header << std::endl; + return ""; + } + + readU32(ss); // Length + readU32(ss); // Version + + // Look for the node + while (ss.good()) { + const u32 name = readU32_le(ss); + const u32 length = readU32_le(ss); + + // name != "NODE" + if (name != 0x45444f4e) { + ss.ignore(length); + continue; + } + + // Node name + ss.ignore(length, '\x00'); + + // Node position + std::streampos p = ss.tellg(); + const v3f offset_pos = readV3F32_le(ss) - v3f(0, BS, 0); + + // Write the new position back to the stringstream + ss.seekp(p); + writeV3F32_le(ss, offset_pos); + + return ss.str(); + } + + warningstream << "Could not find base position in B3D file" << std::endl; + return ""; +} + bool Server::addMediaFile(const std::string &filename, const std::string &filepath, std::string *filedata_to, std::string *digest_to) @@ -2467,7 +2608,7 @@ bool Server::addMediaFile(const std::string &filename, std::string filedata; if (!fs::ReadFile(filepath, filedata)) { errorstream << "Server::addMediaFile(): Failed to open \"" - << filename << "\" for reading" << std::endl; + << filepath << "\" for reading" << std::endl; return false; } @@ -2482,15 +2623,38 @@ bool Server::addMediaFile(const std::string &filename, unsigned char *digest = sha1.getDigest(); std::string sha1_base64 = base64_encode(digest, 20); - std::string sha1_hex = hex_encode((char*) digest, 20); if (digest_to) *digest_to = std::string((char*) digest, 20); free(digest); // Put in list m_media[filename] = MediaInfo(filepath, sha1_base64); - verbosestream << "Server: " << sha1_hex << " is " << filename - << std::endl; + + // Add a compatibility model if required + if (isCompatPlayerModel(filename)) { + // Offset the mesh + const std::string filedata_compat = makeCompatPlayerModel(filedata); + if (filedata_compat != "") { + SHA1 sha1; + sha1.addBytes(filedata_compat.c_str(), filedata_compat.length()); + unsigned char *digest = sha1.getDigest(); + std::string sha1_base64 = base64_encode(digest, 20); + free(digest); + + // If the original model is being sent then rename the + // compatibility one so it doesn't conflict. The renamed model is + // used in player_sao.cpp if the setting is enabled. + std::string fn_compat = filename; + if (g_settings->getBool("compat_send_original_model")) { + fn_compat = "_mc_compat_" + fn_compat; + + // Add a dummy m_media entry + m_media[fn_compat] = MediaInfo("", ""); + } + + m_compat_media[fn_compat] = InMemoryMediaInfo(filedata_compat, sha1_base64); + } + } if (filedata_to) *filedata_to = std::move(filedata); @@ -2532,6 +2696,8 @@ void Server::fillMediaCache() void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code) { + const u16 protocol_version = m_clients.getProtocolVersion(peer_id); + // Make packet NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id); @@ -2543,6 +2709,9 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co continue; if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix)) continue; + // Skip dummy entries on 5.0+ clients + if (protocol_version >= 37 && i.second.sha1_digest.empty()) + continue; media_sent++; } @@ -2553,10 +2722,24 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co continue; if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix)) continue; - pkt << i.first << i.second.sha1_digest; + if (protocol_version >= 37 && i.second.sha1_digest.empty()) + continue; + + pkt << i.first; + + if (protocol_version < 37 && + m_compat_media.find(i.first) != m_compat_media.end()) { + pkt << m_compat_media[i.first].sha1_digest; + } else { + FATAL_ERROR_IF(i.second.sha1_digest.empty(), "Attempt to send dummy media"); + pkt << i.second.sha1_digest; + } } pkt << g_settings->get("remote_media"); + if (g_settings->getBool("disable_texture_packs")) + pkt << true; + Send(&pkt); verbosestream << "Server: Announcing files to id(" << peer_id @@ -2570,7 +2753,7 @@ struct SendableMedia std::string data; SendableMedia(const std::string &name, const std::string &path, - std::string &&data): + const std::string &data): name(name), path(path), data(std::move(data)) {} }; @@ -2591,6 +2774,7 @@ void Server::sendRequestedMedia(session_t peer_id, u32 file_size_bunch_total = 0; + const u16 protocol_version = m_clients.getProtocolVersion(peer_id); for (const std::string &name : tosend) { if (m_media.find(name) == m_media.end()) { errorstream<<"Server::sendRequestedMedia(): Client asked for " @@ -2600,6 +2784,20 @@ void Server::sendRequestedMedia(session_t peer_id, const auto &m = m_media[name]; + // Use compatibility media on older clients + if (protocol_version < 37 && + m_compat_media.find(name) != m_compat_media.end()) { + file_bunches[file_bunches.size()-1].emplace_back(name, m.path, + m_compat_media[name].data); + continue; + } + + if (m.path.empty()) { + errorstream<<"Server::sendRequestedMedia(): New client asked for " + <<"compatibility media file \""<<(name)<<"\""<(os_str.size()); // HACK: to keep compatibility with 5.0.0 clients pkt.putRawString(os_str); + legacy_pkt.putRawString(os_str); } - if (peer_id == PEER_ID_INEXISTENT) - m_clients.sendToAll(&pkt); - else - Send(&pkt); + if (peer_id == PEER_ID_INEXISTENT) { + m_clients.newSendToAll(&pkt); + if (inventory) + m_clients.oldSendToAll(&legacy_pkt); + } else { + RemoteClient *client = getClientNoEx(peer_id, CS_Created); + if (!client) { + warningstream << "Could not get client in sendDetachedInventory!" + << std::endl; + } + + if (!client || client->net_proto_version >= 37) + Send(&pkt); + else if (inventory) + Send(&legacy_pkt); + } } void Server::sendDetachedInventories(session_t peer_id, bool incremental) @@ -2772,7 +2985,11 @@ void Server::RespawnPlayer(session_t peer_id) bool repositioned = m_script->on_respawnplayer(playersao); if (!repositioned) { // setPos will send the new position to client - playersao->setPos(findSpawnPos()); + const v3f pos = findSpawnPos(); + actionstream << "Moving " << playersao->getPlayer()->getName() << + " to spawnpoint at (" << pos.X << ", " << pos.Y << ", " << + pos.Z << ")" << std::endl; + playersao->setPos(pos); } } @@ -2784,29 +3001,10 @@ void Server::DenySudoAccess(session_t peer_id) } -void Server::DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason, bool reconnect) -{ - SendAccessDenied(peer_id, reason, str_reason, reconnect); - - m_clients.event(peer_id, CSE_SetDenied); - DisconnectPeer(peer_id); -} - - void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason) + const std::string &custom_reason, bool reconnect) { - SendAccessDenied(peer_id, reason, custom_reason); - m_clients.event(peer_id, CSE_SetDenied); - DisconnectPeer(peer_id); -} - -// 13/03/15: remove this function when protocol version 25 will become -// the minimum version for MT users, maybe in 1 year -void Server::DenyAccess_Legacy(session_t peer_id, const std::wstring &reason) -{ - SendAccessDenied_Legacy(peer_id, reason); + SendAccessDenied(peer_id, reason, custom_reason, reconnect); m_clients.event(peer_id, CSE_SetDenied); DisconnectPeer(peer_id); } @@ -2822,7 +3020,7 @@ void Server::acceptAuth(session_t peer_id, bool forSudoMode) if (!forSudoMode) { RemoteClient* client = getClient(peer_id, CS_Invalid); - NetworkPacket resp_pkt(TOCLIENT_AUTH_ACCEPT, 1 + 6 + 8 + 4, peer_id); + NetworkPacket resp_pkt(TOCLIENT_AUTH_ACCEPT, 1 + 6 + 8 + 4, peer_id, client->net_proto_version); // Right now, the auth mechs don't change between login and sudo mode. u32 sudo_auth_mechs = client->allowed_auth_mechs; @@ -2975,14 +3173,17 @@ void Server::handleChatInterfaceEvent(ChatEvent *evt) std::wstring Server::handleChat(const std::string &name, std::wstring wmessage, bool check_shout_priv, RemotePlayer *player) { +#if USE_SQLITE // If something goes wrong, this player is to blame RollbackScopeActor rollback_scope(m_rollback, std::string("player:") + name); +#endif if (g_settings->getBool("strip_color_codes")) wmessage = unescape_enriched(wmessage); - if (player) { + const bool sscsm_com = wmessage.find(L"/admin \x01SSCSM_COM\x01", 0) == 0; + if (player && !sscsm_com) { switch (player->canSendChatMessage()) { case RPLAYER_CHATRESULT_FLOODING: { std::wstringstream ws; @@ -2992,8 +3193,8 @@ std::wstring Server::handleChat(const std::string &name, return ws.str(); } case RPLAYER_CHATRESULT_KICK: - DenyAccess_Legacy(player->getPeerId(), - L"You have been kicked due to message flooding."); + DenyAccess(player->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, + "You have been kicked due to message flooding."); return L""; case RPLAYER_CHATRESULT_OK: break; @@ -3017,8 +3218,11 @@ std::wstring Server::handleChat(const std::string &name, } // Run script hook, exit if script ate the chat message - if (m_script->on_chat_message(name, message)) + if (m_script->on_chat_message(name, message)) { + if (!sscsm_com) + actionstream << name << " issued command: " << message << std::endl; return L""; + } // Line to send std::wstring line; @@ -3106,6 +3310,7 @@ PlayerSAO *Server::getPlayerSAO(session_t peer_id) std::string Server::getStatusString() { std::ostringstream os(std::ios_base::binary); + os << "# Server: "; // Version os << "version: " << g_version_string; @@ -3117,8 +3322,9 @@ std::string Server::getStatusString() os << " | max lag: " << std::setprecision(3); os << (m_env ? m_env->getMaxLagEstimate() : 0) << "s"; + // Disabled due to misuse. // Information about clients - bool first = true; + /*bool first = true; os << " | clients: "; if (m_env) { std::vector clients = m_clients.getClientIDs(); @@ -3135,7 +3341,7 @@ std::string Server::getStatusString() first = false; os << name; } - } + }*/ if (m_env && !((ServerMap*)(&m_env->getMap()))->isSavingEnabled()) os << std::endl << "# Server: " << " WARNING: Map saving is disabled."; @@ -3596,6 +3802,7 @@ bool Server::dynamicAddMedia(std::string filepath, // actions: time-reversed list // Return value: success/failure +#if USE_SQLITE bool Server::rollbackRevertActions(const std::list &actions, std::list *log) { @@ -3637,6 +3844,7 @@ bool Server::rollbackRevertActions(const std::list &actions, // Call it done if less than half failed return num_failed <= num_tried/2; } +#endif // IGameDef interface // Under envlock @@ -3699,7 +3907,8 @@ v3f Server::findSpawnPos() { ServerMap &map = m_env->getServerMap(); v3f nodeposf; - if (g_settings->getV3FNoEx("static_spawnpoint", nodeposf)) + if (g_settings->getV3FNoEx("static_spawnpoint", nodeposf) || + m_env->getWorldSpawnpoint(nodeposf)) return nodeposf * BS; bool is_good = false; diff --git a/src/server.h b/src/server.h index f3eb44f44..ac2c70acc 100644 --- a/src/server.h +++ b/src/server.h @@ -94,6 +94,19 @@ struct MediaInfo } }; +struct InMemoryMediaInfo +{ + std::string data; + std::string sha1_digest; + + InMemoryMediaInfo(const std::string &data_="", + const std::string &sha1_digest_=""): + data(data_), + sha1_digest(sha1_digest_) + { + } +}; + struct ServerSoundParams { enum Type { @@ -137,7 +150,7 @@ struct ClientInfo { u8 ser_vers; u16 prot_vers; u8 major, minor, patch; - std::string vers_string, lang_code; + std::string vers_string, platform, sysinfo, lang_code; }; class Server : public con::PeerHandler, public MapEventReceiver, @@ -263,6 +276,8 @@ public: bool dynamicAddMedia(std::string filepath, u32 token, const std::string &to_player, bool ephemeral); + bool addMediaFile(const std::string &filename, const std::string &filepath, + std::string *filedata = nullptr, std::string *digest = nullptr); ServerInventoryManager *getInventoryMgr() const { return m_inventory_mgr.get(); } void sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id); @@ -338,12 +353,9 @@ public: void deletingPeer(con::Peer *peer, bool timeout); void DenySudoAccess(session_t peer_id); - void DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, - const std::string &str_reason = "", bool reconnect = false); void DenyAccess(session_t peer_id, AccessDeniedCode reason, - const std::string &custom_reason = ""); + const std::string &custom_reason = "", bool reconnect = false); void acceptAuth(session_t peer_id, bool forSudoMode); - void DenyAccess_Legacy(session_t peer_id, const std::wstring &reason); void DisconnectPeer(session_t peer_id); bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval); bool getClientInfo(session_t peer_id, ClientInfo &ret); @@ -392,6 +404,15 @@ public: // Environment mutex (envlock) std::mutex m_env_mutex; + inline bool isCompatPlayerModel(const std::string &model_name) + { + return std::find(m_compat_player_models.begin(), m_compat_player_models.end(), model_name) != m_compat_player_models.end(); + } + const std::vector getCompatPlayerModels() + { + return m_compat_player_models; + } + private: friend class EmergeThread; friend class RemoteClient; @@ -421,7 +442,7 @@ private: void init(); - void SendMovement(session_t peer_id); + void SendMovement(session_t peer_id, u16 protocol_version); void SendHP(session_t peer_id, u16 hp); void SendBreath(session_t peer_id, u16 breath); void SendAccessDenied(session_t peer_id, AccessDeniedCode reason, @@ -483,8 +504,6 @@ private: // Sends blocks to clients (locks env and con on its own) void SendBlocks(float dtime); - bool addMediaFile(const std::string &filename, const std::string &filepath, - std::string *filedata = nullptr, std::string *digest = nullptr); void fillMediaCache(); void sendMediaAnnouncement(session_t peer_id, const std::string &lang_code); void sendRequestedMedia(session_t peer_id, @@ -673,6 +692,7 @@ private: // media files known to server std::unordered_map m_media; + std::unordered_map m_compat_media; // pending dynamic media callbacks, clients inform the server when they have a file fetched std::unordered_map m_pending_dyn_media; @@ -711,6 +731,8 @@ private: MetricCounterPtr m_aom_buffer_counter; MetricCounterPtr m_packet_recv_counter; MetricCounterPtr m_packet_recv_processed_counter; + + std::vector m_compat_player_models; }; /* diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp index 3680a47cd..c1b85d16f 100644 --- a/src/server/luaentity_sao.cpp +++ b/src/server/luaentity_sao.cpp @@ -131,9 +131,10 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) if(!m_properties_sent) { m_properties_sent = true; - std::string str = getPropertyPacket(); + std::string str = getPropertyPacket(37); + std::string legacy_str = getPropertyPacket(32); // create message and add to list - m_messages_out.emplace(getId(), true, str); + m_messages_out.emplace(getId(), true, str, legacy_str); } // If attached, check that our parent is still there. If it isn't, detach. @@ -238,19 +239,26 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) os << serializeString16(""); // name writeU8(os, 0); // is_player writeU16(os, getId()); //id - writeV3F32(os, m_base_position); - writeV3F32(os, m_rotation); + writeV3F(os, m_base_position, protocol_version); + if (protocol_version >= 37) + writeV3F32(os, m_rotation); + else + writeF1000(os, m_rotation.Y); writeU16(os, m_hp); std::ostringstream msg_os(std::ios::binary); - msg_os << serializeString32(getPropertyPacket()); // message 1 + msg_os << serializeString32(getPropertyPacket( + protocol_version)); // message 1 msg_os << serializeString32(generateUpdateArmorGroupsCommand()); // 2 - msg_os << serializeString32(generateUpdateAnimationCommand()); // 3 + msg_os << serializeString32(generateUpdateAnimationCommand( + protocol_version)); // 3 for (const auto &bone_pos : m_bone_position) { msg_os << serializeString32(generateUpdateBonePositionCommand( - bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // 3 + N + bone_pos.first, bone_pos.second.X, bone_pos.second.Y, + protocol_version)); // 3 + N } - msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + m_bone_position.size + msg_os << serializeString32(generateUpdateAttachmentCommand( + protocol_version)); // 4 + m_bone_position.size int message_count = 4 + m_bone_position.size(); @@ -484,9 +492,9 @@ std::string LuaEntitySAO::getName() return m_init_name; } -std::string LuaEntitySAO::getPropertyPacket() +std::string LuaEntitySAO::getPropertyPacket(const u16 protocol_version) { - return generateSetPropertiesCommand(m_prop); + return generateSetPropertiesCommand(m_prop, protocol_version); } void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) @@ -515,10 +523,23 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) m_rotation, do_interpolate, is_movement_end, - update_interval + update_interval, + 37 ); + + std::string legacy_str = generateUpdatePositionCommand( + m_base_position, + m_velocity, + m_acceleration, + m_rotation, + do_interpolate, + is_movement_end, + update_interval, + 32 + ); + // create message and add to list - m_messages_out.emplace(getId(), false, str); + m_messages_out.emplace(getId(), false, str, legacy_str); } bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const diff --git a/src/server/luaentity_sao.h b/src/server/luaentity_sao.h index d80d368f5..31b97b157 100644 --- a/src/server/luaentity_sao.h +++ b/src/server/luaentity_sao.h @@ -78,7 +78,7 @@ protected: virtual void onMarkedForRemoval() { dispatchScriptDeactivate(); } private: - std::string getPropertyPacket(); + std::string getPropertyPacket(const u16 protocol_version); void sendPosition(bool do_interpolate, bool is_movement_end); std::string generateSetTextureModCommand() const; static std::string generateSetSpriteCommand(v2s16 p, u16 num_frames, diff --git a/src/server/mods.cpp b/src/server/mods.cpp index b9aaaca02..9c1856db5 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "scripting_server.h" #include "content/subgames.h" #include "porting.h" +#include "settings.h" #include "util/metricsbackend.h" /** @@ -59,13 +60,43 @@ void ServerModManager::loadMods(ServerScripting *script) infostream << mod.name << " "; } infostream << std::endl; + + const bool log_mem = g_settings->getBool("log_mod_memory_usage_on_load"); + // Load and run "mod" scripts for (const ModSpec &mod : m_sorted_mods) { mod.checkAndLog(); std::string script_path = mod.path + DIR_DELIM + "init.lua"; auto t = porting::getTimeMs(); - script->loadMod(script_path, mod.name); + + // This is behind a setting since getMemoryUsageKB calls + // collectgarbage() first which will slow down load times. + size_t old_usage = log_mem ? script->getMemoryUsageKB() : 0; + + try { + script->loadMod(script_path, mod.name); + } catch (ModError &e) { + // Only re-throw the error if the file exists + // The PathExists check is done here since init.lua is expected to + // exist so checking it first would just waste time + if (fs::PathExists(script_path)) + throw; + + errorstream << "Ignoring invalid mod directory: " << e.what() << std::endl; + continue; + } + + if (log_mem) { + size_t new_usage = script->getMemoryUsageKB(); + actionstream << "Mod \"" << mod.name << "\" loaded, "; + if (new_usage >= old_usage) + actionstream << "using " << (new_usage - old_usage); + else + actionstream << "somehow freeing " << (old_usage - new_usage); + actionstream << "KB of memory" << std::endl; + } + infostream << "Mod \"" << mod.name << "\" loaded after " << (porting::getTimeMs() - t) << " ms" << std::endl; } diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 9b13e3ea9..faa2eaea5 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -113,20 +113,37 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) os << serializeString16(m_player->getName()); // name writeU8(os, 1); // is_player writeS16(os, getId()); // id - writeV3F32(os, m_base_position); - writeV3F32(os, m_rotation); - writeU16(os, getHP()); + if (protocol_version >= 37) { + writeV3F32(os, m_base_position); + writeV3F32(os, m_rotation); + writeU16(os, getHP()); + } else { + writeV3F1000(os, m_base_position + v3f(0, BS, 0)); + writeF1000(os, m_rotation.Y); + + // HP is sent as a signed integer + const u16 hp = getHP(); + if (hp > S16_MAX) + writeS16(os, S16_MAX); + else + writeS16(os, static_cast(hp)); + } std::ostringstream msg_os(std::ios::binary); - msg_os << serializeString32(getPropertyPacket()); // message 1 + msg_os << serializeString32(getPropertyPacket( + protocol_version)); // message 1 msg_os << serializeString32(generateUpdateArmorGroupsCommand()); // 2 - msg_os << serializeString32(generateUpdateAnimationCommand()); // 3 + msg_os << serializeString32(generateUpdateAnimationCommand( + protocol_version)); // 3 for (const auto &bone_pos : m_bone_position) { msg_os << serializeString32(generateUpdateBonePositionCommand( - bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // 3 + N + bone_pos.first, bone_pos.second.X, bone_pos.second.Y, + protocol_version)); // 3 + N } - msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + m_bone_position.size - msg_os << serializeString32(generateUpdatePhysicsOverrideCommand()); // 5 + m_bone_position.size + msg_os << serializeString32(generateUpdateAttachmentCommand( + protocol_version)); // 4 + m_bone_position.size + msg_os << serializeString32(generateUpdatePhysicsOverrideCommand( + protocol_version)); // 5 + m_bone_position.size int message_count = 5 + m_bone_position.size(); @@ -159,14 +176,16 @@ void PlayerSAO::step(float dtime, bool send_recommended) MapNode n = m_env->getMap().getNode(p); const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); // If node generates drown - if (c.drowning > 0 && m_hp > 0) { + const bool noclip = m_privs.count("noclip") && (!m_is_singleplayer || g_settings->getBool("noclip")); + int drowning = (c.walkable && c.drawtype != NDT_NODEBOX && c.drawtype != NDT_AIRLIKE && c.drawtype != NDT_PLANTLIKE) ? 1 : c.drowning; + if (drowning > 0 && m_hp > 0 && !noclip) { if (m_breath > 0) setBreath(m_breath - 1); // No more breath, damage player if (m_breath == 0) { PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING); - setHP(m_hp - c.drowning, reason); + setHP(m_hp - drowning, reason); } } } @@ -177,7 +196,8 @@ void PlayerSAO::step(float dtime, bool send_recommended) MapNode n = m_env->getMap().getNode(p); const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); // If player is alive & not drowning & not in ignore & not immortal, breathe - if (m_breath < m_prop.breath_max && c.drowning == 0 && + int drowning = (c.walkable && c.drawtype != NDT_NODEBOX) ? 1 : c.drowning; + if (m_breath < m_prop.breath_max && drowning == 0 && n.getContent() != CONTENT_IGNORE && m_hp > 0) setBreath(m_breath + 1); } @@ -220,9 +240,10 @@ void PlayerSAO::step(float dtime, bool send_recommended) if (!m_properties_sent) { m_properties_sent = true; - std::string str = getPropertyPacket(); + std::string str = getPropertyPacket(37); + std::string legacy_str = getPropertyPacket(32); // create message and add to list - m_messages_out.emplace(getId(), true, str); + m_messages_out.emplace(getId(), true, str, legacy_str); m_env->getScriptIface()->player_event(this, "properties_changed"); } @@ -288,30 +309,45 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_rotation, true, false, - update_interval + update_interval, + 37 ); + + std::string legacy_str = generateUpdatePositionCommand( + pos + v3f(0.0f, BS, 0.0f), + v3f(0.0f, 0.0f, 0.0f), + v3f(0.0f, 0.0f, 0.0f), + m_rotation, + true, + false, + update_interval, + 32 + ); + // create message and add to list - m_messages_out.emplace(getId(), false, str); + m_messages_out.emplace(getId(), false, str, legacy_str); } if (!m_physics_override_sent) { m_physics_override_sent = true; // create message and add to list - m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand()); + m_messages_out.emplace(getId(), true, + generateUpdatePhysicsOverrideCommand(37), + generateUpdatePhysicsOverrideCommand(32)); } sendOutdatedData(); } -std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const +std::string PlayerSAO::generateUpdatePhysicsOverrideCommand(const u16 protocol_version) const { std::ostringstream os(std::ios::binary); // command writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE); // parameters - writeF32(os, m_physics_override_speed); - writeF32(os, m_physics_override_jump); - writeF32(os, m_physics_override_gravity); + writeF(os, m_physics_override_speed, protocol_version); + writeF(os, m_physics_override_jump, protocol_version); + writeF(os, m_physics_override_gravity, protocol_version); // these are sent inverted so we get true when the server sends nothing writeU8(os, !m_physics_override_sneak); writeU8(os, !m_physics_override_sneak_glitch); @@ -550,10 +586,24 @@ void PlayerSAO::unlinkPlayerSessionAndSave() m_env->removePlayer(m_player); } -std::string PlayerSAO::getPropertyPacket() +std::string PlayerSAO::getPropertyPacket(const u16 protocol_version) { m_prop.is_visible = (true); - return generateSetPropertiesCommand(m_prop); + + ObjectProperties prop = m_prop; + + // Use the renamed model if compat_send_original_model is enabled + if (protocol_version < 37 && m_env->getCompatSendOriginalModel() && + m_env->isCompatPlayerModel(m_prop.mesh)) { + prop.mesh = "_mc_compat_" + m_prop.mesh; + } + + // Remove a one-node offset from a copy of the object properties for MT 0.4 + if (protocol_version < 37) { + prop.selectionbox.MinEdge.Y -= 1.0f; + prop.selectionbox.MaxEdge.Y -= 1.0f; + } + return generateSetPropertiesCommand(prop, protocol_version); } void PlayerSAO::setMaxSpeedOverride(const v3f &vel) diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 578a25f14..77ba2a5b6 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -184,9 +184,10 @@ public: inline Metadata &getMeta() { return m_meta; } private: - std::string getPropertyPacket(); + std::string getPropertyPacket(const u16 protocol_version); void unlinkPlayerSessionAndSave(); - std::string generateUpdatePhysicsOverrideCommand() const; + std::string generateUpdatePhysicsOverrideCommand( + const u16 protocol_version) const; RemotePlayer *m_player = nullptr; session_t m_peer_id = 0; diff --git a/src/server/serverinventorymgr.h b/src/server/serverinventorymgr.h index be360f42f..7ff7db319 100644 --- a/src/server/serverinventorymgr.h +++ b/src/server/serverinventorymgr.h @@ -43,7 +43,8 @@ public: Inventory *createDetachedInventory(const std::string &name, IItemDefManager *idef, const std::string &player = ""); bool removeDetachedInventory(const std::string &name); - bool checkDetachedInventoryAccess(const InventoryLocation &loc, const std::string &player) const; + bool checkDetachedInventoryAccess( + const InventoryLocation &loc, const std::string &player) const; void sendDetachedInventories(const std::string &peer_name, bool incremental, std::function apply_cb); diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index 1a136e512..0a50a257f 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -102,24 +102,34 @@ void UnitSAO::sendOutdatedData() if (!m_animation_sent) { m_animation_sent = true; m_animation_speed_sent = true; - m_messages_out.emplace(getId(), true, generateUpdateAnimationCommand()); + m_messages_out.emplace(getId(), true, + generateUpdateAnimationCommand(37), + generateUpdateAnimationCommand(32)); } else if (!m_animation_speed_sent) { // Animation speed is also sent when 'm_animation_sent == false' m_animation_speed_sent = true; - m_messages_out.emplace(getId(), true, generateUpdateAnimationSpeedCommand()); + m_messages_out.emplace(getId(), true, + generateUpdateAnimationSpeedCommand(), + // MT 0.4 has no update animation speed command + generateUpdateAnimationCommand(32)); } if (!m_bone_position_sent) { m_bone_position_sent = true; for (const auto &bone_pos : m_bone_position) { - m_messages_out.emplace(getId(), true, generateUpdateBonePositionCommand( - bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); + std::string str = generateUpdateBonePositionCommand( + bone_pos.first, bone_pos.second.X, bone_pos.second.Y, 37); + std::string legacy_str = generateUpdateBonePositionCommand( + bone_pos.first, bone_pos.second.X, bone_pos.second.Y, 32); + m_messages_out.emplace(getId(), true, str, legacy_str); } } if (!m_attachment_sent) { m_attachment_sent = true; - m_messages_out.emplace(getId(), true, generateUpdateAttachmentCommand()); + m_messages_out.emplace(getId(), true, + generateUpdateAttachmentCommand(37), + generateUpdateAttachmentCommand(32)); } } // clang-format on @@ -258,7 +268,7 @@ void UnitSAO::notifyObjectPropertiesModified() m_properties_sent = false; } -std::string UnitSAO::generateUpdateAttachmentCommand() const +std::string UnitSAO::generateUpdateAttachmentCommand(const u16 protocol_version) const { std::ostringstream os(std::ios::binary); // command @@ -266,22 +276,38 @@ std::string UnitSAO::generateUpdateAttachmentCommand() const // parameters writeS16(os, m_attachment_parent_id); os << serializeString16(m_attachment_bone); - writeV3F32(os, m_attachment_position); - writeV3F32(os, m_attachment_rotation); + + // Add/remove offsets to compensate for MT 0.4 + if (protocol_version >= 37) { + writeV3F32(os, m_attachment_position); + } else { + v3f compat_attachment_position = m_attachment_position; + if (getType() == ACTIVEOBJECT_TYPE_PLAYER) { + compat_attachment_position.Y += BS; + } else { + ServerActiveObject *p = + m_env->getActiveObject(m_attachment_parent_id); + if (p && p->getType() == ACTIVEOBJECT_TYPE_PLAYER) + compat_attachment_position.Y -= BS; + } + writeV3F1000(os, compat_attachment_position); + } + + writeV3F(os, m_attachment_rotation, protocol_version); writeU8(os, m_force_visible); return os.str(); } -std::string UnitSAO::generateUpdateBonePositionCommand( - const std::string &bone, const v3f &position, const v3f &rotation) +std::string UnitSAO::generateUpdateBonePositionCommand(const std::string &bone, + const v3f &position, const v3f &rotation, const u16 protocol_version) { std::ostringstream os(std::ios::binary); // command writeU8(os, AO_CMD_SET_BONE_POSITION); // parameters os << serializeString16(bone); - writeV3F32(os, position); - writeV3F32(os, rotation); + writeV3F(os, position, protocol_version); + writeV3F(os, rotation, protocol_version); return os.str(); } @@ -295,15 +321,15 @@ std::string UnitSAO::generateUpdateAnimationSpeedCommand() const return os.str(); } -std::string UnitSAO::generateUpdateAnimationCommand() const +std::string UnitSAO::generateUpdateAnimationCommand(const u16 protocol_version) const { std::ostringstream os(std::ios::binary); // command writeU8(os, AO_CMD_SET_ANIMATION); // parameters - writeV2F32(os, m_animation_range); - writeF32(os, m_animation_speed); - writeF32(os, m_animation_blend); + writeV2F(os, m_animation_range, protocol_version); + writeF(os, m_animation_speed, protocol_version); + writeF(os, m_animation_blend, protocol_version); // these are sent inverted so we get true when the server sends nothing writeU8(os, !m_animation_loop); return os.str(); @@ -323,33 +349,38 @@ std::string UnitSAO::generateUpdateArmorGroupsCommand() const std::string UnitSAO::generateUpdatePositionCommand(const v3f &position, const v3f &velocity, const v3f &acceleration, const v3f &rotation, - bool do_interpolate, bool is_movement_end, f32 update_interval) + bool do_interpolate, bool is_movement_end, f32 update_interval, + const u16 protocol_version) { std::ostringstream os(std::ios::binary); // command writeU8(os, AO_CMD_UPDATE_POSITION); // pos - writeV3F32(os, position); + writeV3F(os, position, protocol_version); // velocity - writeV3F32(os, velocity); + writeV3F(os, velocity, protocol_version); // acceleration - writeV3F32(os, acceleration); + writeV3F(os, acceleration, protocol_version); // rotation - writeV3F32(os, rotation); + if (protocol_version >= 37) + writeV3F32(os, rotation); + else + writeF1000(os, rotation.Y); // do_interpolate writeU8(os, do_interpolate); // is_end_position (for interpolation) writeU8(os, is_movement_end); // update_interval (for interpolation) - writeF32(os, update_interval); + writeF(os, update_interval, protocol_version); return os.str(); } -std::string UnitSAO::generateSetPropertiesCommand(const ObjectProperties &prop) const +std::string UnitSAO::generateSetPropertiesCommand( + const ObjectProperties &prop, const u16 protocol_version) const { std::ostringstream os(std::ios::binary); writeU8(os, AO_CMD_SET_PROPERTIES); - prop.serialize(os); + prop.serialize(os, protocol_version); return os.str(); } @@ -363,7 +394,19 @@ std::string UnitSAO::generatePunchCommand(u16 result_hp) const return os.str(); } +std::string UnitSAO::generateLegacyPunchCommand(u16 result_hp) const +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_PUNCHED); + // result_hp + writeU16(os, result_hp); + return os.str(); +} + void UnitSAO::sendPunchCommand() { - m_messages_out.emplace(getId(), true, generatePunchCommand(getHP())); + const u16 result_hp = getHP(); + m_messages_out.emplace(getId(), true, generatePunchCommand(result_hp), + generateLegacyPunchCommand(result_hp)); } diff --git a/src/server/unit_sao.h b/src/server/unit_sao.h index 0afcfd5d8..8f421dc3d 100644 --- a/src/server/unit_sao.h +++ b/src/server/unit_sao.h @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "constants.h" #include "object_properties.h" #include "serveractiveobject.h" @@ -79,16 +80,19 @@ public: void sendOutdatedData(); // Update packets - std::string generateUpdateAttachmentCommand() const; + std::string generateUpdateAttachmentCommand(const u16 protocol_version) const; std::string generateUpdateAnimationSpeedCommand() const; - std::string generateUpdateAnimationCommand() const; + std::string generateUpdateAnimationCommand(const u16 protocol_version) const; std::string generateUpdateArmorGroupsCommand() const; static std::string generateUpdatePositionCommand(const v3f &position, const v3f &velocity, const v3f &acceleration, const v3f &rotation, - bool do_interpolate, bool is_movement_end, f32 update_interval); - std::string generateSetPropertiesCommand(const ObjectProperties &prop) const; + bool do_interpolate, bool is_movement_end, f32 update_interval, + const u16 protocol_version); + std::string generateSetPropertiesCommand( + const ObjectProperties &prop, const u16 protocol_version) const; static std::string generateUpdateBonePositionCommand(const std::string &bone, - const v3f &position, const v3f &rotation); + const v3f &position, const v3f &rotation, + const u16 protocol_version); void sendPunchCommand(); protected: @@ -112,6 +116,7 @@ private: void onDetach(int parent_id); std::string generatePunchCommand(u16 result_hp) const; + std::string generateLegacyPunchCommand(u16 result_hp) const; // Armor groups bool m_armor_groups_sent = false; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index d92275a67..796939b79 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -40,7 +40,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gameparams.h" #include "database/database-dummy.h" #include "database/database-files.h" +#if USE_SQLITE #include "database/database-sqlite3.h" +#endif #if USE_POSTGRESQL #include "database/database-postgresql.h" #endif @@ -405,8 +407,13 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, std::string conf_path = path_world + DIR_DELIM + "world.mt"; Settings conf; +#if !defined(__ANDROID__) && !defined(__APPLE__) std::string player_backend_name = "sqlite3"; std::string auth_backend_name = "sqlite3"; +#else + std::string player_backend_name = "leveldb"; + std::string auth_backend_name = "leveldb"; +#endif bool succeeded = conf.readConfigFile(conf_path.c_str()); @@ -444,6 +451,7 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, } } +#ifdef SERVER if (player_backend_name == "files") { warningstream << "/!\\ You are using old player file backend. " << "This backend is deprecated and will be removed in a future release /!\\" @@ -454,12 +462,16 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, if (auth_backend_name == "files") { warningstream << "/!\\ You are using old auth file backend. " << "This backend is deprecated and will be removed in a future release /!\\" - << std::endl << "Switching to SQLite3 is advised, " + << std::endl << "Switching to LevelDB or SQLite3 is advised, " << "please read http://wiki.minetest.net/Database_backends." << std::endl; } +#endif m_player_database = openPlayerDatabase(player_backend_name, path_world, conf); m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf); + + m_compat_send_original_model = !server->getCompatPlayerModels().empty() && + g_settings->getBool("compat_send_original_model"); } ServerEnvironment::~ServerEnvironment() @@ -552,10 +564,8 @@ bool ServerEnvironment::removePlayerFromDatabase(const std::string &name) void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, const std::string &str_reason, bool reconnect) { - for (RemotePlayer *player : m_players) { - m_server->DenyAccessVerCompliant(player->getPeerId(), - player->protocol_version, reason, str_reason, reconnect); - } + for (RemotePlayer *player : m_players) + m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect); } void ServerEnvironment::saveLoadedPlayers(bool force) @@ -640,6 +650,10 @@ void ServerEnvironment::saveMeta() args.set("lbm_introduction_times", m_lbm_mgr.createIntroductionTimesString()); args.setU64("day_count", m_day_count); + + if (m_has_world_spawnpoint) + args.setV3F("static_spawnpoint", m_world_spawnpoint); + args.writeLines(ss); if(!fs::safeWriteToFile(path, ss.str())) @@ -691,7 +705,7 @@ void ServerEnvironment::loadMeta() setTimeOfDay(args.exists("time_of_day") ? // set day to early morning by default - args.getU64("time_of_day") : 5250); + args.getU64("time_of_day") : 6000); m_last_clear_objects_time = args.exists("last_clear_objects_time") ? // If missing, do as if clearObjects was never called @@ -713,6 +727,8 @@ void ServerEnvironment::loadMeta() m_day_count = args.exists("day_count") ? args.getU64("day_count") : 0; + + m_has_world_spawnpoint = args.getV3FNoEx("static_spawnpoint", m_world_spawnpoint); } /** @@ -1925,7 +1941,7 @@ void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s) <<" objects)"<m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block")); if (large_amount) { - errorstream<<"suspiciously large amount of objects detected: " + warningstream<<"suspiciously large amount of objects detected: " <m_static_objects.m_stored.size()<<" in " <getPos()) <<"; removing all of them."<isCompatPlayerModel(model_name); +} diff --git a/src/serverenvironment.h b/src/serverenvironment.h index e8aea3a6a..f2c025e2a 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "server/activeobjectmgr.h" #include "util/numeric.h" +#include #include #include @@ -373,13 +374,31 @@ public: AuthDatabase *getAuthDatabase() { return m_auth_database; } static bool migrateAuthDatabase(const GameParams &game_params, const Settings &cmd_args); -private: + + const bool isCompatPlayerModel(const std::string &model_name); + inline bool getCompatSendOriginalModel() { return m_compat_send_original_model; } /** * called if env_meta.txt doesn't exist (e.g. new world) */ void loadDefaultMeta(); + bool getWorldSpawnpoint(v3f &spawnpoint) { + if (m_has_world_spawnpoint) + spawnpoint = m_world_spawnpoint; + return m_has_world_spawnpoint; + } + + void setWorldSpawnpoint(const v3f &spawnpoint) { + m_world_spawnpoint = spawnpoint; + m_has_world_spawnpoint = true; + } + + void resetWorldSpawnpoint() { + m_has_world_spawnpoint = false; + } + +private: static PlayerDatabase *openPlayerDatabase(const std::string &name, const std::string &savedir, const Settings &conf); static AuthDatabase *openAuthDatabase(const std::string &name, @@ -487,5 +506,11 @@ private: std::unordered_map m_particle_spawners; std::unordered_map m_particle_spawner_attachments; + std::vector m_compat_player_models; + bool m_compat_send_original_model; + + v3f m_world_spawnpoint = v3f(0.f, 0.f, 0.f); + bool m_has_world_spawnpoint = false; + ServerActiveObject* createSAO(ActiveObjectType type, v3f pos, const std::string &data); }; diff --git a/src/serverlist.cpp b/src/serverlist.cpp index b74be2b7e..718a94402 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -27,10 +27,31 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "convert_json.h" #include "httpfetch.h" +#include "util/base64.h" namespace ServerList { #if USE_CURL +static const char *aa_names[] = {"start", "update", "delete"}; + +void sendAnnounceInner(const AnnounceAction action, const std::string &json, + const std::string &serverlist_url) { + if (action == AA_START) { + actionstream << "Announcing " << aa_names[action] << " to " << + serverlist_url << std::endl; + } else { + infostream << "Announcing " << aa_names[action] << " to " << + serverlist_url << std::endl; + } + + HTTPFetchRequest fetch_request; + fetch_request.url = serverlist_url + std::string("/announce"); + fetch_request.method = HTTP_POST; + fetch_request.fields["json"] = json; + fetch_request.multipart = true; + httpfetch_async(fetch_request); +} + void sendAnnounce(AnnounceAction action, const u16 port, const std::vector &clients_names, @@ -42,7 +63,6 @@ void sendAnnounce(AnnounceAction action, const std::vector &mods, bool dedicated) { - static const char *aa_names[] = {"start", "update", "delete"}; Json::Value server; server["action"] = aa_names[action]; server["port"] = port; @@ -51,10 +71,12 @@ void sendAnnounce(AnnounceAction action, } if (action != AA_DELETE) { bool strict_checking = g_settings->getBool("strict_protocol_version_checking"); + bool proto_compat = g_settings->getBool("enable_protocol_compat"); server["name"] = g_settings->get("server_name"); server["description"] = g_settings->get("server_description"); server["version"] = g_version_string; - server["proto_min"] = strict_checking ? LATEST_PROTOCOL_VERSION : SERVER_PROTOCOL_VERSION_MIN; + server["server_id"] = PROJECT_NAME; + server["proto_min"] = strict_checking ? LATEST_PROTOCOL_VERSION : (proto_compat ? SERVER_PROTOCOL_VERSION_MIN : SERVER_PROTOCOL_VERSION_MIN_NOCOMPAT); server["proto_max"] = strict_checking ? LATEST_PROTOCOL_VERSION : SERVER_PROTOCOL_VERSION_MAX; server["url"] = g_settings->get("server_url"); server["creative"] = g_settings->getBool("creative_mode"); @@ -69,8 +91,7 @@ void sendAnnounce(AnnounceAction action, for (const std::string &clients_name : clients_names) { server["clients_list"].append(clients_name); } - if (!gameid.empty()) - server["gameid"] = gameid; + server["gameid"] = "MultiCraft"; } if (action == AA_START) { @@ -79,30 +100,19 @@ void sendAnnounce(AnnounceAction action, server["mapgen"] = mg_name; server["privs"] = g_settings->get("default_privs"); server["can_see_far_names"] = g_settings->getS16("player_transfer_distance") <= 0; - server["mods"] = Json::Value(Json::arrayValue); + /*server["mods"] = Json::Value(Json::arrayValue); for (const ModSpec &mod : mods) { server["mods"].append(mod.name); - } + }*/ } else if (action == AA_UPDATE) { if (lag) server["lag"] = lag; } - if (action == AA_START) { - actionstream << "Announcing " << aa_names[action] << " to " << - g_settings->get("serverlist_url") << std::endl; - } else { - infostream << "Announcing " << aa_names[action] << " to " << - g_settings->get("serverlist_url") << std::endl; - } - - HTTPFetchRequest fetch_request; - fetch_request.caller = HTTPFETCH_PRINT_ERR; - fetch_request.url = g_settings->get("serverlist_url") + std::string("/announce"); - fetch_request.method = HTTP_POST; - fetch_request.fields["json"] = fastWriteJson(server); - fetch_request.multipart = true; - httpfetch_async(fetch_request); + const std::string json = fastWriteJson(server); + sendAnnounceInner(action, json, g_settings->get("serverlist_url")); + if (g_settings->getBool("announce_mt")) + sendAnnounceInner(action, json, base64_decode("c2VydmVycy5taW5ldGVzdC5uZXQ")); } #endif diff --git a/src/skyparams.h b/src/skyparams.h index 513dc1ad3..ce5f23bdd 100644 --- a/src/skyparams.h +++ b/src/skyparams.h @@ -101,13 +101,13 @@ public: { SkyColor sky; // Horizon colors - sky.day_horizon = video::SColor(255, 144, 211, 246); + sky.day_horizon = video::SColor(255, 5, 155, 245); sky.indoors = video::SColor(255, 100, 100, 100); - sky.dawn_horizon = video::SColor(255, 186, 193, 240); + sky.dawn_horizon = video::SColor(255, 180, 186, 255); sky.night_horizon = video::SColor(255, 64, 144, 255); // Sky colors - sky.day_sky = video::SColor(255, 97, 181, 245); - sky.dawn_sky = video::SColor(255, 180, 186, 250); + sky.day_sky = video::SColor(255, 5, 155, 245); + sky.dawn_sky = video::SColor(255, 180, 186, 255); sky.night_sky = video::SColor(255, 0, 107, 255); return sky; } diff --git a/src/sound.h b/src/sound.h index e26a84aaa..dcf2afa96 100644 --- a/src/sound.h +++ b/src/sound.h @@ -40,6 +40,12 @@ struct SimpleSoundSpec void serialize(std::ostream &os, u8 cf_version) const { os << serializeString16(name); + if (cf_version < 13) { + writeF1000(os, gain); + if (cf_version > 10) + writeF1000(os, pitch); + return; + } writeF32(os, gain); writeF32(os, pitch); writeF32(os, fade); diff --git a/src/terminal_chat_console.h b/src/terminal_chat_console.h index 067d560af..5b971f975 100644 --- a/src/terminal_chat_console.h +++ b/src/terminal_chat_console.h @@ -19,8 +19,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "IrrCompileConfig.h" + #include "chat.h" +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include "threading/sdl_thread.h" +#else #include "threading/thread.h" +#endif #include "util/container.h" #include "log.h" #include diff --git a/src/threading/CMakeLists.txt b/src/threading/CMakeLists.txt index 8f86158be..5b7ded611 100644 --- a/src/threading/CMakeLists.txt +++ b/src/threading/CMakeLists.txt @@ -2,5 +2,7 @@ set(JTHREAD_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/event.cpp ${CMAKE_CURRENT_SOURCE_DIR}/thread.cpp ${CMAKE_CURRENT_SOURCE_DIR}/semaphore.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sdl_semaphore.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sdl_thread.cpp PARENT_SCOPE) diff --git a/src/threading/sdl_semaphore.cpp b/src/threading/sdl_semaphore.cpp new file mode 100644 index 000000000..2c8379f36 --- /dev/null +++ b/src/threading/sdl_semaphore.cpp @@ -0,0 +1,71 @@ +/* +MultiCraft +Copyright (C) 2013 sapier +Copyright (C) 2023 Dawid Gan + +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 3.0 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 "threading/sdl_semaphore.h" + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + +#include + +#define UNUSED(expr) \ + do { \ + (void)(expr); \ + } while (0) + +Semaphore::Semaphore(int val) +{ + semaphore = SDL_CreateSemaphore(val); +} + +Semaphore::~Semaphore() +{ + SDL_DestroySemaphore(semaphore); +} + +void Semaphore::post(unsigned int num) +{ + assert(num > 0); + for (unsigned i = 0; i < num; i++) { + int ret = SDL_SemPost(semaphore); + assert(!ret); + UNUSED(ret); + } +} + +void Semaphore::wait() +{ + int ret = SDL_SemWait(semaphore); + assert(!ret); + UNUSED(ret); +} + +bool Semaphore::wait(unsigned int time_ms) +{ + int ret; + if (time_ms > 0) { + ret = SDL_SemWaitTimeout(semaphore, time_ms); + } else { + ret = SDL_SemTryWait(semaphore); + } + assert(!ret || ret == SDL_MUTEX_TIMEDOUT); + return !ret; +} + +#endif diff --git a/src/threading/sdl_semaphore.h b/src/threading/sdl_semaphore.h new file mode 100644 index 000000000..534943910 --- /dev/null +++ b/src/threading/sdl_semaphore.h @@ -0,0 +1,48 @@ +/* +MultiCraft +Copyright (C) 2013 sapier +Copyright (C) 2023 Dawid Gan + +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 3.0 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 "IrrCompileConfig.h" + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + +#include "porting.h" +#include "util/basic_macros.h" + +#include + +class Semaphore +{ +public: + Semaphore(int val = 0); + ~Semaphore(); + + DISABLE_CLASS_COPY(Semaphore); + + void post(unsigned int num = 1); + void wait(); + bool wait(unsigned int time_ms); + +private: + SDL_sem *semaphore; +}; + +#endif diff --git a/src/threading/sdl_thread.cpp b/src/threading/sdl_thread.cpp new file mode 100644 index 000000000..d80f7ae85 --- /dev/null +++ b/src/threading/sdl_thread.cpp @@ -0,0 +1,153 @@ +/* +This file is a part of the JThread package, which contains some object- +oriented thread wrappers for different thread implementations. + +Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com) +Copyright (c) 2023 Dawid Gan + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "threading/sdl_thread.h" +#include "threading/mutex_auto_lock.h" +#include "log.h" +#include "porting.h" + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + +Thread::Thread(const std::string &name) : + m_name(name), m_request_stop(false), m_running(false) +{ +} + +Thread::~Thread() +{ + m_running = false; + wait(); + + // Make sure start finished mutex is unlocked before it's destroyed + if (m_start_finished_mutex.try_lock()) + m_start_finished_mutex.unlock(); +} + +bool Thread::start() +{ + MutexAutoLock lock(m_mutex); + + if (m_running) + return false; + + m_request_stop = false; + + // The mutex may already be locked if the thread is being restarted + m_start_finished_mutex.try_lock(); + + m_thread_obj = SDL_CreateThread(&threadProc, m_name.c_str(), this); + + if (!m_thread_obj) + return false; + + // Allow spawned thread to continue + m_start_finished_mutex.unlock(); + + while (!m_running) + sleep_ms(1); + + m_joinable = true; + + return true; +} + +bool Thread::stop() +{ + m_request_stop = true; + return true; +} + +bool Thread::wait() +{ + MutexAutoLock lock(m_mutex); + + if (!m_joinable) + return false; + + SDL_WaitThread(m_thread_obj, NULL); + + m_thread_obj = nullptr; + + assert(m_running == false); + m_joinable = false; + return true; +} + +bool Thread::getReturnValue(void **ret) +{ + if (m_running) + return false; + + *ret = m_retval; + return true; +} + +int Thread::threadProc(void *data) +{ + Thread *thr = (Thread *)data; + + g_logger.registerThread(thr->m_name); + thr->m_running = true; + + // Wait for the thread that started this one to finish initializing the + // thread handle so that getThreadId/getThreadHandle will work. + thr->m_start_finished_mutex.lock(); + + thr->m_retval = thr->run(); + + thr->m_running = false; + // Unlock m_start_finished_mutex to prevent data race condition on Windows. + // On Windows with VS2017 build TerminateThread is called and this mutex is not + // released. We try to unlock it from caller thread and it's refused by system. + thr->m_start_finished_mutex.unlock(); + g_logger.deregisterThread(); + + return 0; +} + +void Thread::setName(const std::string &name) +{ + // Name can be set only during thread creation. +} + +unsigned int Thread::getNumberOfProcessors() +{ + return SDL_GetCPUCount(); +} + +bool Thread::bindToProcessor(unsigned int proc_number) +{ + // Not available in SDL + return false; +} + +bool Thread::setPriority(SDL_ThreadPriority prio) +{ + int result = SDL_SetThreadPriority(prio); + return result == 0; +} + +#endif diff --git a/src/threading/sdl_thread.h b/src/threading/sdl_thread.h new file mode 100644 index 000000000..c38be503a --- /dev/null +++ b/src/threading/sdl_thread.h @@ -0,0 +1,130 @@ +/* +This file is a part of the JThread package, which contains some object- +oriented thread wrappers for different thread implementations. + +Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com) +Copyright (c) 2023 Dawid Gan + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#pragma once + +#include "IrrCompileConfig.h" + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + +#include "util/basic_macros.h" + +#include +#include +#include + +#include + +class Thread +{ +public: + Thread(const std::string &name = ""); + virtual ~Thread(); + DISABLE_CLASS_COPY(Thread) + + /* + * Begins execution of a new thread at the pure virtual method Thread::run(). + * Execution of the thread is guaranteed to have started after this function + * returns. + */ + bool start(); + + /* + * Requests that the thread exit gracefully. + * Returns immediately; thread execution is guaranteed to be complete after + * a subsequent call to Thread::wait. + */ + bool stop(); + + /* + * Waits for thread to finish. + * Note: This does not stop a thread, you have to do this on your own. + * Returns false immediately if the thread is not started or has been waited + * on before. + */ + bool wait(); + + /* + * Returns true if the calling thread is this Thread object. + */ + bool isCurrentThread() { return SDL_ThreadID() == getThreadId(); } + + bool isRunning() { return m_running; } + bool stopRequested() { return m_request_stop; } + + SDL_threadID getThreadId() { return SDL_GetThreadID(m_thread_obj); } + + /* + * Gets the thread return value. + * Returns true if the thread has exited and the return value was available, + * or false if the thread has yet to finish. + */ + bool getReturnValue(void **ret); + + /* + * Binds (if possible, otherwise sets the affinity of) the thread to the + * specific processor specified by proc_number. + */ + bool bindToProcessor(unsigned int proc_number); + + /* + * Sets the thread priority to the specified priority. + * + * prio can be one of: SDL_THREAD_PRIORITY_LOW, SDL_THREAD_PRIORITY_NORMAL, + * SDL_THREAD_PRIORITY_HIGH. + */ + bool setPriority(SDL_ThreadPriority prio); + + /* + * Sets the currently executing thread's name to where supported; useful + * for debugging. + */ + static void setName(const std::string &name); + + /* + * Returns the number of processors/cores configured and active on this machine. + */ + static unsigned int getNumberOfProcessors(); + +protected: + std::string m_name; + + virtual void *run() = 0; + +private: + static int threadProc(void *data); + + void *m_retval = nullptr; + bool m_joinable = false; + std::atomic m_request_stop; + std::atomic m_running; + std::mutex m_mutex; + std::mutex m_start_finished_mutex; + + SDL_Thread *m_thread_obj = nullptr; +}; + +#endif diff --git a/src/threading/semaphore.cpp b/src/threading/semaphore.cpp index 87b7af5b1..0dcb93e51 100644 --- a/src/threading/semaphore.cpp +++ b/src/threading/semaphore.cpp @@ -19,6 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "threading/semaphore.h" +#ifndef _IRR_COMPILE_WITH_SDL_DEVICE_ + #include #include #include @@ -165,3 +167,4 @@ bool Semaphore::wait(unsigned int time_ms) #endif } +#endif diff --git a/src/threading/semaphore.h b/src/threading/semaphore.h index b3725c672..79bb4f939 100644 --- a/src/threading/semaphore.h +++ b/src/threading/semaphore.h @@ -19,6 +19,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "IrrCompileConfig.h" + +#ifndef _IRR_COMPILE_WITH_SDL_DEVICE_ + #if defined(_WIN32) #include #elif defined(__MACH__) && defined(__APPLE__) @@ -50,3 +54,5 @@ private: sem_t semaphore; #endif }; + +#endif diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index 5cfc60995..b40eeecdb 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -28,6 +28,8 @@ DEALINGS IN THE SOFTWARE. #include "log.h" #include "porting.h" +#ifndef _IRR_COMPILE_WITH_SDL_DEVICE_ + // for setName #if defined(__linux__) #include @@ -345,3 +347,4 @@ bool Thread::setPriority(int prio) #endif } +#endif diff --git a/src/threading/thread.h b/src/threading/thread.h index 45fb171da..c18a42a23 100644 --- a/src/threading/thread.h +++ b/src/threading/thread.h @@ -25,6 +25,10 @@ DEALINGS IN THE SOFTWARE. #pragma once +#include "IrrCompileConfig.h" + +#ifndef _IRR_COMPILE_WITH_SDL_DEVICE_ + #include "util/basic_macros.h" #include @@ -158,3 +162,4 @@ private: #endif }; +#endif diff --git a/src/tileanimation.cpp b/src/tileanimation.cpp index 79a7ad0a7..89a04a388 100644 --- a/src/tileanimation.cpp +++ b/src/tileanimation.cpp @@ -21,15 +21,27 @@ with this program; if not, write to the Free Software Foundation, Inc., void TileAnimationParams::serialize(std::ostream &os, u8 tiledef_version) const { + if (tiledef_version < 3 && type != TAT_VERTICAL_FRAMES) { + writeU8(os, TAT_NONE); + writeU16(os, 1); + writeU16(os, 1); + writeF1000(os, 1.0f); + return; + } + writeU8(os, type); + + // Approximate protocol version + const u16 protocol_version = tiledef_version >= 6 ? 37 : 32; + if (type == TAT_VERTICAL_FRAMES) { writeU16(os, vertical_frames.aspect_w); writeU16(os, vertical_frames.aspect_h); - writeF32(os, vertical_frames.length); + writeF(os, vertical_frames.length, protocol_version); } else if (type == TAT_SHEET_2D) { writeU8(os, sheet_2d.frames_w); writeU8(os, sheet_2d.frames_h); - writeF32(os, sheet_2d.frame_length); + writeF(os, sheet_2d.frame_length, protocol_version); } } @@ -40,11 +52,11 @@ void TileAnimationParams::deSerialize(std::istream &is, u8 tiledef_version) if (type == TAT_VERTICAL_FRAMES) { vertical_frames.aspect_w = readU16(is); vertical_frames.aspect_h = readU16(is); - vertical_frames.length = readF32(is); + vertical_frames.length = fabs(readF32(is)); } else if (type == TAT_SHEET_2D) { sheet_2d.frames_w = readU8(is); sheet_2d.frames_h = readU8(is); - sheet_2d.frame_length = readF32(is); + sheet_2d.frame_length = fabs(readF32(is)); } } diff --git a/src/tool.cpp b/src/tool.cpp index f07f19e71..e4d658839 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -59,9 +59,11 @@ void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const { if (protocol_version >= 38) writeU8(os, 5); - else + else if (protocol_version == 37) writeU8(os, 4); // proto == 37 - writeF32(os, full_punch_interval); + else + writeU8(os, 2); // proto >= 18 + writeF(os, full_punch_interval, protocol_version); writeS16(os, max_drop_level); writeU32(os, groupcaps.size()); for (const auto &groupcap : groupcaps) { @@ -73,7 +75,7 @@ void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const writeU32(os, cap->times.size()); for (const auto &time : cap->times) { writeS16(os, time.first); - writeF32(os, time.second); + writeF(os, time.second, protocol_version); } } @@ -380,4 +382,3 @@ f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_h return max_d; } - diff --git a/src/unittest/test_threading.cpp b/src/unittest/test_threading.cpp index f3d7fb060..5f2d69789 100644 --- a/src/unittest/test_threading.cpp +++ b/src/unittest/test_threading.cpp @@ -19,9 +19,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "test.h" +#include "IrrCompileConfig.h" + #include +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include "threading/sdl_semaphore.h" +#include "threading/sdl_thread.h" +#else #include "threading/semaphore.h" #include "threading/thread.h" +#endif class TestThreading : public TestBase { diff --git a/src/util/areastore.h b/src/util/areastore.h index d2001a19d..0957afa44 100644 --- a/src/util/areastore.h +++ b/src/util/areastore.h @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "util/container.h" #include "util/numeric.h" -#ifndef ANDROID +#if !defined(__ANDROID__) && !defined(__APPLE__) #include "cmake_config.h" #endif #if USE_SPATIAL diff --git a/src/util/container.h b/src/util/container.h index fe013314d..b30acb4ea 100644 --- a/src/util/container.h +++ b/src/util/container.h @@ -19,10 +19,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "IrrCompileConfig.h" + #include "irrlichttypes.h" #include "exceptions.h" #include "threading/mutex_auto_lock.h" +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include "threading/sdl_semaphore.h" +#else #include "threading/semaphore.h" +#endif #include #include #include diff --git a/src/util/serialize.h b/src/util/serialize.h index 132a0df92..a9fb2b54b 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -250,6 +250,14 @@ inline v3s32 readV3S32(const u8 *data) return p; } +inline v2f readV2F1000(const u8 *data) +{ + v2f p; + p.X = readF1000(&data[0]); + p.Y = readF1000(&data[4]); + return p; +} + inline v3f readV3F1000(const u8 *data) { v3f p; @@ -357,6 +365,12 @@ inline void writeV3S32(u8 *data, v3s32 p) writeS32(&data[8], p.Z); } +inline void writeV2F1000(u8 *data, v2f p) +{ + writeF1000(&data[0], p.X); + writeF1000(&data[4], p.Y); +} + inline void writeV3F1000(u8 *data, v3f p) { writeF1000(&data[0], p.X); @@ -411,6 +425,7 @@ MAKE_STREAM_READ_FXN(v2s16, V2S16, 4); MAKE_STREAM_READ_FXN(v3s16, V3S16, 6); MAKE_STREAM_READ_FXN(v2s32, V2S32, 8); MAKE_STREAM_READ_FXN(v3s32, V3S32, 12); +MAKE_STREAM_READ_FXN(v2f, V2F1000, 8); MAKE_STREAM_READ_FXN(v3f, V3F1000, 12); MAKE_STREAM_READ_FXN(v2f, V2F32, 8); MAKE_STREAM_READ_FXN(v3f, V3F32, 12); @@ -430,11 +445,47 @@ MAKE_STREAM_WRITE_FXN(v2s16, V2S16, 4); MAKE_STREAM_WRITE_FXN(v3s16, V3S16, 6); MAKE_STREAM_WRITE_FXN(v2s32, V2S32, 8); MAKE_STREAM_WRITE_FXN(v3s32, V3S32, 12); +MAKE_STREAM_WRITE_FXN(v2f, V2F1000, 8); MAKE_STREAM_WRITE_FXN(v3f, V3F1000, 12); MAKE_STREAM_WRITE_FXN(v2f, V2F32, 8); MAKE_STREAM_WRITE_FXN(v3f, V3F32, 12); MAKE_STREAM_WRITE_FXN(video::SColor, ARGB8, 4); +// Make float functions +#define MAKE_FLOAT_FXNS(T, N) \ + inline T read ## N(u8 *data, const u16 protocol_version) \ + { \ + if (protocol_version > 36) \ + return read ## N ## 32(data); \ + else \ + return read ## N ## 1000(data); \ + } \ + inline T read ## N(std::istream &is, const u16 protocol_version) \ + { \ + if (protocol_version > 36) \ + return read ## N ## 32(is); \ + else \ + return read ## N ## 1000(is); \ + } \ + inline void write ## N(u8 *data, T val, const u16 protocol_version) \ + { \ + if (protocol_version > 36) \ + write ## N ## 32(data, val); \ + else \ + write ## N ## 1000(data, val); \ + } \ + inline void write ## N(std::ostream &os, T val, const u16 protocol_version) \ + { \ + if (protocol_version > 36) \ + write ## N ## 32(os, val); \ + else \ + write ## N ## 1000(os, val); \ + } + +MAKE_FLOAT_FXNS(f32, F); +MAKE_FLOAT_FXNS(v2f, V2F); +MAKE_FLOAT_FXNS(v3f, V3F); + //// //// More serialization stuff //// diff --git a/src/util/string.cpp b/src/util/string.cpp index 9d7cbf088..c02d5b058 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -32,7 +32,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -#ifndef _WIN32 +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + #include +#elif !defined(_WIN32) #include #else #define _WIN32_WINNT 0x0501 @@ -49,7 +51,56 @@ with this program; if not, write to the Free Software Foundation, Inc., #define BSD_ICONV_USED #endif -#ifndef _WIN32 +#if defined(__ANDROID__) || defined(__APPLE__) +// On Android iconv disagrees how big a wchar_t is for whatever reason +const char *DEFAULT_ENCODING = "UTF-32LE"; +#else +const char *DEFAULT_ENCODING = "WCHAR_T"; +#endif + +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ + +std::wstring utf8_to_wide(const std::string &input) +{ +#if defined(__ANDROID__) || defined(__APPLE__) + SANITY_CHECK(sizeof(wchar_t) == 4); +#endif + + const char *inbuf = input.c_str(); + size_t inbuf_size = input.size() + 1; + + char *outbuf = SDL_iconv_string(DEFAULT_ENCODING, "UTF-8", inbuf, inbuf_size); + + if (!outbuf) + return L""; + + std::wstring out((wchar_t*)outbuf); + SDL_free(outbuf); + + return out; +} + +std::string wide_to_utf8(const std::wstring &input) +{ +#if defined(__ANDROID__) || defined(__APPLE__) + SANITY_CHECK(sizeof(wchar_t) == 4); +#endif + + const char *inbuf = (const char*)(input.c_str()); + size_t inbuf_size = (input.size() + 1) * sizeof(wchar_t); + + char *outbuf = SDL_iconv_string("UTF-8", DEFAULT_ENCODING, inbuf, inbuf_size); + + if (!outbuf) + return ""; + + std::string out(outbuf); + SDL_free(outbuf); + + return out; +} + +#elif !defined(_WIN32) static bool convert(const char *to, const char *from, char *outbuf, size_t *outbuf_size, char *inbuf, size_t inbuf_size) @@ -81,21 +132,6 @@ static bool convert(const char *to, const char *from, char *outbuf, return true; } -#ifdef __ANDROID__ -// On Android iconv disagrees how big a wchar_t is for whatever reason -const char *DEFAULT_ENCODING = "UTF-32LE"; -#elif defined(__NetBSD__) - // NetBSD does not allow "WCHAR_T" as a charset input to iconv. - #include - #if BYTE_ORDER == BIG_ENDIAN - const char *DEFAULT_ENCODING = "UTF-32BE"; - #else - const char *DEFAULT_ENCODING = "UTF-32LE"; - #endif -#else -const char *DEFAULT_ENCODING = "WCHAR_T"; -#endif - std::wstring utf8_to_wide(const std::string &input) { const size_t inbuf_size = input.length(); @@ -903,3 +939,36 @@ void safe_print_string(std::ostream &os, const std::string &str) } os.setf(flags); } + +std::string insert_formspec_prepend(const std::string &formspec, const std::string &prepend) { + size_t pos = 0; + size_t pos2; + while ((pos2 = formspec.find('[', pos)) != std::string::npos) { + const std::string element_type = trim(formspec.substr(pos, pos2 - pos)); + + // If a no_prepend[] is found then don't insert the prepend + if (element_type == "no_prepend") + return formspec; + + // Insert the prepend before this element if it isn't size, position, + // or anchor. + if (element_type != "size" && element_type != "position" && + element_type != "anchor") { + break; + } + + // Search for the closing ] and continue iterating + // Valid size, position, and anchor elements only have numbers so + // escaping doesn't have to be accounted for here. + pos = formspec.find(']', pos2); + if (pos == std::string::npos) + return formspec; + pos++; + } + + // Make a copy of the const string and insert the formspec prepend in the + // correct location. + std::string prepended_fs = formspec; + prepended_fs.insert(pos, prepend); + return prepended_fs; +} diff --git a/src/util/string.h b/src/util/string.h index 52d45786f..42093ad7f 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -761,3 +761,10 @@ std::string sanitizeDirName(const std::string &str, const std::string &optional_ * brackets (e.g. "a\x1eb" -> "a<1e>b"). */ void safe_print_string(std::ostream &os, const std::string &str); + +/** +* @param formspec The version 1 formspec string + * @param prepend The formspec prepend + * @return A copy of \p formspec with \p prepend added if possible. + */ +std::string insert_formspec_prepend(const std::string &formspec, const std::string &prepend); diff --git a/src/util/thread.h b/src/util/thread.h index 66dc7d1fe..93571aa0e 100644 --- a/src/util/thread.h +++ b/src/util/thread.h @@ -19,8 +19,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "IrrCompileConfig.h" + #include "irrlichttypes.h" +#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_ +#include "threading/sdl_thread.h" +#else #include "threading/thread.h" +#endif #include "threading/mutex_auto_lock.h" #include "porting.h" #include "log.h"