summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt82
-rw-r--r--src/activeobject.h17
-rw-r--r--src/client/camera.cpp131
-rw-r--r--src/client/camera.h21
-rw-r--r--src/client/client.cpp99
-rw-r--r--src/client/client.h31
-rw-r--r--src/client/clientenvironment.cpp35
-rw-r--r--src/client/clientevent.h46
-rw-r--r--src/client/clientlauncher.cpp2
-rw-r--r--src/client/clientmedia.cpp10
-rw-r--r--src/client/clientmedia.h5
-rw-r--r--src/client/clientobject.h7
-rw-r--r--src/client/clientsimpleobject.h (renamed from src/clientsimpleobject.h)0
-rw-r--r--src/client/content_cao.cpp360
-rw-r--r--src/client/content_cao.h20
-rw-r--r--src/client/content_mapblock.cpp70
-rw-r--r--src/client/filecache.cpp8
-rw-r--r--src/client/filecache.h1
-rw-r--r--src/client/fontengine.cpp2
-rw-r--r--src/client/game.cpp184
-rw-r--r--src/client/gameui.cpp27
-rw-r--r--src/client/hud.cpp269
-rw-r--r--src/client/hud.h11
-rw-r--r--src/client/imagefilters.cpp10
-rw-r--r--src/client/localplayer.cpp8
-rw-r--r--src/client/localplayer.h5
-rw-r--r--src/client/mapblock_mesh.cpp93
-rw-r--r--src/client/mesh.cpp20
-rw-r--r--src/client/mesh.h6
-rw-r--r--src/client/particles.cpp390
-rw-r--r--src/client/particles.h60
-rw-r--r--src/client/render/factory.h2
-rw-r--r--src/client/renderingengine.cpp104
-rw-r--r--src/client/shader.cpp6
-rw-r--r--src/client/sky.cpp49
-rw-r--r--src/client/sky.h2
-rw-r--r--src/client/sound_openal.cpp49
-rw-r--r--src/client/tile.cpp21
-rw-r--r--src/client/tile.h2
-rw-r--r--src/client/wieldmesh.cpp23
-rw-r--r--src/client/wieldmesh.h2
-rw-r--r--src/clientiface.cpp9
-rw-r--r--src/clientiface.h6
-rw-r--r--src/cmake_config.h.in2
-rw-r--r--src/collision.cpp301
-rw-r--r--src/collision.h3
-rw-r--r--src/config.h16
-rw-r--r--src/constants.h7
-rw-r--r--src/content/mods.cpp2
-rw-r--r--src/content/mods.h2
-rw-r--r--src/content/subgames.cpp2
-rw-r--r--src/content_sao.cpp1535
-rw-r--r--src/craftdef.cpp4
-rw-r--r--src/database/database-files.cpp2
-rw-r--r--src/database/database-leveldb.cpp210
-rw-r--r--src/database/database-leveldb.h32
-rw-r--r--src/database/database-postgresql.cpp179
-rw-r--r--src/database/database-postgresql.h24
-rw-r--r--src/database/database-sqlite3.cpp2
-rw-r--r--src/defaultsettings.cpp24
-rw-r--r--src/emerge.cpp71
-rw-r--r--src/emerge.h54
-rw-r--r--src/environment.cpp19
-rw-r--r--src/environment.h10
-rw-r--r--src/filesys.cpp6
-rw-r--r--src/genericobject.cpp207
-rw-r--r--src/genericobject.h87
-rw-r--r--src/gettext.cpp5
-rw-r--r--src/gui/CMakeLists.txt1
-rw-r--r--src/gui/StyleSpec.h119
-rw-r--r--src/gui/guiButton.cpp204
-rw-r--r--src/gui/guiButton.h36
-rw-r--r--src/gui/guiButtonImage.cpp107
-rw-r--r--src/gui/guiButtonImage.h28
-rw-r--r--src/gui/guiButtonItemImage.cpp15
-rw-r--r--src/gui/guiButtonItemImage.h11
-rw-r--r--src/gui/guiChatConsole.cpp4
-rw-r--r--src/gui/guiConfirmRegistration.cpp8
-rw-r--r--src/gui/guiConfirmRegistration.h4
-rw-r--r--src/gui/guiEngine.cpp4
-rw-r--r--src/gui/guiFormSpecMenu.cpp508
-rw-r--r--src/gui/guiFormSpecMenu.h14
-rw-r--r--src/gui/guiHyperText.cpp20
-rw-r--r--src/gui/guiHyperText.h2
-rw-r--r--src/gui/guiInventoryList.cpp42
-rw-r--r--src/gui/guiInventoryList.h5
-rw-r--r--src/gui/guiKeyChangeMenu.cpp12
-rw-r--r--src/gui/guiKeyChangeMenu.h5
-rw-r--r--src/gui/guiPasswordChange.cpp10
-rw-r--r--src/gui/guiPasswordChange.h5
-rw-r--r--src/gui/guiScrollContainer.cpp70
-rw-r--r--src/gui/guiScrollContainer.h60
-rw-r--r--src/gui/guiVolumeChange.cpp7
-rw-r--r--src/gui/guiVolumeChange.h7
-rw-r--r--src/gui/modalMenu.cpp59
-rw-r--r--src/gui/touchscreengui.cpp421
-rw-r--r--src/gui/touchscreengui.h55
-rw-r--r--src/hud.cpp2
-rw-r--r--src/hud.h7
-rw-r--r--src/inventory.cpp29
-rw-r--r--src/inventory.h4
-rw-r--r--src/inventorymanager.cpp2
-rw-r--r--src/itemdef.cpp59
-rw-r--r--src/itemdef.h5
-rw-r--r--src/log.cpp74
-rw-r--r--src/log.h52
-rw-r--r--src/main.cpp21
-rw-r--r--src/map.cpp58
-rw-r--r--src/map.h6
-rw-r--r--src/map_settings_manager.cpp11
-rw-r--r--src/mapgen/cavegen.cpp8
-rw-r--r--src/mapgen/cavegen.h8
-rw-r--r--src/mapgen/mapgen.cpp25
-rw-r--r--src/mapgen/mapgen.h23
-rw-r--r--src/mapgen/mapgen_carpathian.cpp3
-rw-r--r--src/mapgen/mapgen_carpathian.h2
-rw-r--r--src/mapgen/mapgen_flat.cpp3
-rw-r--r--src/mapgen/mapgen_flat.h2
-rw-r--r--src/mapgen/mapgen_fractal.cpp3
-rw-r--r--src/mapgen/mapgen_fractal.h2
-rw-r--r--src/mapgen/mapgen_singlenode.cpp2
-rw-r--r--src/mapgen/mapgen_singlenode.h2
-rw-r--r--src/mapgen/mapgen_v5.cpp3
-rw-r--r--src/mapgen/mapgen_v5.h2
-rw-r--r--src/mapgen/mapgen_v6.cpp21
-rw-r--r--src/mapgen/mapgen_v6.h4
-rw-r--r--src/mapgen/mapgen_v7.cpp105
-rw-r--r--src/mapgen/mapgen_v7.h23
-rw-r--r--src/mapgen/mapgen_valleys.cpp2
-rw-r--r--src/mapgen/mapgen_valleys.h2
-rw-r--r--src/mapgen/mg_biome.cpp65
-rw-r--r--src/mapgen/mg_biome.h20
-rw-r--r--src/mapgen/mg_decoration.cpp71
-rw-r--r--src/mapgen/mg_decoration.h18
-rw-r--r--src/mapgen/mg_ore.cpp125
-rw-r--r--src/mapgen/mg_ore.h42
-rw-r--r--src/mapgen/mg_schematic.cpp38
-rw-r--r--src/mapgen/mg_schematic.h11
-rw-r--r--src/mapgen/treegen.cpp51
-rw-r--r--src/mapgen/treegen.h5
-rw-r--r--src/mapnode.cpp25
-rw-r--r--src/mapnode.h6
-rw-r--r--src/network/clientopcodes.cpp20
-rw-r--r--src/network/clientpackethandler.cpp238
-rw-r--r--src/network/connection.cpp39
-rw-r--r--src/network/connection.h22
-rw-r--r--src/network/connectionthreads.cpp302
-rw-r--r--src/network/connectionthreads.h2
-rw-r--r--src/network/networkprotocol.h33
-rw-r--r--src/network/serveropcodes.cpp22
-rw-r--r--src/network/serverpackethandler.cpp640
-rw-r--r--src/nodedef.cpp113
-rw-r--r--src/nodedef.h28
-rw-r--r--src/noise.cpp5
-rw-r--r--src/objdef.cpp19
-rw-r--r--src/objdef.h19
-rw-r--r--src/object_properties.cpp11
-rw-r--r--src/object_properties.h2
-rw-r--r--src/particles.cpp63
-rw-r--r--src/particles.h79
-rw-r--r--src/pathfinder.cpp102
-rw-r--r--src/pathfinder.h17
-rw-r--r--src/player.h12
-rw-r--r--src/porting.cpp57
-rw-r--r--src/porting.h3
-rw-r--r--src/porting_android.cpp162
-rw-r--r--src/porting_android.h21
-rw-r--r--src/remoteplayer.cpp8
-rw-r--r--src/script/common/c_content.cpp203
-rw-r--r--src/script/common/c_content.h4
-rw-r--r--src/script/common/c_converter.cpp106
-rw-r--r--src/script/common/c_converter.h6
-rw-r--r--src/script/common/c_internal.cpp9
-rw-r--r--src/script/common/c_internal.h10
-rw-r--r--src/script/cpp_api/s_base.cpp15
-rw-r--r--src/script/cpp_api/s_base.h4
-rw-r--r--src/script/cpp_api/s_entity.cpp12
-rw-r--r--src/script/cpp_api/s_entity.h4
-rw-r--r--src/script/cpp_api/s_env.cpp2
-rw-r--r--src/script/cpp_api/s_node.cpp2
-rw-r--r--src/script/cpp_api/s_node.h2
-rw-r--r--src/script/cpp_api/s_player.cpp19
-rw-r--r--src/script/cpp_api/s_player.h5
-rw-r--r--src/script/cpp_api/s_security.cpp7
-rw-r--r--src/script/cpp_api/s_server.cpp14
-rw-r--r--src/script/cpp_api/s_server.h3
-rw-r--r--src/script/lua_api/l_base.cpp14
-rw-r--r--src/script/lua_api/l_base.h7
-rw-r--r--src/script/lua_api/l_camera.cpp59
-rw-r--r--src/script/lua_api/l_client.cpp46
-rw-r--r--src/script/lua_api/l_client.h6
-rw-r--r--src/script/lua_api/l_env.cpp140
-rw-r--r--src/script/lua_api/l_env.h3
-rw-r--r--src/script/lua_api/l_http.cpp51
-rw-r--r--src/script/lua_api/l_http.h7
-rw-r--r--src/script/lua_api/l_internal.h29
-rw-r--r--src/script/lua_api/l_inventory.cpp27
-rw-r--r--src/script/lua_api/l_item.cpp40
-rw-r--r--src/script/lua_api/l_item.h3
-rw-r--r--src/script/lua_api/l_localplayer.cpp74
-rw-r--r--src/script/lua_api/l_localplayer.h21
-rw-r--r--src/script/lua_api/l_mainmenu.cpp109
-rw-r--r--src/script/lua_api/l_mainmenu.h3
-rw-r--r--src/script/lua_api/l_mapgen.cpp82
-rw-r--r--src/script/lua_api/l_mapgen.h2
-rw-r--r--src/script/lua_api/l_metadata.cpp8
-rw-r--r--src/script/lua_api/l_nodemeta.cpp2
-rw-r--r--src/script/lua_api/l_nodetimer.cpp44
-rw-r--r--src/script/lua_api/l_nodetimer.h10
-rw-r--r--src/script/lua_api/l_noise.cpp14
-rw-r--r--src/script/lua_api/l_object.cpp78
-rw-r--r--src/script/lua_api/l_particles.cpp200
-rw-r--r--src/script/lua_api/l_particles_local.cpp159
-rw-r--r--src/script/lua_api/l_particles_local.h2
-rw-r--r--src/script/lua_api/l_server.cpp127
-rw-r--r--src/script/lua_api/l_server.h3
-rw-r--r--src/script/lua_api/l_util.cpp10
-rw-r--r--src/script/lua_api/l_util.h2
-rw-r--r--src/script/lua_api/l_vmanip.cpp6
-rw-r--r--src/script/scripting_client.cpp8
-rw-r--r--src/script/scripting_client.h3
-rw-r--r--src/script/scripting_mainmenu.cpp3
-rw-r--r--src/script/scripting_server.cpp4
-rw-r--r--src/server.cpp670
-rw-r--r--src/server.h106
-rw-r--r--src/server/CMakeLists.txt5
-rw-r--r--src/server/activeobjectmgr.cpp10
-rw-r--r--src/server/activeobjectmgr.h7
-rw-r--r--src/server/luaentity_sao.cpp541
-rw-r--r--src/server/luaentity_sao.h93
-rw-r--r--src/server/mods.cpp10
-rw-r--r--src/server/mods.h3
-rw-r--r--src/server/player_sao.cpp665
-rw-r--r--src/server/player_sao.h (renamed from src/content_sao.h)233
-rw-r--r--src/server/serveractiveobject.cpp (renamed from src/serverobject.cpp)60
-rw-r--r--src/server/serveractiveobject.h (renamed from src/serverobject.h)24
-rw-r--r--src/server/serverinventorymgr.cpp192
-rw-r--r--src/server/serverinventorymgr.h60
-rw-r--r--src/server/unit_sao.cpp345
-rw-r--r--src/server/unit_sao.h136
-rw-r--r--src/serverenvironment.cpp99
-rw-r--r--src/serverenvironment.h23
-rw-r--r--src/settings_translation_file.cpp44
-rw-r--r--src/skyparams.h10
-rw-r--r--src/staticobject.cpp2
-rw-r--r--src/texture_override.cpp120
-rw-r--r--src/texture_override.h72
-rw-r--r--src/tool.cpp2
-rw-r--r--src/translation.cpp13
-rw-r--r--src/translation.h5
-rw-r--r--src/unittest/CMakeLists.txt2
-rw-r--r--src/unittest/test_ban.cpp3
-rw-r--r--src/unittest/test_collision.cpp64
-rw-r--r--src/unittest/test_objdef.cpp76
-rw-r--r--src/unittest/test_player.cpp1
-rw-r--r--src/unittest/test_serveractiveobjectmgr.cpp18
-rw-r--r--src/unittest/test_servermodmanager.cpp10
-rw-r--r--src/util/CMakeLists.txt1
-rw-r--r--src/util/md32_common.h2
-rw-r--r--src/util/metricsbackend.cpp140
-rw-r--r--src/util/metricsbackend.h140
-rw-r--r--src/util/numeric.h10
-rw-r--r--src/util/serialize.cpp17
-rw-r--r--src/util/serialize.h4
-rw-r--r--src/util/string.cpp49
-rw-r--r--src/util/string.h4
-rw-r--r--src/version.cpp19
267 files changed, 9586 insertions, 6402 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 6afa5b8fe..3d6d1b0ea 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -6,7 +6,7 @@ INCLUDE(CheckIncludeFiles)
INCLUDE(CheckLibraryExists)
# Add custom SemiDebug build mode
-set(CMAKE_CXX_FLAGS_SEMIDEBUG "-O1 -g -Wall -Wabi" CACHE STRING
+set(CMAKE_CXX_FLAGS_SEMIDEBUG "-O1 -g -Wall" CACHE STRING
"Flags used by the C++ compiler during semidebug builds."
FORCE
)
@@ -62,7 +62,7 @@ set(USE_GETTEXT FALSE)
if(ENABLE_GETTEXT)
find_package(GettextLib)
- if(GETTEXT_FOUND)
+ if(GETTEXTLIB_FOUND)
if(WIN32)
message(STATUS "GetText library: ${GETTEXT_LIBRARY}")
message(STATUS "GetText DLL: ${GETTEXT_DLL}")
@@ -70,7 +70,7 @@ if(ENABLE_GETTEXT)
endif()
set(USE_GETTEXT TRUE)
message(STATUS "GetText enabled; locales found: ${GETTEXT_AVAILABLE_LOCALES}")
- endif(GETTEXT_FOUND)
+ endif(GETTEXTLIB_FOUND)
else()
mark_as_advanced(GETTEXT_ICONV_DLL GETTEXT_INCLUDE_DIR GETTEXT_LIBRARY GETTEXT_MSGFMT)
message(STATUS "GetText disabled.")
@@ -217,6 +217,26 @@ endif(ENABLE_REDIS)
find_package(SQLite3 REQUIRED)
+OPTION(ENABLE_PROMETHEUS "Enable prometheus client support" FALSE)
+set(USE_PROMETHEUS FALSE)
+
+if(ENABLE_PROMETHEUS)
+ find_path(PROMETHEUS_CPP_INCLUDE_DIR NAMES prometheus/counter.h)
+ find_library(PROMETHEUS_PULL_LIBRARY NAMES prometheus-cpp-pull)
+ find_library(PROMETHEUS_CORE_LIBRARY NAMES prometheus-cpp-core)
+ if(PROMETHEUS_CPP_INCLUDE_DIR AND PROMETHEUS_PULL_LIBRARY AND PROMETHEUS_CORE_LIBRARY)
+ set(PROMETHEUS_LIBRARIES ${PROMETHEUS_PULL_LIBRARY} ${PROMETHEUS_CORE_LIBRARY})
+ set(USE_PROMETHEUS TRUE)
+ include_directories(${PROMETHEUS_CPP_INCLUDE_DIR})
+ endif(PROMETHEUS_CPP_INCLUDE_DIR AND PROMETHEUS_PULL_LIBRARY AND PROMETHEUS_CORE_LIBRARY)
+endif(ENABLE_PROMETHEUS)
+
+if(USE_PROMETHEUS)
+ message(STATUS "Prometheus client enabled.")
+else(USE_PROMETHEUS)
+ message(STATUS "Prometheus client disabled.")
+endif(USE_PROMETHEUS)
+
OPTION(ENABLE_SPATIAL "Enable SpatialIndex AreaStore backend" TRUE)
set(USE_SPATIAL FALSE)
@@ -375,7 +395,6 @@ set(common_SRCS
collision.cpp
content_mapnode.cpp
content_nodemeta.cpp
- content_sao.cpp
convert_json.cpp
craftdef.cpp
debug.cpp
@@ -384,7 +403,6 @@ set(common_SRCS
environment.cpp
face_position_cache.cpp
filesys.cpp
- genericobject.cpp
gettext.cpp
httpfetch.cpp
hud.cpp
@@ -409,6 +427,7 @@ set(common_SRCS
noise.cpp
objdef.cpp
object_properties.cpp
+ particles.cpp
pathfinder.cpp
player.cpp
porting.cpp
@@ -422,10 +441,10 @@ set(common_SRCS
server.cpp
serverenvironment.cpp
serverlist.cpp
- serverobject.cpp
settings.cpp
staticobject.cpp
terminal_chat_console.cpp
+ texture_override.cpp
tileanimation.cpp
tool.cpp
translation.cpp
@@ -437,9 +456,12 @@ set(common_SRCS
${JTHREAD_SRCS}
${common_SCRIPT_SRCS}
${UTIL_SRCS}
- ${UNITTEST_SRCS}
)
+if(BUILD_UNITTESTS)
+ set(common_SRCS ${common_SRCS} ${UNITTEST_SRCS})
+endif()
+
# This gives us the icon and file version information
if(WIN32)
@@ -454,7 +476,7 @@ if(WIN32)
-i${WINRESOURCE_FILE}
-o ${CMAKE_CURRENT_BINARY_DIR}/winresource_rc.o
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
- DEPENDS ${WINRESOURCE_FILE})
+ DEPENDS ${WINRESOURCE_FILE} ${MINETEST_EXE_MANIFEST_FILE})
SET(extra_windows_SRCS ${CMAKE_CURRENT_BINARY_DIR}/winresource_rc.o)
else(MINGW) # Probably MSVC
set(extra_windows_SRCS ${WINRESOURCE_FILE} ${MINETEST_EXE_MANIFEST_FILE})
@@ -474,8 +496,12 @@ set(client_SRCS
${client_network_SRCS}
${client_irrlicht_changes_SRCS}
${client_SCRIPT_SRCS}
- ${UNITTEST_CLIENT_SRCS}
)
+
+if(BUILD_UNITTESTS)
+ set(client_SRCS ${client_SRCS} ${UNITTEST_CLIENT_SRCS})
+endif()
+
list(SORT client_SRCS)
# Server sources
@@ -592,6 +618,9 @@ if(BUILD_CLIENT)
if (USE_REDIS)
target_link_libraries(${PROJECT_NAME} ${REDIS_LIBRARY})
endif()
+ if (USE_PROMETHEUS)
+ target_link_libraries(${PROJECT_NAME} ${PROMETHEUS_LIBRARIES})
+ endif()
if (USE_SPATIAL)
target_link_libraries(${PROJECT_NAME} ${SPATIAL_LIBRARY})
endif()
@@ -627,6 +656,9 @@ if(BUILD_SERVER)
if (USE_REDIS)
target_link_libraries(${PROJECT_NAME}server ${REDIS_LIBRARY})
endif()
+ if (USE_PROMETHEUS)
+ target_link_libraries(${PROJECT_NAME}server ${PROMETHEUS_LIBRARIES})
+ endif()
if (USE_SPATIAL)
target_link_libraries(${PROJECT_NAME}server ${SPATIAL_LIBRARY})
endif()
@@ -649,7 +681,7 @@ set(GETTEXT_BLACKLISTED_LOCALES
option(APPLY_LOCALE_BLACKLIST "Use a blacklist to avoid broken locales" TRUE)
-if (GETTEXT_FOUND AND APPLY_LOCALE_BLACKLIST)
+if (GETTEXTLIB_FOUND AND APPLY_LOCALE_BLACKLIST)
set(GETTEXT_USED_LOCALES "")
foreach(LOCALE ${GETTEXT_AVAILABLE_LOCALES})
if (NOT ";${GETTEXT_BLACKLISTED_LOCALES};" MATCHES ";${LOCALE};")
@@ -661,7 +693,7 @@ endif()
# Set some optimizations and tweaks
-include(CheckCXXCompilerFlag)
+include(CheckCSourceCompiles)
if(MSVC)
# Visual Studio
@@ -671,9 +703,12 @@ if(MSVC)
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:SSE")
endif()
- #set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG /NODEFAULTLIB:\"libcmtd.lib\" /NODEFAULTLIB:\"libcmt.lib\"")
- set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG /INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF")
-
+
+ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF")
+ else()
+ set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/LTCG /INCREMENTAL:NO /DEBUG /OPT:REF /OPT:ICF")
+ endif()
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
@@ -695,13 +730,19 @@ else()
else()
set(RELEASE_WARNING_FLAGS "")
endif()
-
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(WARNING_FLAGS "${WARNING_FLAGS} -Wsign-compare")
endif()
+
if(APPLE AND USE_LUAJIT)
# required per http://luajit.org/install.html
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pagezero_size 10000 -image_base 100000000")
+ elseif(UNIX AND USE_LUAJIT)
+ check_c_source_compiles("#ifndef __aarch64__\n#error\n#endif\nint main(){}" IS_AARCH64)
+ if(IS_AARCH64)
+ # Move text segment below LuaJIT's 47-bit limit (see issue #9367)
+ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Ttext-segment=0x200000000")
+ endif()
endif()
if(MINGW)
@@ -709,6 +750,11 @@ else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWIN32_LEAN_AND_MEAN")
endif()
+ # Use a safe subset of flags to speed up math calculations:
+ # - we don't need errno or math exceptions
+ # - we don't deal with Inf/NaN or signed zero
+ set(MATH_FLAGS "-fno-math-errno -fno-trapping-math -ffinite-math-only -fno-signed-zeros")
+
set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG ${RELEASE_WARNING_FLAGS} ${WARNING_FLAGS} ${OTHER_FLAGS} -Wall -pipe -funroll-loops")
if(CMAKE_SYSTEM_NAME MATCHES "(Darwin|BSD|DragonFly)")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os")
@@ -719,11 +765,11 @@ else()
AND CMAKE_CXX_COMPILER_VERSION MATCHES "^9\\.")
# Clang 9 has broken -ffast-math on glibc
else()
- set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffast-math")
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${MATH_FLAGS}")
endif()
endif(CMAKE_SYSTEM_NAME MATCHES "(Darwin|BSD|DragonFly)")
- set(CMAKE_CXX_FLAGS_SEMIDEBUG "-g -O1 -Wall -Wabi ${WARNING_FLAGS} ${OTHER_FLAGS}")
- set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wabi ${WARNING_FLAGS} ${OTHER_FLAGS}")
+ set(CMAKE_CXX_FLAGS_SEMIDEBUG "-g -O1 -Wall ${WARNING_FLAGS} ${OTHER_FLAGS}")
+ set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall ${WARNING_FLAGS} ${OTHER_FLAGS}")
if(USE_GPROF)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg")
diff --git a/src/activeobject.h b/src/activeobject.h
index 4a2de92cd..85e160d10 100644
--- a/src/activeobject.h
+++ b/src/activeobject.h
@@ -55,6 +55,23 @@ struct ActiveObjectMessage
std::string datastring;
};
+enum ActiveObjectCommand {
+ AO_CMD_SET_PROPERTIES,
+ AO_CMD_UPDATE_POSITION,
+ AO_CMD_SET_TEXTURE_MOD,
+ AO_CMD_SET_SPRITE,
+ AO_CMD_PUNCHED,
+ AO_CMD_UPDATE_ARMOR_GROUPS,
+ AO_CMD_SET_ANIMATION,
+ AO_CMD_SET_BONE_POSITION,
+ AO_CMD_ATTACH_TO,
+ AO_CMD_SET_PHYSICS_OVERRIDE,
+ AO_CMD_OBSOLETE1,
+ // ^ UPDATE_NAMETAG_ATTRIBUTES deprecated since 0.4.14, removed in 5.3.0
+ AO_CMD_SPAWN_INFANT,
+ AO_CMD_SET_ANIMATION_SPEED
+};
+
/*
Parent class for ServerActiveObject and ClientActiveObject
*/
diff --git a/src/client/camera.cpp b/src/client/camera.cpp
index 871ea709d..abc55e4b7 100644
--- a/src/client/camera.cpp
+++ b/src/client/camera.cpp
@@ -86,6 +86,51 @@ Camera::~Camera()
m_wieldmgr->drop();
}
+void Camera::notifyFovChange()
+{
+ LocalPlayer *player = m_client->getEnv().getLocalPlayer();
+ assert(player);
+
+ PlayerFovSpec spec = player->getFov();
+
+ /*
+ * Update m_old_fov_degrees first - it serves as the starting point of the
+ * upcoming transition.
+ *
+ * If an FOV transition is already active, mark current FOV as the start of
+ * the new transition. If not, set it to the previous transition's target FOV.
+ */
+ if (m_fov_transition_active)
+ m_old_fov_degrees = m_curr_fov_degrees;
+ else
+ m_old_fov_degrees = m_server_sent_fov ? m_target_fov_degrees : m_cache_fov;
+
+ /*
+ * Update m_server_sent_fov next - it corresponds to the target FOV of the
+ * upcoming transition.
+ *
+ * Set it to m_cache_fov, if server-sent FOV is 0. Otherwise check if
+ * server-sent FOV is a multiplier, and multiply it with m_cache_fov instead
+ * of overriding.
+ */
+ if (spec.fov == 0.0f) {
+ m_server_sent_fov = false;
+ m_target_fov_degrees = m_cache_fov;
+ } else {
+ m_server_sent_fov = true;
+ m_target_fov_degrees = spec.is_multiplier ? m_cache_fov * spec.fov : spec.fov;
+ }
+
+ if (spec.transition_time > 0.0f)
+ m_fov_transition_active = true;
+
+ // If FOV smooth transition is active, initialize required variables
+ if (m_fov_transition_active) {
+ m_transition_time = spec.transition_time;
+ m_fov_diff = m_target_fov_degrees - m_old_fov_degrees;
+ }
+}
+
bool Camera::successfullyCreated(std::string &error_message)
{
if (!m_playernode) {
@@ -297,9 +342,13 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
if (player->getParent())
player_position = player->getParent()->getPosition();
- if(player->touching_ground &&
- player_position.Y > old_player_position.Y)
- {
+ // Smooth the camera movement when the player instantly moves upward due to stepheight.
+ // To smooth the 'not touching_ground' stepheight, smoothing is necessary when jumping
+ // or swimming (for when moving from liquid to land).
+ // Disable smoothing if climbing or flying, to avoid upwards offset of player model
+ // when seen in 3rd person view.
+ bool flying = g_settings->getBool("free_move") && m_client->checkLocalPrivilege("fly");
+ if (player_position.Y > old_player_position.Y && !player->is_climbing && !flying) {
f32 oldy = old_player_position.Y;
f32 newy = player_position.Y;
f32 t = std::exp(-23 * frametime);
@@ -333,17 +382,21 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
fall_bobbing *= m_cache_fall_bobbing_amount;
}
- // Calculate players eye offset for different camera modes
- v3f PlayerEyeOffset = player->getEyeOffset();
- if (m_camera_mode == CAMERA_MODE_FIRST)
- PlayerEyeOffset += player->eye_offset_first;
- else
- PlayerEyeOffset += player->eye_offset_third;
-
- // Set head node transformation
- m_headnode->setPosition(PlayerEyeOffset+v3f(0,cameratilt*-player->hurt_tilt_strength+fall_bobbing,0));
- m_headnode->setRotation(v3f(player->getPitch(), 0, cameratilt*player->hurt_tilt_strength));
- m_headnode->updateAbsolutePosition();
+ // Calculate and translate the head SceneNode offsets
+ {
+ v3f eye_offset = player->getEyeOffset();
+ if (m_camera_mode == CAMERA_MODE_FIRST)
+ eye_offset += player->eye_offset_first;
+ else
+ eye_offset += player->eye_offset_third;
+
+ // Set head node transformation
+ eye_offset.Y += cameratilt * -player->hurt_tilt_strength + fall_bobbing;
+ m_headnode->setPosition(eye_offset);
+ m_headnode->setRotation(v3f(player->getPitch(), 0,
+ cameratilt * player->hurt_tilt_strength));
+ m_headnode->updateAbsolutePosition();
+ }
// Compute relative camera position and target
v3f rel_cam_pos = v3f(0,0,0);
@@ -458,35 +511,40 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
m_camera_position = my_cp;
/*
- * Apply server-sent FOV. If server doesn't enforce FOV,
- * check for zoom and set to zoom FOV.
- * Otherwise, default to m_cache_fov
+ * Apply server-sent FOV, instantaneous or smooth transition.
+ * If not, check for zoom and set to zoom FOV.
+ * Otherwise, default to m_cache_fov.
*/
-
- f32 fov_degrees;
- PlayerFovSpec fov_spec = player->getFov();
- if (fov_spec.fov > 0.0f) {
- // If server-sent FOV is a multiplier, multiply
- // it with m_cache_fov instead of overriding
- if (fov_spec.is_multiplier)
- fov_degrees = m_cache_fov * fov_spec.fov;
- else
- fov_degrees = fov_spec.fov;
+ if (m_fov_transition_active) {
+ // Smooth FOV transition
+ // Dynamically calculate FOV delta based on frametimes
+ f32 delta = (frametime / m_transition_time) * m_fov_diff;
+ m_curr_fov_degrees += delta;
+
+ // Mark transition as complete if target FOV has been reached
+ if ((m_fov_diff > 0.0f && m_curr_fov_degrees >= m_target_fov_degrees) ||
+ (m_fov_diff < 0.0f && m_curr_fov_degrees <= m_target_fov_degrees)) {
+ m_fov_transition_active = false;
+ m_curr_fov_degrees = m_target_fov_degrees;
+ }
+ } else if (m_server_sent_fov) {
+ // Instantaneous FOV change
+ m_curr_fov_degrees = m_target_fov_degrees;
} else if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) {
// Player requests zoom, apply zoom FOV
- fov_degrees = player->getZoomFOV();
+ m_curr_fov_degrees = player->getZoomFOV();
} else {
// Set to client's selected FOV
- fov_degrees = m_cache_fov;
+ m_curr_fov_degrees = m_cache_fov;
}
- fov_degrees = rangelim(fov_degrees, 1.0f, 160.0f);
+ m_curr_fov_degrees = rangelim(m_curr_fov_degrees, 1.0f, 160.0f);
// FOV and aspect ratio
const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
m_aspect = (f32) window_size.X / (f32) window_size.Y;
- m_fov_y = fov_degrees * M_PI / 180.0;
+ m_fov_y = m_curr_fov_degrees * M_PI / 180.0;
// Increase vertical FOV on lower aspect ratios (<16:10)
- m_fov_y *= MYMAX(1.0, MYMIN(1.4, sqrt(16./10. / m_aspect)));
+ m_fov_y *= core::clamp(sqrt(16./10. / m_aspect), 1.0, 1.4);
m_fov_x = 2 * atan(m_aspect * tan(0.5 * m_fov_y));
m_cameranode->setAspectRatio(m_aspect);
m_cameranode->setFOV(m_fov_y);
@@ -538,7 +596,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
m_wieldnode->setPosition(wield_position);
m_wieldnode->setRotation(wield_rotation);
- m_wieldnode->setColor(player->light_color);
+ m_wieldnode->setNodeLightColor(player->light_color);
// Set render distance
updateViewingRange();
@@ -553,14 +611,11 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
const bool walking = movement_XZ && player->touching_ground;
const bool swimming = (movement_XZ || player->swimming_vertical) && player->in_liquid;
const bool climbing = movement_Y && player->is_climbing;
- if ((walking || swimming || climbing) &&
- (!g_settings->getBool("free_move") || !m_client->checkLocalPrivilege("fly"))) {
+ if ((walking || swimming || climbing) && !flying) {
// Start animation
m_view_bobbing_state = 1;
m_view_bobbing_speed = MYMIN(speed.getLength(), 70);
- }
- else if (m_view_bobbing_state == 1)
- {
+ } else if (m_view_bobbing_state == 1) {
// Stop animation
m_view_bobbing_state = 2;
m_view_bobbing_speed = 60;
diff --git a/src/client/camera.h b/src/client/camera.h
index 88de3570a..3a59637bc 100644
--- a/src/client/camera.h
+++ b/src/client/camera.h
@@ -75,6 +75,12 @@ public:
return m_camera_position;
}
+ // Returns the absolute position of the head SceneNode in the world
+ inline v3f getHeadPosition() const
+ {
+ return m_headnode->getAbsolutePosition();
+ }
+
// Get the camera direction (in absolute camera coordinates).
// This has view bobbing applied.
inline v3f getDirection() const
@@ -106,6 +112,9 @@ public:
return MYMAX(m_fov_x, m_fov_y);
}
+ // Notify about new server-sent FOV and initialize smooth FOV transition
+ void notifyFovChange();
+
// Checks if the constructor was able to create the scene nodes
bool successfullyCreated(std::string &error_message);
@@ -180,6 +189,9 @@ private:
Client *m_client;
+ // Default Client FOV (as defined by the "fov" setting)
+ f32 m_cache_fov;
+
// Absolute camera position
v3f m_camera_position;
// Absolute camera direction
@@ -187,6 +199,14 @@ private:
// Camera offset
v3s16 m_camera_offset;
+ // Server-sent FOV variables
+ bool m_server_sent_fov = false;
+ f32 m_curr_fov_degrees, m_old_fov_degrees, m_target_fov_degrees;
+
+ // FOV transition variables
+ bool m_fov_transition_active = false;
+ f32 m_fov_diff, m_transition_time;
+
v2f m_wieldmesh_offset = v2f(55.0f, -35.0f);
v2f m_arm_dir;
v2f m_cam_vel;
@@ -224,7 +244,6 @@ private:
f32 m_cache_fall_bobbing_amount;
f32 m_cache_view_bobbing_amount;
- f32 m_cache_fov;
bool m_arm_inertia;
std::list<Nametag *> m_nametags;
diff --git a/src/client/client.cpp b/src/client/client.cpp
index c6d28ce80..65e5b3d8c 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -61,6 +61,28 @@ with this program; if not, write to the Free Software Foundation, Inc.,
extern gui::IGUIEnvironment* guienv;
/*
+ Utility classes
+*/
+
+u32 PacketCounter::sum() const
+{
+ u32 n = 0;
+ for (const auto &it : m_packets)
+ n += it.second;
+ return n;
+}
+
+void PacketCounter::print(std::ostream &o) const
+{
+ for (const auto &it : m_packets) {
+ auto name = it.first >= TOCLIENT_NUM_MSG_TYPES ? "?"
+ : toClientCommandTable[it.first].name;
+ o << "cmd " << it.first << " (" << name << ") count "
+ << it.second << std::endl;
+ }
+}
+
+/*
Client
*/
@@ -164,7 +186,7 @@ void Client::loadMods()
infostream << mod.name << " ";
infostream << std::endl;
- // Load and run "mod" scripts
+ // Load "mod" scripts
for (const ModSpec &mod : m_mods) {
if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) {
throw ModError("Error loading mod \"" + mod.name +
@@ -174,7 +196,7 @@ void Client::loadMods()
scanModIntoMemory(mod.name, mod.path);
}
- // Load and run "mod" scripts
+ // Run them
for (const ModSpec &mod : m_mods)
m_script->loadModFromMemory(mod.name);
@@ -183,10 +205,14 @@ void Client::loadMods()
// Run a callback when mods are loaded
m_script->on_mods_loaded();
+
+ // Create objects if they're ready
if (m_state == LC_Ready)
m_script->on_client_ready(m_env.getLocalPlayer());
if (m_camera)
m_script->on_camera_ready(m_camera);
+ if (m_minimap)
+ m_script->on_minimap_ready(m_minimap);
}
bool Client::checkBuiltinIntegrity()
@@ -336,12 +362,14 @@ void Client::step(float dtime)
{
float &counter = m_packetcounter_timer;
counter -= dtime;
- if(counter <= 0.0)
+ if(counter <= 0.0f)
{
- counter = 20.0;
+ counter = 30.0f;
+ u32 sum = m_packetcounter.sum();
+ float avg = sum / counter;
- infostream << "Client packetcounter (" << m_packetcounter_timer
- << "):"<<std::endl;
+ infostream << "Client packetcounter (" << counter << "s): "
+ << "sum=" << sum << " avg=" << avg << "/s" << std::endl;
m_packetcounter.print(infostream);
m_packetcounter.clear();
}
@@ -431,12 +459,9 @@ void Client::step(float dtime)
/*
Handle environment
*/
- // Control local player (0ms)
LocalPlayer *player = m_env.getLocalPlayer();
- assert(player);
- player->applyControl(dtime, &m_env);
- // Step environment
+ // Step environment (also handles player controls)
m_env.step(dtime);
m_sound->step(dtime);
@@ -621,14 +646,17 @@ void Client::step(float dtime)
m_mod_storage_save_timer -= dtime;
if (m_mod_storage_save_timer <= 0.0f) {
- verbosestream << "Saving registered mod storages." << std::endl;
m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
+ int n = 0;
for (std::unordered_map<std::string, ModMetadata *>::const_iterator
it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) {
if (it->second->isModified()) {
it->second->save(getModStoragePath());
+ n++;
}
}
+ if (n > 0)
+ infostream << "Saved " << n << " modified mod storages." << std::endl;
}
// Write server map
@@ -639,11 +667,9 @@ void Client::step(float dtime)
}
}
-bool Client::loadMedia(const std::string &data, const std::string &filename)
+bool Client::loadMedia(const std::string &data, const std::string &filename,
+ bool from_media_push)
{
- // Silly irrlicht's const-incorrectness
- Buffer<char> data_rw(data.c_str(), data.size());
-
std::string name;
const char *image_ext[] = {
@@ -653,12 +679,15 @@ bool Client::loadMedia(const std::string &data, const std::string &filename)
};
name = removeStringEnd(filename, image_ext);
if (!name.empty()) {
- verbosestream<<"Client: Attempting to load image "
- <<"file \""<<filename<<"\""<<std::endl;
+ TRACESTREAM(<< "Client: Attempting to load image "
+ << "file \"" << filename << "\"" << std::endl);
io::IFileSystem *irrfs = RenderingEngine::get_filesystem();
video::IVideoDriver *vdrv = RenderingEngine::get_video_driver();
+ // Silly irrlicht's const-incorrectness
+ Buffer<char> data_rw(data.c_str(), data.size());
+
// Create an irrlicht memory file
io::IReadFile *rfile = irrfs->createMemoryReadFile(
*data_rw, data_rw.getSize(), "_tempreadfile");
@@ -687,17 +716,15 @@ bool Client::loadMedia(const std::string &data, const std::string &filename)
};
name = removeStringEnd(filename, sound_ext);
if (!name.empty()) {
- verbosestream<<"Client: Attempting to load sound "
- <<"file \""<<filename<<"\""<<std::endl;
- m_sound->loadSoundData(name, data);
- return true;
+ TRACESTREAM(<< "Client: Attempting to load sound "
+ << "file \"" << filename << "\"" << std::endl);
+ return m_sound->loadSoundData(name, data);
}
const char *model_ext[] = {
".x", ".b3d", ".md2", ".obj",
NULL
};
-
name = removeStringEnd(filename, model_ext);
if (!name.empty()) {
verbosestream<<"Client: Storing model into memory: "
@@ -714,9 +741,11 @@ bool Client::loadMedia(const std::string &data, const std::string &filename)
};
name = removeStringEnd(filename, translate_ext);
if (!name.empty()) {
- verbosestream << "Client: Loading translation: "
- << "\"" << filename << "\"" << std::endl;
- g_translations->loadTranslation(data);
+ if (from_media_push)
+ return false;
+ TRACESTREAM(<< "Client: Loading translation: "
+ << "\"" << filename << "\"" << std::endl);
+ g_client_translations->loadTranslation(data);
return true;
}
@@ -1722,8 +1751,11 @@ void Client::afterContentReceived()
text = wgettext("Initializing nodes...");
RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 72);
m_nodedef->updateAliases(m_itemdef);
- for (const auto &path : getTextureDirs())
- m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt");
+ for (const auto &path : getTextureDirs()) {
+ TextureOverrideSource override_source(path + DIR_DELIM + "override.txt");
+ m_nodedef->applyTextureOverrides(override_source.getNodeTileOverrides());
+ m_itemdef->applyTextureOverrides(override_source.getItemTextureOverrides());
+ }
m_nodedef->setNodeRegistrationStatus(true);
m_nodedef->runNodeResolveCallbacks();
delete[] text;
@@ -1780,13 +1812,24 @@ void Client::makeScreenshot()
char timetstamp_c[64];
strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm);
- std::string filename_base = g_settings->get("screenshot_path")
+ std::string screenshot_dir;
+
+ if (fs::IsPathAbsolute(g_settings->get("screenshot_path")))
+ screenshot_dir = g_settings->get("screenshot_path");
+ else
+ screenshot_dir = porting::path_user + DIR_DELIM + g_settings->get("screenshot_path");
+
+ std::string filename_base = screenshot_dir
+ DIR_DELIM
+ std::string("screenshot_")
+ std::string(timetstamp_c);
std::string filename_ext = "." + g_settings->get("screenshot_format");
std::string filename;
+ // Create the directory if it doesn't already exist.
+ // Otherwise, saving the screenshot would fail.
+ fs::CreateDir(screenshot_dir);
+
u32 quality = (u32)g_settings->getS32("screenshot_quality");
quality = MYMIN(MYMAX(quality, 0), 100) / 100.0 * 255;
diff --git a/src/client/client.h b/src/client/client.h
index 1291b944c..733634db1 100644
--- a/src/client/client.h
+++ b/src/client/client.h
@@ -82,34 +82,24 @@ public:
void add(u16 command)
{
- std::map<u16, u16>::iterator n = m_packets.find(command);
- if(n == m_packets.end())
- {
+ auto n = m_packets.find(command);
+ if (n == m_packets.end())
m_packets[command] = 1;
- }
else
- {
n->second++;
- }
}
void clear()
{
- for (auto &m_packet : m_packets) {
- m_packet.second = 0;
- }
+ m_packets.clear();
}
- void print(std::ostream &o)
- {
- for (const auto &m_packet : m_packets) {
- o << "cmd "<< m_packet.first <<" count "<< m_packet.second << std::endl;
- }
- }
+ u32 sum() const;
+ void print(std::ostream &o) const;
private:
// command, count
- std::map<u16, u16> m_packets;
+ std::map<u16, u32> m_packets;
};
class ClientScripting;
@@ -232,6 +222,7 @@ public:
void handleCommand_FormspecPrepend(NetworkPacket *pkt);
void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt);
void handleCommand_PlayerSpeed(NetworkPacket *pkt);
+ void handleCommand_MediaPush(NetworkPacket *pkt);
void ProcessData(NetworkPacket *pkt);
@@ -386,7 +377,8 @@ public:
// The following set of functions is used by ClientMediaDownloader
// Insert a media file appropriately into the appropriate manager
- bool loadMedia(const std::string &data, const std::string &filename);
+ bool loadMedia(const std::string &data, const std::string &filename,
+ bool from_media_push = false);
// Send a request for conventional media transfer
void request_media(const std::vector<std::string> &file_requests);
@@ -498,6 +490,7 @@ private:
Camera *m_camera = nullptr;
Minimap *m_minimap = nullptr;
bool m_minimap_disabled_by_server = false;
+
// Server serialization version
u8 m_server_ser_ver;
@@ -539,7 +532,6 @@ private:
AuthMechanism m_chosen_auth_mech;
void *m_auth_data = nullptr;
-
bool m_access_denied = false;
bool m_access_denied_reconnect = false;
std::string m_access_denied_reason = "";
@@ -548,7 +540,10 @@ private:
bool m_nodedef_received = false;
bool m_activeobjects_received = false;
bool m_mods_loaded = false;
+
ClientMediaDownloader *m_media_downloader;
+ // Set of media filenames pushed by server at runtime
+ std::unordered_set<std::string> m_media_pushed_files;
// time_of_day speed approximation for old protocol
bool m_time_of_day_set = false;
diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp
index 52d133781..895b0193c 100644
--- a/src/client/clientenvironment.cpp
+++ b/src/client/clientenvironment.cpp
@@ -216,6 +216,9 @@ void ClientEnvironment::step(float dtime)
*/
{
+ // Control local player
+ lplayer->applyControl(dtime_part, this);
+
// Apply physics
if (!free_move && !is_climbing) {
// Gravity
@@ -320,21 +323,8 @@ void ClientEnvironment::step(float dtime)
// Step object
cao->step(dtime, this);
- if (update_lighting) {
- // Update lighting
- u8 light = 0;
- bool pos_ok;
-
- // Get node at head
- v3s16 p = cao->getLightPosition();
- MapNode n = this->m_map->getNode(p, &pos_ok);
- if (pos_ok)
- light = n.getLightBlend(day_night_ratio, m_client->ndef());
- else
- light = blend_light(day_night_ratio, LIGHT_SUN, 0);
-
- cao->updateLight(light);
- }
+ if (update_lighting)
+ cao->updateLight(day_night_ratio);
};
m_ao_manager.step(dtime, cb_state);
@@ -402,18 +392,7 @@ u16 ClientEnvironment::addActiveObject(ClientActiveObject *object)
object->addToScene(m_texturesource);
// Update lighting immediately
- u8 light = 0;
- bool pos_ok;
-
- // Get node at head
- v3s16 p = object->getLightPosition();
- MapNode n = m_map->getNode(p, &pos_ok);
- if (pos_ok)
- light = n.getLightBlend(getDayNightRatio(), m_client->ndef());
- else
- light = blend_light(getDayNightRatio(), LIGHT_SUN, 0);
-
- object->updateLight(light);
+ object->updateLight(getDayNightRatio());
return object->getId();
}
@@ -450,7 +429,7 @@ void ClientEnvironment::addActiveObject(u16 id, u8 type,
// Object initialized:
if ((obj = getActiveObject(new_id))) {
// Final step is to update all children which are already known
- // Data provided by GENERIC_CMD_SPAWN_INFANT
+ // Data provided by AO_CMD_SPAWN_INFANT
const auto &children = obj->getAttachmentChildIds();
for (auto c_id : children) {
if (auto *o = getActiveObject(c_id))
diff --git a/src/client/clientevent.h b/src/client/clientevent.h
index f5689c25b..9bd31efce 100644
--- a/src/client/clientevent.h
+++ b/src/client/clientevent.h
@@ -21,8 +21,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string>
#include "irrlichttypes_bloated.h"
-#include "hud.h"
-#include "skyparams.h"
+
+struct ParticleParameters;
+struct ParticleSpawnerParameters;
+struct SkyboxParams;
+struct SunParams;
+struct MoonParams;
+struct StarParams;
enum ClientEventType : u8
{
@@ -77,44 +82,12 @@ struct ClientEvent
} show_formspec;
// struct{
//} textures_updated;
+ ParticleParameters *spawn_particle;
struct
{
- v3f *pos;
- v3f *vel;
- v3f *acc;
- f32 expirationtime;
- f32 size;
- bool collisiondetection;
- bool collision_removal;
- bool object_collision;
- bool vertical;
- std::string *texture;
- struct TileAnimationParams animation;
- u8 glow;
- } spawn_particle;
- struct
- {
- u16 amount;
- f32 spawntime;
- v3f *minpos;
- v3f *maxpos;
- v3f *minvel;
- v3f *maxvel;
- v3f *minacc;
- v3f *maxacc;
- f32 minexptime;
- f32 maxexptime;
- f32 minsize;
- f32 maxsize;
- bool collisiondetection;
- bool collision_removal;
- bool object_collision;
+ ParticleSpawnerParameters *p;
u16 attached_id;
- bool vertical;
- std::string *texture;
u64 id;
- struct TileAnimationParams animation;
- u8 glow;
} add_particlespawner;
struct
{
@@ -136,6 +109,7 @@ struct ClientEvent
v3f *world_pos;
v2s32 *size;
s16 z_index;
+ std::string *text2;
} hudadd;
struct
{
diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp
index 2a9d6097f..f18915a55 100644
--- a/src/client/clientlauncher.cpp
+++ b/src/client/clientlauncher.cpp
@@ -105,7 +105,7 @@ bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args)
}
RenderingEngine::get_instance()->setupTopLevelWindow(PROJECT_NAME_C);
-
+
/*
This changes the minimum allowed number of vertices in a VBO.
Default is 500.
diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp
index 6da99bbbf..8cd3b6bcc 100644
--- a/src/client/clientmedia.cpp
+++ b/src/client/clientmedia.cpp
@@ -35,6 +35,15 @@ static std::string getMediaCacheDir()
return porting::path_cache + DIR_DELIM + "media";
}
+bool clientMediaUpdateCache(const std::string &raw_hash, const std::string &filedata)
+{
+ FileCache media_cache(getMediaCacheDir());
+ std::string sha1_hex = hex_encode(raw_hash);
+ if (!media_cache.exists(sha1_hex))
+ return media_cache.update(sha1_hex, filedata);
+ return true;
+}
+
/*
ClientMediaDownloader
*/
@@ -559,7 +568,6 @@ bool ClientMediaDownloader::checkAndLoad(
return true;
}
-
/*
Minetest Hashset File Format
diff --git a/src/client/clientmedia.h b/src/client/clientmedia.h
index 92831082c..5a918535b 100644
--- a/src/client/clientmedia.h
+++ b/src/client/clientmedia.h
@@ -33,6 +33,11 @@ struct HTTPFetchResult;
#define MTHASHSET_FILE_SIGNATURE 0x4d544853 // 'MTHS'
#define MTHASHSET_FILE_NAME "index.mth"
+// Store file into media cache (unless it exists already)
+// Validating the hash is responsibility of the caller
+bool clientMediaUpdateCache(const std::string &raw_hash,
+ const std::string &filedata);
+
class ClientMediaDownloader
{
public:
diff --git a/src/client/clientobject.h b/src/client/clientobject.h
index 12e0db35b..ecd8059ef 100644
--- a/src/client/clientobject.h
+++ b/src/client/clientobject.h
@@ -41,10 +41,9 @@ public:
virtual void addToScene(ITextureSource *tsrc) {}
virtual void removeFromScene(bool permanent) {}
- // 0 <= light_at_pos <= LIGHT_SUN
- virtual void updateLight(u8 light_at_pos) {}
- virtual void updateLightNoCheck(u8 light_at_pos) {}
- virtual v3s16 getLightPosition() { return v3s16(0, 0, 0); }
+
+ virtual void updateLight(u32 day_night_ratio) {}
+
virtual bool getCollisionBox(aabb3f *toset) const { return false; }
virtual bool getSelectionBox(aabb3f *toset) const { return false; }
virtual bool collideWithObjects() const { return false; }
diff --git a/src/clientsimpleobject.h b/src/client/clientsimpleobject.h
index f4a40bcd3..f4a40bcd3 100644
--- a/src/clientsimpleobject.h
+++ b/src/client/clientsimpleobject.h
diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp
index d148df522..4f949f6b0 100644
--- a/src/client/content_cao.cpp
+++ b/src/client/content_cao.cpp
@@ -162,6 +162,15 @@ static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
matrix.setTextureScale(txs, tys);
}
+// Evaluate transform chain recursively; irrlicht does not do this for us
+static void updatePositionRecursive(scene::ISceneNode *node)
+{
+ scene::ISceneNode *parent = node->getParent();
+ if (parent)
+ updatePositionRecursive(parent);
+ node->updateAbsolutePosition();
+}
+
/*
TestCAO
*/
@@ -181,8 +190,7 @@ public:
void addToScene(ITextureSource *tsrc);
void removeFromScene(bool permanent);
- void updateLight(u8 light_at_pos);
- v3s16 getLightPosition();
+ void updateLight(u32 day_night_ratio);
void updateNodePos();
void step(float dtime, ClientEnvironment *env);
@@ -254,13 +262,8 @@ void TestCAO::removeFromScene(bool permanent)
m_node = NULL;
}
-void TestCAO::updateLight(u8 light_at_pos)
-{
-}
-
-v3s16 TestCAO::getLightPosition()
+void TestCAO::updateLight(u32 day_night_ratio)
{
- return floatToInt(m_position, BS);
}
void TestCAO::updateNodePos()
@@ -304,7 +307,6 @@ void TestCAO::processMessage(const std::string &data)
GenericCAO
*/
-#include "genericobject.h"
#include "clientobject.h"
GenericCAO::GenericCAO(Client *client, ClientEnvironment *env):
@@ -476,6 +478,7 @@ void GenericCAO::setAttachment(int parent_id, const std::string &bone, v3f posit
parent->addAttachmentChild(m_id);
}
+
updateAttachments();
}
@@ -576,16 +579,23 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
m_visuals_expired = false;
- if (!m_prop.is_visible) {
+ if (!m_prop.is_visible)
return;
- }
+
+ infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl;
if (m_enable_shaders) {
IShaderSource *shader_source = m_client->getShaderSource();
- u32 shader_id = shader_source->getShader(
- "object_shader",
- TILE_MATERIAL_BASIC,
- NDT_NORMAL);
+ MaterialType material_type;
+
+ if (m_prop.shaded && m_prop.glow == 0)
+ material_type = (m_prop.use_texture_alpha) ?
+ TILE_MATERIAL_ALPHA : TILE_MATERIAL_BASIC;
+ else
+ material_type = (m_prop.use_texture_alpha) ?
+ TILE_MATERIAL_PLAIN_ALPHA : TILE_MATERIAL_PLAIN;
+
+ u32 shader_id = shader_source->getShader("object_shader", material_type, NDT_NORMAL);
m_material_type = shader_source->getShaderInfo(shader_id).material;
} else {
m_material_type = (m_prop.use_texture_alpha) ?
@@ -593,7 +603,6 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
}
auto grabMatrixNode = [this] {
- infostream << "GenericCAO::addToScene(): " << m_prop.visual << std::endl;
m_matrixnode = RenderingEngine::get_scene_manager()->
addDummyTransformationSceneNode();
m_matrixnode->grab();
@@ -714,6 +723,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
mesh->drop();
m_meshnode->setScale(m_prop.visual_size);
+ m_meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING,
+ m_prop.backface_culling);
setSceneNodeMaterial(m_meshnode);
} else if (m_prop.visual == "mesh") {
@@ -724,6 +735,14 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
addAnimatedMeshSceneNode(mesh, m_matrixnode);
m_animated_meshnode->grab();
mesh->drop(); // The scene node took hold of it
+
+ if (!checkMeshNormals(mesh)) {
+ infostream << "GenericCAO: recalculating normals for mesh "
+ << m_prop.mesh << std::endl;
+ m_smgr->getMeshManipulator()->
+ recalculateNormals(mesh, true, false);
+ }
+
m_animated_meshnode->animateJoints(); // Needed for some animations
m_animated_meshnode->setScale(m_prop.visual_size);
@@ -775,15 +794,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
if (node && m_matrixnode)
node->setParent(m_matrixnode);
- if (node && !m_prop.nametag.empty() && !m_is_local_player) {
- // Add nametag
- v3f pos;
- pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f;
- m_nametag = m_client->getCamera()->addNametag(node,
- m_prop.nametag, m_prop.nametag_color,
- pos);
- }
-
+ updateNametag();
updateNodePos();
updateAnimation();
updateBonePosition();
@@ -791,34 +802,32 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
setNodeLight(m_last_light);
}
-void GenericCAO::updateLight(u8 light_at_pos)
+void GenericCAO::updateLight(u32 day_night_ratio)
{
- // Don't update light of attached one
- if (getParent() != NULL) {
+ if (m_glow < 0)
return;
- }
-
- updateLightNoCheck(light_at_pos);
- // Update light of all children
- for (u16 i : m_attachment_child_ids) {
- ClientActiveObject *obj = m_env->getActiveObject(i);
- if (obj) {
- obj->updateLightNoCheck(light_at_pos);
+ u8 light_at_pos = 0;
+ bool pos_ok = false;
+
+ v3s16 pos[3];
+ u16 npos = getLightPosition(pos);
+ for (u16 i = 0; i < npos; i++) {
+ bool this_ok;
+ MapNode n = m_env->getMap().getNode(pos[i], &this_ok);
+ if (this_ok) {
+ u8 this_light = n.getLightBlend(day_night_ratio, m_client->ndef());
+ light_at_pos = MYMAX(light_at_pos, this_light);
+ pos_ok = true;
}
}
-}
-
-void GenericCAO::updateLightNoCheck(u8 light_at_pos)
-{
- if (m_glow < 0)
- return;
+ if (!pos_ok)
+ light_at_pos = blend_light(day_night_ratio, LIGHT_SUN, 0);
- u8 li = decode_light(light_at_pos + m_glow);
-
- if (li != m_last_light) {
- m_last_light = li;
- setNodeLight(li);
+ u8 light = decode_light(light_at_pos + m_glow);
+ if (light != m_last_light) {
+ m_last_light = light;
+ setNodeLight(light);
}
}
@@ -827,24 +836,26 @@ void GenericCAO::setNodeLight(u8 light)
video::SColor color(255, light, light, light);
if (m_prop.visual == "wielditem" || m_prop.visual == "item") {
- // Since these types of visuals are using their own shader
- // they should be handled separately
if (m_wield_meshnode)
- m_wield_meshnode->setColor(color);
- } else if (m_enable_shaders) {
- scene::ISceneNode *node = getSceneNode();
-
- if (node == nullptr)
- return;
+ m_wield_meshnode->setNodeLightColor(color);
+ return;
+ }
+ if (m_enable_shaders) {
if (m_prop.visual == "upright_sprite") {
+ if (!m_meshnode)
+ return;
+
scene::IMesh *mesh = m_meshnode->getMesh();
for (u32 i = 0; i < mesh->getMeshBufferCount(); ++i) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
- video::SMaterial &material = buf->getMaterial();
- material.EmissiveColor = color;
+ buf->getMaterial().EmissiveColor = color;
}
} else {
+ scene::ISceneNode *node = getSceneNode();
+ if (!node)
+ return;
+
for (u32 i = 0; i < node->getMaterialCount(); ++i) {
video::SMaterial &material = node->getMaterial(i);
material.EmissiveColor = color;
@@ -861,12 +872,49 @@ void GenericCAO::setNodeLight(u8 light)
}
}
-v3s16 GenericCAO::getLightPosition()
+u16 GenericCAO::getLightPosition(v3s16 *pos)
+{
+ const auto &box = m_prop.collisionbox;
+ pos[0] = floatToInt(m_position + box.MinEdge * BS, BS);
+ pos[1] = floatToInt(m_position + box.MaxEdge * BS, BS);
+
+ // Skip center pos if it falls into the same node as Min or MaxEdge
+ if ((box.MaxEdge - box.MinEdge).getLengthSQ() < 3.0f)
+ return 2;
+ pos[2] = floatToInt(m_position + box.getCenter() * BS, BS);
+ return 3;
+}
+
+void GenericCAO::updateNametag()
{
- if (m_is_player)
- return floatToInt(m_position + v3f(0, 0.5 * BS, 0), BS);
+ if (m_is_local_player) // No nametag for local player
+ return;
+
+ if (m_prop.nametag.empty()) {
+ // Delete nametag
+ if (m_nametag) {
+ m_client->getCamera()->removeNametag(m_nametag);
+ m_nametag = nullptr;
+ }
+ return;
+ }
- return floatToInt(m_position, BS);
+ scene::ISceneNode *node = getSceneNode();
+ if (!node)
+ return;
+
+ v3f pos;
+ pos.Y = m_prop.selectionbox.MaxEdge.Y + 0.3f;
+ if (!m_nametag) {
+ // Add nametag
+ m_nametag = m_client->getCamera()->addNametag(node,
+ m_prop.nametag, m_prop.nametag_color, pos);
+ } else {
+ // Update nametag
+ m_nametag->nametag_text = m_prop.nametag;
+ m_nametag->nametag_color = m_prop.nametag_color;
+ m_nametag->nametag_pos = pos;
+ }
}
void GenericCAO::updateNodePos()
@@ -1072,10 +1120,13 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
updateTextures(m_previous_texture_modifier);
}
}
+
if (!getParent() && std::fabs(m_prop.automatic_rotate) > 0.001) {
- m_rotation.Y += dtime * m_prop.automatic_rotate * 180 / M_PI;
- rot_translator.val_current = m_rotation;
- updateNodePos();
+ // This is the child node's rotation. It is only used for automatic_rotate.
+ v3f local_rot = node->getRotation();
+ local_rot.Y = modulo360f(local_rot.Y - dtime * core::RADTODEG *
+ m_prop.automatic_rotate);
+ node->setRotation(local_rot);
}
if (!getParent() && m_prop.automatic_face_movement_dir &&
@@ -1096,6 +1147,18 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
rot_translator.val_current = m_rotation;
updateNodePos();
}
+
+ if (m_animated_meshnode) {
+ // Everything must be updated; the whole transform
+ // chain as well as the animated mesh node.
+ // Otherwise, bone attachments would be relative to
+ // a position that's one frame old.
+ if (m_matrixnode)
+ updatePositionRecursive(m_matrixnode);
+ m_animated_meshnode->updateAbsolutePosition();
+ m_animated_meshnode->animateJoints();
+ updateBonePosition();
+ }
}
void GenericCAO::updateTexturePos()
@@ -1360,16 +1423,53 @@ void GenericCAO::updateBonePosition()
return;
m_animated_meshnode->setJointMode(irr::scene::EJUOR_CONTROL); // To write positions to the mesh on render
- for(std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
- ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
- std::string bone_name = (*ii).first;
- v3f bone_pos = (*ii).second.X;
- v3f bone_rot = (*ii).second.Y;
+ for (auto &it : m_bone_position) {
+ std::string bone_name = it.first;
irr::scene::IBoneSceneNode* bone = m_animated_meshnode->getJointNode(bone_name.c_str());
- if(bone)
- {
- bone->setPosition(bone_pos);
+ if (bone) {
+ bone->setPosition(it.second.X);
+ bone->setRotation(it.second.Y);
+ }
+ }
+
+ // search through bones to find mistakenly rotated bones due to bug in Irrlicht
+ for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) {
+ irr::scene::IBoneSceneNode *bone = m_animated_meshnode->getJointNode(i);
+ if (!bone)
+ continue;
+
+ //If bone is manually positioned there is no need to perform the bug check
+ bool skip = false;
+ for (auto &it : m_bone_position) {
+ if (it.first == bone->getName()) {
+ skip = true;
+ break;
+ }
+ }
+ if (skip)
+ continue;
+
+ // Workaround for Irrlicht bug
+ // We check each bone to see if it has been rotated ~180deg from its expected position due to a bug in Irricht
+ // when using EJUOR_CONTROL joint control. If the bug is detected we update the bone to the proper position
+ // and update the bones transformation.
+ v3f bone_rot = bone->getRelativeTransformation().getRotationDegrees();
+ float offset = fabsf(bone_rot.X - bone->getRotation().X);
+ if (offset > 179.9f && offset < 180.1f) {
bone->setRotation(bone_rot);
+ bone->updateAbsolutePosition();
+ }
+ }
+ // The following is needed for set_bone_pos to propagate to
+ // attached objects correctly.
+ // Irrlicht ought to do this, but doesn't when using EJUOR_CONTROL.
+ for (u32 i = 0; i < m_animated_meshnode->getJointCount(); ++i) {
+ auto bone = m_animated_meshnode->getJointNode(i);
+ // Look for the root bone.
+ if (bone && bone->getParent() == m_animated_meshnode) {
+ // Update entire skeleton.
+ bone->updateAbsolutePositionOfAllChildren();
+ break;
}
}
}
@@ -1394,15 +1494,17 @@ void GenericCAO::updateAttachments()
if (!parent) { // Detach or don't attach
if (m_matrixnode) {
+ v3s16 camera_offset = m_env->getCameraOffset();
v3f old_pos = getPosition();
m_matrixnode->setParent(m_smgr->getRootSceneNode());
- getPosRotMatrix().setTranslation(old_pos);
+ getPosRotMatrix().setTranslation(old_pos - intToFloat(camera_offset, BS));
m_matrixnode->updateAbsolutePosition();
}
}
else // Attach
{
+ parent->updateAttachments();
scene::ISceneNode *parent_node = parent->getSceneNode();
scene::IAnimatedMeshSceneNode *parent_animated_mesh_node =
parent->getAnimatedMeshSceneNode();
@@ -1412,6 +1514,7 @@ void GenericCAO::updateAttachments()
if (m_matrixnode && parent_node) {
m_matrixnode->setParent(parent_node);
+ parent_node->updateAbsolutePosition();
getPosRotMatrix().setTranslation(m_attachment_position);
//setPitchYawRoll(getPosRotMatrix(), m_attachment_rotation);
// use Irrlicht eulers instead
@@ -1421,21 +1524,55 @@ void GenericCAO::updateAttachments()
}
}
+bool GenericCAO::visualExpiryRequired(const ObjectProperties &new_) const
+{
+ const ObjectProperties &old = m_prop;
+ /* Visuals do not need to be expired for:
+ * - nametag props: handled by updateNametag()
+ * - textures: handled by updateTextures()
+ * - sprite props: handled by updateTexturePos()
+ * - glow: handled by updateLight()
+ * - any other properties that do not change appearance
+ */
+
+ bool uses_legacy_texture = new_.wield_item.empty() &&
+ (new_.visual == "wielditem" || new_.visual == "item");
+ // Ordered to compare primitive types before std::vectors
+ return old.backface_culling != new_.backface_culling ||
+ old.is_visible != new_.is_visible ||
+ old.mesh != new_.mesh ||
+ old.shaded != new_.shaded ||
+ old.use_texture_alpha != new_.use_texture_alpha ||
+ old.visual != new_.visual ||
+ old.visual_size != new_.visual_size ||
+ old.wield_item != new_.wield_item ||
+ old.colors != new_.colors ||
+ (uses_legacy_texture && old.textures != new_.textures);
+}
+
void GenericCAO::processMessage(const std::string &data)
{
//infostream<<"GenericCAO: Got message"<<std::endl;
std::istringstream is(data, std::ios::binary);
// command
u8 cmd = readU8(is);
- if (cmd == GENERIC_CMD_SET_PROPERTIES) {
- m_prop = gob_read_set_properties(is);
+ if (cmd == AO_CMD_SET_PROPERTIES) {
+ ObjectProperties newprops;
+ newprops.deSerialize(is);
+
+ // Check what exactly changed
+ bool expire_visuals = visualExpiryRequired(newprops);
+ bool textures_changed = m_prop.textures != newprops.textures;
+
+ // Apply changes
+ m_prop = std::move(newprops);
m_selection_box = m_prop.selectionbox;
m_selection_box.MinEdge *= BS;
m_selection_box.MaxEdge *= BS;
- m_tx_size.X = 1.0 / m_prop.spritediv.X;
- m_tx_size.Y = 1.0 / m_prop.spritediv.Y;
+ m_tx_size.X = 1.0f / m_prop.spritediv.X;
+ m_tx_size.Y = 1.0f / m_prop.spritediv.Y;
if(!m_initial_tx_basepos_set){
m_initial_tx_basepos_set = true;
@@ -1455,18 +1592,25 @@ void GenericCAO::processMessage(const std::string &data)
if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty())
m_prop.nametag = m_name;
- expireVisuals();
- } else if (cmd == GENERIC_CMD_UPDATE_POSITION) {
+ if (expire_visuals) {
+ expireVisuals();
+ } else {
+ infostream << "GenericCAO: properties updated but expiring visuals"
+ << " not necessary" << std::endl;
+ if (textures_changed) {
+ // don't update while punch texture modifier is active
+ if (m_reset_textures_timer < 0)
+ updateTextures(m_current_texture_modifier);
+ }
+ updateNametag();
+ }
+ } else if (cmd == AO_CMD_UPDATE_POSITION) {
// Not sent by the server if this object is an attachment.
// We might however get here if the server notices the object being detached before the client.
m_position = readV3F32(is);
m_velocity = readV3F32(is);
m_acceleration = readV3F32(is);
-
- if (std::fabs(m_prop.automatic_rotate) < 0.001f)
- m_rotation = readV3F32(is);
- else
- readV3F32(is);
+ m_rotation = readV3F32(is);
m_rotation = wrapDegrees_0_360_v3f(m_rotation);
bool do_interpolate = readU8(is);
@@ -1490,16 +1634,16 @@ void GenericCAO::processMessage(const std::string &data)
}
rot_translator.update(m_rotation, false, update_interval);
updateNodePos();
- } else if (cmd == GENERIC_CMD_SET_TEXTURE_MOD) {
+ } else if (cmd == AO_CMD_SET_TEXTURE_MOD) {
std::string mod = deSerializeString(is);
- // immediatly reset a engine issued texture modifier if a mod sends a different one
+ // immediately reset a engine issued texture modifier if a mod sends a different one
if (m_reset_textures_timer > 0) {
m_reset_textures_timer = -1;
updateTextures(m_previous_texture_modifier);
}
updateTextures(mod);
- } else if (cmd == GENERIC_CMD_SET_SPRITE) {
+ } else if (cmd == AO_CMD_SET_SPRITE) {
v2s16 p = readV2S16(is);
int num_frames = readU16(is);
float framelength = readF32(is);
@@ -1511,7 +1655,7 @@ void GenericCAO::processMessage(const std::string &data)
m_tx_select_horiz_by_yawpitch = select_horiz_by_yawpitch;
updateTexturePos();
- } else if (cmd == GENERIC_CMD_SET_PHYSICS_OVERRIDE) {
+ } else if (cmd == AO_CMD_SET_PHYSICS_OVERRIDE) {
float override_speed = readF32(is);
float override_jump = readF32(is);
float override_gravity = readF32(is);
@@ -1531,7 +1675,7 @@ void GenericCAO::processMessage(const std::string &data)
player->physics_override_sneak_glitch = sneak_glitch;
player->physics_override_new_move = new_move;
}
- } else if (cmd == GENERIC_CMD_SET_ANIMATION) {
+ } else if (cmd == AO_CMD_SET_ANIMATION) {
// TODO: change frames send as v2s32 value
v2f range = readV2F32(is);
if (!m_is_local_player) {
@@ -1565,17 +1709,17 @@ void GenericCAO::processMessage(const std::string &data)
updateAnimation();
}
}
- } else if (cmd == GENERIC_CMD_SET_ANIMATION_SPEED) {
+ } else if (cmd == AO_CMD_SET_ANIMATION_SPEED) {
m_animation_speed = readF32(is);
updateAnimationSpeed();
- } else if (cmd == GENERIC_CMD_SET_BONE_POSITION) {
+ } else if (cmd == AO_CMD_SET_BONE_POSITION) {
std::string bone = deSerializeString(is);
v3f position = readV3F32(is);
v3f rotation = readV3F32(is);
m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
- updateBonePosition();
- } else if (cmd == GENERIC_CMD_ATTACH_TO) {
+ // updateBonePosition(); now called every step
+ } else if (cmd == AO_CMD_ATTACH_TO) {
u16 parent_id = readS16(is);
std::string bone = deSerializeString(is);
v3f position = readV3F32(is);
@@ -1586,7 +1730,7 @@ void GenericCAO::processMessage(const std::string &data)
// localplayer itself can't be attached to localplayer
if (!m_is_local_player)
m_is_visible = !m_attached_to_local;
- } else if (cmd == GENERIC_CMD_PUNCHED) {
+ } else if (cmd == AO_CMD_PUNCHED) {
u16 result_hp = readU16(is);
// Use this instead of the send damage to not interfere with prediction
@@ -1607,13 +1751,11 @@ void GenericCAO::processMessage(const std::string &data)
m_smgr, m_env, m_position,
v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS);
m_env->addSimpleObject(simple);
- } else if (m_reset_textures_timer < 0) {
- // TODO: Execute defined fast response
- // Flashing shall suffice as there is no definition
+ } else if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) {
m_reset_textures_timer = 0.05;
if(damage >= 2)
m_reset_textures_timer += 0.05 * damage;
- updateTextures(m_current_texture_modifier + "^[brighten");
+ updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier);
}
}
@@ -1624,7 +1766,7 @@ void GenericCAO::processMessage(const std::string &data)
if (!m_is_player)
clearChildAttachments();
}
- } else if (cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) {
+ } else if (cmd == AO_CMD_UPDATE_ARMOR_GROUPS) {
m_armor_groups.clear();
int armor_groups_size = readU16(is);
for(int i=0; i<armor_groups_size; i++)
@@ -1633,22 +1775,14 @@ void GenericCAO::processMessage(const std::string &data)
int rating = readS16(is);
m_armor_groups[name] = rating;
}
- } else if (cmd == GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) {
- // Deprecated, for backwards compatibility only.
- readU8(is); // version
- m_prop.nametag_color = readARGB8(is);
- if (m_nametag != NULL) {
- m_nametag->nametag_color = m_prop.nametag_color;
- v3f pos;
- pos.Y = m_prop.collisionbox.MaxEdge.Y + 0.3f;
- m_nametag->nametag_pos = pos;
- }
- } else if (cmd == GENERIC_CMD_SPAWN_INFANT) {
+ } else if (cmd == AO_CMD_SPAWN_INFANT) {
u16 child_id = readU16(is);
u8 type = readU8(is); // maybe this will be useful later
(void)type;
addAttachmentChild(child_id);
+ } else if (cmd == AO_CMD_OBSOLETE1) {
+ // Don't do anything and also don't log a warning
} else {
warningstream << FUNCTION_NAME
<< ": unknown command or outdated client \""
@@ -1684,13 +1818,11 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem,
v2f(m_prop.visual_size.X, m_prop.visual_size.Y) * BS);
m_env->addSimpleObject(simple);
}
- // TODO: Execute defined fast response
- // Flashing shall suffice as there is no definition
- if (m_reset_textures_timer < 0) {
+ if (m_reset_textures_timer < 0 && !m_prop.damage_texture_modifier.empty()) {
m_reset_textures_timer = 0.05;
if (result.damage >= 2)
m_reset_textures_timer += 0.05 * result.damage;
- updateTextures(m_current_texture_modifier + "^[brighten");
+ updateTextures(m_current_texture_modifier + m_prop.damage_texture_modifier);
}
}
diff --git a/src/client/content_cao.h b/src/client/content_cao.h
index 7c29cbf17..974ff9a1e 100644
--- a/src/client/content_cao.h
+++ b/src/client/content_cao.h
@@ -130,6 +130,8 @@ private:
// Settings
bool m_enable_shaders = false;
+ bool visualExpiryRequired(const ObjectProperties &newprops) const;
+
public:
GenericCAO(Client *client, ClientEnvironment *env);
@@ -186,10 +188,11 @@ public:
return m_matrixnode->getRelativeTransformationMatrix();
}
- inline const core::matrix4 &getAbsolutePosRotMatrix() const
+ inline const core::matrix4 *getAbsolutePosRotMatrix() const
{
- assert(m_matrixnode);
- return m_matrixnode->getAbsoluteTransformation();
+ if (!m_matrixnode)
+ return nullptr;
+ return &m_matrixnode->getAbsoluteTransformation();
}
inline f32 getStepHeight() const
@@ -234,13 +237,16 @@ public:
m_visuals_expired = true;
}
- void updateLight(u8 light_at_pos);
-
- void updateLightNoCheck(u8 light_at_pos);
+ void updateLight(u32 day_night_ratio);
void setNodeLight(u8 light);
- v3s16 getLightPosition();
+ /* Get light position(s).
+ * returns number of positions written into pos[], which must have space
+ * for at least 3 vectors. */
+ u16 getLightPosition(v3s16 *pos);
+
+ void updateNametag();
void updateNodePos();
diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp
index 9b4fd221e..3d06584c4 100644
--- a/src/client/content_mapblock.cpp
+++ b/src/client/content_mapblock.cpp
@@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <cmath>
#include "content_mapblock.h"
#include "util/numeric.h"
#include "util/directiontables.h"
@@ -366,6 +367,7 @@ void MapblockMeshGenerator::generateCuboidTextureCoords(const aabb3f &box, f32 *
void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
TileSpec *tiles, int tile_count)
{
+ bool scale = std::fabs(f->visual_scale - 1.0f) > 1e-3f;
f32 texture_coord_buf[24];
f32 dx1 = box.MinEdge.X;
f32 dy1 = box.MinEdge.Y;
@@ -373,6 +375,14 @@ void MapblockMeshGenerator::drawAutoLightedCuboid(aabb3f box, const f32 *txc,
f32 dx2 = box.MaxEdge.X;
f32 dy2 = box.MaxEdge.Y;
f32 dz2 = box.MaxEdge.Z;
+ if (scale) {
+ if (!txc) { // generate texture coords before scaling
+ generateCuboidTextureCoords(box, texture_coord_buf);
+ txc = texture_coord_buf;
+ }
+ box.MinEdge *= f->visual_scale;
+ box.MaxEdge *= f->visual_scale;
+ }
box.MinEdge += origin;
box.MaxEdge += origin;
if (!txc) {
@@ -405,8 +415,8 @@ void MapblockMeshGenerator::prepareLiquidNodeDrawing()
MapNode ntop = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y + 1, p.Z));
MapNode nbottom = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(p.X, p.Y - 1, p.Z));
- c_flowing = nodedef->getId(f->liquid_alternative_flowing);
- c_source = nodedef->getId(f->liquid_alternative_source);
+ c_flowing = f->liquid_alternative_flowing_id;
+ c_source = f->liquid_alternative_source_id;
top_is_same_liquid = (ntop.getContent() == c_flowing) || (ntop.getContent() == c_source);
draw_liquid_bottom = (nbottom.getContent() != c_flowing) && (nbottom.getContent() != c_source);
if (draw_liquid_bottom) {
@@ -512,8 +522,7 @@ f32 MapblockMeshGenerator::getCornerLevel(int i, int k)
return 0;
}
-void MapblockMeshGenerator::drawLiquidSides()
-{
+namespace {
struct LiquidFaceDesc {
v3s16 dir; // XZ
v3s16 p[2]; // XZ only; 1 means +, 0 means -
@@ -521,20 +530,23 @@ void MapblockMeshGenerator::drawLiquidSides()
struct UV {
int u, v;
};
- static const LiquidFaceDesc base_faces[4] = {
+ static const LiquidFaceDesc liquid_base_faces[4] = {
{v3s16( 1, 0, 0), {v3s16(1, 0, 1), v3s16(1, 0, 0)}},
{v3s16(-1, 0, 0), {v3s16(0, 0, 0), v3s16(0, 0, 1)}},
{v3s16( 0, 0, 1), {v3s16(0, 0, 1), v3s16(1, 0, 1)}},
{v3s16( 0, 0, -1), {v3s16(1, 0, 0), v3s16(0, 0, 0)}},
};
- static const UV base_vertices[4] = {
+ static const UV liquid_base_vertices[4] = {
{0, 1},
{1, 1},
{1, 0},
{0, 0}
};
+}
- for (const auto &face : base_faces) {
+void MapblockMeshGenerator::drawLiquidSides()
+{
+ for (const auto &face : liquid_base_faces) {
const NeighborData &neighbor = liquid_neighbors[face.dir.Z + 1][face.dir.X + 1];
// No face between nodes of the same liquid, unless there is node
@@ -554,7 +566,7 @@ void MapblockMeshGenerator::drawLiquidSides()
video::S3DVertex vertices[4];
for (int j = 0; j < 4; j++) {
- const UV &vertex = base_vertices[j];
+ const UV &vertex = liquid_base_vertices[j];
const v3s16 &base = face.p[vertex.u];
float v = vertex.v;
@@ -1193,15 +1205,14 @@ bool MapblockMeshGenerator::isSameRail(v3s16 dir)
(def2.getGroup(raillike_groupname) == raillike_group));
}
-void MapblockMeshGenerator::drawRaillikeNode()
-{
- static const v3s16 direction[4] = {
+namespace {
+ static const v3s16 rail_direction[4] = {
v3s16( 0, 0, 1),
v3s16( 0, 0, -1),
v3s16(-1, 0, 0),
v3s16( 1, 0, 0),
};
- static const int slope_angle[4] = {0, 180, 90, -90};
+ static const int rail_slope_angle[4] = {0, 180, 90, -90};
enum RailTile {
straight,
@@ -1214,8 +1225,8 @@ void MapblockMeshGenerator::drawRaillikeNode()
int angle;
};
static const RailDesc rail_kinds[16] = {
- // +x -x -z +z
- //-------------
+ // +x -x -z +z
+ //-------------
{straight, 0}, // . . . .
{straight, 0}, // . . . +Z
{straight, 0}, // . . -Z .
@@ -1233,7 +1244,10 @@ void MapblockMeshGenerator::drawRaillikeNode()
{junction, 270}, // +X -X -Z .
{ cross, 0}, // +X -X -Z +Z
};
+}
+void MapblockMeshGenerator::drawRaillikeNode()
+{
raillike_group = nodedef->get(n).getGroup(raillike_groupname);
int code = 0;
@@ -1241,14 +1255,14 @@ void MapblockMeshGenerator::drawRaillikeNode()
int tile_index;
bool sloped = false;
for (int dir = 0; dir < 4; dir++) {
- bool rail_above = isSameRail(direction[dir] + v3s16(0, 1, 0));
+ bool rail_above = isSameRail(rail_direction[dir] + v3s16(0, 1, 0));
if (rail_above) {
sloped = true;
- angle = slope_angle[dir];
+ angle = rail_slope_angle[dir];
}
if (rail_above ||
- isSameRail(direction[dir]) ||
- isSameRail(direction[dir] + v3s16(0, -1, 0)))
+ isSameRail(rail_direction[dir]) ||
+ isSameRail(rail_direction[dir] + v3s16(0, -1, 0)))
code |= 1 << dir;
}
@@ -1276,9 +1290,8 @@ void MapblockMeshGenerator::drawRaillikeNode()
drawQuad(vertices);
}
-void MapblockMeshGenerator::drawNodeboxNode()
-{
- static const v3s16 tile_dirs[6] = {
+namespace {
+ static const v3s16 nodebox_tile_dirs[6] = {
v3s16(0, 1, 0),
v3s16(0, -1, 0),
v3s16(1, 0, 0),
@@ -1288,7 +1301,7 @@ void MapblockMeshGenerator::drawNodeboxNode()
};
// we have this order for some reason...
- static const v3s16 connection_dirs[6] = {
+ static const v3s16 nodebox_connection_dirs[6] = {
v3s16( 0, 1, 0), // top
v3s16( 0, -1, 0), // bottom
v3s16( 0, 0, -1), // front
@@ -1296,19 +1309,22 @@ void MapblockMeshGenerator::drawNodeboxNode()
v3s16( 0, 0, 1), // back
v3s16( 1, 0, 0), // right
};
+}
+void MapblockMeshGenerator::drawNodeboxNode()
+{
TileSpec tiles[6];
for (int face = 0; face < 6; face++) {
// Handles facedir rotation for textures
- getTile(tile_dirs[face], &tiles[face]);
+ getTile(nodebox_tile_dirs[face], &tiles[face]);
}
// locate possible neighboring nodes to connect to
- int neighbors_set = 0;
+ u8 neighbors_set = 0;
if (f->node_box.type == NODEBOX_CONNECTED) {
for (int dir = 0; dir != 6; dir++) {
- int flag = 1 << dir;
- v3s16 p2 = blockpos_nodes + p + connection_dirs[dir];
+ u8 flag = 1 << dir;
+ v3s16 p2 = blockpos_nodes + p + nodebox_connection_dirs[dir];
MapNode n2 = data->m_vmanip.getNodeNoEx(p2);
if (nodedef->nodeboxConnects(n, n2, flag))
neighbors_set |= flag;
@@ -1317,7 +1333,7 @@ void MapblockMeshGenerator::drawNodeboxNode()
std::vector<aabb3f> boxes;
n.getNodeBoxes(nodedef, &boxes, neighbors_set);
- for (const auto &box : boxes)
+ for (auto &box : boxes)
drawAutoLightedCuboid(box, nullptr, tiles, 6);
}
diff --git a/src/client/filecache.cpp b/src/client/filecache.cpp
index 3d1b302a8..46bbe4059 100644
--- a/src/client/filecache.cpp
+++ b/src/client/filecache.cpp
@@ -82,8 +82,16 @@ bool FileCache::update(const std::string &name, const std::string &data)
std::string path = m_dir + DIR_DELIM + name;
return updateByPath(path, data);
}
+
bool FileCache::load(const std::string &name, std::ostream &os)
{
std::string path = m_dir + DIR_DELIM + name;
return loadByPath(path, os);
}
+
+bool FileCache::exists(const std::string &name)
+{
+ std::string path = m_dir + DIR_DELIM + name;
+ std::ifstream fis(path.c_str(), std::ios_base::binary);
+ return fis.good();
+}
diff --git a/src/client/filecache.h b/src/client/filecache.h
index 96e4c8ba1..ea6afc4b2 100644
--- a/src/client/filecache.h
+++ b/src/client/filecache.h
@@ -33,6 +33,7 @@ public:
bool update(const std::string &name, const std::string &data);
bool load(const std::string &name, std::ostream &os);
+ bool exists(const std::string &name);
private:
std::string m_dir;
diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp
index 2b5841cd8..61d52cc2f 100644
--- a/src/client/fontengine.cpp
+++ b/src/client/fontengine.cpp
@@ -239,7 +239,7 @@ void FontEngine::updateSkin()
FATAL_ERROR_IF(font == NULL, "Could not create/get font");
u32 text_height = font->getDimension(L"Hello, world!").Height;
- infostream << "text_height=" << text_height << std::endl;
+ infostream << "FontEngine: measured text_height=" << text_height << std::endl;
}
/******************************************************************************/
diff --git a/src/client/game.cpp b/src/client/game.cpp
index 0201ded69..069c482ca 100644
--- a/src/client/game.cpp
+++ b/src/client/game.cpp
@@ -266,6 +266,7 @@ class SoundMaker
public:
bool makes_footstep_sound;
float m_player_step_timer;
+ float m_player_jump_timer;
SimpleSoundSpec m_player_step_sound;
SimpleSoundSpec m_player_leftpunch_sound;
@@ -275,7 +276,8 @@ public:
m_sound(sound),
m_ndef(ndef),
makes_footstep_sound(true),
- m_player_step_timer(0)
+ m_player_step_timer(0.0f),
+ m_player_jump_timer(0.0f)
{
}
@@ -288,6 +290,14 @@ public:
}
}
+ void playPlayerJump()
+ {
+ if (m_player_jump_timer <= 0.0f) {
+ m_player_jump_timer = 0.2f;
+ m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false);
+ }
+ }
+
static void viewBobbingStep(MtEvent *e, void *data)
{
SoundMaker *sm = (SoundMaker *)data;
@@ -302,7 +312,8 @@ public:
static void playerJump(MtEvent *e, void *data)
{
- //SoundMaker *sm = (SoundMaker*)data;
+ SoundMaker *sm = (SoundMaker *)data;
+ sm->playPlayerJump();
}
static void cameraPunchLeft(MtEvent *e, void *data)
@@ -351,6 +362,7 @@ public:
void step(float dtime)
{
m_player_step_timer -= dtime;
+ m_player_jump_timer -= dtime;
}
};
@@ -843,6 +855,7 @@ private:
SoundMaker *soundmaker = nullptr;
ChatBackend *chat_backend = nullptr;
+ LogOutputBuffer m_chat_log_buf;
EventManager *eventmgr = nullptr;
QuicktuneShortcutter *quicktune = nullptr;
@@ -914,6 +927,7 @@ private:
};
Game::Game() :
+ m_chat_log_buf(g_logger),
m_game_ui(new GameUI())
{
g_settings->registerChangedCallback("doubletap_jump",
@@ -1043,7 +1057,7 @@ bool Game::startup(bool *kill,
m_invert_mouse = g_settings->getBool("invert_mouse");
m_first_loop_after_window_activation = true;
- g_translations->clear();
+ g_client_translations->clear();
if (!init(map_dir, address, port, gamespec))
return false;
@@ -1159,6 +1173,10 @@ void Game::shutdown()
if (formspec)
formspec->quitMenu();
+#ifdef HAVE_TOUCHSCREENGUI
+ g_touchscreengui->hide();
+#endif
+
showOverlayMessage(N_("Shutting down..."), 0, 0, false);
if (clouds)
@@ -1180,6 +1198,7 @@ void Game::shutdown()
chat_backend->addMessage(L"", L"# Disconnected.");
chat_backend->addMessage(L"", L"");
+ m_chat_log_buf.clear();
if (client) {
client->Stop();
@@ -1237,7 +1256,7 @@ bool Game::init(
bool Game::initSound()
{
#if USE_SOUND
- if (g_settings->getBool("enable_sound")) {
+ if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get()) {
infostream << "Attempting to use OpenAL audio" << std::endl;
sound = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
if (!sound)
@@ -1290,7 +1309,6 @@ bool Game::createSingleplayerServer(const std::string &map_dir,
}
server = new Server(map_dir, gamespec, simple_singleplayer_mode, bind_addr, false);
- server->init();
server->start();
return true;
@@ -1407,8 +1425,11 @@ bool Game::createClient(const std::string &playername,
}
mapper = client->getMinimap();
- if (mapper)
+ if (mapper) {
mapper->setMinimapMode(MINIMAP_MODE_OFF);
+ if (client->modsLoaded())
+ client->getScript()->on_minimap_ready(mapper);
+ }
return true;
}
@@ -1553,7 +1574,8 @@ bool Game::connectToServer(const std::string &playername,
} else {
registration_confirmation_shown = true;
(new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
- &g_menumgr, client, playername, password, connection_aborted))->drop();
+ &g_menumgr, client, playername, password,
+ connection_aborted, texture_src))->drop();
}
} else {
wait_time += dtime;
@@ -1708,19 +1730,19 @@ inline bool Game::handleCallbacks()
if (g_gamecallback->changepassword_requested) {
(new GUIPasswordChange(guienv, guiroot, -1,
- &g_menumgr, client))->drop();
+ &g_menumgr, client, texture_src))->drop();
g_gamecallback->changepassword_requested = false;
}
if (g_gamecallback->changevolume_requested) {
(new GUIVolumeChange(guienv, guiroot, -1,
- &g_menumgr))->drop();
+ &g_menumgr, texture_src))->drop();
g_gamecallback->changevolume_requested = false;
}
if (g_gamecallback->keyconfig_requested) {
(new GUIKeyChangeMenu(guienv, guiroot, -1,
- &g_menumgr))->drop();
+ &g_menumgr, texture_src))->drop();
g_gamecallback->keyconfig_requested = false;
}
@@ -1906,29 +1928,47 @@ void Game::processKeyInput()
toggleFast();
} else if (wasKeyDown(KeyType::NOCLIP)) {
toggleNoClip();
+#if USE_SOUND
} else if (wasKeyDown(KeyType::MUTE)) {
- bool new_mute_sound = !g_settings->getBool("mute_sound");
- g_settings->setBool("mute_sound", new_mute_sound);
- if (new_mute_sound)
- m_game_ui->showTranslatedStatusText("Sound muted");
- else
- m_game_ui->showTranslatedStatusText("Sound unmuted");
+ if (g_settings->getBool("enable_sound")) {
+ bool new_mute_sound = !g_settings->getBool("mute_sound");
+ g_settings->setBool("mute_sound", new_mute_sound);
+ if (new_mute_sound)
+ m_game_ui->showTranslatedStatusText("Sound muted");
+ else
+ m_game_ui->showTranslatedStatusText("Sound unmuted");
+ } else {
+ m_game_ui->showTranslatedStatusText("Sound system is disabled");
+ }
} else if (wasKeyDown(KeyType::INC_VOLUME)) {
- float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
- wchar_t buf[100];
- g_settings->setFloat("sound_volume", new_volume);
- const wchar_t *str = wgettext("Volume changed to %d%%");
- swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
- delete[] str;
- m_game_ui->showStatusText(buf);
+ if (g_settings->getBool("enable_sound")) {
+ float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f);
+ wchar_t buf[100];
+ g_settings->setFloat("sound_volume", new_volume);
+ const wchar_t *str = wgettext("Volume changed to %d%%");
+ swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
+ delete[] str;
+ m_game_ui->showStatusText(buf);
+ } else {
+ m_game_ui->showTranslatedStatusText("Sound system is disabled");
+ }
} else if (wasKeyDown(KeyType::DEC_VOLUME)) {
- float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
- wchar_t buf[100];
- g_settings->setFloat("sound_volume", new_volume);
- const wchar_t *str = wgettext("Volume changed to %d%%");
- swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
- delete[] str;
- m_game_ui->showStatusText(buf);
+ if (g_settings->getBool("enable_sound")) {
+ float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f);
+ wchar_t buf[100];
+ g_settings->setFloat("sound_volume", new_volume);
+ const wchar_t *str = wgettext("Volume changed to %d%%");
+ swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100));
+ delete[] str;
+ m_game_ui->showStatusText(buf);
+ } else {
+ m_game_ui->showTranslatedStatusText("Sound system is disabled");
+ }
+#else
+ } else if (wasKeyDown(KeyType::MUTE) || wasKeyDown(KeyType::INC_VOLUME)
+ || wasKeyDown(KeyType::DEC_VOLUME)) {
+ m_game_ui->showTranslatedStatusText("Sound system is not supported on this build");
+#endif
} else if (wasKeyDown(KeyType::CINEMATIC)) {
toggleCinematic();
} else if (wasKeyDown(KeyType::SCREENSHOT)) {
@@ -2010,7 +2050,6 @@ void Game::processItemSelection(u16 *new_playeritem)
for (u16 i = 0; i <= max_item; i++) {
if (wasKeyDown((GameKeyType) (KeyType::SLOT_1 + i))) {
*new_playeritem = i;
- infostream << "Selected item: " << new_playeritem << std::endl;
break;
}
}
@@ -2039,7 +2078,7 @@ void Game::openInventory()
if (!player || !player->getCAO())
return;
- infostream << "the_game: " << "Launching inventory" << std::endl;
+ infostream << "Game: Launching inventory" << std::endl;
PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
@@ -2451,7 +2490,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE)
);
- u32 keypress_bits =
+ u32 keypress_bits = (
( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) |
( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) |
( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) |
@@ -2460,7 +2499,8 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
( (u32)(isKeyDown(KeyType::SPECIAL1) & 0x1) << 5) |
( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) |
( (u32)(input->getLeftState() & 0x1) << 7) |
- ( (u32)(input->getRightState() & 0x1) << 8
+ ( (u32)(input->getRightState() & 0x1) << 8) |
+ ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9)
);
#ifdef ANDROID
@@ -2640,6 +2680,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
delete event->hudadd.offset;
delete event->hudadd.world_pos;
delete event->hudadd.size;
+ delete event->hudadd.text2;
return;
}
@@ -2657,6 +2698,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
e->world_pos = *event->hudadd.world_pos;
e->size = *event->hudadd.size;
e->z_index = event->hudadd.z_index;
+ e->text2 = *event->hudadd.text2;
hud_server_to_client[server_id] = player->addHud(e);
delete event->hudadd.pos;
@@ -2667,6 +2709,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
delete event->hudadd.offset;
delete event->hudadd.world_pos;
delete event->hudadd.size;
+ delete event->hudadd.text2;
}
void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
@@ -2739,6 +2782,10 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca
case HUD_STAT_Z_INDEX:
e->z_index = event->hudchange.data;
break;
+
+ case HUD_STAT_TEXT2:
+ e->text2 = *event->hudchange.sdata;
+ break;
}
delete event->hudchange.v3fdata;
@@ -2764,11 +2811,11 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
// Shows the mesh skybox
sky->setVisible(true);
// Update mesh based skybox colours if applicable.
- sky->setSkyColors(*event->set_sky);
+ sky->setSkyColors(event->set_sky->sky_color);
sky->setHorizonTint(
- event->set_sky->sun_tint,
- event->set_sky->moon_tint,
- event->set_sky->tint_type
+ event->set_sky->fog_sun_tint,
+ event->set_sky->fog_moon_tint,
+ event->set_sky->fog_tint_type
);
} else if (event->set_sky->type == "skybox" &&
event->set_sky->textures.size() == 6) {
@@ -2778,9 +2825,9 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
sky->setFallbackBgColor(event->set_sky->bgcolor);
// Set sunrise and sunset fog tinting:
sky->setHorizonTint(
- event->set_sky->sun_tint,
- event->set_sky->moon_tint,
- event->set_sky->tint_type
+ event->set_sky->fog_sun_tint,
+ event->set_sky->fog_moon_tint,
+ event->set_sky->fog_tint_type
);
// Add textures to skybox.
for (int i = 0; i < 6; i++)
@@ -2864,18 +2911,9 @@ void Game::processClientEvents(CameraOrientation *cam)
void Game::updateChat(f32 dtime, const v2u32 &screensize)
{
- // Add chat log output for errors to be shown in chat
- static LogOutputBuffer chat_log_error_buf(g_logger, LL_ERROR);
-
// Get new messages from error log buffer
- while (!chat_log_error_buf.empty()) {
- std::wstring error_message = utf8_to_wide(chat_log_error_buf.get());
- if (!g_settings->getBool("disable_escape_sequences")) {
- error_message.insert(0, L"\x1b(c@red)");
- error_message.append(L"\x1b(c@white)");
- }
- chat_backend->addMessage(L"", error_message);
- }
+ while (!m_chat_log_buf.empty())
+ chat_backend->addMessage(L"", utf8_to_wide(m_chat_log_buf.get()));
// Get new messages from client
std::wstring message;
@@ -2996,16 +3034,8 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
- v3f player_position = player->getPosition();
- v3f player_eye_position = player->getEyePosition();
- v3f camera_position = camera->getPosition();
- v3f camera_direction = camera->getDirection();
- v3s16 camera_offset = camera->getOffset();
-
- if (camera->getCameraMode() == CAMERA_MODE_FIRST)
- player_eye_position += player->eye_offset_first;
- else
- player_eye_position += player->eye_offset_third;
+ const v3f camera_direction = camera->getDirection();
+ const v3s16 camera_offset = camera->getOffset();
/*
Calculate what block is the crosshair pointing to
@@ -3019,13 +3049,22 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
core::line3d<f32> shootline;
- if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) {
- shootline = core::line3d<f32>(player_eye_position,
- player_eye_position + camera_direction * BS * d);
- } else {
+ switch (camera->getCameraMode()) {
+ case CAMERA_MODE_FIRST:
+ // Shoot from camera position, with bobbing
+ shootline.start = camera->getPosition();
+ break;
+ case CAMERA_MODE_THIRD:
+ // Shoot from player head, no bobbing
+ shootline.start = camera->getHeadPosition();
+ break;
+ case CAMERA_MODE_THIRD_FRONT:
+ shootline.start = camera->getHeadPosition();
// prevent player pointing anything in front-view
- shootline = core::line3d<f32>(camera_position, camera_position);
+ d = 0;
+ break;
}
+ shootline.end = shootline.start + camera_direction * BS * d;
#ifdef HAVE_TOUCHSCREENGUI
@@ -3112,6 +3151,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
} else if (pointed.type == POINTEDTHING_NODE) {
handlePointingAtNode(pointed, selected_item, hand_item, dtime);
} else if (pointed.type == POINTEDTHING_OBJECT) {
+ v3f player_position = player->getPosition();
handlePointingAtObject(pointed, tool_item, player_position, show_debug);
} else if (input->getLeftState()) {
// When button is held down in air, show continuous animation
@@ -4147,8 +4187,12 @@ void Game::showPauseMenu()
}
#ifndef __ANDROID__
- os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
- << strgettext("Sound Volume") << "]";
+#if USE_SOUND
+ if (g_settings->getBool("enable_sound")) {
+ os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
+ << strgettext("Sound Volume") << "]";
+ }
+#endif
os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
<< strgettext("Change Keys") << "]";
#endif
@@ -4245,7 +4289,6 @@ void the_game(bool *kill,
reconnect_requested, &chat_backend, gamespec,
simple_singleplayer_mode)) {
game.run();
- game.shutdown();
}
} catch (SerializationError &e) {
@@ -4261,4 +4304,5 @@ void the_game(bool *kill,
strgettext("\nCheck debug.txt for details.");
errorstream << error_message << std::endl;
}
+ game.shutdown();
}
diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp
index 138dfb4da..c216f405d 100644
--- a/src/client/gameui.cpp
+++ b/src/client/gameui.cpp
@@ -76,6 +76,11 @@ void GameUI::init()
m_guitext_chat = gui::StaticText::add(guienv, L"", core::rect<s32>(0, 0, 0, 0),
//false, false); // Disable word wrap as of now
false, true, guiroot);
+ u16 chat_font_size = g_settings->getU16("chat_font_size");
+ if (chat_font_size != 0) {
+ m_guitext_chat->setOverrideFont(g_fontengine->getFont(
+ chat_font_size, FM_Unspecified));
+ }
// Profiler text (size is updated when text is updated)
m_guitext_profiler = gui::StaticText::add(guienv, L"<Profiler>",
@@ -128,9 +133,9 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
<< "pos: (" << (player_position.X / BS)
<< ", " << (player_position.Y / BS)
<< ", " << (player_position.Z / BS)
- << ") | yaw: " << (wrapDegrees_0_360(cam.camera_yaw)) << "° "
+ << ") | yaw: " << (wrapDegrees_0_360(cam.camera_yaw)) << "\xC2\xB0 "
<< yawToDirectionString(cam.camera_yaw)
- << " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "°"
+ << " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "\xC2\xB0"
<< " | seed: " << ((u64)client->getMapSeed());
if (pointed_old.type == POINTEDTHING_NODE) {
@@ -213,7 +218,6 @@ void GameUI::showTranslatedStatusText(const char *str)
void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count)
{
- setStaticText(m_guitext_chat, chat_text);
// Update gui element size and position
s32 chat_y = 5;
@@ -221,16 +225,15 @@ void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count)
if (m_flags.show_debug)
chat_y += 2 * g_fontengine->getLineHeight();
- // first pass to calculate height of text to be set
const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
- s32 width = std::min(g_fontengine->getTextWidth(chat_text.c_str()) + 10,
- window_size.X - 20);
- m_guitext_chat->setRelativePosition(core::rect<s32>(10, chat_y, width,
- chat_y + window_size.Y));
-
- // now use real height of text and adjust rect according to this size
- m_guitext_chat->setRelativePosition(core::rect<s32>(10, chat_y, width,
- chat_y + m_guitext_chat->getTextHeight()));
+
+ core::rect<s32> chat_size(10, chat_y,
+ window_size.X - 20, 0);
+ chat_size.LowerRightCorner.Y = std::min((s32)window_size.Y,
+ m_guitext_chat->getTextHeight() + chat_y);
+
+ m_guitext_chat->setRelativePosition(chat_size);
+ setStaticText(m_guitext_chat, chat_text);
m_recent_chat_count = recent_chat_count;
}
diff --git a/src/client/hud.cpp b/src/client/hud.cpp
index 37de6640b..31e633bc2 100644
--- a/src/client/hud.cpp
+++ b/src/client/hud.cpp
@@ -51,6 +51,7 @@ Hud::Hud(gui::IGUIEnvironment *guienv, Client *client, LocalPlayer *player,
this->inventory = inventory;
m_hud_scaling = g_settings->getFloat("hud_scaling");
+ m_scale_factor = m_hud_scaling * RenderingEngine::getDisplayDensity();
m_hotbar_imagesize = std::floor(HOTBAR_IMAGE_SIZE *
RenderingEngine::getDisplayDensity() + 0.5f);
m_hotbar_imagesize *= m_hud_scaling;
@@ -213,9 +214,7 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
}
// Position of upper left corner of bar
- v2s32 pos = screen_offset;
- pos.X *= m_hud_scaling * RenderingEngine::getDisplayDensity();
- pos.Y *= m_hud_scaling * RenderingEngine::getDisplayDensity();
+ v2s32 pos = screen_offset * m_scale_factor;
pos += upperleftpos;
// Store hotbar_image in member variable, used by drawItem()
@@ -272,6 +271,25 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
}
}
+// Calculates screen position of waypoint. Returns true if waypoint is visible (in front of the player), else false.
+bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos)
+{
+ v3f w_pos = e->world_pos * BS;
+ scene::ICameraSceneNode* camera =
+ RenderingEngine::get_scene_manager()->getActiveCamera();
+ w_pos -= intToFloat(camera_offset, BS);
+ core::matrix4 trans = camera->getProjectionMatrix();
+ trans *= camera->getViewMatrix();
+ f32 transformed_pos[4] = { w_pos.X, w_pos.Y, w_pos.Z, 1.0f };
+ trans.multiplyWith1x4Matrix(transformed_pos);
+ if (transformed_pos[3] < 0)
+ return false;
+ f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
+ core::reciprocal(transformed_pos[3]);
+ pos->X = m_screensize.X * (0.5 * transformed_pos[0] * zDiv + 0.5);
+ pos->Y = m_screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5);
+ return true;
+}
void Hud::drawLuaElements(const v3s16 &camera_offset)
{
@@ -299,43 +317,47 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
v2s32 pos(floor(e->pos.X * (float) m_screensize.X + 0.5),
floor(e->pos.Y * (float) m_screensize.Y + 0.5));
switch (e->type) {
- case HUD_ELEM_IMAGE: {
- video::ITexture *texture = tsrc->getTexture(e->text);
- if (!texture)
- continue;
-
- 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,
- 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);
- v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
- (e->align.Y - 1.0) * dstsize.Y / 2);
- core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
- rect += pos + offset + v2s32(e->offset.X, e->offset.Y);
- draw2DImageFilterScaled(driver, texture, rect,
- core::rect<s32>(core::position2d<s32>(0,0), imgsize),
- NULL, colors, true);
- break; }
case HUD_ELEM_TEXT: {
+ irr::gui::IGUIFont *textfont = font;
+ unsigned int font_size = g_fontengine->getDefaultFontSize();
+
+ if (e->size.X > 0)
+ font_size *= e->size.X;
+
+ if (font_size != g_fontengine->getDefaultFontSize())
+ textfont = g_fontengine->getFont(font_size);
+
video::SColor color(255, (e->number >> 16) & 0xFF,
(e->number >> 8) & 0xFF,
(e->number >> 0) & 0xFF);
- core::rect<s32> size(0, 0, e->scale.X, text_height * e->scale.Y);
std::wstring text = unescape_translate(utf8_to_wide(e->text));
- core::dimension2d<u32> textsize = font->getDimension(text.c_str());
+ core::dimension2d<u32> textsize = textfont->getDimension(text.c_str());
+#ifdef __ANDROID__
+ // The text size on Android is not proportional with the actual scaling
+ irr::gui::IGUIFont *font_scaled = font_size <= 3 ?
+ textfont : g_fontengine->getFont(font_size - 3);
+ if (e->offset.X < -20)
+ textsize = font_scaled->getDimension(text.c_str());
+#endif
v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2),
(e->align.Y - 1.0) * (textsize.Height / 2));
- v2s32 offs(e->offset.X, e->offset.Y);
- font->draw(text.c_str(), size + pos + offset + offs, color);
+ core::rect<s32> size(0, 0, e->scale.X * m_scale_factor,
+ text_height * e->scale.Y * m_scale_factor);
+ v2s32 offs(e->offset.X * m_scale_factor,
+ e->offset.Y * m_scale_factor);
+#ifdef __ANDROID__
+ if (e->offset.X < -20)
+ font_scaled->draw(text.c_str(), size + pos + offset + offs, color);
+ else
+#endif
+ {
+ textfont->draw(text.c_str(), size + pos + offset + offs, color);
+ }
break; }
case HUD_ELEM_STATBAR: {
v2s32 offs(e->offset.X, e->offset.Y);
- drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->number, offs, e->size);
+ drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->text2,
+ e->number, e->item, offs, e->size);
break; }
case HUD_ELEM_INVENTORY: {
InventoryList *inv = inventory->getList(e->text);
@@ -343,34 +365,59 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
inv, e->item, e->dir);
break; }
case HUD_ELEM_WAYPOINT: {
- v3f p_pos = player->getPosition() / BS;
- v3f w_pos = e->world_pos * BS;
- float distance = std::floor(10 * p_pos.getDistanceFrom(e->world_pos)) /
- 10.0f;
- scene::ICameraSceneNode* camera =
- RenderingEngine::get_scene_manager()->getActiveCamera();
- w_pos -= intToFloat(camera_offset, BS);
- core::matrix4 trans = camera->getProjectionMatrix();
- trans *= camera->getViewMatrix();
- f32 transformed_pos[4] = { w_pos.X, w_pos.Y, w_pos.Z, 1.0f };
- trans.multiplyWith1x4Matrix(transformed_pos);
- if (transformed_pos[3] < 0)
+ if (!calculateScreenPos(camera_offset, e, &pos))
break;
- f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
- core::reciprocal(transformed_pos[3]);
- pos.X = m_screensize.X * (0.5 * transformed_pos[0] * zDiv + 0.5);
- pos.Y = m_screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5);
+ v3f p_pos = player->getPosition() / BS;
+ pos += v2s32(e->offset.X, e->offset.Y);
video::SColor color(255, (e->number >> 16) & 0xFF,
(e->number >> 8) & 0xFF,
(e->number >> 0) & 0xFF);
- core::rect<s32> size(0, 0, 200, 2 * text_height);
std::wstring text = unescape_translate(utf8_to_wide(e->name));
- font->draw(text.c_str(), size + pos, color);
- std::ostringstream os;
- os << distance << e->text;
- text = unescape_translate(utf8_to_wide(os.str()));
- pos.Y += text_height;
- font->draw(text.c_str(), size + pos, color);
+ const std::string &unit = e->text;
+ // waypoints reuse the item field to store precision, item = precision + 1
+ u32 item = e->item;
+ float precision = (item == 0) ? 10.0f : (item - 1.f);
+ bool draw_precision = precision > 0;
+
+ core::rect<s32> bounds(0, 0, font->getDimension(text.c_str()).Width, (draw_precision ? 2:1) * text_height);
+ pos.Y += (e->align.Y - 1.0) * bounds.getHeight() / 2;
+ bounds += pos;
+ font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0) * bounds.getWidth() / 2, 0), color);
+ if (draw_precision) {
+ std::ostringstream os;
+ float distance = std::floor(precision * p_pos.getDistanceFrom(e->world_pos)) / precision;
+ os << distance << unit;
+ text = unescape_translate(utf8_to_wide(os.str()));
+ bounds.LowerRightCorner.X = bounds.UpperLeftCorner.X + font->getDimension(text.c_str()).Width;
+ font->draw(text.c_str(), bounds + v2s32((e->align.X - 1.0f) * bounds.getWidth() / 2, text_height), color);
+ }
+ break; }
+ case HUD_ELEM_IMAGE_WAYPOINT: {
+ if (!calculateScreenPos(camera_offset, e, &pos))
+ break;
+ }
+ case HUD_ELEM_IMAGE: {
+ video::ITexture *texture = tsrc->getTexture(e->text);
+ if (!texture)
+ continue;
+
+ 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);
+ 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);
+ v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
+ (e->align.Y - 1.0) * dstsize.Y / 2);
+ core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
+ rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
+ e->offset.Y * m_scale_factor);
+ draw2DImageFilterScaled(driver, texture, rect,
+ core::rect<s32>(core::position2d<s32>(0,0), imgsize),
+ NULL, colors, true);
break; }
default:
infostream << "Hud::drawLuaElements: ignoring drawform " << e->type <<
@@ -380,8 +427,9 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
}
-void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &texture,
- s32 count, v2s32 offset, v2s32 size)
+void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
+ const std::string &texture, const std::string &bgtexture,
+ s32 count, s32 maxcount, v2s32 offset, v2s32 size)
{
const video::SColor color(255, 255, 255, 255);
const video::SColor colors[] = {color, color, color, color};
@@ -390,16 +438,24 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &tex
if (!stat_texture)
return;
+ video::ITexture *stat_texture_bg = nullptr;
+ if (!bgtexture.empty()) {
+ stat_texture_bg = tsrc->getTexture(bgtexture);
+ }
+
core::dimension2di srcd(stat_texture->getOriginalSize());
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 {
- float size_factor = m_hud_scaling * RenderingEngine::getDisplayDensity();
- dstd.Height = size.Y * size_factor;
- dstd.Width = size.X * size_factor;
- offset.X *= size_factor;
- offset.Y *= size_factor;
+ dstd.Height = size.Y * m_scale_factor;
+ dstd.Width = size.X * m_scale_factor;
+ offset.X *= m_scale_factor;
+ offset.Y *= m_scale_factor;
}
v2s32 p = pos;
@@ -409,43 +465,100 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &tex
p += offset;
v2s32 steppos;
- core::rect<s32> srchalfrect, dsthalfrect;
switch (drawdir) {
case HUD_DIR_RIGHT_LEFT:
steppos = v2s32(-1, 0);
- srchalfrect = core::rect<s32>(srcd.Width / 2, 0, srcd.Width, srcd.Height);
- dsthalfrect = core::rect<s32>(dstd.Width / 2, 0, dstd.Width, dstd.Height);
break;
case HUD_DIR_TOP_BOTTOM:
steppos = v2s32(0, 1);
- srchalfrect = core::rect<s32>(0, 0, srcd.Width, srcd.Height / 2);
- dsthalfrect = core::rect<s32>(0, 0, dstd.Width, dstd.Height / 2);
break;
case HUD_DIR_BOTTOM_TOP:
steppos = v2s32(0, -1);
- srchalfrect = core::rect<s32>(0, srcd.Height / 2, srcd.Width, srcd.Height);
- dsthalfrect = core::rect<s32>(0, dstd.Height / 2, dstd.Width, dstd.Height);
break;
default:
+ // From left to right
steppos = v2s32(1, 0);
- srchalfrect = core::rect<s32>(0, 0, srcd.Width / 2, srcd.Height);
- dsthalfrect = core::rect<s32>(0, 0, dstd.Width / 2, dstd.Height);
+ break;
}
+
+ auto calculate_clipping_rect = [] (core::dimension2di src,
+ v2s32 steppos) -> core::rect<s32> {
+
+ // Create basic rectangle
+ core::rect<s32> rect(0, 0,
+ src.Width - std::abs(steppos.X) * src.Width / 2,
+ src.Height - std::abs(steppos.Y) * src.Height / 2
+ );
+ // Move rectangle left or down
+ if (steppos.X == -1)
+ rect += v2s32(src.Width / 2, 0);
+ if (steppos.Y == -1)
+ rect += v2s32(0, src.Height / 2);
+ return rect;
+ };
+ // Rectangles for 1/2 the actual value to display
+ core::rect<s32> srchalfrect, dsthalfrect;
+ // Rectangles for 1/2 the "off state" texture
+ core::rect<s32> srchalfrect2, dsthalfrect2;
+
+ if (count % 2 == 1) {
+ // 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);
+ dsthalfrect2 = calculate_clipping_rect(dstd, steppos * -1);
+ }
+
steppos.X *= dstd.Width;
steppos.Y *= dstd.Height;
+ // Draw full textures
for (s32 i = 0; i < count / 2; i++) {
core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
- core::rect<s32> dstrect(0,0, dstd.Width, dstd.Height);
+ core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
dstrect += p;
- draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true);
+ draw2DImageFilterScaled(driver, stat_texture,
+ dstrect, srcrect, NULL, colors, true);
p += steppos;
}
if (count % 2 == 1) {
- dsthalfrect += p;
- draw2DImageFilterScaled(driver, stat_texture, dsthalfrect, srchalfrect, NULL, colors, true);
+ // Draw half a texture
+ draw2DImageFilterScaled(driver, stat_texture,
+ dsthalfrect + p, srchalfrect, NULL, colors, true);
+
+ if (stat_texture_bg && maxcount > count) {
+ draw2DImageFilterScaled(driver, stat_texture_bg,
+ dsthalfrect2 + p, srchalfrect2,
+ NULL, colors, true);
+ p += steppos;
+ }
+ }
+
+ if (stat_texture_bg && maxcount > count / 2) {
+ // Draw "off state" textures
+ s32 start_offset;
+ if (count % 2 == 1)
+ start_offset = count / 2 + 1;
+ else
+ start_offset = count / 2;
+ for (s32 i = start_offset; i < maxcount / 2; i++) {
+ core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
+ core::rect<s32> dstrect(0, 0, dstd.Width, dstd.Height);
+
+ dstrect += p;
+ draw2DImageFilterScaled(driver, stat_texture_bg,
+ dstrect, srcrect,
+ NULL, colors, true);
+ p += steppos;
+ }
+
+ if (maxcount % 2 == 1) {
+ draw2DImageFilterScaled(driver, stat_texture_bg,
+ dsthalfrect + p, srchalfrect,
+ NULL, colors, true);
+ }
}
}
@@ -465,7 +578,7 @@ void Hud::drawHotbar(u16 playeritem) {
v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3);
const v2u32 &window_size = RenderingEngine::get_instance()->getWindowSize();
- if ( (float) width / (float) window_size.X <=
+ if ((float) width / (float) window_size.X <=
g_settings->getFloat("hud_hotbar_max_width")) {
if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, playeritem + 1, 0);
@@ -517,12 +630,10 @@ void Hud::drawSelectionMesh()
// Draw 3D selection boxes
video::SMaterial oldmaterial = driver->getMaterial2D();
driver->setMaterial(m_selection_material);
- for (std::vector<aabb3f>::const_iterator
- i = m_selection_boxes.begin();
- i != m_selection_boxes.end(); ++i) {
+ for (auto & selection_box : m_selection_boxes) {
aabb3f box = aabb3f(
- i->MinEdge + m_selection_pos_with_offset,
- i->MaxEdge + m_selection_pos_with_offset);
+ selection_box.MinEdge + m_selection_pos_with_offset,
+ selection_box.MaxEdge + m_selection_pos_with_offset);
u32 r = (selectionbox_argb.getRed() *
m_selection_mesh_color.getRed() / 255);
diff --git a/src/client/hud.h b/src/client/hud.h
index d9b5e0686..6f4c54626 100644
--- a/src/client/hud.h
+++ b/src/client/hud.h
@@ -18,8 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef CLIENT_HUD_HEADER
-#define CLIENT_HUD_HEADER
+#pragma once
#include <vector>
#include <IGUIFont.h>
@@ -81,8 +80,10 @@ public:
void drawLuaElements(const v3s16 &camera_offset);
private:
- void drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &texture,
- s32 count, v2s32 offset, v2s32 size = v2s32());
+ bool calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos);
+ void drawStatbar(v2s32 pos, u16 corner, u16 drawdir,
+ const std::string &texture, const std::string& bgtexture,
+ s32 count, s32 maxcount, v2s32 offset, v2s32 size = v2s32());
void drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
s32 inv_offset, InventoryList *mainlist, u16 selectitem,
@@ -91,6 +92,7 @@ private:
void drawItem(const ItemStack &item, const core::rect<s32> &rect, bool selected);
float m_hud_scaling; // cached minetest setting
+ float m_scale_factor;
v3s16 m_camera_offset;
v2u32 m_screensize;
v2s32 m_displaycenter;
@@ -145,4 +147,3 @@ void drawItemStack(
const v3s16 &angle,
const v3s16 &rotation_speed);
-#endif
diff --git a/src/client/imagefilters.cpp b/src/client/imagefilters.cpp
index dd029628f..0fa501410 100644
--- a/src/client/imagefilters.cpp
+++ b/src/client/imagefilters.cpp
@@ -91,7 +91,7 @@ void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::I
u32 dy, dx;
video::SColor pxl;
- // Cache rectsngle boundaries.
+ // Cache rectangle boundaries.
double sox = srcrect.UpperLeftCorner.X * 1.0;
double soy = srcrect.UpperLeftCorner.Y * 1.0;
double sw = srcrect.getWidth() * 1.0;
@@ -107,15 +107,15 @@ void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::I
// Do some basic clipping, and for mirrored/flipped rects,
// make sure min/max are in the right order.
minsx = sox + (dx * sw / dim.Width);
- minsx = rangelim(minsx, 0, sw);
+ minsx = rangelim(minsx, 0, sox + sw);
maxsx = minsx + sw / dim.Width;
- maxsx = rangelim(maxsx, 0, sw);
+ maxsx = rangelim(maxsx, 0, sox + sw);
if (minsx > maxsx)
SWAP(double, minsx, maxsx);
minsy = soy + (dy * sh / dim.Height);
- minsy = rangelim(minsy, 0, sh);
+ minsy = rangelim(minsy, 0, soy + sh);
maxsy = minsy + sh / dim.Height;
- maxsy = rangelim(maxsy, 0, sh);
+ maxsy = rangelim(maxsy, 0, soy + sh);
if (minsy > maxsy)
SWAP(double, minsy, maxsy);
diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp
index c20c3619f..1e7040d57 100644
--- a/src/client/localplayer.cpp
+++ b/src/client/localplayer.cpp
@@ -200,6 +200,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
if (noclip && free_move) {
position += m_speed * dtime;
setPosition(position);
+
+ touching_ground = false;
added_velocity = v3f(0.0f); // ignored
return;
}
@@ -436,9 +438,11 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
Check properties of the node on which the player is standing
*/
const ContentFeatures &f = nodemgr->get(map->getNode(m_standing_node));
+ const ContentFeatures &f1 = nodemgr->get(map->getNode(m_standing_node + v3s16(0, 1, 0)));
// Determine if jumping is possible
- m_disable_jump = itemgroup_get(f.groups, "disable_jump");
+ m_disable_jump = itemgroup_get(f.groups, "disable_jump") ||
+ itemgroup_get(f1.groups, "disable_jump");
m_can_jump = ((touching_ground && !is_climbing) || sneak_can_jump) && !m_disable_jump;
// Jump key pressed while jumping off from a bouncy block
@@ -785,6 +789,8 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
if (free_move) {
position += m_speed * dtime;
setPosition(position);
+
+ touching_ground = false;
m_sneak_node_exists = false;
added_velocity = v3f(0.0f);
return;
diff --git a/src/client/localplayer.h b/src/client/localplayer.h
index 95dceb1f4..345aec9d9 100644
--- a/src/client/localplayer.h
+++ b/src/client/localplayer.h
@@ -135,12 +135,17 @@ public:
}
v3f getPosition() const { return m_position; }
+
+ // Non-transformed eye offset getters
+ // For accurate positions, use the Camera functions
v3f getEyePosition() const { return m_position + getEyeOffset(); }
v3f getEyeOffset() const;
void setEyeHeight(float eye_height) { m_eye_height = eye_height; }
void setCollisionbox(const aabb3f &box) { m_collisionbox = box; }
+ const aabb3f& getCollisionbox() const { return m_collisionbox; }
+
float getZoomFOV() const { return m_zoom_fov; }
void setZoomFOV(float zoom_fov) { m_zoom_fov = zoom_fov; }
diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp
index a5bee6b88..1020e35f5 100644
--- a/src/client/mapblock_mesh.cpp
+++ b/src/client/mapblock_mesh.cpp
@@ -225,7 +225,7 @@ static u16 getSmoothLightCombined(const v3s16 &p,
return f.light_propagates;
};
- std::array<bool, 4> obstructed = {{ 1, 1, 1, 1 }};
+ bool obstructed[4] = { true, true, true, true };
add_node(0);
bool opaque1 = !add_node(1);
bool opaque2 = !add_node(2);
@@ -372,6 +372,32 @@ void final_color_blend(video::SColor *result,
Mesh generation helpers
*/
+// This table is moved outside getNodeVertexDirs to avoid the compiler using
+// a mutex to initialize this table at runtime right in the hot path.
+// For details search the internet for "cxa_guard_acquire".
+static const v3s16 vertex_dirs_table[] = {
+ // ( 1, 0, 0)
+ v3s16( 1,-1, 1), v3s16( 1,-1,-1),
+ v3s16( 1, 1,-1), v3s16( 1, 1, 1),
+ // ( 0, 1, 0)
+ v3s16( 1, 1,-1), v3s16(-1, 1,-1),
+ v3s16(-1, 1, 1), v3s16( 1, 1, 1),
+ // ( 0, 0, 1)
+ v3s16(-1,-1, 1), v3s16( 1,-1, 1),
+ v3s16( 1, 1, 1), v3s16(-1, 1, 1),
+ // invalid
+ v3s16(), v3s16(), v3s16(), v3s16(),
+ // ( 0, 0,-1)
+ v3s16( 1,-1,-1), v3s16(-1,-1,-1),
+ v3s16(-1, 1,-1), v3s16( 1, 1,-1),
+ // ( 0,-1, 0)
+ v3s16( 1,-1, 1), v3s16(-1,-1, 1),
+ v3s16(-1,-1,-1), v3s16( 1,-1,-1),
+ // (-1, 0, 0)
+ v3s16(-1,-1,-1), v3s16(-1,-1, 1),
+ v3s16(-1, 1, 1), v3s16(-1, 1,-1)
+};
+
/*
vertex_dirs: v3s16[4]
*/
@@ -384,44 +410,16 @@ static void getNodeVertexDirs(const v3s16 &dir, v3s16 *vertex_dirs)
2: top-left
3: top-right
*/
- if (dir == v3s16(0, 0, 1)) {
- // If looking towards z+, this is the face that is behind
- // the center point, facing towards z+.
- vertex_dirs[0] = v3s16(-1,-1, 1);
- vertex_dirs[1] = v3s16( 1,-1, 1);
- vertex_dirs[2] = v3s16( 1, 1, 1);
- vertex_dirs[3] = v3s16(-1, 1, 1);
- } else if (dir == v3s16(0, 0, -1)) {
- // faces towards Z-
- vertex_dirs[0] = v3s16( 1,-1,-1);
- vertex_dirs[1] = v3s16(-1,-1,-1);
- vertex_dirs[2] = v3s16(-1, 1,-1);
- vertex_dirs[3] = v3s16( 1, 1,-1);
- } else if (dir == v3s16(1, 0, 0)) {
- // faces towards X+
- vertex_dirs[0] = v3s16( 1,-1, 1);
- vertex_dirs[1] = v3s16( 1,-1,-1);
- vertex_dirs[2] = v3s16( 1, 1,-1);
- vertex_dirs[3] = v3s16( 1, 1, 1);
- } else if (dir == v3s16(-1, 0, 0)) {
- // faces towards X-
- vertex_dirs[0] = v3s16(-1,-1,-1);
- vertex_dirs[1] = v3s16(-1,-1, 1);
- vertex_dirs[2] = v3s16(-1, 1, 1);
- vertex_dirs[3] = v3s16(-1, 1,-1);
- } else if (dir == v3s16(0, 1, 0)) {
- // faces towards Y+ (assume Z- as "down" in texture)
- vertex_dirs[0] = v3s16( 1, 1,-1);
- vertex_dirs[1] = v3s16(-1, 1,-1);
- vertex_dirs[2] = v3s16(-1, 1, 1);
- vertex_dirs[3] = v3s16( 1, 1, 1);
- } else if (dir == v3s16(0, -1, 0)) {
- // faces towards Y- (assume Z+ as "down" in texture)
- vertex_dirs[0] = v3s16( 1,-1, 1);
- vertex_dirs[1] = v3s16(-1,-1, 1);
- vertex_dirs[2] = v3s16(-1,-1,-1);
- vertex_dirs[3] = v3s16( 1,-1,-1);
- }
+
+ // Direction must be (1,0,0), (-1,0,0), (0,1,0), (0,-1,0),
+ // (0,0,1), (0,0,-1)
+ assert(dir.X * dir.X + dir.Y * dir.Y + dir.Z * dir.Z == 1);
+
+ // Convert direction to single integer for table lookup
+ u8 idx = (dir.X + 2 * dir.Y + 3 * dir.Z) & 7;
+ idx = (idx - 1) * 4;
+
+ memcpy(vertex_dirs, &vertex_dirs_table[idx], 4 * sizeof(v3s16));
}
static void getNodeTextureCoords(v3f base, const v3f &scale, const v3s16 &dir, float *u, float *v)
@@ -890,8 +888,10 @@ static void updateFastFaceRow(
v3s16 p_corrected;
v3s16 face_dir_corrected;
u16 lights[4] = {0, 0, 0, 0};
- u8 waving;
+ u8 waving = 0;
TileSpec tile;
+
+ // Get info of first tile
getTileInfo(data, p, face_dir,
makes_face, p_corrected, face_dir_corrected,
lights, waving, tile);
@@ -902,8 +902,6 @@ static void updateFastFaceRow(
// If tiling can be done, this is set to false in the next step
bool next_is_different = true;
- v3s16 p_next;
-
bool next_makes_face = false;
v3s16 next_p_corrected;
v3s16 next_face_dir_corrected;
@@ -912,9 +910,9 @@ static void updateFastFaceRow(
// If at last position, there is nothing to compare to and
// the face must be drawn anyway
if (j != MAP_BLOCKSIZE - 1) {
- p_next = p + translate_dir;
+ p += translate_dir;
- getTileInfo(data, p_next, face_dir,
+ getTileInfo(data, p, face_dir,
next_makes_face, next_p_corrected,
next_face_dir_corrected, next_lights,
waving,
@@ -923,7 +921,7 @@ static void updateFastFaceRow(
if (next_makes_face == makes_face
&& next_p_corrected == p_corrected + translate_dir
&& next_face_dir_corrected == face_dir_corrected
- && memcmp(next_lights, lights, ARRLEN(lights) * sizeof(u16)) == 0
+ && memcmp(next_lights, lights, sizeof(lights)) == 0
// Don't apply fast faces to waving water.
&& (waving != 3 || !waving_liquids)
&& next_tile.isTileable(tile)) {
@@ -961,10 +959,9 @@ static void updateFastFaceRow(
makes_face = next_makes_face;
p_corrected = next_p_corrected;
face_dir_corrected = next_face_dir_corrected;
- std::memcpy(lights, next_lights, ARRLEN(lights) * sizeof(u16));
+ memcpy(lights, next_lights, sizeof(lights));
if (next_is_different)
- tile = next_tile;
- p = p_next;
+ tile = std::move(next_tile); // faster than copy
}
}
diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp
index 4d73ead8a..e1ec22068 100644
--- a/src/client/mesh.cpp
+++ b/src/client/mesh.cpp
@@ -328,6 +328,26 @@ void recalculateBoundingBox(scene::IMesh *src_mesh)
src_mesh->setBoundingBox(bbox);
}
+bool checkMeshNormals(scene::IMesh *mesh)
+{
+ u32 buffer_count = mesh->getMeshBufferCount();
+
+ for (u32 i = 0; i < buffer_count; i++) {
+ scene::IMeshBuffer *buffer = mesh->getMeshBuffer(i);
+
+ // Here we intentionally check only first normal, assuming that if buffer
+ // has it valid, then most likely all other ones are fine too. We can
+ // check all of the normals to have length, but it seems like an overkill
+ // hurting the performance and covering only really weird broken models.
+ f32 length = buffer->getNormal(0).getLength();
+
+ if (!std::isfinite(length) || length < 1e-10f)
+ return false;
+ }
+
+ return true;
+}
+
scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
{
switch (mesh_buffer->getVertexType()) {
diff --git a/src/client/mesh.h b/src/client/mesh.h
index 0c4094de2..103c61e45 100644
--- a/src/client/mesh.h
+++ b/src/client/mesh.h
@@ -122,6 +122,12 @@ scene::IMesh* convertNodeboxesToMesh(const std::vector<aabb3f> &boxes,
void recalculateBoundingBox(scene::IMesh *src_mesh);
/*
+ Check if mesh has valid normals and return true if it does.
+ We assume normal to be valid when it's 0 < length < Inf. and not NaN
+ */
+bool checkMeshNormals(scene::IMesh *mesh);
+
+/*
Vertex cache optimization according to the Forsyth paper:
http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
Ported from irrlicht 1.8
diff --git a/src/client/particles.cpp b/src/client/particles.cpp
index a0e4e54eb..7acd996dc 100644
--- a/src/client/particles.cpp
+++ b/src/client/particles.cpp
@@ -37,32 +37,31 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Utility
*/
-v3f random_v3f(v3f min, v3f max)
+static f32 random_f32(f32 min, f32 max)
+{
+ return rand() / (float)RAND_MAX * (max - min) + min;
+}
+
+static v3f random_v3f(v3f min, v3f max)
{
return v3f(
- rand() / (float)RAND_MAX * (max.X - min.X) + min.X,
- rand() / (float)RAND_MAX * (max.Y - min.Y) + min.Y,
- rand() / (float)RAND_MAX * (max.Z - min.Z) + min.Z);
+ random_f32(min.X, max.X),
+ random_f32(min.Y, max.Y),
+ random_f32(min.Z, max.Z));
}
+/*
+ Particle
+*/
+
Particle::Particle(
IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
- v3f pos,
- v3f velocity,
- v3f acceleration,
- float expirationtime,
- float size,
- bool collisiondetection,
- bool collision_removal,
- bool object_collision,
- bool vertical,
+ const ParticleParameters &p,
video::ITexture *texture,
v2f texpos,
v2f texsize,
- const struct TileAnimationParams &anim,
- u8 glow,
video::SColor color
):
scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
@@ -81,33 +80,28 @@ Particle::Particle(
m_material.setTexture(0, texture);
m_texpos = texpos;
m_texsize = texsize;
- m_animation = anim;
+ m_animation = p.animation;
// Color
m_base_color = color;
m_color = color;
// Particle related
- m_pos = pos;
- m_velocity = velocity;
- m_acceleration = acceleration;
- m_expiration = expirationtime;
+ m_pos = p.pos;
+ m_velocity = p.vel;
+ m_acceleration = p.acc;
+ m_expiration = p.expirationtime;
m_player = player;
- m_size = size;
- m_collisiondetection = collisiondetection;
- m_collision_removal = collision_removal;
- m_object_collision = object_collision;
- m_vertical = vertical;
- m_glow = glow;
+ m_size = p.size;
+ m_collisiondetection = p.collisiondetection;
+ m_collision_removal = p.collision_removal;
+ m_object_collision = p.object_collision;
+ m_vertical = p.vertical;
+ m_glow = p.glow;
// Irrlicht stuff
- m_collisionbox = aabb3f(
- -size / 2,
- -size / 2,
- -size / 2,
- size / 2,
- size / 2,
- size / 2);
+ const float c = p.size / 2;
+ m_collisionbox = aabb3f(-c, -c, -c, c, c, c);
this->setAutomaticCulling(scene::EAC_OFF);
// Init lighting
@@ -255,52 +249,22 @@ void Particle::updateVertices()
ParticleSpawner::ParticleSpawner(
IGameDef *gamedef,
LocalPlayer *player,
- u16 amount,
- float time,
- v3f minpos, v3f maxpos,
- v3f minvel, v3f maxvel,
- v3f minacc, v3f maxacc,
- float minexptime, float maxexptime,
- float minsize, float maxsize,
- bool collisiondetection,
- bool collision_removal,
- bool object_collision,
+ const ParticleSpawnerParameters &p,
u16 attached_id,
- bool vertical,
video::ITexture *texture,
- const struct TileAnimationParams &anim,
- u8 glow,
ParticleManager *p_manager
):
- m_particlemanager(p_manager)
+ m_particlemanager(p_manager), p(p)
{
m_gamedef = gamedef;
m_player = player;
- m_amount = amount;
- m_spawntime = time;
- m_minpos = minpos;
- m_maxpos = maxpos;
- m_minvel = minvel;
- m_maxvel = maxvel;
- m_minacc = minacc;
- m_maxacc = maxacc;
- m_minexptime = minexptime;
- m_maxexptime = maxexptime;
- m_minsize = minsize;
- m_maxsize = maxsize;
- m_collisiondetection = collisiondetection;
- m_collision_removal = collision_removal;
- m_object_collision = object_collision;
m_attached_id = attached_id;
- m_vertical = vertical;
m_texture = texture;
m_time = 0;
- m_animation = anim;
- m_glow = glow;
- for (u16 i = 0; i <= m_amount; i++)
- {
- float spawntime = (float)rand() / (float)RAND_MAX * m_spawntime;
+ m_spawntimes.reserve(p.amount + 1);
+ for (u16 i = 0; i <= p.amount; i++) {
+ float spawntime = rand() / (float)RAND_MAX * p.time;
m_spawntimes.push_back(spawntime);
}
}
@@ -309,7 +273,7 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
const core::matrix4 *attached_absolute_pos_rot_matrix)
{
v3f ppos = m_player->getPosition() / BS;
- v3f pos = random_v3f(m_minpos, m_maxpos);
+ v3f pos = random_v3f(p.minpos, p.maxpos);
// Need to apply this first or the following check
// will be wrong for attached spawners
@@ -326,41 +290,51 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
if (pos.getDistanceFrom(ppos) > radius)
return;
- v3f vel = random_v3f(m_minvel, m_maxvel);
- v3f acc = random_v3f(m_minacc, m_maxacc);
+ // Parameters for the single particle we're about to spawn
+ ParticleParameters pp;
+ pp.pos = pos;
+
+ pp.vel = random_v3f(p.minvel, p.maxvel);
+ pp.acc = random_v3f(p.minacc, p.maxacc);
if (attached_absolute_pos_rot_matrix) {
// Apply attachment rotation
- attached_absolute_pos_rot_matrix->rotateVect(vel);
- attached_absolute_pos_rot_matrix->rotateVect(acc);
+ attached_absolute_pos_rot_matrix->rotateVect(pp.vel);
+ attached_absolute_pos_rot_matrix->rotateVect(pp.acc);
}
- float exptime = rand() / (float)RAND_MAX
- * (m_maxexptime - m_minexptime)
- + m_minexptime;
+ pp.expirationtime = random_f32(p.minexptime, p.maxexptime);
+ p.copyCommon(pp);
+
+ video::ITexture *texture;
+ v2f texpos, texsize;
+ video::SColor color(0xFFFFFFFF);
+
+ if (p.node.getContent() != CONTENT_IGNORE) {
+ const ContentFeatures &f =
+ m_particlemanager->m_env->getGameDef()->ndef()->get(p.node);
+ if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture,
+ texpos, texsize, &color, p.node_tile))
+ return;
+ } else {
+ texture = m_texture;
+ texpos = v2f(0.0f, 0.0f);
+ texsize = v2f(1.0f, 1.0f);
+ }
- float size = rand() / (float)RAND_MAX
- * (m_maxsize - m_minsize)
- + m_minsize;
+ // Allow keeping default random size
+ if (p.maxsize > 0.0f)
+ pp.size = random_f32(p.minsize, p.maxsize);
m_particlemanager->addParticle(new Particle(
m_gamedef,
m_player,
env,
- pos,
- vel,
- acc,
- exptime,
- size,
- m_collisiondetection,
- m_collision_removal,
- m_object_collision,
- m_vertical,
- m_texture,
- v2f(0.0, 0.0),
- v2f(1.0, 1.0),
- m_animation,
- m_glow
+ pp,
+ texture,
+ texpos,
+ texsize,
+ color
));
}
@@ -375,18 +349,17 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr;
if (m_attached_id) {
if (GenericCAO *attached = dynamic_cast<GenericCAO *>(env->getActiveObject(m_attached_id))) {
- attached_absolute_pos_rot_matrix = &attached->getAbsolutePosRotMatrix();
+ attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix();
} else {
unloaded = true;
}
}
- if (m_spawntime != 0) {
+ if (p.time != 0) {
// Spawner exists for a predefined timespan
- for (std::vector<float>::iterator i = m_spawntimes.begin();
- i != m_spawntimes.end();) {
- if ((*i) <= m_time && m_amount > 0) {
- --m_amount;
+ for (auto i = m_spawntimes.begin(); i != m_spawntimes.end(); ) {
+ if ((*i) <= m_time && p.amount > 0) {
+ --p.amount;
// Pretend to, but don't actually spawn a particle if it is
// attached to an unloaded object or distant from player.
@@ -405,13 +378,16 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
if (unloaded)
return;
- for (int i = 0; i <= m_amount; i++) {
+ for (int i = 0; i <= p.amount; i++) {
if (rand() / (float)RAND_MAX < dtime)
spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
}
}
}
+/*
+ ParticleManager
+*/
ParticleManager::ParticleManager(ClientEnvironment *env) :
m_env(env)
@@ -479,99 +455,106 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
{
switch (event->type) {
case CE_DELETE_PARTICLESPAWNER: {
- MutexAutoLock lock(m_spawner_list_lock);
- if (m_particle_spawners.find(event->delete_particlespawner.id) !=
- m_particle_spawners.end()) {
- delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
- m_particle_spawners.erase(event->delete_particlespawner.id);
- }
+ deleteParticleSpawner(event->delete_particlespawner.id);
// no allocated memory in delete event
break;
}
case CE_ADD_PARTICLESPAWNER: {
- {
- MutexAutoLock lock(m_spawner_list_lock);
- if (m_particle_spawners.find(event->add_particlespawner.id) !=
- m_particle_spawners.end()) {
- delete m_particle_spawners.find(event->add_particlespawner.id)->second;
- m_particle_spawners.erase(event->add_particlespawner.id);
- }
- }
+ deleteParticleSpawner(event->add_particlespawner.id);
+
+ const ParticleSpawnerParameters &p = *event->add_particlespawner.p;
video::ITexture *texture =
- client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
+ client->tsrc()->getTextureForMesh(p.texture);
auto toadd = new ParticleSpawner(client, player,
- event->add_particlespawner.amount,
- event->add_particlespawner.spawntime,
- *event->add_particlespawner.minpos,
- *event->add_particlespawner.maxpos,
- *event->add_particlespawner.minvel,
- *event->add_particlespawner.maxvel,
- *event->add_particlespawner.minacc,
- *event->add_particlespawner.maxacc,
- event->add_particlespawner.minexptime,
- event->add_particlespawner.maxexptime,
- event->add_particlespawner.minsize,
- event->add_particlespawner.maxsize,
- event->add_particlespawner.collisiondetection,
- event->add_particlespawner.collision_removal,
- event->add_particlespawner.object_collision,
+ p,
event->add_particlespawner.attached_id,
- event->add_particlespawner.vertical,
texture,
- event->add_particlespawner.animation,
- event->add_particlespawner.glow,
this);
- /* delete allocated content of event */
- delete event->add_particlespawner.minpos;
- delete event->add_particlespawner.maxpos;
- delete event->add_particlespawner.minvel;
- delete event->add_particlespawner.maxvel;
- delete event->add_particlespawner.minacc;
- delete event->add_particlespawner.texture;
- delete event->add_particlespawner.maxacc;
-
- {
- MutexAutoLock lock(m_spawner_list_lock);
- m_particle_spawners[event->add_particlespawner.id] = toadd;
- }
+ addParticleSpawner(event->add_particlespawner.id, toadd);
+
+ delete event->add_particlespawner.p;
break;
}
case CE_SPAWN_PARTICLE: {
- video::ITexture *texture =
- client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
-
- Particle *toadd = new Particle(client, player, m_env,
- *event->spawn_particle.pos,
- *event->spawn_particle.vel,
- *event->spawn_particle.acc,
- event->spawn_particle.expirationtime,
- event->spawn_particle.size,
- event->spawn_particle.collisiondetection,
- event->spawn_particle.collision_removal,
- event->spawn_particle.object_collision,
- event->spawn_particle.vertical,
- texture,
- v2f(0.0, 0.0),
- v2f(1.0, 1.0),
- event->spawn_particle.animation,
- event->spawn_particle.glow);
+ ParticleParameters &p = *event->spawn_particle;
- addParticle(toadd);
+ video::ITexture *texture;
+ v2f texpos, texsize;
+ video::SColor color(0xFFFFFFFF);
- delete event->spawn_particle.pos;
- delete event->spawn_particle.vel;
- delete event->spawn_particle.acc;
- delete event->spawn_particle.texture;
+ f32 oldsize = p.size;
+
+ if (p.node.getContent() != CONTENT_IGNORE) {
+ const ContentFeatures &f = m_env->getGameDef()->ndef()->get(p.node);
+ if (!getNodeParticleParams(p.node, f, p, &texture, texpos,
+ texsize, &color, p.node_tile))
+ texture = nullptr;
+ } else {
+ texture = client->tsrc()->getTextureForMesh(p.texture);
+ texpos = v2f(0.0f, 0.0f);
+ texsize = v2f(1.0f, 1.0f);
+ }
+ // Allow keeping default random size
+ if (oldsize > 0.0f)
+ p.size = oldsize;
+
+ if (texture) {
+ Particle *toadd = new Particle(client, player, m_env,
+ p, texture, texpos, texsize, color);
+
+ addParticle(toadd);
+ }
+
+ delete event->spawn_particle;
break;
}
default: break;
}
}
+bool ParticleManager::getNodeParticleParams(const MapNode &n,
+ const ContentFeatures &f, ParticleParameters &p, video::ITexture **texture,
+ v2f &texpos, v2f &texsize, video::SColor *color, u8 tilenum)
+{
+ // No particles for "airlike" nodes
+ if (f.drawtype == NDT_AIRLIKE)
+ return false;
+
+ // Texture
+ u8 texid;
+ if (tilenum > 0 && tilenum <= 6)
+ texid = tilenum - 1;
+ else
+ texid = rand() % 6;
+ const TileLayer &tile = f.tiles[texid].layers[0];
+ p.animation.type = TAT_NONE;
+
+ // Only use first frame of animated texture
+ if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
+ *texture = (*tile.frames)[0].texture;
+ else
+ *texture = tile.texture;
+
+ float size = (rand() % 8) / 64.0f;
+ p.size = BS * size;
+ if (tile.scale)
+ size /= tile.scale;
+ texsize = v2f(size * 2.0f, size * 2.0f);
+ texpos.X = (rand() % 64) / 64.0f - texsize.X;
+ texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
+
+ if (tile.has_color)
+ *color = tile.color;
+ else
+ n.getColor(f, color);
+
+ return true;
+}
+
// The final burst of particles when a node is finally dug, *not* particles
// spawned during the digging of a node.
@@ -593,73 +576,41 @@ void ParticleManager::addDiggingParticles(IGameDef *gamedef,
void ParticleManager::addNodeParticle(IGameDef *gamedef,
LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
{
- // No particles for "airlike" nodes
- if (f.drawtype == NDT_AIRLIKE)
- return;
-
- // Texture
- u8 texid = myrand_range(0, 5);
- const TileLayer &tile = f.tiles[texid].layers[0];
+ ParticleParameters p;
video::ITexture *texture;
- struct TileAnimationParams anim;
- anim.type = TAT_NONE;
+ v2f texpos, texsize;
+ video::SColor color;
- // Only use first frame of animated texture
- if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
- texture = (*tile.frames)[0].texture;
- else
- texture = tile.texture;
+ if (!getNodeParticleParams(n, f, p, &texture, texpos, texsize, &color))
+ return;
- float size = (rand() % 8) / 64.0f;
- float visual_size = BS * size;
- if (tile.scale)
- size /= tile.scale;
- v2f texsize(size * 2.0f, size * 2.0f);
- v2f texpos;
- texpos.X = (rand() % 64) / 64.0f - texsize.X;
- texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
+ p.expirationtime = (rand() % 100) / 100.0f;
// Physics
- v3f velocity(
+ p.vel = v3f(
(rand() % 150) / 50.0f - 1.5f,
(rand() % 150) / 50.0f,
(rand() % 150) / 50.0f - 1.5f
);
- v3f acceleration(
+ p.acc = v3f(
0.0f,
-player->movement_gravity * player->physics_override_gravity / BS,
0.0f
);
- v3f particlepos = v3f(
+ p.pos = v3f(
(f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
(f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
(f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
);
- video::SColor color;
- if (tile.has_color)
- color = tile.color;
- else
- n.getColor(f, &color);
-
Particle *toadd = new Particle(
gamedef,
player,
m_env,
- particlepos,
- velocity,
- acceleration,
- (rand() % 100) / 100.0f, // expiration time
- visual_size,
- true,
- false,
- false,
- false,
+ p,
texture,
texpos,
texsize,
- anim,
- 0,
color);
addParticle(toadd);
@@ -670,3 +621,20 @@ void ParticleManager::addParticle(Particle *toadd)
MutexAutoLock lock(m_particle_list_lock);
m_particles.push_back(toadd);
}
+
+
+void ParticleManager::addParticleSpawner(u64 id, ParticleSpawner *toadd)
+{
+ MutexAutoLock lock(m_spawner_list_lock);
+ m_particle_spawners[id] = toadd;
+}
+
+void ParticleManager::deleteParticleSpawner(u64 id)
+{
+ MutexAutoLock lock(m_spawner_list_lock);
+ auto it = m_particle_spawners.find(id);
+ if (it != m_particle_spawners.end()) {
+ delete it->second;
+ m_particle_spawners.erase(it);
+ }
+}
diff --git a/src/client/particles.h b/src/client/particles.h
index e7b8cbe24..2011f0262 100644
--- a/src/client/particles.h
+++ b/src/client/particles.h
@@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes_extrabloated.h"
#include "client/tile.h"
#include "localplayer.h"
-#include "tileanimation.h"
+#include "../particles.h"
struct ClientEvent;
class ParticleManager;
@@ -38,21 +38,11 @@ class Particle : public scene::ISceneNode
IGameDef* gamedef,
LocalPlayer *player,
ClientEnvironment *env,
- v3f pos,
- v3f velocity,
- v3f acceleration,
- float expirationtime,
- float size,
- bool collisiondetection,
- bool collision_removal,
- bool object_collision,
- bool vertical,
+ const ParticleParameters &p,
video::ITexture *texture,
v2f texpos,
v2f texsize,
- const struct TileAnimationParams &anim,
- u8 glow,
- video::SColor color = video::SColor(0xFFFFFFFF)
+ video::SColor color
);
~Particle() = default;
@@ -119,20 +109,9 @@ class ParticleSpawner
public:
ParticleSpawner(IGameDef* gamedef,
LocalPlayer *player,
- u16 amount,
- float time,
- v3f minp, v3f maxp,
- v3f minvel, v3f maxvel,
- v3f minacc, v3f maxacc,
- float minexptime, float maxexptime,
- float minsize, float maxsize,
- bool collisiondetection,
- bool collision_removal,
- bool object_collision,
+ const ParticleSpawnerParameters &p,
u16 attached_id,
- bool vertical,
video::ITexture *texture,
- const struct TileAnimationParams &anim, u8 glow,
ParticleManager* p_manager);
~ParticleSpawner() = default;
@@ -140,7 +119,7 @@ public:
void step(float dtime, ClientEnvironment *env);
bool get_expired ()
- { return (m_amount <= 0) && m_spawntime != 0; }
+ { return p.amount <= 0 && p.time != 0; }
private:
void spawnParticle(ClientEnvironment *env, float radius,
@@ -150,27 +129,10 @@ private:
float m_time;
IGameDef *m_gamedef;
LocalPlayer *m_player;
- u16 m_amount;
- float m_spawntime;
- v3f m_minpos;
- v3f m_maxpos;
- v3f m_minvel;
- v3f m_maxvel;
- v3f m_minacc;
- v3f m_maxacc;
- float m_minexptime;
- float m_maxexptime;
- float m_minsize;
- float m_maxsize;
+ ParticleSpawnerParameters p;
video::ITexture *m_texture;
std::vector<float> m_spawntimes;
- bool m_collisiondetection;
- bool m_collision_removal;
- bool m_object_collision;
- bool m_vertical;
u16 m_attached_id;
- struct TileAnimationParams m_animation;
- u8 m_glow;
};
/**
@@ -197,8 +159,8 @@ public:
/**
* This function is only used by client particle spawners
*
- * We don't need to check the particle spawner list because client ID will n
- * ever overlap (u64)
+ * We don't need to check the particle spawner list because client ID will
+ * never overlap (u64)
* @return new id
*/
u64 generateSpawnerId()
@@ -207,9 +169,15 @@ public:
}
protected:
+ static bool getNodeParticleParams(const MapNode &n, const ContentFeatures &f,
+ ParticleParameters &p, video::ITexture **texture, v2f &texpos,
+ v2f &texsize, video::SColor *color, u8 tilenum = 0);
+
void addParticle(Particle* toadd);
private:
+ void addParticleSpawner(u64 id, ParticleSpawner *toadd);
+ void deleteParticleSpawner(u64 id);
void stepParticles(float dtime);
void stepSpawners(float dtime);
diff --git a/src/client/render/factory.h b/src/client/render/factory.h
index 22738404a..e3339a836 100644
--- a/src/client/render/factory.h
+++ b/src/client/render/factory.h
@@ -18,6 +18,8 @@ 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 <string>
#include "core.h"
diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp
index 6e6509eeb..f5aca8f58 100644
--- a/src/client/renderingengine.cpp
+++ b/src/client/renderingengine.cpp
@@ -45,7 +45,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
+#endif
+#ifdef _WIN32
+#include <windows.h>
+#include <winuser.h>
#endif
#if ENABLE_GLES
@@ -126,12 +130,9 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu");
params.ZBufferBits = 24;
#ifdef __ANDROID__
- // clang-format off
params.PrivateData = porting::app_global;
- params.OGLES2ShaderPath = std::string(porting::path_user + DIR_DELIM + "media" +
- DIR_DELIM + "Shaders" + DIR_DELIM).c_str();
- // clang-format on
-#elif ENABLE_GLES
+#endif
+#if ENABLE_GLES
// there is no standardized path for these on desktop
std::string rel_path = std::string("client") + DIR_DELIM
+ "shaders" + DIR_DELIM + "Irrlicht";
@@ -226,27 +227,17 @@ bool RenderingEngine::setupTopLevelWindow(const std::string &name)
{
// FIXME: It would make more sense for there to be a switch of some
// sort here that would call the correct toplevel setup methods for
- // the environment Minetest is running in but for now not deviating
- // from the original pattern.
+ // the environment Minetest is running in.
/* Setting Xorg properties for the top level window */
setupTopLevelXorgWindow(name);
- /* Done with Xorg properties */
/* Setting general properties for the top level window */
verbosestream << "Client: Configuring general top level"
<< " window properties"
<< std::endl;
-
bool result = setWindowIcon();
- verbosestream << "Client: Finished configuring general top level"
- << " window properties"
- << std::endl;
- /* Done with general properties */
-
- // FIXME: setWindowIcon returns a bool result but it is unused.
- // For now continue to return this result.
return result;
}
@@ -262,7 +253,7 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name)
return;
}
- verbosestream << "Client: Configuring Xorg specific top level"
+ verbosestream << "Client: Configuring X11-specific top level"
<< " window properties"
<< std::endl;
@@ -309,8 +300,6 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name)
Atom NET_WM_PID = XInternAtom(x11_dpl, "_NET_WM_PID", false);
pid_t pid = getpid();
- infostream << "Client: PID is '" << static_cast<long>(pid) << "'"
- << std::endl;
XChangeProperty(x11_dpl, x11_win, NET_WM_PID,
XA_CARDINAL, 32, PropModeReplace,
@@ -327,13 +316,31 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name)
XChangeProperty (x11_dpl, x11_win, WM_CLIENT_LEADER,
XA_WINDOW, 32, PropModeReplace,
reinterpret_cast<unsigned char *>(&x11_win), 1);
-
- verbosestream << "Client: Finished configuring Xorg specific top level"
- << " window properties"
- << std::endl;
#endif
}
+#ifdef _WIN32
+static bool getWindowHandle(irr::video::IVideoDriver *driver, HWND &hWnd)
+{
+ const video::SExposedVideoData exposedData = driver->getExposedVideoData();
+
+ switch (driver->getDriverType()) {
+ case video::EDT_DIRECT3D8:
+ hWnd = reinterpret_cast<HWND>(exposedData.D3D8.HWnd);
+ break;
+ case video::EDT_DIRECT3D9:
+ hWnd = reinterpret_cast<HWND>(exposedData.D3D9.HWnd);
+ break;
+ case video::EDT_OPENGL:
+ hWnd = reinterpret_cast<HWND>(exposedData.OpenGLWin32.HWnd);
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+#endif
bool RenderingEngine::setWindowIcon()
{
@@ -351,22 +358,9 @@ bool RenderingEngine::setWindowIcon()
"-xorg-icon-128.png");
#endif
#elif defined(_WIN32)
- const video::SExposedVideoData exposedData = driver->getExposedVideoData();
HWND hWnd; // Window handle
-
- switch (driver->getDriverType()) {
- case video::EDT_DIRECT3D8:
- hWnd = reinterpret_cast<HWND>(exposedData.D3D8.HWnd);
- break;
- case video::EDT_DIRECT3D9:
- hWnd = reinterpret_cast<HWND>(exposedData.D3D9.HWnd);
- break;
- case video::EDT_OPENGL:
- hWnd = reinterpret_cast<HWND>(exposedData.OpenGLWin32.HWnd);
- break;
- default:
+ if (!getWindowHandle(driver, hWnd))
return false;
- }
// Load the ICON from resource file
const HICON hicon = LoadIcon(GetModuleHandle(NULL),
@@ -648,7 +642,7 @@ const char *RenderingEngine::getVideoDriverFriendlyName(irr::video::E_DRIVER_TYP
}
#ifndef __ANDROID__
-#ifdef XORG_USED
+#if defined(XORG_USED)
static float calcDisplayDensity()
{
@@ -683,12 +677,42 @@ float RenderingEngine::getDisplayDensity()
return cached_display_density;
}
-#else // XORG_USED
+#elif defined(_WIN32)
+
+
+static float calcDisplayDensity(irr::video::IVideoDriver *driver)
+{
+ HWND hWnd;
+ if (getWindowHandle(driver, hWnd)) {
+ HDC hdc = GetDC(hWnd);
+ float dpi = GetDeviceCaps(hdc, LOGPIXELSX);
+ ReleaseDC(hWnd, hdc);
+ return dpi / 96.0f;
+ }
+
+ /* return manually specified dpi */
+ return g_settings->getFloat("screen_dpi") / 96.0f;
+}
+
+float RenderingEngine::getDisplayDensity()
+{
+ static bool cached = false;
+ static float display_density;
+ if (!cached) {
+ display_density = calcDisplayDensity(get_video_driver());
+ cached = true;
+ }
+ return display_density;
+}
+
+#else
+
float RenderingEngine::getDisplayDensity()
{
return g_settings->getFloat("screen_dpi") / 96.0;
}
-#endif // XORG_USED
+
+#endif
v2u32 RenderingEngine::getDisplaySize()
{
diff --git a/src/client/shader.cpp b/src/client/shader.cpp
index eda415ce6..ee6079f7a 100644
--- a/src/client/shader.cpp
+++ b/src/client/shader.cpp
@@ -537,11 +537,13 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp
shaderinfo.base_material = video::EMT_SOLID;
break;
case TILE_MATERIAL_ALPHA:
+ case TILE_MATERIAL_PLAIN_ALPHA:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
break;
case TILE_MATERIAL_BASIC:
+ case TILE_MATERIAL_PLAIN:
case TILE_MATERIAL_WAVING_LEAVES:
case TILE_MATERIAL_WAVING_PLANTS:
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
@@ -644,9 +646,11 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp
"TILE_MATERIAL_WAVING_LIQUID_BASIC",
"TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT",
"TILE_MATERIAL_WAVING_LIQUID_OPAQUE",
+ "TILE_MATERIAL_PLAIN",
+ "TILE_MATERIAL_PLAIN_ALPHA",
};
- for (int i = 0; i < 10; i++){
+ for (int i = 0; i < 12; i++){
shaders_header += "#define ";
shaders_header += materialTypes[i];
shaders_header += " ";
diff --git a/src/client/sky.cpp b/src/client/sky.cpp
index 7a7b188ce..2e0cbca86 100644
--- a/src/client/sky.cpp
+++ b/src/client/sky.cpp
@@ -252,35 +252,10 @@ void Sky::render()
if (m_visible) {
driver->setMaterial(m_materials[1]);
for (u32 j = 0; j < 4; j++) {
- video::SColor c = cloudyfogcolor.getInterpolated(m_skycolor, 0.45);
- vertices[0] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( 1, 0.12, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-1, 0.12, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- if (j == 0)
- // Don't switch
- {}
- else if (j == 1)
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- else if (j == 2)
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- else
- // Switch from -Z (south) to +Z (north)
- vertex.Pos.rotateXZBy(-180);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- }
-
- // Draw far cloudy fog thing at and below all horizons
- for (u32 j = 0; j < 4; j++) {
- video::SColor c = cloudyfogcolor;
- vertices[0] = video::S3DVertex(-1, -1.0, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( 1, -1.0, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( 1, 0.08, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-1, 0.08, -1, 0, 0, 1, c, t, o);
+ vertices[0] = video::S3DVertex(-1, -0.02, -1, 0, 0, 1, m_bgcolor, t, t);
+ vertices[1] = video::S3DVertex( 1, -0.02, -1, 0, 0, 1, m_bgcolor, o, t);
+ vertices[2] = video::S3DVertex( 1, 0.45, -1, 0, 0, 1, m_skycolor, o, o);
+ vertices[3] = video::S3DVertex(-1, 0.45, -1, 0, 0, 1, m_skycolor, t, o);
for (video::S3DVertex &vertex : vertices) {
if (j == 0)
// Don't switch
@@ -529,7 +504,7 @@ void Sky::update(float time_of_day, float time_brightness,
pointcolor_sun_f.g = pointcolor_light *
(float)m_materials[3].EmissiveColor.getGreen() / 255;
} else if (!m_default_tint) {
- pointcolor_sun_f = m_sky_params.sun_tint;
+ pointcolor_sun_f = m_sky_params.fog_sun_tint;
} else {
pointcolor_sun_f.r = pointcolor_light * 1;
pointcolor_sun_f.b = pointcolor_light *
@@ -548,9 +523,9 @@ void Sky::update(float time_of_day, float time_brightness,
);
} else {
pointcolor_moon_f = video::SColorf(
- (m_sky_params.moon_tint.getRed() / 255) * pointcolor_light,
- (m_sky_params.moon_tint.getGreen() / 255) * pointcolor_light,
- (m_sky_params.moon_tint.getBlue() / 255) * pointcolor_light,
+ (m_sky_params.fog_moon_tint.getRed() / 255) * pointcolor_light,
+ (m_sky_params.fog_moon_tint.getGreen() / 255) * pointcolor_light,
+ (m_sky_params.fog_moon_tint.getBlue() / 255) * pointcolor_light,
1
);
}
@@ -932,17 +907,17 @@ void Sky::setStarCount(u16 star_count, bool force_update)
}
}
-void Sky::setSkyColors(const SkyboxParams sky)
+void Sky::setSkyColors(const SkyColor &sky_color)
{
- m_sky_params.sky_color = sky.sky_color;
+ m_sky_params.sky_color = sky_color;
}
void Sky::setHorizonTint(video::SColor sun_tint, video::SColor moon_tint,
std::string use_sun_tint)
{
// Change sun and moon tinting:
- m_sky_params.sun_tint = sun_tint;
- m_sky_params.moon_tint = moon_tint;
+ m_sky_params.fog_sun_tint = sun_tint;
+ m_sky_params.fog_moon_tint = moon_tint;
// Faster than comparing strings every rendering frame
if (use_sun_tint == "default")
m_default_tint = true;
diff --git a/src/client/sky.h b/src/client/sky.h
index 8637f96d4..3227e8f59 100644
--- a/src/client/sky.h
+++ b/src/client/sky.h
@@ -94,7 +94,7 @@ public:
m_bgcolor = bgcolor;
m_skycolor = skycolor;
}
- void setSkyColors(const SkyboxParams sky);
+ void setSkyColors(const SkyColor &sky_color);
void setHorizonTint(video::SColor sun_tint, video::SColor moon_tint,
std::string use_sun_tint);
void setInClouds(bool clouds) { m_in_clouds = clouds; }
diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp
index 8e696f302..20a651c1d 100644
--- a/src/client/sound_openal.cpp
+++ b/src/client/sound_openal.cpp
@@ -165,8 +165,8 @@ SoundBuffer *load_opened_ogg_file(OggVorbis_File *oggFile,
<< "preparing sound buffer" << std::endl;
}
- infostream << "Audio file "
- << filename_for_logging << " loaded" << std::endl;
+ //infostream << "Audio file "
+ // << filename_for_logging << " loaded" << std::endl;
// Clean up!
ov_clear(oggFile);
@@ -275,25 +275,38 @@ public:
m_device(nullptr, delete_alcdevice),
m_context(nullptr, delete_alccontext)
{
- if (!(m_device = unique_ptr_alcdevice(alcOpenDevice(nullptr), delete_alcdevice)))
- throw std::runtime_error("Audio: Global Initialization: Device Open");
+ }
+
+ bool init()
+ {
+ if (!(m_device = unique_ptr_alcdevice(alcOpenDevice(nullptr), delete_alcdevice))) {
+ errorstream << "Audio: Global Initialization: Failed to open device" << std::endl;
+ return false;
+ }
if (!(m_context = unique_ptr_alccontext(
alcCreateContext(m_device.get(), nullptr), delete_alccontext))) {
- throw std::runtime_error("Audio: Global Initialization: Context Create");
+ errorstream << "Audio: Global Initialization: Failed to create context" << std::endl;
+ return false;
}
- if (!alcMakeContextCurrent(m_context.get()))
- throw std::runtime_error("Audio: Global Initialization: Context Current");
+ if (!alcMakeContextCurrent(m_context.get())) {
+ errorstream << "Audio: Global Initialization: Failed to make current context" << std::endl;
+ return false;
+ }
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
- if (alGetError() != AL_NO_ERROR)
- throw std::runtime_error("Audio: Global Initialization: OpenAL Error");
+ if (alGetError() != AL_NO_ERROR) {
+ errorstream << "Audio: Global Initialization: OpenAL Error " << alGetError() << std::endl;
+ return false;
+ }
infostream << "Audio: Global Initialized: OpenAL " << alGetString(AL_VERSION)
<< ", using " << alcGetString(m_device.get(), ALC_DEVICE_SPECIFIER)
<< std::endl;
+
+ return true;
}
~SoundManagerSingleton()
@@ -498,9 +511,11 @@ public:
// Remove stopped sounds
void maintain()
{
- verbosestream<<"OpenALSoundManager::maintain(): "
- <<m_sounds_playing.size()<<" playing sounds, "
- <<m_buffers.size()<<" sound names loaded"<<std::endl;
+ if (!m_sounds_playing.empty()) {
+ verbosestream << "OpenALSoundManager::maintain(): "
+ << m_sounds_playing.size() <<" playing sounds, "
+ << m_buffers.size() <<" sound names loaded"<<std::endl;
+ }
std::unordered_set<int> del_list;
for (const auto &sp : m_sounds_playing) {
int id = sp.first;
@@ -530,7 +545,7 @@ public:
SoundBuffer *buf = load_ogg_from_file(filepath);
if (buf)
addBuffer(name, buf);
- return false;
+ return !!buf;
}
bool loadSoundData(const std::string &name,
@@ -539,7 +554,7 @@ public:
SoundBuffer *buf = load_ogg_from_buffer(filedata, name);
if (buf)
addBuffer(name, buf);
- return false;
+ return !!buf;
}
void updateListener(const v3f &pos, const v3f &vel, const v3f &at, const v3f &up)
@@ -680,7 +695,11 @@ public:
std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton()
{
- return std::shared_ptr<SoundManagerSingleton>(new SoundManagerSingleton());
+ auto smg = std::make_shared<SoundManagerSingleton>();
+ if (!smg->init()) {
+ smg.reset();
+ }
+ return smg;
}
ISoundManager *createOpenALSoundManager(SoundManagerSingleton *smg, OnDemandSoundFetcher *fetcher)
diff --git a/src/client/tile.cpp b/src/client/tile.cpp
index 3189ab28c..d03588b2b 100644
--- a/src/client/tile.cpp
+++ b/src/client/tile.cpp
@@ -471,8 +471,8 @@ TextureSource::~TextureSource()
driver->removeTexture(t);
}
- infostream << "~TextureSource() "<< textures_before << "/"
- << driver->getTextureCount() << std::endl;
+ infostream << "~TextureSource() before cleanup: "<< textures_before
+ << " after: " << driver->getTextureCount() << std::endl;
}
u32 TextureSource::getTextureId(const std::string &name)
@@ -668,7 +668,14 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
{
- return getTexture(name + "^[applyfiltersformesh", id);
+ static thread_local bool filter_needed =
+ g_settings->getBool("texture_clean_transparent") ||
+ ((m_setting_trilinear_filter || m_setting_bilinear_filter) &&
+ g_settings->getS32("texture_min_size") > 1);
+ // Avoid duplicating texture if it won't actually change
+ if (filter_needed)
+ return getTexture(name + "^[applyfiltersformesh", id);
+ return getTexture(name, id);
}
Palette* TextureSource::getPalette(const std::string &name)
@@ -763,6 +770,9 @@ void TextureSource::rebuildImagesAndTextures()
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
sanity_check(driver);
+ infostream << "TextureSource: recreating " << m_textureinfo_cache.size()
+ << " textures" << std::endl;
+
// Recreate textures
for (TextureInfo &ti : m_textureinfo_cache) {
video::IImage *img = generateImage(ti.name);
@@ -1270,8 +1280,6 @@ bool TextureSource::generateImagePart(std::string part_of_name,
video::IImage *img = generateImage(filename);
if (img) {
core::dimension2d<u32> dim = img->getDimension();
- infostream<<"Size "<<dim.Width
- <<"x"<<dim.Height<<std::endl;
core::position2d<s32> pos_base(x, y);
video::IImage *img2 =
driver->createImage(video::ECF_A8R8G8B8, dim);
@@ -1622,6 +1630,9 @@ bool TextureSource::generateImagePart(std::string part_of_name,
*/
else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
{
+ /* IMPORTANT: When changing this, getTextureForMesh() needs to be
+ * updated too. */
+
// Apply the "clean transparent" filter, if configured.
if (g_settings->getBool("texture_clean_transparent"))
imageCleanTransparent(baseimg, 127);
diff --git a/src/client/tile.h b/src/client/tile.h
index 533df676e..52e0a2b2b 100644
--- a/src/client/tile.h
+++ b/src/client/tile.h
@@ -150,6 +150,8 @@ enum MaterialType{
TILE_MATERIAL_WAVING_LIQUID_BASIC,
TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT,
TILE_MATERIAL_WAVING_LIQUID_OPAQUE,
+ TILE_MATERIAL_PLAIN,
+ TILE_MATERIAL_PLAIN_ALPHA
};
// Material flags
diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp
index 2c6807fab..8cd3e29a9 100644
--- a/src/client/wieldmesh.cpp
+++ b/src/client/wieldmesh.cpp
@@ -347,7 +347,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
scene::SMesh *mesh = nullptr;
if (m_enable_shaders) {
- u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL);
+ u32 shader_id = shdrsrc->getShader("object_shader", TILE_MATERIAL_BASIC, NDT_NORMAL);
m_material_type = shdrsrc->getShaderInfo(shader_id).material;
}
@@ -467,10 +467,29 @@ void WieldMeshSceneNode::setColor(video::SColor c)
bc.getGreen() * green / 255,
bc.getBlue() * blue / 255);
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
- colorizeMeshBuffer(buf, &buffercolor);
+
+ if (m_enable_shaders)
+ setMeshBufferColor(buf, buffercolor);
+ else
+ colorizeMeshBuffer(buf, &buffercolor);
}
}
+void WieldMeshSceneNode::setNodeLightColor(video::SColor color)
+{
+ if (!m_meshnode)
+ return;
+
+ if (m_enable_shaders) {
+ for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) {
+ video::SMaterial &material = m_meshnode->getMaterial(i);
+ material.EmissiveColor = color;
+ }
+ }
+
+ setColor(color);
+}
+
void WieldMeshSceneNode::render()
{
// note: if this method is changed to actually do something,
diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h
index 7c80a811b..933097230 100644
--- a/src/client/wieldmesh.h
+++ b/src/client/wieldmesh.h
@@ -87,6 +87,8 @@ public:
// Must only be used if the constructor was called with lighting = false
void setColor(video::SColor color);
+ void setNodeLightColor(video::SColor color);
+
scene::IMesh *getMesh() { return m_meshnode->getMesh(); }
virtual void render();
diff --git a/src/clientiface.cpp b/src/clientiface.cpp
index dceaa64f2..602a44c90 100644
--- a/src/clientiface.cpp
+++ b/src/clientiface.cpp
@@ -27,7 +27,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "serverenvironment.h"
#include "map.h"
#include "emerge.h"
-#include "content_sao.h" // TODO this is used for cleanup of only
+#include "server/luaentity_sao.h"
+#include "server/player_sao.h"
#include "log.h"
#include "util/srp.h"
#include "face_position_cache.h"
@@ -343,10 +344,10 @@ void RemoteClient::GetNextBlocks (
}
/*
- If block has been marked to not exist on disk (dummy)
- and generating new ones is not wanted, skip block.
+ If block has been marked to not exist on disk (dummy) or is
+ not generated and generating new ones is not wanted, skip block.
*/
- if (!generate && surely_not_found_on_disk) {
+ if (!generate && (surely_not_found_on_disk || block_is_invalid)) {
// get next one.
continue;
}
diff --git a/src/clientiface.h b/src/clientiface.h
index bf95df4a8..83fa6fe99 100644
--- a/src/clientiface.h
+++ b/src/clientiface.h
@@ -339,12 +339,18 @@ public:
u8 getMinor() const { return m_version_minor; }
u8 getPatch() const { return m_version_patch; }
const std::string &getFull() const { return m_full_version; }
+
+ void setLangCode(const std::string &code) { m_lang_code = code; }
+ const std::string &getLangCode() const { return m_lang_code; }
private:
// Version is stored in here after INIT before INIT2
u8 m_pending_serialization_version = SER_FMT_VER_INVALID;
/* current state of client */
ClientState m_state = CS_Created;
+
+ // Client sent language code
+ std::string m_lang_code;
/*
Blocks that have been sent to client.
diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in
index cb54cb488..cfcee4b58 100644
--- a/src/cmake_config.h.in
+++ b/src/cmake_config.h.in
@@ -23,6 +23,7 @@
#cmakedefine01 USE_LEVELDB
#cmakedefine01 USE_LUAJIT
#cmakedefine01 USE_POSTGRESQL
+#cmakedefine01 USE_PROMETHEUS
#cmakedefine01 USE_SPATIAL
#cmakedefine01 USE_SYSTEM_GMP
#cmakedefine01 USE_REDIS
@@ -34,3 +35,4 @@
#cmakedefine01 CURSES_HAVE_NCURSES_CURSES_H
#cmakedefine01 CURSES_HAVE_NCURSESW_NCURSES_H
#cmakedefine01 CURSES_HAVE_NCURSESW_CURSES_H
+#cmakedefine01 BUILD_UNITTESTS
diff --git a/src/collision.cpp b/src/collision.cpp
index a443be7ab..d85a56884 100644
--- a/src/collision.cpp
+++ b/src/collision.cpp
@@ -25,35 +25,63 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "gamedef.h"
#ifndef SERVER
#include "client/clientenvironment.h"
+#include "client/localplayer.h"
#endif
#include "serverenvironment.h"
-#include "serverobject.h"
+#include "server/serveractiveobject.h"
#include "util/timetaker.h"
#include "profiler.h"
-// float error is 10 - 9.96875 = 0.03125
-//#define COLL_ZERO 0.032 // broken unit tests
-#define COLL_ZERO 0
-
+#ifdef __FAST_MATH__
+#warning "-ffast-math is known to cause bugs in collision code, do not use!"
+#endif
struct NearbyCollisionInfo {
- NearbyCollisionInfo(bool is_ul, bool is_obj, int bouncy,
- const v3s16 &pos, const aabb3f &box) :
+ // node
+ NearbyCollisionInfo(bool is_ul, int bouncy, const v3s16 &pos,
+ const aabb3f &box) :
is_unloaded(is_ul),
- is_object(is_obj),
+ obj(nullptr),
bouncy(bouncy),
position(pos),
box(box)
{}
+ // object
+ NearbyCollisionInfo(ActiveObject *obj, int bouncy,
+ const aabb3f &box) :
+ is_unloaded(false),
+ obj(obj),
+ bouncy(bouncy),
+ box(box)
+ {}
+
+ inline bool isObject() const { return obj != nullptr; }
+
bool is_unloaded;
bool is_step_up = false;
- bool is_object;
+ ActiveObject *obj;
int bouncy;
v3s16 position;
aabb3f box;
};
+// Helper functions:
+// Truncate floating point numbers to specified number of decimal places
+// in order to move all the floating point error to one side of the correct value
+static inline f32 truncate(const f32 val, const f32 factor)
+{
+ return truncf(val * factor) / factor;
+}
+
+static inline v3f truncate(const v3f& vec, const f32 factor)
+{
+ return v3f(
+ truncate(vec.X, factor),
+ truncate(vec.Y, factor),
+ truncate(vec.Z, factor)
+ );
+}
// Helper function:
// Checks for collision of a moving aabbox with a static aabbox
@@ -61,118 +89,101 @@ struct NearbyCollisionInfo {
// The time after which the collision occurs is stored in dtime.
CollisionAxis axisAlignedCollision(
const aabb3f &staticbox, const aabb3f &movingbox,
- const v3f &speed, f32 d, f32 *dtime)
+ const v3f &speed, f32 *dtime)
{
//TimeTaker tt("axisAlignedCollision");
- f32 xsize = (staticbox.MaxEdge.X - staticbox.MinEdge.X) - COLL_ZERO; // reduce box size for solve collision stuck (flying sand)
- f32 ysize = (staticbox.MaxEdge.Y - staticbox.MinEdge.Y); // - COLL_ZERO; // Y - no sense for falling, but maybe try later
- f32 zsize = (staticbox.MaxEdge.Z - staticbox.MinEdge.Z) - COLL_ZERO;
-
aabb3f relbox(
- movingbox.MinEdge.X - staticbox.MinEdge.X,
- movingbox.MinEdge.Y - staticbox.MinEdge.Y,
- movingbox.MinEdge.Z - staticbox.MinEdge.Z,
- movingbox.MaxEdge.X - staticbox.MinEdge.X,
- movingbox.MaxEdge.Y - staticbox.MinEdge.Y,
- movingbox.MaxEdge.Z - staticbox.MinEdge.Z
+ (movingbox.MaxEdge.X - movingbox.MinEdge.X) + (staticbox.MaxEdge.X - staticbox.MinEdge.X), // sum of the widths
+ (movingbox.MaxEdge.Y - movingbox.MinEdge.Y) + (staticbox.MaxEdge.Y - staticbox.MinEdge.Y),
+ (movingbox.MaxEdge.Z - movingbox.MinEdge.Z) + (staticbox.MaxEdge.Z - staticbox.MinEdge.Z),
+ std::max(movingbox.MaxEdge.X, staticbox.MaxEdge.X) - std::min(movingbox.MinEdge.X, staticbox.MinEdge.X), //outer bounding 'box' dimensions
+ std::max(movingbox.MaxEdge.Y, staticbox.MaxEdge.Y) - std::min(movingbox.MinEdge.Y, staticbox.MinEdge.Y),
+ std::max(movingbox.MaxEdge.Z, staticbox.MaxEdge.Z) - std::min(movingbox.MinEdge.Z, staticbox.MinEdge.Z)
);
- if(speed.X > 0) // Check for collision with X- plane
- {
- if (relbox.MaxEdge.X <= d) {
- *dtime = -relbox.MaxEdge.X / speed.X;
- if ((relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) &&
- (relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO) &&
- (relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
- (relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
- return COLLISION_AXIS_X;
- }
- else if(relbox.MinEdge.X > xsize)
- {
- return COLLISION_AXIS_NONE;
- }
- }
- else if(speed.X < 0) // Check for collision with X+ plane
- {
- if (relbox.MinEdge.X >= xsize - d) {
- *dtime = (xsize - relbox.MinEdge.X) / speed.X;
- if ((relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) &&
- (relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO) &&
- (relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
- (relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
- return COLLISION_AXIS_X;
+ const f32 dtime_max = *dtime;
+ f32 inner_margin; // the distance of clipping recovery
+ f32 distance;
+ f32 time;
+
+
+ if (speed.Y) {
+ distance = relbox.MaxEdge.Y - relbox.MinEdge.Y;
+ *dtime = distance / std::abs(speed.Y);
+ time = std::max(*dtime, 0.0f);
+
+ if (*dtime <= dtime_max) {
+ inner_margin = std::max(-0.5f * (staticbox.MaxEdge.Y - staticbox.MinEdge.Y), -2.0f);
+
+ if ((speed.Y > 0 && staticbox.MinEdge.Y - movingbox.MaxEdge.Y > inner_margin) ||
+ (speed.Y < 0 && movingbox.MinEdge.Y - staticbox.MaxEdge.Y > inner_margin)) {
+ if (
+ (std::max(movingbox.MaxEdge.X + speed.X * time, staticbox.MaxEdge.X)
+ - std::min(movingbox.MinEdge.X + speed.X * time, staticbox.MinEdge.X)
+ - relbox.MinEdge.X < 0) &&
+ (std::max(movingbox.MaxEdge.Z + speed.Z * time, staticbox.MaxEdge.Z)
+ - std::min(movingbox.MinEdge.Z + speed.Z * time, staticbox.MinEdge.Z)
+ - relbox.MinEdge.Z < 0)
+ )
+ return COLLISION_AXIS_Y;
+ }
}
- else if(relbox.MaxEdge.X < 0)
- {
+ else {
return COLLISION_AXIS_NONE;
}
}
// NO else if here
- if(speed.Y > 0) // Check for collision with Y- plane
- {
- if (relbox.MaxEdge.Y <= d) {
- *dtime = -relbox.MaxEdge.Y / speed.Y;
- if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) &&
- (relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
- (relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
- (relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
- return COLLISION_AXIS_Y;
- }
- else if(relbox.MinEdge.Y > ysize)
- {
- return COLLISION_AXIS_NONE;
- }
- }
- else if(speed.Y < 0) // Check for collision with Y+ plane
- {
- if (relbox.MinEdge.Y >= ysize - d) {
- *dtime = (ysize - relbox.MinEdge.Y) / speed.Y;
- if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) &&
- (relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
- (relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
- (relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
- return COLLISION_AXIS_Y;
- }
- else if(relbox.MaxEdge.Y < 0)
- {
+ if (speed.X) {
+ distance = relbox.MaxEdge.X - relbox.MinEdge.X;
+ *dtime = distance / std::abs(speed.X);
+ time = std::max(*dtime, 0.0f);
+
+ if (*dtime <= dtime_max) {
+ inner_margin = std::max(-0.5f * (staticbox.MaxEdge.X - staticbox.MinEdge.X), -2.0f);
+
+ if ((speed.X > 0 && staticbox.MinEdge.X - movingbox.MaxEdge.X > inner_margin) ||
+ (speed.X < 0 && movingbox.MinEdge.X - staticbox.MaxEdge.X > inner_margin)) {
+ if (
+ (std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y)
+ - std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y)
+ - relbox.MinEdge.Y < 0) &&
+ (std::max(movingbox.MaxEdge.Z + speed.Z * time, staticbox.MaxEdge.Z)
+ - std::min(movingbox.MinEdge.Z + speed.Z * time, staticbox.MinEdge.Z)
+ - relbox.MinEdge.Z < 0)
+ )
+ return COLLISION_AXIS_X;
+ }
+ } else {
return COLLISION_AXIS_NONE;
}
}
// NO else if here
- if(speed.Z > 0) // Check for collision with Z- plane
- {
- if (relbox.MaxEdge.Z <= d) {
- *dtime = -relbox.MaxEdge.Z / speed.Z;
- if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) &&
- (relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
- (relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) &&
- (relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO))
- return COLLISION_AXIS_Z;
- }
- //else if(relbox.MinEdge.Z > zsize)
- //{
- // return COLLISION_AXIS_NONE;
- //}
- }
- else if(speed.Z < 0) // Check for collision with Z+ plane
- {
- if (relbox.MinEdge.Z >= zsize - d) {
- *dtime = (zsize - relbox.MinEdge.Z) / speed.Z;
- if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) &&
- (relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
- (relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) &&
- (relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO))
- return COLLISION_AXIS_Z;
+ if (speed.Z) {
+ distance = relbox.MaxEdge.Z - relbox.MinEdge.Z;
+ *dtime = distance / std::abs(speed.Z);
+ time = std::max(*dtime, 0.0f);
+
+ if (*dtime <= dtime_max) {
+ inner_margin = std::max(-0.5f * (staticbox.MaxEdge.Z - staticbox.MinEdge.Z), -2.0f);
+
+ if ((speed.Z > 0 && staticbox.MinEdge.Z - movingbox.MaxEdge.Z > inner_margin) ||
+ (speed.Z < 0 && movingbox.MinEdge.Z - staticbox.MaxEdge.Z > inner_margin)) {
+ if (
+ (std::max(movingbox.MaxEdge.X + speed.X * time, staticbox.MaxEdge.X)
+ - std::min(movingbox.MinEdge.X + speed.X * time, staticbox.MinEdge.X)
+ - relbox.MinEdge.X < 0) &&
+ (std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y)
+ - std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y)
+ - relbox.MinEdge.Y < 0)
+ )
+ return COLLISION_AXIS_Z;
+ }
}
- //else if(relbox.MaxEdge.Z < 0)
- //{
- // return COLLISION_AXIS_NONE;
- //}
}
return COLLISION_AXIS_NONE;
@@ -249,6 +260,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
speed_f->X = rangelim(speed_f->X, -5000, 5000);
speed_f->Z = rangelim(speed_f->Z, -5000, 5000);
+ *speed_f = truncate(*speed_f, 10000.0f);
+
/*
Collect node boxes in movement range
*/
@@ -328,13 +341,13 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
for (auto box : nodeboxes) {
box.MinEdge += posf;
box.MaxEdge += posf;
- cinfo.emplace_back(false, false, n_bouncy_value, p, box);
+ cinfo.emplace_back(false, n_bouncy_value, p, box);
}
} else {
// Collide with unloaded nodes (position invalid) and loaded
// CONTENT_IGNORE nodes (position valid)
aabb3f box = getNodeBox(p, BS);
- cinfo.emplace_back(true, false, 0, p, box);
+ cinfo.emplace_back(true, 0, p, box);
}
}
@@ -379,17 +392,20 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
// Calculate distance by speed, add own extent and 1.5m of tolerance
f32 distance = speed_f->getLength() * dtime +
box_0.getExtent().getLength() + 1.5f * BS;
- std::vector<u16> s_objects;
- s_env->getObjectsInsideRadius(s_objects, *pos_f, distance);
-
- for (u16 obj_id : s_objects) {
- ServerActiveObject *current = s_env->getActiveObject(obj_id);
- if (!self || (self != current &&
- self != current->getParent())) {
- objects.push_back((ActiveObject*)current);
+ // search for objects which are not us, or we are not its parent
+ // we directly use the callback to populate the result to prevent
+ // a useless result loop here
+ auto include_obj_cb = [self, &objects] (ServerActiveObject *obj) {
+ if (!obj->isGone() &&
+ (!self || (self != obj && self != obj->getParent()))) {
+ objects.push_back((ActiveObject *)obj);
}
- }
+ return false;
+ };
+
+ std::vector<ServerActiveObject *> s_objects;
+ s_env->getObjectsInsideRadius(s_objects, *pos_f, distance, include_obj_cb);
}
}
@@ -397,30 +413,32 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
iter != objects.end(); ++iter) {
ActiveObject *object = *iter;
- if (object) {
+ if (object && object->collideWithObjects()) {
aabb3f object_collisionbox;
- if (object->getCollisionBox(&object_collisionbox) &&
- object->collideWithObjects()) {
- cinfo.emplace_back(false, true, 0, v3s16(), object_collisionbox);
- }
+ if (object->getCollisionBox(&object_collisionbox))
+ cinfo.emplace_back(object, 0, object_collisionbox);
}
}
+#ifndef SERVER
+ if (self && c_env) {
+ LocalPlayer *lplayer = c_env->getLocalPlayer();
+ if (lplayer->getParent() == nullptr) {
+ aabb3f lplayer_collisionbox = lplayer->getCollisionbox();
+ v3f lplayer_pos = lplayer->getPosition();
+ lplayer_collisionbox.MinEdge += lplayer_pos;
+ lplayer_collisionbox.MaxEdge += lplayer_pos;
+ ActiveObject *obj = (ActiveObject*) lplayer->getCAO();
+ cinfo.emplace_back(obj, 0, lplayer_collisionbox);
+ }
+ }
+#endif
} //tt3
/*
Collision detection
*/
- /*
- Collision uncertainty radius
- Make it a bit larger than the maximum distance of movement
- */
- f32 d = pos_max_d * 1.1f;
- // A fairly large value in here makes moving smoother
- //f32 d = 0.15*BS;
-
- // This should always apply, otherwise there are glitches
- assert(d > pos_max_d); // invariant
+ f32 d = 0.0f;
int loopcount = 0;
@@ -450,9 +468,9 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
continue;
// Find nearest collision of the two boxes (raytracing-like)
- f32 dtime_tmp;
+ f32 dtime_tmp = nearest_dtime;
CollisionAxis collided = axisAlignedCollision(box_info.box,
- movingbox, *speed_f, d, &dtime_tmp);
+ movingbox, *speed_f, &dtime_tmp);
if (collided == -1 || dtime_tmp >= nearest_dtime)
continue;
@@ -464,17 +482,24 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
if (nearest_collided == COLLISION_AXIS_NONE) {
// No collision with any collision box.
- *pos_f += *speed_f * dtime;
+ *pos_f += truncate(*speed_f * dtime, 100.0f);
dtime = 0; // Set to 0 to avoid "infinite" loop due to small FP numbers
} else {
// Otherwise, a collision occurred.
NearbyCollisionInfo &nearest_info = cinfo[nearest_boxindex];
const aabb3f& cbox = nearest_info.box;
+
+ //movingbox except moved to the horizontal position it would be after step up
+ aabb3f stepbox = movingbox;
+ stepbox.MinEdge.X += speed_f->X * dtime;
+ stepbox.MinEdge.Z += speed_f->Z * dtime;
+ stepbox.MaxEdge.X += speed_f->X * dtime;
+ stepbox.MaxEdge.Z += speed_f->Z * dtime;
// Check for stairs.
bool step_up = (nearest_collided != COLLISION_AXIS_Y) && // must not be Y direction
(movingbox.MinEdge.Y < cbox.MaxEdge.Y) &&
(movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) &&
- (!wouldCollideWithCeiling(cinfo, movingbox,
+ (!wouldCollideWithCeiling(cinfo, stepbox,
cbox.MaxEdge.Y - movingbox.MinEdge.Y,
d));
@@ -483,7 +508,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
// Move to the point of collision and reduce dtime by nearest_dtime
if (nearest_dtime < 0) {
- // Handle negative nearest_dtime (can be caused by the d allowance)
+ // Handle negative nearest_dtime
if (!step_up) {
if (nearest_collided == COLLISION_AXIS_X)
pos_f->X += speed_f->X * nearest_dtime;
@@ -493,7 +518,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
pos_f->Z += speed_f->Z * nearest_dtime;
}
} else {
- *pos_f += *speed_f * nearest_dtime;
+ *pos_f += truncate(*speed_f * nearest_dtime, 100.0f);
dtime -= nearest_dtime;
}
@@ -502,12 +527,13 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
is_collision = false;
CollisionInfo info;
- if (nearest_info.is_object)
+ if (nearest_info.isObject())
info.type = COLLISION_OBJECT;
else
info.type = COLLISION_NODE;
info.node_p = nearest_info.position;
+ info.object = nearest_info.obj;
info.old_speed = *speed_f;
info.plane = nearest_collided;
@@ -562,9 +588,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
Object touches ground if object's minimum Y is near node's
maximum Y and object's X-Z-area overlaps with the node's
X-Z-area.
-
- Use 0.15*BS so that it is easier to get on a node.
*/
+
if (cbox.MaxEdge.X - d > box.MinEdge.X && cbox.MinEdge.X + d < box.MaxEdge.X &&
cbox.MaxEdge.Z - d > box.MinEdge.Z &&
cbox.MinEdge.Z + d < box.MaxEdge.Z) {
@@ -574,10 +599,10 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
box.MinEdge += *pos_f;
box.MaxEdge += *pos_f;
}
- if (std::fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.15f * BS) {
+ if (std::fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.05f) {
result.touching_ground = true;
- if (box_info.is_object)
+ if (box_info.isObject())
result.standing_on_object = true;
}
}
diff --git a/src/collision.h b/src/collision.h
index 85df02c96..87a502828 100644
--- a/src/collision.h
+++ b/src/collision.h
@@ -48,6 +48,7 @@ struct CollisionInfo
CollisionType type = COLLISION_NODE;
CollisionAxis axis = COLLISION_AXIS_NONE;
v3s16 node_p = v3s16(-32768,-32768,-32768); // COLLISION_NODE
+ ActiveObject *object = nullptr; // COLLISION_OBJECT
v3f old_speed;
v3f new_speed;
int plane = -1;
@@ -77,7 +78,7 @@ collisionMoveResult collisionMoveSimple(Environment *env,IGameDef *gamedef,
// dtime receives time until first collision, invalid if -1 is returned
CollisionAxis axisAlignedCollision(
const aabb3f &staticbox, const aabb3f &movingbox,
- const v3f &speed, f32 d, f32 *dtime);
+ const v3f &speed, f32 *dtime);
// Helper function:
// Checks if moving the movingbox up by the given distance would hit a ceiling.
diff --git a/src/config.h b/src/config.h
index 039100914..5e1164642 100644
--- a/src/config.h
+++ b/src/config.h
@@ -11,12 +11,12 @@
#if defined USE_CMAKE_CONFIG_H
#include "cmake_config.h"
-#elif defined (__ANDROID__) || defined (ANDROID)
+#elif defined (__ANDROID__)
#define PROJECT_NAME "minetest"
#define PROJECT_NAME_C "Minetest"
#define STATIC_SHAREDIR ""
- #include "android_version.h"
- #ifdef NDEBUG
+ #define VERSION_STRING STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_PATCH) STR(VERSION_EXTRA)
+#ifdef NDEBUG
#define BUILD_TYPE "Release"
#else
#define BUILD_TYPE "Debug"
@@ -28,13 +28,3 @@
#define BUILD_TYPE "Debug"
#endif
#endif
-
-#define BUILD_INFO \
- "BUILD_TYPE=" BUILD_TYPE "\n" \
- "RUN_IN_PLACE=" STR(RUN_IN_PLACE) "\n" \
- "USE_GETTEXT=" STR(USE_GETTEXT) "\n" \
- "USE_SOUND=" STR(USE_SOUND) "\n" \
- "USE_CURL=" STR(USE_CURL) "\n" \
- "USE_FREETYPE=" STR(USE_FREETYPE) "\n" \
- "USE_LUAJIT=" STR(USE_LUAJIT) "\n" \
- "STATIC_SHAREDIR=" STR(STATIC_SHAREDIR);
diff --git a/src/constants.h b/src/constants.h
index 7636b38e0..c17f3b6af 100644
--- a/src/constants.h
+++ b/src/constants.h
@@ -93,7 +93,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define PLAYER_MAX_HP_DEFAULT 20
// Default maximal breath of a player
-#define PLAYER_MAX_BREATH_DEFAULT 11
+#define PLAYER_MAX_BREATH_DEFAULT 10
// Number of different files to try to save a player to if the first fails
// (because of a case-insensitive filesystem)
@@ -110,10 +110,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
GUI related things
*/
-// TODO: implement dpi-based scaling for windows and remove this hack
-#if defined(_WIN32)
-#define TTF_DEFAULT_FONT_SIZE (18)
-#else
#define TTF_DEFAULT_FONT_SIZE (16)
-#endif
#define DEFAULT_FONT_SIZE (10)
diff --git a/src/content/mods.cpp b/src/content/mods.cpp
index 676666f78..95ab0290a 100644
--- a/src/content/mods.cpp
+++ b/src/content/mods.cpp
@@ -167,7 +167,7 @@ std::map<std::string, ModSpec> getModsInPath(
return result;
}
-std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
+std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods)
{
std::vector<ModSpec> result;
for (const auto &it : mods) {
diff --git a/src/content/mods.h b/src/content/mods.h
index 6e2506dbf..b3500fbc8 100644
--- a/src/content/mods.h
+++ b/src/content/mods.h
@@ -68,7 +68,7 @@ std::map<std::string, ModSpec> getModsInPath(
const std::string &path, bool part_of_modpack = false);
// replaces modpack Modspecs with their content
-std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods);
+std::vector<ModSpec> flattenMods(const std::map<std::string, ModSpec> &mods);
// a ModConfiguration is a subset of installed mods, expected to have
// all dependencies fullfilled, so it can be used as a list of mods to
diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
index bf947cf85..170f54e20 100644
--- a/src/content/subgames.cpp
+++ b/src/content/subgames.cpp
@@ -253,7 +253,7 @@ std::vector<WorldSpec> getAvailableWorlds()
worldspaths.insert(porting::path_user + DIR_DELIM + "worlds");
infostream << "Searching worlds..." << std::endl;
for (const std::string &worldspath : worldspaths) {
- infostream << " In " << worldspath << ": " << std::endl;
+ infostream << " In " << worldspath << ": ";
std::vector<fs::DirListNode> dirvector = fs::GetDirListing(worldspath);
for (const fs::DirListNode &dln : dirvector) {
if (!dln.dir)
diff --git a/src/content_sao.cpp b/src/content_sao.cpp
deleted file mode 100644
index 75c3eaf37..000000000
--- a/src/content_sao.cpp
+++ /dev/null
@@ -1,1535 +0,0 @@
-/*
-Minetest
-Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "content_sao.h"
-#include "util/serialize.h"
-#include "collision.h"
-#include "environment.h"
-#include "tool.h" // For ToolCapabilities
-#include "gamedef.h"
-#include "nodedef.h"
-#include "remoteplayer.h"
-#include "server.h"
-#include "scripting_server.h"
-#include "genericobject.h"
-#include "settings.h"
-#include <algorithm>
-#include <cmath>
-
-std::map<u16, ServerActiveObject::Factory> ServerActiveObject::m_types;
-
-/*
- TestSAO
-*/
-
-class TestSAO : public ServerActiveObject
-{
-public:
- TestSAO(ServerEnvironment *env, v3f pos):
- ServerActiveObject(env, pos),
- m_timer1(0),
- m_age(0)
- {
- ServerActiveObject::registerType(getType(), create);
- }
- ActiveObjectType getType() const
- { return ACTIVEOBJECT_TYPE_TEST; }
-
- static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
- const std::string &data)
- {
- return new TestSAO(env, pos);
- }
-
- void step(float dtime, bool send_recommended)
- {
- m_age += dtime;
- if(m_age > 10)
- {
- m_pending_removal = true;
- return;
- }
-
- m_base_position.Y += dtime * BS * 2;
- if(m_base_position.Y > 8*BS)
- m_base_position.Y = 2*BS;
-
- if (!send_recommended)
- return;
-
- m_timer1 -= dtime;
- if(m_timer1 < 0.0)
- {
- m_timer1 += 0.125;
-
- std::string data;
-
- data += itos(0); // 0 = position
- data += " ";
- data += itos(m_base_position.X);
- data += " ";
- data += itos(m_base_position.Y);
- data += " ";
- data += itos(m_base_position.Z);
-
- ActiveObjectMessage aom(getId(), false, data);
- m_messages_out.push(aom);
- }
- }
-
- bool getCollisionBox(aabb3f *toset) const { return false; }
-
- virtual bool getSelectionBox(aabb3f *toset) const { return false; }
-
- bool collideWithObjects() const { return false; }
-
-private:
- float m_timer1;
- float m_age;
-};
-
-// Prototype (registers item for deserialization)
-TestSAO proto_TestSAO(NULL, v3f(0,0,0));
-
-/*
- UnitSAO
- */
-
-UnitSAO::UnitSAO(ServerEnvironment *env, v3f pos):
- ServerActiveObject(env, pos)
-{
- // Initialize something to armor groups
- m_armor_groups["fleshy"] = 100;
-}
-
-ServerActiveObject *UnitSAO::getParent() const
-{
- if (!m_attachment_parent_id)
- return nullptr;
- // Check if the parent still exists
- ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id);
-
- return obj;
-}
-
-void UnitSAO::setArmorGroups(const ItemGroupList &armor_groups)
-{
- m_armor_groups = armor_groups;
- m_armor_groups_sent = false;
-}
-
-const ItemGroupList &UnitSAO::getArmorGroups() const
-{
- return m_armor_groups;
-}
-
-void UnitSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend, bool frame_loop)
-{
- // store these so they can be updated to clients
- m_animation_range = frame_range;
- m_animation_speed = frame_speed;
- m_animation_blend = frame_blend;
- m_animation_loop = frame_loop;
- m_animation_sent = false;
-}
-
-void UnitSAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, bool *frame_loop)
-{
- *frame_range = m_animation_range;
- *frame_speed = m_animation_speed;
- *frame_blend = m_animation_blend;
- *frame_loop = m_animation_loop;
-}
-
-void UnitSAO::setAnimationSpeed(float frame_speed)
-{
- m_animation_speed = frame_speed;
- m_animation_speed_sent = false;
-}
-
-void UnitSAO::setBonePosition(const std::string &bone, v3f position, v3f rotation)
-{
- // store these so they can be updated to clients
- m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
- m_bone_position_sent = false;
-}
-
-void UnitSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation)
-{
- *position = m_bone_position[bone].X;
- *rotation = m_bone_position[bone].Y;
-}
-
-void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation)
-{
- // Attachments need to be handled on both the server and client.
- // If we just attach on the server, we can only copy the position of the parent. Attachments
- // are still sent to clients at an interval so players might see them lagging, plus we can't
- // read and attach to skeletal bones.
- // If we just attach on the client, the server still sees the child at its original location.
- // This breaks some things so we also give the server the most accurate representation
- // even if players only see the client changes.
-
- int old_parent = m_attachment_parent_id;
- m_attachment_parent_id = parent_id;
- m_attachment_bone = bone;
- m_attachment_position = position;
- m_attachment_rotation = rotation;
- m_attachment_sent = false;
-
- if (parent_id != old_parent) {
- onDetach(old_parent);
- onAttach(parent_id);
- }
-}
-
-void UnitSAO::getAttachment(int *parent_id, std::string *bone, v3f *position,
- v3f *rotation) const
-{
- *parent_id = m_attachment_parent_id;
- *bone = m_attachment_bone;
- *position = m_attachment_position;
- *rotation = m_attachment_rotation;
-}
-
-void UnitSAO::clearChildAttachments()
-{
- for (int child_id : m_attachment_child_ids) {
- // Child can be NULL if it was deleted earlier
- if (ServerActiveObject *child = m_env->getActiveObject(child_id))
- child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
- }
- m_attachment_child_ids.clear();
-}
-
-void UnitSAO::clearParentAttachment()
-{
- ServerActiveObject *parent = nullptr;
- if (m_attachment_parent_id) {
- parent = m_env->getActiveObject(m_attachment_parent_id);
- setAttachment(0, "", m_attachment_position, m_attachment_rotation);
- } else {
- setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
- }
- // Do it
- if (parent)
- parent->removeAttachmentChild(m_id);
-}
-
-void UnitSAO::addAttachmentChild(int child_id)
-{
- m_attachment_child_ids.insert(child_id);
-}
-
-void UnitSAO::removeAttachmentChild(int child_id)
-{
- m_attachment_child_ids.erase(child_id);
-}
-
-const std::unordered_set<int> &UnitSAO::getAttachmentChildIds() const
-{
- return m_attachment_child_ids;
-}
-
-void UnitSAO::onAttach(int parent_id)
-{
- if (!parent_id)
- return;
-
- ServerActiveObject *parent = m_env->getActiveObject(parent_id);
-
- if (!parent || parent->isGone())
- return; // Do not try to notify soon gone parent
-
- if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) {
- // Call parent's on_attach field
- m_env->getScriptIface()->luaentity_on_attach_child(parent_id, this);
- }
-}
-
-void UnitSAO::onDetach(int parent_id)
-{
- if (!parent_id)
- return;
-
- ServerActiveObject *parent = m_env->getActiveObject(parent_id);
- if (getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
- m_env->getScriptIface()->luaentity_on_detach(m_id, parent);
-
- if (!parent || parent->isGone())
- return; // Do not try to notify soon gone parent
-
- if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
- m_env->getScriptIface()->luaentity_on_detach_child(parent_id, this);
-}
-
-ObjectProperties* UnitSAO::accessObjectProperties()
-{
- return &m_prop;
-}
-
-void UnitSAO::notifyObjectPropertiesModified()
-{
- m_properties_sent = false;
-}
-
-/*
- LuaEntitySAO
-*/
-
-// Prototype (registers item for deserialization)
-LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", "");
-
-LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos,
- const std::string &name, const std::string &state):
- UnitSAO(env, pos),
- m_init_name(name),
- m_init_state(state)
-{
- // Only register type if no environment supplied
- if(env == NULL){
- ServerActiveObject::registerType(getType(), create);
- return;
- }
-}
-
-LuaEntitySAO::~LuaEntitySAO()
-{
- if(m_registered){
- m_env->getScriptIface()->luaentity_Remove(m_id);
- }
-
- for (u32 attached_particle_spawner : m_attached_particle_spawners) {
- m_env->deleteParticleSpawner(attached_particle_spawner, false);
- }
-}
-
-void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
-{
- ServerActiveObject::addedToEnvironment(dtime_s);
-
- // Create entity from name
- m_registered = m_env->getScriptIface()->
- luaentity_Add(m_id, m_init_name.c_str());
-
- if(m_registered){
- // Get properties
- m_env->getScriptIface()->
- luaentity_GetProperties(m_id, this, &m_prop);
- // Initialize HP from properties
- m_hp = m_prop.hp_max;
- // Activate entity, supplying serialized state
- m_env->getScriptIface()->
- luaentity_Activate(m_id, m_init_state, dtime_s);
- } else {
- m_prop.infotext = m_init_name;
- }
-}
-
-ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos,
- const std::string &data)
-{
- std::string name;
- std::string state;
- u16 hp = 1;
- v3f velocity;
- v3f rotation;
-
- while (!data.empty()) { // breakable, run for one iteration
- std::istringstream is(data, std::ios::binary);
- // 'version' does not allow to incrementally extend the parameter list thus
- // we need another variable to build on top of 'version=1'. Ugly hack but works™
- u8 version2 = 0;
- u8 version = readU8(is);
-
- name = deSerializeString(is);
- state = deSerializeLongString(is);
-
- if (version < 1)
- break;
-
- hp = readU16(is);
- velocity = readV3F1000(is);
- // yaw must be yaw to be backwards-compatible
- rotation.Y = readF1000(is);
-
- if (is.good()) // EOF for old formats
- version2 = readU8(is);
-
- if (version2 < 1) // PROTOCOL_VERSION < 37
- break;
-
- // version2 >= 1
- rotation.X = readF1000(is);
- rotation.Z = readF1000(is);
-
- // if (version2 < 2)
- // break;
- // <read new values>
- break;
- }
- // create object
- infostream << "LuaEntitySAO::create(name=\"" << name << "\" state=\""
- << state << "\")" << std::endl;
- LuaEntitySAO *sao = new LuaEntitySAO(env, pos, name, state);
- sao->m_hp = hp;
- sao->m_velocity = velocity;
- sao->m_rotation = rotation;
- return sao;
-}
-
-void LuaEntitySAO::step(float dtime, bool send_recommended)
-{
- if(!m_properties_sent)
- {
- m_properties_sent = true;
- std::string str = getPropertyPacket();
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
-
- // If attached, check that our parent is still there. If it isn't, detach.
- if(m_attachment_parent_id && !isAttached())
- {
- m_attachment_parent_id = 0;
- m_attachment_bone = "";
- m_attachment_position = v3f(0,0,0);
- m_attachment_rotation = v3f(0,0,0);
- sendPosition(false, true);
- }
-
- m_last_sent_position_timer += dtime;
-
- // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
- // If the object gets detached this comes into effect automatically from the last known origin
- if(isAttached())
- {
- v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
- m_base_position = pos;
- m_velocity = v3f(0,0,0);
- m_acceleration = v3f(0,0,0);
- }
- else
- {
- if(m_prop.physical){
- aabb3f box = m_prop.collisionbox;
- box.MinEdge *= BS;
- box.MaxEdge *= BS;
- collisionMoveResult moveresult;
- f32 pos_max_d = BS*0.25; // Distance per iteration
- v3f p_pos = m_base_position;
- v3f p_velocity = m_velocity;
- v3f p_acceleration = m_acceleration;
- moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
- pos_max_d, box, m_prop.stepheight, dtime,
- &p_pos, &p_velocity, p_acceleration,
- this, m_prop.collideWithObjects);
-
- // Apply results
- m_base_position = p_pos;
- m_velocity = p_velocity;
- m_acceleration = p_acceleration;
- } else {
- m_base_position += dtime * m_velocity + 0.5 * dtime
- * dtime * m_acceleration;
- m_velocity += dtime * m_acceleration;
- }
-
- if (m_prop.automatic_face_movement_dir &&
- (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
- float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
- + m_prop.automatic_face_movement_dir_offset;
- float max_rotation_per_sec =
- m_prop.automatic_face_movement_max_rotation_per_sec;
-
- if (max_rotation_per_sec > 0) {
- m_rotation.Y = wrapDegrees_0_360(m_rotation.Y);
- wrappedApproachShortest(m_rotation.Y, target_yaw,
- dtime * max_rotation_per_sec, 360.f);
- } else {
- // Negative values of max_rotation_per_sec mean disabled.
- m_rotation.Y = target_yaw;
- }
- }
- }
-
- if(m_registered){
- m_env->getScriptIface()->luaentity_Step(m_id, dtime);
- }
-
- if (!send_recommended)
- return;
-
- if(!isAttached())
- {
- // TODO: force send when acceleration changes enough?
- float minchange = 0.2*BS;
- if(m_last_sent_position_timer > 1.0){
- minchange = 0.01*BS;
- } else if(m_last_sent_position_timer > 0.2){
- minchange = 0.05*BS;
- }
- float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
- move_d += m_last_sent_move_precision;
- float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
- if (move_d > minchange || vel_d > minchange ||
- std::fabs(m_rotation.X - m_last_sent_rotation.X) > 1.0f ||
- std::fabs(m_rotation.Y - m_last_sent_rotation.Y) > 1.0f ||
- std::fabs(m_rotation.Z - m_last_sent_rotation.Z) > 1.0f) {
-
- sendPosition(true, false);
- }
- }
-
- if (!m_armor_groups_sent) {
- m_armor_groups_sent = true;
- std::string str = gob_cmd_update_armor_groups(
- m_armor_groups);
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
-
- if (!m_animation_sent) {
- m_animation_sent = true;
- std::string str = gob_cmd_update_animation(
- m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
-
- if (!m_animation_speed_sent) {
- m_animation_speed_sent = true;
- std::string str = gob_cmd_update_animation_speed(m_animation_speed);
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
-
- if (!m_bone_position_sent) {
- m_bone_position_sent = true;
- for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
- ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){
- std::string str = gob_cmd_update_bone_position((*ii).first,
- (*ii).second.X, (*ii).second.Y);
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
- }
-
- if (!m_attachment_sent) {
- m_attachment_sent = true;
- std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation);
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
-}
-
-std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
-{
- std::ostringstream os(std::ios::binary);
-
- // PROTOCOL_VERSION >= 37
- writeU8(os, 1); // version
- os << serializeString(""); // name
- writeU8(os, 0); // is_player
- writeU16(os, getId()); //id
- writeV3F32(os, m_base_position);
- writeV3F32(os, m_rotation);
- writeU16(os, m_hp);
-
- std::ostringstream msg_os(std::ios::binary);
- msg_os << serializeLongString(getPropertyPacket()); // message 1
- msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
- msg_os << serializeLongString(gob_cmd_update_animation(
- m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
- for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
- ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
- msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
- (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
- }
- msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
- m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
- int message_count = 4 + m_bone_position.size();
- for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
- (ii != m_attachment_child_ids.end()); ++ii) {
- if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
- message_count++;
- // TODO after a protocol bump: only send the object initialization data
- // to older clients (superfluous since this message exists)
- msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
- obj->getClientInitializationData(protocol_version)));
- }
- }
-
- msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier));
- message_count++;
-
- writeU8(os, message_count);
- os.write(msg_os.str().c_str(), msg_os.str().size());
-
- // return result
- return os.str();
-}
-
-void LuaEntitySAO::getStaticData(std::string *result) const
-{
- verbosestream<<FUNCTION_NAME<<std::endl;
- std::ostringstream os(std::ios::binary);
- // version must be 1 to keep backwards-compatibility. See version2
- writeU8(os, 1);
- // name
- os<<serializeString(m_init_name);
- // state
- if(m_registered){
- std::string state = m_env->getScriptIface()->
- luaentity_GetStaticdata(m_id);
- os<<serializeLongString(state);
- } else {
- os<<serializeLongString(m_init_state);
- }
- writeU16(os, m_hp);
- writeV3F1000(os, m_velocity);
- // yaw
- writeF1000(os, m_rotation.Y);
-
- // version2. Increase this variable for new values
- writeU8(os, 1); // PROTOCOL_VERSION >= 37
-
- writeF1000(os, m_rotation.X);
- writeF1000(os, m_rotation.Z);
-
- // <write new values>
-
- *result = os.str();
-}
-
-u16 LuaEntitySAO::punch(v3f dir,
- const ToolCapabilities *toolcap,
- ServerActiveObject *puncher,
- float time_from_last_punch)
-{
- if (!m_registered) {
- // Delete unknown LuaEntities when punched
- m_pending_removal = true;
- return 0;
- }
-
- FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
-
- s32 old_hp = getHP();
- ItemStack selected_item, hand_item;
- ItemStack tool_item = puncher->getWieldedItem(&selected_item, &hand_item);
-
- PunchDamageResult result = getPunchDamage(
- m_armor_groups,
- toolcap,
- &tool_item,
- time_from_last_punch);
-
- bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
- time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
-
- if (!damage_handled) {
- if (result.did_punch) {
- setHP((s32)getHP() - result.damage,
- PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
-
- std::string str = gob_cmd_punched(getHP());
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
- }
-
- if (getHP() == 0 && !isGone()) {
- clearParentAttachment();
- clearChildAttachments();
- m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
- m_pending_removal = true;
- }
-
- actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
- ", hp=" << puncher->getHP() << ") punched " <<
- getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
- "), damage=" << (old_hp - (s32)getHP()) <<
- (damage_handled ? " (handled by Lua)" : "") << std::endl;
-
- // TODO: give Lua control over wear
- return result.wear;
-}
-
-void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
-{
- if (!m_registered)
- return;
-
- m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
-}
-
-void LuaEntitySAO::setPos(const v3f &pos)
-{
- if(isAttached())
- return;
- m_base_position = pos;
- sendPosition(false, true);
-}
-
-void LuaEntitySAO::moveTo(v3f pos, bool continuous)
-{
- if(isAttached())
- return;
- m_base_position = pos;
- if(!continuous)
- sendPosition(true, true);
-}
-
-float LuaEntitySAO::getMinimumSavedMovement()
-{
- return 0.1 * BS;
-}
-
-std::string LuaEntitySAO::getDescription()
-{
- std::ostringstream os(std::ios::binary);
- os<<"LuaEntitySAO at (";
- os<<(m_base_position.X/BS)<<",";
- os<<(m_base_position.Y/BS)<<",";
- os<<(m_base_position.Z/BS);
- os<<")";
- return os.str();
-}
-
-void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
-{
- m_hp = rangelim(hp, 0, U16_MAX);
-}
-
-u16 LuaEntitySAO::getHP() const
-{
- return m_hp;
-}
-
-void LuaEntitySAO::setVelocity(v3f velocity)
-{
- m_velocity = velocity;
-}
-
-v3f LuaEntitySAO::getVelocity()
-{
- return m_velocity;
-}
-
-void LuaEntitySAO::setAcceleration(v3f acceleration)
-{
- m_acceleration = acceleration;
-}
-
-v3f LuaEntitySAO::getAcceleration()
-{
- return m_acceleration;
-}
-
-void LuaEntitySAO::setTextureMod(const std::string &mod)
-{
- std::string str = gob_cmd_set_texture_mod(mod);
- m_current_texture_modifier = mod;
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
-}
-
-std::string LuaEntitySAO::getTextureMod() const
-{
- return m_current_texture_modifier;
-}
-
-void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
- bool select_horiz_by_yawpitch)
-{
- std::string str = gob_cmd_set_sprite(
- p,
- num_frames,
- framelength,
- select_horiz_by_yawpitch
- );
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
-}
-
-std::string LuaEntitySAO::getName()
-{
- return m_init_name;
-}
-
-std::string LuaEntitySAO::getPropertyPacket()
-{
- return gob_cmd_set_properties(m_prop);
-}
-
-void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
-{
- // If the object is attached client-side, don't waste bandwidth sending its position to clients
- if(isAttached())
- return;
-
- m_last_sent_move_precision = m_base_position.getDistanceFrom(
- m_last_sent_position);
- m_last_sent_position_timer = 0;
- m_last_sent_position = m_base_position;
- m_last_sent_velocity = m_velocity;
- //m_last_sent_acceleration = m_acceleration;
- m_last_sent_rotation = m_rotation;
-
- float update_interval = m_env->getSendRecommendedInterval();
-
- std::string str = gob_cmd_update_position(
- m_base_position,
- m_velocity,
- m_acceleration,
- m_rotation,
- do_interpolate,
- is_movement_end,
- update_interval
- );
- // create message and add to list
- ActiveObjectMessage aom(getId(), false, str);
- m_messages_out.push(aom);
-}
-
-bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
-{
- if (m_prop.physical)
- {
- //update collision box
- toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
- toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
-
- toset->MinEdge += m_base_position;
- toset->MaxEdge += m_base_position;
-
- return true;
- }
-
- return false;
-}
-
-bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
-{
- if (!m_prop.is_visible || !m_prop.pointable) {
- return false;
- }
-
- toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
- toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
-
- return true;
-}
-
-bool LuaEntitySAO::collideWithObjects() const
-{
- return m_prop.collideWithObjects;
-}
-
-/*
- PlayerSAO
-*/
-
-// No prototype, PlayerSAO does not need to be deserialized
-
-PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
- bool is_singleplayer):
- UnitSAO(env_, v3f(0,0,0)),
- m_player(player_),
- m_peer_id(peer_id_),
- m_is_singleplayer(is_singleplayer)
-{
- SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT);
-
- m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
- m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
- m_prop.physical = false;
- m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
- m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
- m_prop.pointable = true;
- // Start of default appearance, this should be overwritten by Lua
- m_prop.visual = "upright_sprite";
- m_prop.visual_size = v3f(1, 2, 1);
- m_prop.textures.clear();
- m_prop.textures.emplace_back("player.png");
- m_prop.textures.emplace_back("player_back.png");
- m_prop.colors.clear();
- m_prop.colors.emplace_back(255, 255, 255, 255);
- m_prop.spritediv = v2s16(1,1);
- m_prop.eye_height = 1.625f;
- // End of default appearance
- m_prop.is_visible = true;
- m_prop.backface_culling = false;
- m_prop.makes_footstep_sound = true;
- m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
- m_hp = m_prop.hp_max;
- m_breath = m_prop.breath_max;
- // Disable zoom in survival mode using a value of 0
- m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
-
- if (!g_settings->getBool("enable_damage"))
- m_armor_groups["immortal"] = 1;
-}
-
-void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
-{
- assert(player);
- m_player = player;
- m_privs = privs;
-}
-
-v3f PlayerSAO::getEyeOffset() const
-{
- return v3f(0, BS * m_prop.eye_height, 0);
-}
-
-std::string PlayerSAO::getDescription()
-{
- return std::string("player ") + m_player->getName();
-}
-
-// Called after id has been set and has been inserted in environment
-void PlayerSAO::addedToEnvironment(u32 dtime_s)
-{
- ServerActiveObject::addedToEnvironment(dtime_s);
- ServerActiveObject::setBasePosition(m_base_position);
- m_player->setPlayerSAO(this);
- m_player->setPeerId(m_peer_id);
- m_last_good_position = m_base_position;
-}
-
-// Called before removing from environment
-void PlayerSAO::removingFromEnvironment()
-{
- ServerActiveObject::removingFromEnvironment();
- if (m_player->getPlayerSAO() == this) {
- unlinkPlayerSessionAndSave();
- for (u32 attached_particle_spawner : m_attached_particle_spawners) {
- m_env->deleteParticleSpawner(attached_particle_spawner, false);
- }
- }
-}
-
-std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
-{
- std::ostringstream os(std::ios::binary);
-
- // Protocol >= 15
- writeU8(os, 1); // version
- os << serializeString(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());
-
- std::ostringstream msg_os(std::ios::binary);
- msg_os << serializeLongString(getPropertyPacket()); // message 1
- msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2
- msg_os << serializeLongString(gob_cmd_update_animation(
- m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3
- for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
- ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
- msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first,
- (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
- }
- msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id,
- m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
- msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
- m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
- m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5
- // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only.
- msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6
- int message_count = 6 + m_bone_position.size();
- for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin();
- ii != m_attachment_child_ids.end(); ++ii) {
- if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) {
- message_count++;
- msg_os << serializeLongString(gob_cmd_update_infant(*ii, obj->getSendType(),
- obj->getClientInitializationData(protocol_version)));
- }
- }
-
- writeU8(os, message_count);
- os.write(msg_os.str().c_str(), msg_os.str().size());
-
- // return result
- return os.str();
-}
-
-void PlayerSAO::getStaticData(std::string * result) const
-{
- FATAL_ERROR("Obsolete function");
-}
-
-void PlayerSAO::step(float dtime, bool send_recommended)
-{
- if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) {
- // Get nose/mouth position, approximate with eye position
- v3s16 p = floatToInt(getEyePosition(), BS);
- 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) {
- 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);
- m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
- }
- }
- }
-
- if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) {
- // Get nose/mouth position, approximate with eye position
- v3s16 p = floatToInt(getEyePosition(), BS);
- 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 &&
- n.getContent() != CONTENT_IGNORE && m_hp > 0)
- setBreath(m_breath + 1);
- }
-
- if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) {
- u32 damage_per_second = 0;
- std::string nodename;
- // Lowest and highest damage points are 0.1 within collisionbox
- float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
-
- // Sequence of damage points, starting 0.1 above feet and progressing
- // upwards in 1 node intervals, stopping below top damage point.
- for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
- v3s16 p = floatToInt(m_base_position +
- v3f(0.0f, dam_height * BS, 0.0f), BS);
- MapNode n = m_env->getMap().getNode(p);
- const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
- if (c.damage_per_second > damage_per_second) {
- damage_per_second = c.damage_per_second;
- nodename = c.name;
- }
- }
-
- // Top damage point
- v3s16 ptop = floatToInt(m_base_position +
- v3f(0.0f, dam_top * BS, 0.0f), BS);
- MapNode ntop = m_env->getMap().getNode(ptop);
- const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
- if (c.damage_per_second > damage_per_second) {
- damage_per_second = c.damage_per_second;
- nodename = c.name;
- }
-
- if (damage_per_second != 0 && m_hp > 0) {
- s32 newhp = (s32)m_hp - (s32)damage_per_second;
- PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
- setHP(newhp, reason);
- m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
- }
- }
-
- if (!m_properties_sent) {
- m_properties_sent = true;
- std::string str = getPropertyPacket();
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- m_env->getScriptIface()->player_event(this, "properties_changed");
- }
-
- // If attached, check that our parent is still there. If it isn't, detach.
- if (m_attachment_parent_id && !isAttached()) {
- m_attachment_parent_id = 0;
- m_attachment_bone = "";
- m_attachment_position = v3f(0.0f, 0.0f, 0.0f);
- m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f);
- setBasePosition(m_last_good_position);
- m_env->getGameDef()->SendMovePlayer(m_peer_id);
- }
-
- //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
-
- // Set lag pool maximums based on estimated lag
- const float LAG_POOL_MIN = 5.0f;
- float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
- if(lag_pool_max < LAG_POOL_MIN)
- lag_pool_max = LAG_POOL_MIN;
- m_dig_pool.setMax(lag_pool_max);
- m_move_pool.setMax(lag_pool_max);
-
- // Increment cheat prevention timers
- m_dig_pool.add(dtime);
- m_move_pool.add(dtime);
- m_time_from_last_teleport += dtime;
- m_time_from_last_punch += dtime;
- m_nocheat_dig_time += dtime;
- m_max_speed_override_time = MYMAX(m_max_speed_override_time - dtime, 0.0f);
-
- // Each frame, parent position is copied if the object is attached,
- // otherwise it's calculated normally.
- // If the object gets detached this comes into effect automatically from
- // the last known origin.
- if (isAttached()) {
- v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
- m_last_good_position = pos;
- setBasePosition(pos);
- }
-
- if (!send_recommended)
- return;
-
- if (m_position_not_sent) {
- m_position_not_sent = false;
- float update_interval = m_env->getSendRecommendedInterval();
- v3f pos;
- // When attached, the position is only sent to clients where the
- // parent isn't known
- if (isAttached())
- pos = m_last_good_position;
- else
- pos = m_base_position;
-
- std::string str = gob_cmd_update_position(
- pos,
- v3f(0.0f, 0.0f, 0.0f),
- v3f(0.0f, 0.0f, 0.0f),
- m_rotation,
- true,
- false,
- update_interval
- );
- // create message and add to list
- ActiveObjectMessage aom(getId(), false, str);
- m_messages_out.push(aom);
- }
-
- if (!m_armor_groups_sent) {
- m_armor_groups_sent = true;
- std::string str = gob_cmd_update_armor_groups(
- m_armor_groups);
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
-
- if (!m_physics_override_sent) {
- m_physics_override_sent = true;
- std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
- m_physics_override_jump, m_physics_override_gravity,
- m_physics_override_sneak, m_physics_override_sneak_glitch,
- m_physics_override_new_move);
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
-
- if (!m_animation_sent) {
- m_animation_sent = true;
- std::string str = gob_cmd_update_animation(
- m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop);
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
-
- if (!m_bone_position_sent) {
- m_bone_position_sent = true;
- for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator
- ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) {
- std::string str = gob_cmd_update_bone_position((*ii).first,
- (*ii).second.X, (*ii).second.Y);
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
- }
-
- if (!m_attachment_sent) {
- m_attachment_sent = true;
- std::string str = gob_cmd_update_attachment(m_attachment_parent_id,
- m_attachment_bone, m_attachment_position, m_attachment_rotation);
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
-}
-
-void PlayerSAO::setBasePosition(const v3f &position)
-{
- if (m_player && position != m_base_position)
- m_player->setDirty(true);
-
- // This needs to be ran for attachments too
- ServerActiveObject::setBasePosition(position);
-
- // Updating is not wanted/required for player migration
- if (m_env) {
- m_position_not_sent = true;
- }
-}
-
-void PlayerSAO::setPos(const v3f &pos)
-{
- if(isAttached())
- return;
-
- // Send mapblock of target location
- v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
- m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
-
- setBasePosition(pos);
- // Movement caused by this command is always valid
- m_last_good_position = pos;
- m_move_pool.empty();
- m_time_from_last_teleport = 0.0;
- m_env->getGameDef()->SendMovePlayer(m_peer_id);
-}
-
-void PlayerSAO::moveTo(v3f pos, bool continuous)
-{
- if(isAttached())
- return;
-
- setBasePosition(pos);
- // Movement caused by this command is always valid
- m_last_good_position = pos;
- m_move_pool.empty();
- m_time_from_last_teleport = 0.0;
- m_env->getGameDef()->SendMovePlayer(m_peer_id);
-}
-
-void PlayerSAO::setPlayerYaw(const float yaw)
-{
- v3f rotation(0, yaw, 0);
- if (m_player && yaw != m_rotation.Y)
- m_player->setDirty(true);
-
- // Set player model yaw, not look view
- UnitSAO::setRotation(rotation);
-}
-
-void PlayerSAO::setFov(const float fov)
-{
- if (m_player && fov != m_fov)
- m_player->setDirty(true);
-
- m_fov = fov;
-}
-
-void PlayerSAO::setWantedRange(const s16 range)
-{
- if (m_player && range != m_wanted_range)
- m_player->setDirty(true);
-
- m_wanted_range = range;
-}
-
-void PlayerSAO::setPlayerYawAndSend(const float yaw)
-{
- setPlayerYaw(yaw);
- m_env->getGameDef()->SendMovePlayer(m_peer_id);
-}
-
-void PlayerSAO::setLookPitch(const float pitch)
-{
- if (m_player && pitch != m_pitch)
- m_player->setDirty(true);
-
- m_pitch = pitch;
-}
-
-void PlayerSAO::setLookPitchAndSend(const float pitch)
-{
- setLookPitch(pitch);
- m_env->getGameDef()->SendMovePlayer(m_peer_id);
-}
-
-u16 PlayerSAO::punch(v3f dir,
- const ToolCapabilities *toolcap,
- ServerActiveObject *puncher,
- float time_from_last_punch)
-{
- if (!toolcap)
- return 0;
-
- FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
-
- // No effect if PvP disabled or if immortal
- if (isImmortal() || !g_settings->getBool("enable_pvp")) {
- if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
- std::string str = gob_cmd_punched(getHP());
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- return 0;
- }
- }
-
- s32 old_hp = getHP();
- HitParams hitparams = getHitParams(m_armor_groups, toolcap,
- time_from_last_punch);
-
- PlayerSAO *playersao = m_player->getPlayerSAO();
-
- bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
- puncher, time_from_last_punch, toolcap, dir,
- hitparams.hp);
-
- if (!damage_handled) {
- setHP((s32)getHP() - (s32)hitparams.hp,
- PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
- } else { // override client prediction
- if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
- std::string str = gob_cmd_punched(getHP());
- // create message and add to list
- ActiveObjectMessage aom(getId(), true, str);
- m_messages_out.push(aom);
- }
- }
-
- actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
- ", hp=" << puncher->getHP() << ") punched " <<
- getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
- "), damage=" << (old_hp - (s32)getHP()) <<
- (damage_handled ? " (handled by Lua)" : "") << std::endl;
-
- return hitparams.wear;
-}
-
-void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
-{
- s32 oldhp = m_hp;
-
- hp = rangelim(hp, 0, m_prop.hp_max);
-
- if (oldhp != hp) {
- s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
- if (hp_change == 0)
- return;
-
- hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
- }
-
- if (hp < oldhp && isImmortal())
- return;
-
- m_hp = hp;
-
- // Update properties on death
- if ((hp == 0) != (oldhp == 0))
- m_properties_sent = false;
-}
-
-void PlayerSAO::setBreath(const u16 breath, bool send)
-{
- if (m_player && breath != m_breath)
- m_player->setDirty(true);
-
- m_breath = rangelim(breath, 0, m_prop.breath_max);
-
- if (send)
- m_env->getGameDef()->SendPlayerBreath(this);
-}
-
-Inventory *PlayerSAO::getInventory() const
-{
- return m_player ? &m_player->inventory : nullptr;
-}
-
-InventoryLocation PlayerSAO::getInventoryLocation() const
-{
- InventoryLocation loc;
- loc.setPlayer(m_player->getName());
- return loc;
-}
-
-u16 PlayerSAO::getWieldIndex() const
-{
- return m_player->getWieldIndex();
-}
-
-ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
-{
- return m_player->getWieldedItem(selected, hand);
-}
-
-bool PlayerSAO::setWieldedItem(const ItemStack &item)
-{
- InventoryList *mlist = m_player->inventory.getList(getWieldList());
- if (mlist) {
- mlist->changeItem(m_player->getWieldIndex(), item);
- return true;
- }
- return false;
-}
-
-void PlayerSAO::disconnected()
-{
- m_peer_id = PEER_ID_INEXISTENT;
- m_pending_removal = true;
-}
-
-void PlayerSAO::unlinkPlayerSessionAndSave()
-{
- assert(m_player->getPlayerSAO() == this);
- m_player->setPeerId(PEER_ID_INEXISTENT);
- m_env->savePlayer(m_player);
- m_player->setPlayerSAO(NULL);
- m_env->removePlayer(m_player);
-}
-
-std::string PlayerSAO::getPropertyPacket()
-{
- m_prop.is_visible = (true);
- return gob_cmd_set_properties(m_prop);
-}
-
-void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
-{
- if (m_max_speed_override_time == 0.0f)
- m_max_speed_override = vel;
- else
- m_max_speed_override += vel;
- if (m_player) {
- float accel = MYMIN(m_player->movement_acceleration_default,
- m_player->movement_acceleration_air);
- m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
- }
-}
-
-bool PlayerSAO::checkMovementCheat()
-{
- if (isAttached() || m_is_singleplayer ||
- g_settings->getBool("disable_anticheat")) {
- m_last_good_position = m_base_position;
- return false;
- }
-
- bool cheated = false;
- /*
- Check player movements
-
- NOTE: Actually the server should handle player physics like the
- client does and compare player's position to what is calculated
- on our side. This is required when eg. players fly due to an
- explosion. Altough a node-based alternative might be possible
- too, and much more lightweight.
- */
-
- float override_max_H, override_max_V;
- if (m_max_speed_override_time > 0.0f) {
- override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
- override_max_V = fabs(m_max_speed_override.Y);
- } else {
- override_max_H = override_max_V = 0.0f;
- }
-
- float player_max_walk = 0; // horizontal movement
- float player_max_jump = 0; // vertical upwards movement
-
- if (m_privs.count("fast") != 0)
- player_max_walk = m_player->movement_speed_fast; // Fast speed
- else
- player_max_walk = m_player->movement_speed_walk; // Normal speed
- player_max_walk *= m_physics_override_speed;
- player_max_walk = MYMAX(player_max_walk, override_max_H);
-
- player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
- // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
- // until this can be verified correctly, tolerate higher jumping speeds
- player_max_jump *= 2.0;
- player_max_jump = MYMAX(player_max_jump, override_max_V);
-
- // Don't divide by zero!
- if (player_max_walk < 0.0001f)
- player_max_walk = 0.0001f;
- if (player_max_jump < 0.0001f)
- player_max_jump = 0.0001f;
-
- v3f diff = (m_base_position - m_last_good_position);
- float d_vert = diff.Y;
- diff.Y = 0;
- float d_horiz = diff.getLength();
- float required_time = d_horiz / player_max_walk;
-
- // FIXME: Checking downwards movement is not easily possible currently,
- // the server could calculate speed differences to examine the gravity
- if (d_vert > 0) {
- // In certain cases (water, ladders) walking speed is applied vertically
- float s = MYMAX(player_max_jump, player_max_walk);
- required_time = MYMAX(required_time, d_vert / s);
- }
-
- if (m_move_pool.grab(required_time)) {
- m_last_good_position = m_base_position;
- } else {
- const float LAG_POOL_MIN = 5.0;
- float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
- lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
- if (m_time_from_last_teleport > lag_pool_max) {
- actionstream << "Player " << m_player->getName()
- << " moved too fast; resetting position"
- << std::endl;
- cheated = true;
- }
- setBasePosition(m_last_good_position);
- }
- return cheated;
-}
-
-bool PlayerSAO::getCollisionBox(aabb3f *toset) const
-{
- //update collision box
- toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
- toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
-
- toset->MinEdge += m_base_position;
- toset->MaxEdge += m_base_position;
- return true;
-}
-
-bool PlayerSAO::getSelectionBox(aabb3f *toset) const
-{
- if (!m_prop.is_visible || !m_prop.pointable) {
- return false;
- }
-
- toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
- toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
-
- return true;
-}
-
-float PlayerSAO::getZoomFOV() const
-{
- return m_prop.zoom_fov;
-}
diff --git a/src/craftdef.cpp b/src/craftdef.cpp
index 0181ceb60..210605198 100644
--- a/src/craftdef.cpp
+++ b/src/craftdef.cpp
@@ -1066,8 +1066,8 @@ public:
}
virtual void registerCraft(CraftDefinition *def, IGameDef *gamedef)
{
- verbosestream << "registerCraft: registering craft definition: "
- << def->dump() << std::endl;
+ TRACESTREAM(<< "registerCraft: registering craft definition: "
+ << def->dump() << std::endl);
m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].push_back(def);
CraftInput input;
diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp
index d09f1c074..d2b0b1543 100644
--- a/src/database/database-files.cpp
+++ b/src/database/database-files.cpp
@@ -20,11 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cassert>
#include <json/json.h>
#include "database-files.h"
-#include "content_sao.h"
#include "remoteplayer.h"
#include "settings.h"
#include "porting.h"
#include "filesys.h"
+#include "server/player_sao.h"
#include "util/string.h"
// !!! WARNING !!!
diff --git a/src/database/database-leveldb.cpp b/src/database/database-leveldb.cpp
index 4a4904c6a..1976ae13d 100644
--- a/src/database/database-leveldb.cpp
+++ b/src/database/database-leveldb.cpp
@@ -26,6 +26,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "log.h"
#include "filesys.h"
#include "exceptions.h"
+#include "remoteplayer.h"
+#include "server/player_sao.h"
+#include "util/serialize.h"
#include "util/string.h"
#include "leveldb/db.h"
@@ -97,5 +100,210 @@ void Database_LevelDB::listAllLoadableBlocks(std::vector<v3s16> &dst)
delete it;
}
-#endif // USE_LEVELDB
+PlayerDatabaseLevelDB::PlayerDatabaseLevelDB(const std::string &savedir)
+{
+ leveldb::Options options;
+ options.create_if_missing = true;
+ leveldb::Status status = leveldb::DB::Open(options,
+ savedir + DIR_DELIM + "players.db", &m_database);
+ ENSURE_STATUS_OK(status);
+}
+
+PlayerDatabaseLevelDB::~PlayerDatabaseLevelDB()
+{
+ delete m_database;
+}
+
+void PlayerDatabaseLevelDB::savePlayer(RemotePlayer *player)
+{
+ /*
+ u8 version = 1
+ u16 hp
+ v3f position
+ f32 pitch
+ f32 yaw
+ u16 breath
+ u32 attribute_count
+ for each attribute {
+ std::string name
+ std::string (long) value
+ }
+ std::string (long) serialized_inventory
+ */
+
+ std::ostringstream os;
+ writeU8(os, 1);
+
+ PlayerSAO *sao = player->getPlayerSAO();
+ sanity_check(sao);
+ writeU16(os, sao->getHP());
+ writeV3F32(os, sao->getBasePosition());
+ writeF32(os, sao->getLookPitch());
+ writeF32(os, sao->getRotation().Y);
+ writeU16(os, sao->getBreath());
+
+ StringMap stringvars = sao->getMeta().getStrings();
+ writeU32(os, stringvars.size());
+ for (const auto &it : stringvars) {
+ os << serializeString(it.first);
+ os << serializeLongString(it.second);
+ }
+ player->inventory.serialize(os);
+
+ leveldb::Status status = m_database->Put(leveldb::WriteOptions(),
+ player->getName(), os.str());
+ ENSURE_STATUS_OK(status);
+ player->onSuccessfulSave();
+}
+
+bool PlayerDatabaseLevelDB::removePlayer(const std::string &name)
+{
+ leveldb::Status s = m_database->Delete(leveldb::WriteOptions(), name);
+ return s.ok();
+}
+
+bool PlayerDatabaseLevelDB::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
+{
+ std::string raw;
+ leveldb::Status s = m_database->Get(leveldb::ReadOptions(),
+ player->getName(), &raw);
+ if (!s.ok())
+ return false;
+ std::istringstream is(raw);
+
+ if (readU8(is) > 1)
+ return false;
+
+ sao->setHPRaw(readU16(is));
+ sao->setBasePosition(readV3F32(is));
+ sao->setLookPitch(readF32(is));
+ sao->setPlayerYaw(readF32(is));
+ sao->setBreath(readU16(is), false);
+
+ u32 attribute_count = readU32(is);
+ for (u32 i = 0; i < attribute_count; i++) {
+ std::string name = deSerializeString(is);
+ std::string value = deSerializeLongString(is);
+ sao->getMeta().setString(name, value);
+ }
+ sao->getMeta().setModified(false);
+
+ // This should always be last.
+ try {
+ player->inventory.deSerialize(is);
+ } catch (SerializationError &e) {
+ errorstream << "Failed to deserialize player inventory. player_name="
+ << player->getName() << " " << e.what() << std::endl;
+ }
+
+ return true;
+}
+
+void PlayerDatabaseLevelDB::listPlayers(std::vector<std::string> &res)
+{
+ leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions());
+ res.clear();
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ res.push_back(it->key().ToString());
+ }
+ delete it;
+}
+
+AuthDatabaseLevelDB::AuthDatabaseLevelDB(const std::string &savedir)
+{
+ leveldb::Options options;
+ options.create_if_missing = true;
+ leveldb::Status status = leveldb::DB::Open(options,
+ savedir + DIR_DELIM + "auth.db", &m_database);
+ ENSURE_STATUS_OK(status);
+}
+
+AuthDatabaseLevelDB::~AuthDatabaseLevelDB()
+{
+ delete m_database;
+}
+
+bool AuthDatabaseLevelDB::getAuth(const std::string &name, AuthEntry &res)
+{
+ std::string raw;
+ leveldb::Status s = m_database->Get(leveldb::ReadOptions(), name, &raw);
+ if (!s.ok())
+ return false;
+ std::istringstream is(raw);
+
+ /*
+ u8 version = 1
+ std::string password
+ u16 number of privileges
+ for each privilege {
+ std::string privilege
+ }
+ s64 last_login
+ */
+
+ if (readU8(is) > 1)
+ return false;
+
+ res.id = 1;
+ res.name = name;
+ res.password = deSerializeString(is);
+
+ u16 privilege_count = readU16(is);
+ res.privileges.clear();
+ res.privileges.reserve(privilege_count);
+ for (u16 i = 0; i < privilege_count; i++) {
+ res.privileges.push_back(deSerializeString(is));
+ }
+
+ res.last_login = readS64(is);
+ return true;
+}
+
+bool AuthDatabaseLevelDB::saveAuth(const AuthEntry &authEntry)
+{
+ std::ostringstream os;
+ writeU8(os, 1);
+ os << serializeString(authEntry.password);
+
+ size_t privilege_count = authEntry.privileges.size();
+ FATAL_ERROR_IF(privilege_count > U16_MAX,
+ "Unsupported number of privileges");
+ writeU16(os, privilege_count);
+ for (const std::string &privilege : authEntry.privileges) {
+ os << serializeString(privilege);
+ }
+
+ writeS64(os, authEntry.last_login);
+ leveldb::Status s = m_database->Put(leveldb::WriteOptions(),
+ authEntry.name, os.str());
+ return s.ok();
+}
+
+bool AuthDatabaseLevelDB::createAuth(AuthEntry &authEntry)
+{
+ return saveAuth(authEntry);
+}
+
+bool AuthDatabaseLevelDB::deleteAuth(const std::string &name)
+{
+ leveldb::Status s = m_database->Delete(leveldb::WriteOptions(), name);
+ return s.ok();
+}
+
+void AuthDatabaseLevelDB::listNames(std::vector<std::string> &res)
+{
+ leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions());
+ res.clear();
+ for (it->SeekToFirst(); it->Valid(); it->Next()) {
+ res.emplace_back(it->key().ToString());
+ }
+ delete it;
+}
+
+void AuthDatabaseLevelDB::reload()
+{
+ // No-op for LevelDB.
+}
+
+#endif // USE_LEVELDB
diff --git a/src/database/database-leveldb.h b/src/database/database-leveldb.h
index d30f9f8f5..61def1256 100644
--- a/src/database/database-leveldb.h
+++ b/src/database/database-leveldb.h
@@ -45,4 +45,36 @@ private:
leveldb::DB *m_database;
};
+class PlayerDatabaseLevelDB : public PlayerDatabase
+{
+public:
+ PlayerDatabaseLevelDB(const std::string &savedir);
+ ~PlayerDatabaseLevelDB();
+
+ void savePlayer(RemotePlayer *player);
+ bool loadPlayer(RemotePlayer *player, PlayerSAO *sao);
+ bool removePlayer(const std::string &name);
+ void listPlayers(std::vector<std::string> &res);
+
+private:
+ leveldb::DB *m_database;
+};
+
+class AuthDatabaseLevelDB : public AuthDatabase
+{
+public:
+ AuthDatabaseLevelDB(const std::string &savedir);
+ virtual ~AuthDatabaseLevelDB();
+
+ virtual bool getAuth(const std::string &name, AuthEntry &res);
+ virtual bool saveAuth(const AuthEntry &authEntry);
+ virtual bool createAuth(AuthEntry &authEntry);
+ virtual bool deleteAuth(const std::string &name);
+ virtual void listNames(std::vector<std::string> &res);
+ virtual void reload();
+
+private:
+ leveldb::DB *m_database;
+};
+
#endif // USE_LEVELDB
diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp
index d7c94ff15..e1bb39928 100644
--- a/src/database/database-postgresql.cpp
+++ b/src/database/database-postgresql.cpp
@@ -36,8 +36,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "debug.h"
#include "exceptions.h"
#include "settings.h"
-#include "content_sao.h"
#include "remoteplayer.h"
+#include "server/player_sao.h"
Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) :
m_connect_string(connect_string)
@@ -160,6 +160,11 @@ void Database_PostgreSQL::endSave()
checkResults(PQexec(m_conn, "COMMIT;"));
}
+void Database_PostgreSQL::rollback()
+{
+ checkResults(PQexec(m_conn, "ROLLBACK;"));
+}
+
MapDatabasePostgreSQL::MapDatabasePostgreSQL(const std::string &connect_string):
Database_PostgreSQL(connect_string),
MapDatabase()
@@ -301,7 +306,7 @@ void MapDatabasePostgreSQL::listAllLoadableBlocks(std::vector<v3s16> &dst)
int numrows = PQntuples(results);
for (int row = 0; row < numrows; ++row)
- dst.push_back(pg_to_v3s16(results, 0, 0));
+ dst.push_back(pg_to_v3s16(results, row, 0));
PQclear(results);
}
@@ -631,4 +636,174 @@ void PlayerDatabasePostgreSQL::listPlayers(std::vector<std::string> &res)
PQclear(results);
}
+AuthDatabasePostgreSQL::AuthDatabasePostgreSQL(const std::string &connect_string) :
+ Database_PostgreSQL(connect_string), AuthDatabase()
+{
+ connectToDatabase();
+}
+
+void AuthDatabasePostgreSQL::createDatabase()
+{
+ createTableIfNotExists("auth",
+ "CREATE TABLE auth ("
+ "id SERIAL,"
+ "name TEXT UNIQUE,"
+ "password TEXT,"
+ "last_login INT NOT NULL DEFAULT 0,"
+ "PRIMARY KEY (id)"
+ ");");
+
+ createTableIfNotExists("user_privileges",
+ "CREATE TABLE user_privileges ("
+ "id INT,"
+ "privilege TEXT,"
+ "PRIMARY KEY (id, privilege),"
+ "CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES auth (id) ON DELETE CASCADE"
+ ");");
+}
+
+void AuthDatabasePostgreSQL::initStatements()
+{
+ prepareStatement("auth_read", "SELECT id, name, password, last_login FROM auth WHERE name = $1");
+ prepareStatement("auth_write", "UPDATE auth SET name = $1, password = $2, last_login = $3 WHERE id = $4");
+ prepareStatement("auth_create", "INSERT INTO auth (name, password, last_login) VALUES ($1, $2, $3) RETURNING id");
+ prepareStatement("auth_delete", "DELETE FROM auth WHERE name = $1");
+
+ prepareStatement("auth_list_names", "SELECT name FROM auth ORDER BY name DESC");
+
+ prepareStatement("auth_read_privs", "SELECT privilege FROM user_privileges WHERE id = $1");
+ prepareStatement("auth_write_privs", "INSERT INTO user_privileges (id, privilege) VALUES ($1, $2)");
+ prepareStatement("auth_delete_privs", "DELETE FROM user_privileges WHERE id = $1");
+}
+
+bool AuthDatabasePostgreSQL::getAuth(const std::string &name, AuthEntry &res)
+{
+ verifyDatabase();
+
+ const char *values[] = { name.c_str() };
+ PGresult *result = execPrepared("auth_read", 1, values, false, false);
+ int numrows = PQntuples(result);
+ if (numrows == 0) {
+ PQclear(result);
+ return false;
+ }
+
+ res.id = pg_to_uint(result, 0, 0);
+ res.name = std::string(PQgetvalue(result, 0, 1), PQgetlength(result, 0, 1));
+ res.password = std::string(PQgetvalue(result, 0, 2), PQgetlength(result, 0, 2));
+ res.last_login = pg_to_int(result, 0, 3);
+
+ PQclear(result);
+
+ std::string playerIdStr = itos(res.id);
+ const char *privsValues[] = { playerIdStr.c_str() };
+ PGresult *results = execPrepared("auth_read_privs", 1, privsValues, false);
+
+ numrows = PQntuples(results);
+ for (int row = 0; row < numrows; row++)
+ res.privileges.emplace_back(PQgetvalue(results, row, 0));
+
+ PQclear(results);
+
+ return true;
+}
+
+bool AuthDatabasePostgreSQL::saveAuth(const AuthEntry &authEntry)
+{
+ verifyDatabase();
+
+ beginSave();
+
+ std::string lastLoginStr = itos(authEntry.last_login);
+ std::string idStr = itos(authEntry.id);
+ const char *values[] = {
+ authEntry.name.c_str() ,
+ authEntry.password.c_str(),
+ lastLoginStr.c_str(),
+ idStr.c_str(),
+ };
+ execPrepared("auth_write", 4, values);
+
+ writePrivileges(authEntry);
+
+ endSave();
+ return true;
+}
+
+bool AuthDatabasePostgreSQL::createAuth(AuthEntry &authEntry)
+{
+ verifyDatabase();
+
+ std::string lastLoginStr = itos(authEntry.last_login);
+ const char *values[] = {
+ authEntry.name.c_str() ,
+ authEntry.password.c_str(),
+ lastLoginStr.c_str()
+ };
+
+ beginSave();
+
+ PGresult *result = execPrepared("auth_create", 3, values, false, false);
+
+ int numrows = PQntuples(result);
+ if (numrows == 0) {
+ errorstream << "Strange behaviour on auth creation, no ID returned." << std::endl;
+ PQclear(result);
+ rollback();
+ return false;
+ }
+
+ authEntry.id = pg_to_uint(result, 0, 0);
+ PQclear(result);
+
+ writePrivileges(authEntry);
+
+ endSave();
+ return true;
+}
+
+bool AuthDatabasePostgreSQL::deleteAuth(const std::string &name)
+{
+ verifyDatabase();
+
+ const char *values[] = { name.c_str() };
+ execPrepared("auth_delete", 1, values);
+
+ // privileges deleted by foreign key on delete cascade
+ return true;
+}
+
+void AuthDatabasePostgreSQL::listNames(std::vector<std::string> &res)
+{
+ verifyDatabase();
+
+ PGresult *results = execPrepared("auth_list_names", 0,
+ NULL, NULL, NULL, false, false);
+
+ int numrows = PQntuples(results);
+
+ for (int row = 0; row < numrows; ++row)
+ res.emplace_back(PQgetvalue(results, row, 0));
+
+ PQclear(results);
+}
+
+void AuthDatabasePostgreSQL::reload()
+{
+ // noop for PgSQL
+}
+
+void AuthDatabasePostgreSQL::writePrivileges(const AuthEntry &authEntry)
+{
+ std::string authIdStr = itos(authEntry.id);
+ const char *values[] = { authIdStr.c_str() };
+ execPrepared("auth_delete_privs", 1, values);
+
+ for (const std::string &privilege : authEntry.privileges) {
+ const char *values[] = { authIdStr.c_str(), privilege.c_str() };
+ execPrepared("auth_write_privs", 2, values);
+ }
+}
+
+
#endif // USE_POSTGRESQL
diff --git a/src/database/database-postgresql.h b/src/database/database-postgresql.h
index db0b505c9..f47deda33 100644
--- a/src/database/database-postgresql.h
+++ b/src/database/database-postgresql.h
@@ -34,6 +34,7 @@ public:
void beginSave();
void endSave();
+ void rollback();
bool initialized() const;
@@ -144,3 +145,26 @@ protected:
private:
bool playerDataExists(const std::string &playername);
};
+
+class AuthDatabasePostgreSQL : private Database_PostgreSQL, public AuthDatabase
+{
+public:
+ AuthDatabasePostgreSQL(const std::string &connect_string);
+ virtual ~AuthDatabasePostgreSQL() = default;
+
+ virtual void verifyDatabase() { Database_PostgreSQL::verifyDatabase(); }
+
+ virtual bool getAuth(const std::string &name, AuthEntry &res);
+ virtual bool saveAuth(const AuthEntry &authEntry);
+ virtual bool createAuth(AuthEntry &authEntry);
+ virtual bool deleteAuth(const std::string &name);
+ virtual void listNames(std::vector<std::string> &res);
+ virtual void reload();
+
+protected:
+ virtual void createDatabase();
+ virtual void initStatements();
+
+private:
+ virtual void writePrivileges(const AuthEntry &authEntry);
+};
diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp
index 1bacdfe6c..4560743b9 100644
--- a/src/database/database-sqlite3.cpp
+++ b/src/database/database-sqlite3.cpp
@@ -33,8 +33,8 @@ SQLite format specification:
#include "settings.h"
#include "porting.h"
#include "util/string.h"
-#include "content_sao.h"
#include "remoteplayer.h"
+#include "server/player_sao.h"
#include <cassert>
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index 472522bf4..abb6593b7 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.cpp
@@ -48,7 +48,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("pitch_move", "false");
settings->setDefault("fast_move", "false");
settings->setDefault("noclip", "false");
- settings->setDefault("screenshot_path", ".");
+ settings->setDefault("screenshot_path", "screenshots");
settings->setDefault("screenshot_format", "png");
settings->setDefault("screenshot_quality", "0");
settings->setDefault("client_unload_unused_data_timeout", "600");
@@ -80,7 +80,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("keymap_chat", "KEY_KEY_T");
settings->setDefault("keymap_cmd", "/");
settings->setDefault("keymap_cmd_local", ".");
- settings->setDefault("keymap_minimap", "KEY_F9");
+ settings->setDefault("keymap_minimap", "KEY_KEY_V");
settings->setDefault("keymap_console", "KEY_F10");
settings->setDefault("keymap_rangeselect", "KEY_KEY_R");
settings->setDefault("keymap_freemove", "KEY_KEY_K");
@@ -103,7 +103,7 @@ void set_default_settings(Settings *settings)
#endif
settings->setDefault("keymap_toggle_debug", "KEY_F5");
settings->setDefault("keymap_toggle_profiler", "KEY_F6");
- settings->setDefault("keymap_camera_mode", "KEY_F7");
+ settings->setDefault("keymap_camera_mode", "KEY_KEY_C");
settings->setDefault("keymap_screenshot", "KEY_F12");
settings->setDefault("keymap_increase_viewing_range_min", "+");
settings->setDefault("keymap_decrease_viewing_range_min", "-");
@@ -321,8 +321,12 @@ void set_default_settings(Settings *settings)
std::string font_size_str = std::to_string(DEFAULT_FONT_SIZE);
#endif
+ // General font settings
settings->setDefault("font_size", font_size_str);
settings->setDefault("mono_font_size", font_size_str);
+ settings->setDefault("chat_font_size", "0"); // Default "font_size"
+
+ // ContentDB
settings->setDefault("contentdb_url", "https://content.minetest.net");
#ifdef __ANDROID__
settings->setDefault("contentdb_flag_blacklist", "nonfree, android_default");
@@ -334,6 +338,9 @@ void set_default_settings(Settings *settings)
// Server
settings->setDefault("disable_escape_sequences", "false");
settings->setDefault("strip_color_codes", "false");
+#if USE_PROMETHEUS
+ settings->setDefault("prometheus_listener_address", "127.0.0.1:30000");
+#endif
// Network
settings->setDefault("enable_ipv6", "true");
@@ -397,6 +404,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("remote_media", "");
settings->setDefault("debug_log_level", "action");
settings->setDefault("debug_log_size_max", "50");
+ settings->setDefault("chat_log_level", "error");
settings->setDefault("emergequeue_limit_total", "512");
settings->setDefault("emergequeue_limit_diskonly", "64");
settings->setDefault("emergequeue_limit_generate", "64");
@@ -443,6 +451,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("high_precision_fpu", "true");
settings->setDefault("enable_console", "false");
+ settings->setDefault("screen_dpi", "72");
// Altered settings for macOS
#if defined(__MACH__) && defined(__APPLE__)
@@ -456,7 +465,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("screen_h", "0");
settings->setDefault("fullscreen", "true");
settings->setDefault("touchtarget", "true");
- settings->setDefault("TMPFolder", porting::getDataPath("tmp" DIR_DELIM));
+ settings->setDefault("TMPFolder", porting::path_cache);
settings->setDefault("touchscreen_threshold","20");
settings->setDefault("fixed_virtual_joystick", "false");
settings->setDefault("virtual_joystick_triggers_aux", "false");
@@ -478,8 +487,8 @@ void set_default_settings(Settings *settings)
settings->setDefault("curl_verify_cert","false");
// Apply settings according to screen size
- float x_inches = ((double) porting::getDisplaySize().X /
- (160 * porting::getDisplayDensity()));
+ float x_inches = (float) porting::getDisplaySize().X /
+ (160.f * porting::getDisplayDensity());
if (x_inches < 3.7f) {
settings->setDefault("hud_scaling", "0.6");
@@ -495,8 +504,5 @@ void set_default_settings(Settings *settings)
settings->setDefault("mono_font_size", "14");
}
// Tablets >= 6.0 use non-Android defaults for these settings
-#else
- settings->setDefault("screen_dpi", "72");
#endif
}
-
diff --git a/src/emerge.cpp b/src/emerge.cpp
index fc1da4ee7..0ac26a682 100644
--- a/src/emerge.cpp
+++ b/src/emerge.cpp
@@ -42,7 +42,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "profiler.h"
#include "scripting_server.h"
#include "server.h"
-#include "serverobject.h"
#include "settings.h"
#include "voxel.h"
@@ -110,6 +109,28 @@ private:
VoxelArea *m_ignorevariable;
};
+EmergeParams::~EmergeParams()
+{
+ infostream << "EmergeParams: destroying " << this << std::endl;
+ // Delete everything that was cloned on creation of EmergeParams
+ delete biomemgr;
+ delete oremgr;
+ delete decomgr;
+ delete schemmgr;
+}
+
+EmergeParams::EmergeParams(EmergeManager *parent, const BiomeManager *biomemgr,
+ const OreManager *oremgr, const DecorationManager *decomgr,
+ const SchematicManager *schemmgr) :
+ ndef(parent->ndef),
+ enable_mapgen_debug_info(parent->enable_mapgen_debug_info),
+ gen_notify_on(parent->gen_notify_on),
+ gen_notify_on_deco_ids(&parent->gen_notify_on_deco_ids),
+ biomemgr(biomemgr->clone()), oremgr(oremgr->clone()),
+ decomgr(decomgr->clone()), schemmgr(schemmgr->clone())
+{
+}
+
////
//// EmergeManager
////
@@ -136,9 +157,9 @@ EmergeManager::EmergeManager(Server *server)
nthreads = Thread::getNumberOfProcessors() - 2;
if (nthreads < 1)
nthreads = 1;
- verbosestream << "Using " << nthreads << " emerge threads." << std::endl;
m_qlimit_total = g_settings->getU16("emergequeue_limit_total");
+ // FIXME: these fallback values are probably not good
if (!g_settings->getU16NoEx("emergequeue_limit_diskonly", m_qlimit_diskonly))
m_qlimit_diskonly = nthreads * 5 + 1;
if (!g_settings->getU16NoEx("emergequeue_limit_generate", m_qlimit_generate))
@@ -184,14 +205,48 @@ EmergeManager::~EmergeManager()
}
+BiomeManager *EmergeManager::getWritableBiomeManager()
+{
+ FATAL_ERROR_IF(!m_mapgens.empty(),
+ "Writable managers can only be returned before mapgen init");
+ return biomemgr;
+}
+
+OreManager *EmergeManager::getWritableOreManager()
+{
+ FATAL_ERROR_IF(!m_mapgens.empty(),
+ "Writable managers can only be returned before mapgen init");
+ return oremgr;
+}
+
+DecorationManager *EmergeManager::getWritableDecorationManager()
+{
+ FATAL_ERROR_IF(!m_mapgens.empty(),
+ "Writable managers can only be returned before mapgen init");
+ return decomgr;
+}
+
+SchematicManager *EmergeManager::getWritableSchematicManager()
+{
+ FATAL_ERROR_IF(!m_mapgens.empty(),
+ "Writable managers can only be returned before mapgen init");
+ return schemmgr;
+}
+
+
void EmergeManager::initMapgens(MapgenParams *params)
{
FATAL_ERROR_IF(!m_mapgens.empty(), "Mapgen already initialised.");
mgparams = params;
- for (u32 i = 0; i != m_threads.size(); i++)
- m_mapgens.push_back(Mapgen::createMapgen(params->mgtype, params, this));
+ for (u32 i = 0; i != m_threads.size(); i++) {
+ EmergeParams *p = new EmergeParams(
+ this, biomemgr, oremgr, decomgr, schemmgr);
+ infostream << "EmergeManager: Created params " << p
+ << " for thread " << i << std::endl;
+ m_mapgens.push_back(Mapgen::createMapgen(params->mgtype, params, p));
+ }
}
@@ -201,8 +256,9 @@ Mapgen *EmergeManager::getCurrentMapgen()
return nullptr;
for (u32 i = 0; i != m_threads.size(); i++) {
- if (m_threads[i]->isCurrentThread())
- return m_threads[i]->m_mapgen;
+ EmergeThread *t = m_threads[i];
+ if (t->isRunning() && t->isCurrentThread())
+ return t->m_mapgen;
}
return nullptr;
@@ -588,8 +644,7 @@ MapBlock *EmergeThread::finishGen(v3s16 pos, BlockMakeData *bmdata,
/*
Clear generate notifier events
*/
- Mapgen *mg = m_emerge->getCurrentMapgen();
- mg->gennotify.clearEvents();
+ m_mapgen->gennotify.clearEvents();
EMERGE_DBG_OUT("ended up with: " << analyze_block(block));
diff --git a/src/emerge.h b/src/emerge.h
index df849e542..6f204666d 100644
--- a/src/emerge.h
+++ b/src/emerge.h
@@ -44,6 +44,7 @@ class OreManager;
class DecorationManager;
class SchematicManager;
class Server;
+class ModApiMapgen;
// Structure containing inputs/outputs for chunk generation
struct BlockMakeData {
@@ -86,7 +87,36 @@ struct BlockEmergeData {
EmergeCallbackList callbacks;
};
+class EmergeParams {
+ friend class EmergeManager;
+public:
+ EmergeParams() = delete;
+ ~EmergeParams();
+ DISABLE_CLASS_COPY(EmergeParams);
+
+ const NodeDefManager *ndef; // shared
+ bool enable_mapgen_debug_info;
+
+ u32 gen_notify_on;
+ const std::set<u32> *gen_notify_on_deco_ids; // shared
+
+ BiomeManager *biomemgr;
+ OreManager *oremgr;
+ DecorationManager *decomgr;
+ SchematicManager *schemmgr;
+
+private:
+ EmergeParams(EmergeManager *parent, const BiomeManager *biomemgr,
+ const OreManager *oremgr, const DecorationManager *decomgr,
+ const SchematicManager *schemmgr);
+};
+
class EmergeManager {
+ /* The mod API needs unchecked access to allow:
+ * - using decomgr or oremgr to place decos/ores
+ * - using schemmgr to load and place schematics
+ */
+ friend class ModApiMapgen;
public:
const NodeDefManager *ndef;
bool enable_mapgen_debug_info;
@@ -106,17 +136,22 @@ public:
// Environment is not created until after script initialization.
MapSettingsManager *map_settings_mgr;
- // Managers of various map generation-related components
- BiomeManager *biomemgr;
- OreManager *oremgr;
- DecorationManager *decomgr;
- SchematicManager *schemmgr;
-
// Methods
EmergeManager(Server *server);
~EmergeManager();
DISABLE_CLASS_COPY(EmergeManager);
+ // no usage restrictions
+ const BiomeManager *getBiomeManager() const { return biomemgr; }
+ const OreManager *getOreManager() const { return oremgr; }
+ const DecorationManager *getDecorationManager() const { return decomgr; }
+ const SchematicManager *getSchematicManager() const { return schemmgr; }
+ // only usable before mapgen init
+ BiomeManager *getWritableBiomeManager();
+ OreManager *getWritableOreManager();
+ DecorationManager *getWritableDecorationManager();
+ SchematicManager *getWritableSchematicManager();
+
void initMapgens(MapgenParams *mgparams);
void startThreads();
@@ -160,6 +195,13 @@ private:
u16 m_qlimit_diskonly;
u16 m_qlimit_generate;
+ // Managers of various map generation-related components
+ // Note that each Mapgen gets a copy(!) of these to work with
+ BiomeManager *biomemgr;
+ OreManager *oremgr;
+ DecorationManager *decomgr;
+ SchematicManager *schemmgr;
+
// Requires m_queue_mutex held
EmergeThread *getOptimalThread();
diff --git a/src/environment.cpp b/src/environment.cpp
index 906f35219..6751f39e4 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -21,7 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "environment.h"
#include "collision.h"
#include "raycast.h"
-#include "serverobject.h"
#include "scripting_server.h"
#include "server.h"
#include "daynightratio.h"
@@ -83,6 +82,24 @@ float Environment::getTimeOfDayF()
return m_time_of_day_f;
}
+bool Environment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p)
+{
+ // Iterate trough nodes on the line
+ voxalgo::VoxelLineIterator iterator(pos1 / BS, (pos2 - pos1) / BS);
+ do {
+ MapNode n = getMap().getNode(iterator.m_current_node_pos);
+
+ // Return non-air
+ if (n.param0 != CONTENT_AIR) {
+ if (p)
+ *p = iterator.m_current_node_pos;
+ return false;
+ }
+ iterator.next();
+ } while (iterator.m_current_index <= iterator.m_last_index);
+ return true;
+}
+
/*
Check if a node is pointable
*/
diff --git a/src/environment.h b/src/environment.h
index f568ba228..91c33ba15 100644
--- a/src/environment.h
+++ b/src/environment.h
@@ -77,6 +77,16 @@ public:
u32 getDayCount();
/*!
+ * Returns false if the given line intersects with a
+ * non-air node, true otherwise.
+ * \param pos1 start of the line
+ * \param pos2 end of the line
+ * \param p output, position of the first non-air node
+ * the line intersects
+ */
+ bool line_of_sight(v3f pos1, v3f pos2, v3s16 *p = nullptr);
+
+ /*!
* Gets the objects pointed by the shootline as
* pointed things.
* If this is a client environment, the local player
diff --git a/src/filesys.cpp b/src/filesys.cpp
index f61b39b94..0bc351669 100644
--- a/src/filesys.cpp
+++ b/src/filesys.cpp
@@ -691,6 +691,12 @@ std::string AbsolutePath(const std::string &path)
const char *GetFilenameFromPath(const char *path)
{
const char *filename = strrchr(path, DIR_DELIM_CHAR);
+ // Consistent with IsDirDelimiter this function handles '/' too
+ if (DIR_DELIM_CHAR != '/') {
+ const char *tmp = strrchr(path, '/');
+ if (tmp && tmp > filename)
+ filename = tmp;
+ }
return filename ? filename + 1 : path;
}
diff --git a/src/genericobject.cpp b/src/genericobject.cpp
deleted file mode 100644
index 49d16001f..000000000
--- a/src/genericobject.cpp
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#include "genericobject.h"
-#include <sstream>
-#include "util/serialize.h"
-
-std::string gob_cmd_set_properties(const ObjectProperties &prop)
-{
- std::ostringstream os(std::ios::binary);
- writeU8(os, GENERIC_CMD_SET_PROPERTIES);
- prop.serialize(os);
- return os.str();
-}
-
-ObjectProperties gob_read_set_properties(std::istream &is)
-{
- ObjectProperties prop;
- prop.deSerialize(is);
- return prop;
-}
-
-std::string gob_cmd_update_position(
- v3f position,
- v3f velocity,
- v3f acceleration,
- v3f rotation,
- bool do_interpolate,
- bool is_movement_end,
- f32 update_interval
-){
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_UPDATE_POSITION);
- // pos
- writeV3F32(os, position);
- // velocity
- writeV3F32(os, velocity);
- // acceleration
- writeV3F32(os, acceleration);
- // rotation
- writeV3F32(os, rotation);
- // do_interpolate
- writeU8(os, do_interpolate);
- // is_end_position (for interpolation)
- writeU8(os, is_movement_end);
- // update_interval (for interpolation)
- writeF32(os, update_interval);
- return os.str();
-}
-
-std::string gob_cmd_set_texture_mod(const std::string &mod)
-{
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_SET_TEXTURE_MOD);
- // parameters
- os<<serializeString(mod);
- return os.str();
-}
-
-std::string gob_cmd_set_sprite(
- v2s16 p,
- u16 num_frames,
- f32 framelength,
- bool select_horiz_by_yawpitch
-){
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_SET_SPRITE);
- // parameters
- writeV2S16(os, p);
- writeU16(os, num_frames);
- writeF32(os, framelength);
- writeU8(os, select_horiz_by_yawpitch);
- return os.str();
-}
-
-std::string gob_cmd_punched(u16 result_hp)
-{
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_PUNCHED);
- // result_hp
- writeU16(os, result_hp);
- return os.str();
-}
-
-std::string gob_cmd_update_armor_groups(const ItemGroupList &armor_groups)
-{
- std::ostringstream os(std::ios::binary);
- writeU8(os, GENERIC_CMD_UPDATE_ARMOR_GROUPS);
- writeU16(os, armor_groups.size());
- for (const auto &armor_group : armor_groups) {
- os<<serializeString(armor_group.first);
- writeS16(os, armor_group.second);
- }
- return os.str();
-}
-
-std::string gob_cmd_update_physics_override(float physics_override_speed, float physics_override_jump,
- float physics_override_gravity, bool sneak, bool sneak_glitch, bool new_move)
-{
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_SET_PHYSICS_OVERRIDE);
- // parameters
- writeF32(os, physics_override_speed);
- writeF32(os, physics_override_jump);
- writeF32(os, physics_override_gravity);
- // these are sent inverted so we get true when the server sends nothing
- writeU8(os, !sneak);
- writeU8(os, !sneak_glitch);
- writeU8(os, !new_move);
- return os.str();
-}
-
-std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend, bool frame_loop)
-{
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_SET_ANIMATION);
- // parameters
- writeV2F32(os, frames);
- writeF32(os, frame_speed);
- writeF32(os, frame_blend);
- // these are sent inverted so we get true when the server sends nothing
- writeU8(os, !frame_loop);
- return os.str();
-}
-
-std::string gob_cmd_update_animation_speed(float frame_speed)
-{
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_SET_ANIMATION_SPEED);
- // parameters
- writeF32(os, frame_speed);
- return os.str();
-}
-
-std::string gob_cmd_update_bone_position(const std::string &bone, v3f position,
- v3f rotation)
-{
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_SET_BONE_POSITION);
- // parameters
- os<<serializeString(bone);
- writeV3F32(os, position);
- writeV3F32(os, rotation);
- return os.str();
-}
-
-std::string gob_cmd_update_attachment(int parent_id, const std::string &bone,
- v3f position, v3f rotation)
-{
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_ATTACH_TO);
- // parameters
- writeS16(os, parent_id);
- os<<serializeString(bone);
- writeV3F32(os, position);
- writeV3F32(os, rotation);
- return os.str();
-}
-
-std::string gob_cmd_update_nametag_attributes(video::SColor color)
-{
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES);
- // parameters
- writeU8(os, 1); // version for forward compatibility
- writeARGB8(os, color);
- return os.str();
-}
-
-std::string gob_cmd_update_infant(u16 id, u8 type,
- const std::string &client_initialization_data)
-{
- std::ostringstream os(std::ios::binary);
- // command
- writeU8(os, GENERIC_CMD_SPAWN_INFANT);
- // parameters
- writeU16(os, id);
- writeU8(os, type);
- os<<serializeLongString(client_initialization_data);
- return os.str();
-}
diff --git a/src/genericobject.h b/src/genericobject.h
deleted file mode 100644
index c83b310d1..000000000
--- a/src/genericobject.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-#pragma once
-
-#include <string>
-#include "irrlichttypes_bloated.h"
-#include <iostream>
-#include "itemgroup.h"
-
-enum GenericCMD {
- GENERIC_CMD_SET_PROPERTIES,
- GENERIC_CMD_UPDATE_POSITION,
- GENERIC_CMD_SET_TEXTURE_MOD,
- GENERIC_CMD_SET_SPRITE,
- GENERIC_CMD_PUNCHED,
- GENERIC_CMD_UPDATE_ARMOR_GROUPS,
- GENERIC_CMD_SET_ANIMATION,
- GENERIC_CMD_SET_BONE_POSITION,
- GENERIC_CMD_ATTACH_TO,
- GENERIC_CMD_SET_PHYSICS_OVERRIDE,
- GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES,
- GENERIC_CMD_SPAWN_INFANT,
- GENERIC_CMD_SET_ANIMATION_SPEED
-};
-
-#include "object_properties.h"
-std::string gob_cmd_set_properties(const ObjectProperties &prop);
-ObjectProperties gob_read_set_properties(std::istream &is);
-
-std::string gob_cmd_update_position(
- v3f position,
- v3f velocity,
- v3f acceleration,
- v3f rotation,
- bool do_interpolate,
- bool is_movement_end,
- f32 update_interval
-);
-
-std::string gob_cmd_set_texture_mod(const std::string &mod);
-
-std::string gob_cmd_set_sprite(
- v2s16 p,
- u16 num_frames,
- f32 framelength,
- bool select_horiz_by_yawpitch
-);
-
-std::string gob_cmd_punched(u16 result_hp);
-
-std::string gob_cmd_update_armor_groups(const ItemGroupList &armor_groups);
-
-std::string gob_cmd_update_physics_override(float physics_override_speed,
- float physics_override_jump, float physics_override_gravity,
- bool sneak, bool sneak_glitch, bool new_move);
-
-std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend, bool frame_loop);
-
-std::string gob_cmd_update_animation_speed(float frame_speed);
-
-std::string gob_cmd_update_bone_position(const std::string &bone, v3f position,
- v3f rotation);
-
-std::string gob_cmd_update_attachment(int parent_id, const std::string &bone,
- v3f position, v3f rotation);
-
-std::string gob_cmd_update_nametag_attributes(video::SColor color);
-
-std::string gob_cmd_update_infant(u16 id, u8 type,
- const std::string &client_initialization_data);
diff --git a/src/gettext.cpp b/src/gettext.cpp
index 81621ba89..6818004df 100644
--- a/src/gettext.cpp
+++ b/src/gettext.cpp
@@ -217,7 +217,10 @@ void init_gettext(const char *path, const std::string &configured_language,
#endif
#endif
- static std::string name = lowercase(PROJECT_NAME);
+ std::string name = lowercase(PROJECT_NAME);
+ infostream << "Gettext: domainname=\"" << name
+ << "\" path=\"" << path << "\"" << std::endl;
+
bindtextdomain(name.c_str(), path);
textdomain(name.c_str());
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 110a00595..147f445f4 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -16,6 +16,7 @@ set(gui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollContainer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiHyperText.cpp
diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h
index 999c1d237..3e842e826 100644
--- a/src/gui/StyleSpec.h
+++ b/src/gui/StyleSpec.h
@@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include "client/tile.h" // ITextureSource
+#include "debug.h"
#include "irrlichttypes_extrabloated.h"
#include "util/string.h"
#include <array>
@@ -31,25 +32,36 @@ public:
{
TEXTCOLOR,
BGCOLOR,
- BGCOLOR_HOVERED,
- BGCOLOR_PRESSED,
+ BGCOLOR_HOVERED, // Note: Deprecated property
+ BGCOLOR_PRESSED, // Note: Deprecated property
NOCLIP,
BORDER,
BGIMG,
- BGIMG_HOVERED,
+ BGIMG_HOVERED, // Note: Deprecated property
BGIMG_MIDDLE,
- BGIMG_PRESSED,
+ BGIMG_PRESSED, // Note: Deprecated property
FGIMG,
- FGIMG_HOVERED,
- FGIMG_PRESSED,
+ FGIMG_HOVERED, // Note: Deprecated property
+ FGIMG_PRESSED, // Note: Deprecated property
ALPHA,
+ CONTENT_OFFSET,
+ PADDING,
NUM_PROPERTIES,
NONE
};
+ enum State
+ {
+ STATE_DEFAULT = 0,
+ STATE_HOVERED = 1 << 0,
+ STATE_PRESSED = 1 << 1,
+ NUM_STATES = 1 << 2,
+ STATE_INVALID = 1 << 3,
+ };
private:
std::array<bool, NUM_PROPERTIES> property_set{};
std::array<std::string, NUM_PROPERTIES> properties;
+ State state_map = STATE_DEFAULT;
public:
static Property GetPropertyByName(const std::string &name)
@@ -82,6 +94,10 @@ public:
return FGIMG_PRESSED;
} else if (name == "alpha") {
return ALPHA;
+ } else if (name == "content_offset") {
+ return CONTENT_OFFSET;
+ } else if (name == "padding") {
+ return PADDING;
} else {
return NONE;
}
@@ -99,6 +115,49 @@ public:
property_set[prop] = true;
}
+ //! Parses a name and returns the corresponding state enum
+ static State getStateByName(const std::string &name)
+ {
+ if (name == "default") {
+ return STATE_DEFAULT;
+ } else if (name == "hovered") {
+ return STATE_HOVERED;
+ } else if (name == "pressed") {
+ return STATE_PRESSED;
+ } else {
+ return STATE_INVALID;
+ }
+ }
+
+ //! Gets the state that this style is intended for
+ State getState() const
+ {
+ return state_map;
+ }
+
+ //! Set the given state on this style
+ void addState(State state)
+ {
+ FATAL_ERROR_IF(state >= NUM_STATES, "Out-of-bounds state received");
+
+ state_map = static_cast<State>(state_map | state);
+ }
+
+ //! Using a list of styles mapped to state values, calculate the final
+ // combined style for a state by propagating values in its component states
+ static StyleSpec getStyleFromStatePropagation(const std::array<StyleSpec, NUM_STATES> &styles, State state)
+ {
+ StyleSpec temp = styles[StyleSpec::STATE_DEFAULT];
+ temp.state_map = state;
+ for (int i = StyleSpec::STATE_DEFAULT + 1; i <= state; i++) {
+ if ((state & i) != 0) {
+ temp = temp | styles[i];
+ }
+ }
+
+ return temp;
+ }
+
video::SColor getColor(Property prop, video::SColor def) const
{
const auto &val = properties[prop];
@@ -143,6 +202,29 @@ public:
return rect;
}
+ irr::core::vector2d<s32> getVector2i(Property prop, irr::core::vector2d<s32> def) const
+ {
+ const auto &val = properties[prop];
+ if (val.empty())
+ return def;
+
+ irr::core::vector2d<s32> vec;
+ if (!parseVector2i(val, &vec))
+ return def;
+
+ return vec;
+ }
+
+ irr::core::vector2d<s32> getVector2i(Property prop) const
+ {
+ const auto &val = properties[prop];
+ FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
+
+ irr::core::vector2d<s32> vec;
+ parseVector2i(val, &vec);
+ return vec;
+ }
+
video::ITexture *getTexture(Property prop, ISimpleTextureSource *tsrc,
video::ITexture *def) const
{
@@ -233,4 +315,29 @@ private:
return true;
}
+
+ bool parseVector2i(const std::string &value, irr::core::vector2d<s32> *parsed_vec) const
+ {
+ irr::core::vector2d<s32> vec;
+ std::vector<std::string> v_vector = split(value, ',');
+
+ if (v_vector.size() == 1) {
+ s32 x = stoi(v_vector[0]);
+ vec.X = x;
+ vec.Y = x;
+ } else if (v_vector.size() == 2) {
+ s32 x = stoi(v_vector[0]);
+ s32 y = stoi(v_vector[1]);
+ vec.X = x;
+ vec.Y = y;
+ } else {
+ warningstream << "Invalid vector2d string format: \"" << value
+ << "\"" << std::endl;
+ return false;
+ }
+
+ *parsed_vec = vec;
+
+ return true;
+ }
};
diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp
index 4c16ee237..6732a9233 100644
--- a/src/gui/guiButton.cpp
+++ b/src/gui/guiButton.cpp
@@ -14,6 +14,7 @@
#include "irrlicht_changes/static_text.h"
#include "porting.h"
#include "StyleSpec.h"
+#include "util/numeric.h"
using namespace irr;
using namespace gui;
@@ -26,14 +27,15 @@ using namespace gui;
//! constructor
GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent,
- s32 id, core::rect<s32> rectangle, bool noclip)
+ s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
+ bool noclip)
: IGUIButton(environment, parent, id, rectangle),
SpriteBank(0), OverrideFont(0),
OverrideColorEnabled(false), OverrideColor(video::SColor(101,255,255,255)),
ClickTime(0), HoverTime(0), FocusTime(0),
ClickShiftState(false), ClickControlState(false),
IsPushButton(false), Pressed(false),
- UseAlphaChannel(false), DrawBorder(true), ScaleImage(false)
+ UseAlphaChannel(false), DrawBorder(true), ScaleImage(false), TSrc(tsrc)
{
setNotClipped(noclip);
@@ -44,14 +46,6 @@ GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent,
// PATCH
for (size_t i = 0; i < 4; i++) {
Colors[i] = Environment->getSkin()->getColor((EGUI_DEFAULT_COLOR)i);
- HoveredColors[i] = irr::video::SColor(Colors[i].getAlpha(),
- core::clamp<u32>(Colors[i].getRed() * COLOR_HOVERED_MOD, 0, 255),
- core::clamp<u32>(Colors[i].getGreen() * COLOR_HOVERED_MOD, 0, 255),
- core::clamp<u32>(Colors[i].getBlue() * COLOR_HOVERED_MOD, 0, 255));
- PressedColors[i] = irr::video::SColor(Colors[i].getAlpha(),
- core::clamp<u32>(Colors[i].getRed() * COLOR_PRESSED_MOD, 0, 255),
- core::clamp<u32>(Colors[i].getGreen() * COLOR_PRESSED_MOD, 0, 255),
- core::clamp<u32>(Colors[i].getBlue() * COLOR_PRESSED_MOD, 0, 255));
}
StaticText = gui::StaticText::add(Environment, Text.c_str(), core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()), false, false, this, id);
StaticText->setTextAlignment(EGUIA_CENTER, EGUIA_CENTER);
@@ -262,6 +256,13 @@ void GUIButton::draw()
return;
// PATCH
+ // Track hovered state, if it has changed then we need to update the style.
+ bool hovered = isHovered();
+ if (hovered != WasHovered) {
+ WasHovered = hovered;
+ setFromState();
+ }
+
GUISkin* skin = dynamic_cast<GUISkin*>(Environment->getSkin());
video::IVideoDriver* driver = Environment->getVideoDriver();
// END PATCH
@@ -271,21 +272,24 @@ void GUIButton::draw()
if (!Pressed)
{
// PATCH
- skin->drawColored3DButtonPaneStandard(this, AbsoluteRect, &AbsoluteClippingRect,
- isHovered() ? HoveredColors : Colors);
+ skin->drawColored3DButtonPaneStandard(this, AbsoluteRect,
+ &AbsoluteClippingRect, Colors);
// END PATCH
}
else
{
// PATCH
- skin->drawColored3DButtonPanePressed(this,
- AbsoluteRect, &AbsoluteClippingRect, PressedColors);
+ skin->drawColored3DButtonPanePressed(this, AbsoluteRect,
+ &AbsoluteClippingRect, Colors);
// END PATCH
}
}
const core::position2di buttonCenter(AbsoluteRect.getCenter());
- EGUI_BUTTON_IMAGE_STATE imageState = getImageState(Pressed);
+ // PATCH
+ // The image changes based on the state, so we use the default every time.
+ EGUI_BUTTON_IMAGE_STATE imageState = EGBIS_IMAGE_UP;
+ // END PATCH
if ( ButtonImages[(u32)imageState].Texture )
{
core::position2d<s32> pos(buttonCenter);
@@ -548,18 +552,6 @@ void GUIButton::setPressedImage(video::ITexture* image, const core::rect<s32>& p
setImage(gui::EGBIS_IMAGE_DOWN, image, pos);
}
-void GUIButton::setHoveredImage(video::ITexture* image)
-{
- setImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image);
- setImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image);
-}
-
-void GUIButton::setHoveredImage(video::ITexture* image, const core::rect<s32>& pos)
-{
- setImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image, pos);
- setImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image, pos);
-}
-
//! Sets the text displayed by the button
void GUIButton::setText(const wchar_t* text)
{
@@ -600,24 +592,7 @@ void GUIButton::setPressed(bool pressed)
{
ClickTime = porting::getTimeMs();
Pressed = pressed;
-
- GUISkin* skin = dynamic_cast<GUISkin*>(Environment->getSkin());
-
- for(IGUIElement *child : getChildren())
- {
- core::rect<s32> originalRect = child->getRelativePosition();
- if (Pressed) {
- child->setRelativePosition(originalRect +
- core::dimension2d<s32>(
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y)));
- } else {
- child->setRelativePosition(originalRect -
- core::dimension2d<s32>(
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
- skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y)));
- }
- }
+ setFromState();
}
}
@@ -729,10 +704,12 @@ void GUIButton::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWri
}
// PATCH
-GUIButton* GUIButton::addButton(IGUIEnvironment *environment, const core::rect<s32>& rectangle,
- IGUIElement* parent, s32 id, const wchar_t* text, const wchar_t *tooltiptext)
+GUIButton* GUIButton::addButton(IGUIEnvironment *environment,
+ const core::rect<s32>& rectangle, ISimpleTextureSource *tsrc,
+ IGUIElement* parent, s32 id, const wchar_t* text,
+ const wchar_t *tooltiptext)
{
- GUIButton* button = new GUIButton(environment, parent ? parent : environment->getRootGUIElement(), id, rectangle);
+ GUIButton* button = new GUIButton(environment, parent ? parent : environment->getRootGUIElement(), id, rectangle, tsrc);
if (text)
button->setText(text);
@@ -749,76 +726,111 @@ void GUIButton::setColor(video::SColor color)
for (size_t i = 0; i < 4; i++) {
video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
Colors[i] = base.getInterpolated(color, d);
- HoveredColors[i] = irr::video::SColor(Colors[i].getAlpha(),
- core::clamp<u32>(Colors[i].getRed() * COLOR_HOVERED_MOD, 0, 255),
- core::clamp<u32>(Colors[i].getGreen() * COLOR_HOVERED_MOD, 0, 255),
- core::clamp<u32>(Colors[i].getBlue() * COLOR_HOVERED_MOD, 0, 255));
- PressedColors[i] = irr::video::SColor(Colors[i].getAlpha(),
- core::clamp<u32>(Colors[i].getRed() * COLOR_PRESSED_MOD, 0, 255),
- core::clamp<u32>(Colors[i].getGreen() * COLOR_PRESSED_MOD, 0, 255),
- core::clamp<u32>(Colors[i].getBlue() * COLOR_PRESSED_MOD, 0, 255));
}
}
-void GUIButton::setHoveredColor(video::SColor color)
-{
- float d = 0.65f;
- for (size_t i = 0; i < 4; i++) {
- video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
- HoveredColors[i] = base.getInterpolated(color, d);
- }
-}
-void GUIButton::setPressedColor(video::SColor color)
+
+//! Set element properties from a StyleSpec corresponding to the button state
+void GUIButton::setFromState()
{
- float d = 0.65f;
- for (size_t i = 0; i < 4; i++) {
- video::SColor base = Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
- PressedColors[i] = base.getInterpolated(color, d);
- }
+ StyleSpec::State state = StyleSpec::STATE_DEFAULT;
+
+ if (isPressed())
+ state = static_cast<StyleSpec::State>(state | StyleSpec::STATE_PRESSED);
+
+ if (isHovered())
+ state = static_cast<StyleSpec::State>(state | StyleSpec::STATE_HOVERED);
+
+ setFromStyle(StyleSpec::getStyleFromStatePropagation(Styles, state));
}
//! Set element properties from a StyleSpec
-void GUIButton::setFromStyle(const StyleSpec& style, ISimpleTextureSource *tsrc)
+void GUIButton::setFromStyle(const StyleSpec& style)
{
+ bool hovered = (style.getState() & StyleSpec::STATE_HOVERED) != 0;
+ bool pressed = (style.getState() & StyleSpec::STATE_PRESSED) != 0;
+
if (style.isNotDefault(StyleSpec::BGCOLOR)) {
+
setColor(style.getColor(StyleSpec::BGCOLOR));
- }
- if (style.isNotDefault(StyleSpec::BGCOLOR_HOVERED)) {
- setHoveredColor(style.getColor(StyleSpec::BGCOLOR_HOVERED));
- }
- if (style.isNotDefault(StyleSpec::BGCOLOR_PRESSED)) {
- setPressedColor(style.getColor(StyleSpec::BGCOLOR_PRESSED));
+
+ // If we have a propagated hover/press color, we need to automatically
+ // lighten/darken it
+ if (!Styles[style.getState()].isNotDefault(StyleSpec::BGCOLOR)) {
+ for (size_t i = 0; i < 4; i++) {
+ if (pressed) {
+ Colors[i] = multiplyColorValue(Colors[i], COLOR_PRESSED_MOD);
+ } else if (hovered) {
+ Colors[i] = multiplyColorValue(Colors[i], COLOR_HOVERED_MOD);
+ }
+ }
+ }
+
+ } else {
+ for (size_t i = 0; i < 4; i++) {
+ video::SColor base =
+ Environment->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
+ if (pressed) {
+ Colors[i] = multiplyColorValue(base, COLOR_PRESSED_MOD);
+ } else if (hovered) {
+ Colors[i] = multiplyColorValue(base, COLOR_HOVERED_MOD);
+ } else {
+ Colors[i] = base;
+ }
+ }
}
if (style.isNotDefault(StyleSpec::TEXTCOLOR)) {
setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR));
+ } else {
+ setOverrideColor(video::SColor(255,255,255,255));
+ OverrideColorEnabled = false;
}
- setNotClipped(style.getBool(StyleSpec::NOCLIP, isNotClipped()));
- setDrawBorder(style.getBool(StyleSpec::BORDER, DrawBorder));
+ setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ setDrawBorder(style.getBool(StyleSpec::BORDER, true));
setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
- const core::position2di buttonCenter(AbsoluteRect.getCenter());
- core::position2d<s32> geom(buttonCenter);
if (style.isNotDefault(StyleSpec::BGIMG)) {
- video::ITexture *texture = style.getTexture(StyleSpec::BGIMG, tsrc);
-
+ video::ITexture *texture = style.getTexture(StyleSpec::BGIMG,
+ getTextureSource());
setImage(guiScalingImageButton(
- Environment->getVideoDriver(), texture, geom.X, geom.Y));
+ Environment->getVideoDriver(), texture,
+ AbsoluteRect.getWidth(), AbsoluteRect.getHeight()));
setScaleImage(true);
+ } else {
+ setImage(nullptr);
}
- if (style.isNotDefault(StyleSpec::BGIMG_HOVERED)) {
- video::ITexture *hovered_texture = style.getTexture(StyleSpec::BGIMG_HOVERED, tsrc);
- setHoveredImage(guiScalingImageButton(
- Environment->getVideoDriver(), hovered_texture, geom.X, geom.Y));
- setScaleImage(true);
- }
- if (style.isNotDefault(StyleSpec::BGIMG_PRESSED)) {
- video::ITexture *pressed_texture = style.getTexture(StyleSpec::BGIMG_PRESSED, tsrc);
+ BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle);
- setPressedImage(guiScalingImageButton(
- Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
- setScaleImage(true);
+ // Child padding and offset
+ Padding = style.getRect(StyleSpec::PADDING, core::rect<s32>());
+ Padding = core::rect<s32>(
+ Padding.UpperLeftCorner + BgMiddle.UpperLeftCorner,
+ Padding.LowerRightCorner + BgMiddle.LowerRightCorner);
+
+ GUISkin* skin = dynamic_cast<GUISkin*>(Environment->getSkin());
+ core::vector2d<s32> defaultPressOffset(
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
+ ContentOffset = style.getVector2i(StyleSpec::CONTENT_OFFSET, isPressed()
+ ? defaultPressOffset
+ : core::vector2d<s32>(0));
+
+ core::rect<s32> childBounds(
+ Padding.UpperLeftCorner.X + ContentOffset.X,
+ Padding.UpperLeftCorner.Y + ContentOffset.Y,
+ AbsoluteRect.getWidth() + Padding.LowerRightCorner.X + ContentOffset.X,
+ AbsoluteRect.getHeight() + Padding.LowerRightCorner.Y + ContentOffset.Y);
+
+ for (IGUIElement *child : getChildren()) {
+ child->setRelativePosition(childBounds);
}
- BgMiddle = style.getRect(StyleSpec::BGIMG_MIDDLE, BgMiddle);
+}
+
+//! Set the styles used for each state
+void GUIButton::setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES>& styles)
+{
+ Styles = styles;
+ setFromState();
}
// END PATCH
diff --git a/src/gui/guiButton.h b/src/gui/guiButton.h
index 3d1f98c32..95fa1a2a1 100644
--- a/src/gui/guiButton.h
+++ b/src/gui/guiButton.h
@@ -13,6 +13,7 @@
#include "ITexture.h"
#include "SColor.h"
#include "guiSkin.h"
+#include "StyleSpec.h"
using namespace irr;
@@ -67,7 +68,6 @@ using namespace irr;
#endif
class ISimpleTextureSource;
-class StyleSpec;
class GUIButton : public gui::IGUIButton
{
@@ -75,7 +75,8 @@ public:
//! constructor
GUIButton(gui::IGUIEnvironment* environment, gui::IGUIElement* parent,
- s32 id, core::rect<s32> rectangle, bool noclip=false);
+ s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
+ bool noclip=false);
//! destructor
virtual ~GUIButton();
@@ -125,16 +126,10 @@ public:
//! Sets an image which should be displayed on the button when it is in pressed state.
virtual void setPressedImage(video::ITexture* image, const core::rect<s32>& pos) override;
- //! Sets an image which should be displayed on the button when it is in hovered state.
- virtual void setHoveredImage(video::ITexture* image=nullptr);
-
//! Sets the text displayed by the button
virtual void setText(const wchar_t* text) override;
// END PATCH
- //! Sets an image which should be displayed on the button when it is in hovered state.
- virtual void setHoveredImage(video::ITexture* image, const core::rect<s32>& pos);
-
//! Sets the sprite bank used by the button
virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override;
@@ -225,22 +220,29 @@ public:
void setColor(video::SColor color);
// PATCH
- void setHoveredColor(video::SColor color);
- void setPressedColor(video::SColor color);
+ //! Set element properties from a StyleSpec corresponding to the button state
+ void setFromState();
//! Set element properties from a StyleSpec
- virtual void setFromStyle(const StyleSpec& style, ISimpleTextureSource *tsrc);
+ virtual void setFromStyle(const StyleSpec& style);
+
+ //! Set the styles used for each state
+ void setStyles(const std::array<StyleSpec, StyleSpec::NUM_STATES>& styles);
// END PATCH
//! Do not drop returned handle
- static GUIButton* addButton(gui::IGUIEnvironment *environment, const core::rect<s32>& rectangle,
- IGUIElement* parent, s32 id, const wchar_t* text, const wchar_t *tooltiptext=L"");
+ static GUIButton* addButton(gui::IGUIEnvironment *environment,
+ const core::rect<s32>& rectangle, ISimpleTextureSource *tsrc,
+ IGUIElement* parent, s32 id, const wchar_t* text,
+ const wchar_t *tooltiptext=L"");
protected:
void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center);
gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const;
+ ISimpleTextureSource *getTextureSource() { return TSrc; }
+
struct ButtonImage
{
ButtonImage() : Texture(0), SourceRect(core::rect<s32>(0,0,0,0))
@@ -308,6 +310,8 @@ private:
ButtonImage ButtonImages[gui::EGBIS_COUNT];
+ std::array<StyleSpec, StyleSpec::NUM_STATES> Styles;
+
gui::IGUIFont* OverrideFont;
bool OverrideColorEnabled;
@@ -326,11 +330,13 @@ private:
video::SColor Colors[4];
// PATCH
- video::SColor HoveredColors[4];
- video::SColor PressedColors[4];
+ bool WasHovered = false;
+ ISimpleTextureSource *TSrc;
gui::IGUIStaticText *StaticText;
core::rect<s32> BgMiddle;
+ core::rect<s32> Padding;
+ core::vector2d<s32> ContentOffset;
// END PATCH
};
diff --git a/src/gui/guiButtonImage.cpp b/src/gui/guiButtonImage.cpp
index 02d920277..b507ffece 100644
--- a/src/gui/guiButtonImage.cpp
+++ b/src/gui/guiButtonImage.cpp
@@ -30,8 +30,9 @@ using namespace irr;
using namespace gui;
GUIButtonImage::GUIButtonImage(gui::IGUIEnvironment *environment,
- gui::IGUIElement *parent, s32 id, core::rect<s32> rectangle, bool noclip)
- : GUIButton (environment, parent, id, rectangle, noclip)
+ gui::IGUIElement *parent, s32 id, core::rect<s32> rectangle,
+ ISimpleTextureSource *tsrc, bool noclip)
+ : GUIButton (environment, parent, id, rectangle, tsrc, noclip)
{
m_image = Environment->addImage(
core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()), this);
@@ -39,100 +40,37 @@ GUIButtonImage::GUIButtonImage(gui::IGUIEnvironment *environment,
sendToBack(m_image);
}
-bool GUIButtonImage::OnEvent(const SEvent& event)
-{
- bool result = GUIButton::OnEvent(event);
-
- EGUI_BUTTON_IMAGE_STATE imageState = getImageState(isPressed(), m_foreground_images);
- video::ITexture *texture = m_foreground_images[(u32)imageState].Texture;
- if (texture != nullptr)
- {
- m_image->setImage(texture);
- }
-
- m_image->setVisible(texture != nullptr);
-
- return result;
-}
-
-void GUIButtonImage::setForegroundImage(EGUI_BUTTON_IMAGE_STATE state,
- video::ITexture *image, const core::rect<s32> &sourceRect)
+void GUIButtonImage::setForegroundImage(video::ITexture *image)
{
- if (state >= EGBIS_COUNT)
+ if (image == m_foreground_image)
return;
- if (image)
+ if (image != nullptr)
image->grab();
- u32 stateIdx = (u32)state;
- if (m_foreground_images[stateIdx].Texture)
- m_foreground_images[stateIdx].Texture->drop();
-
- m_foreground_images[stateIdx].Texture = image;
- m_foreground_images[stateIdx].SourceRect = sourceRect;
-
- EGUI_BUTTON_IMAGE_STATE imageState = getImageState(isPressed(), m_foreground_images);
- if (imageState == stateIdx)
- m_image->setImage(image);
-}
-
-void GUIButtonImage::setForegroundImage(video::ITexture *image)
-{
- setForegroundImage(gui::EGBIS_IMAGE_UP, image);
-}
-
-void GUIButtonImage::setForegroundImage(video::ITexture *image, const core::rect<s32> &pos)
-{
- setForegroundImage(gui::EGBIS_IMAGE_UP, image, pos);
-}
-
-void GUIButtonImage::setPressedForegroundImage(video::ITexture *image)
-{
- setForegroundImage(gui::EGBIS_IMAGE_DOWN, image);
-}
+ if (m_foreground_image != nullptr)
+ m_foreground_image->drop();
-void GUIButtonImage::setPressedForegroundImage(video::ITexture *image, const core::rect<s32> &pos)
-{
- setForegroundImage(gui::EGBIS_IMAGE_DOWN, image, pos);
+ m_foreground_image = image;
+ m_image->setImage(image);
}
-void GUIButtonImage::setHoveredForegroundImage(video::ITexture *image)
+//! Set element properties from a StyleSpec
+void GUIButtonImage::setFromStyle(const StyleSpec& style)
{
- setForegroundImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image);
- setForegroundImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image);
-}
-
-void GUIButtonImage::setHoveredForegroundImage(video::ITexture *image, const core::rect<s32> &pos)
-{
- setForegroundImage(gui::EGBIS_IMAGE_UP_MOUSEOVER, image, pos);
- setForegroundImage(gui::EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER, image, pos);
-}
-
-void GUIButtonImage::setFromStyle(const StyleSpec &style, ISimpleTextureSource *tsrc)
-{
- GUIButton::setFromStyle(style, tsrc);
+ GUIButton::setFromStyle(style);
video::IVideoDriver *driver = Environment->getVideoDriver();
- const core::position2di buttonCenter(AbsoluteRect.getCenter());
- core::position2d<s32> geom(buttonCenter);
if (style.isNotDefault(StyleSpec::FGIMG)) {
- video::ITexture *texture = style.getTexture(StyleSpec::FGIMG, tsrc);
-
- setForegroundImage(guiScalingImageButton(driver, texture, geom.X, geom.Y));
- setScaleImage(true);
- }
- if (style.isNotDefault(StyleSpec::FGIMG_HOVERED)) {
- video::ITexture *hovered_texture = style.getTexture(StyleSpec::FGIMG_HOVERED, tsrc);
-
- setHoveredForegroundImage(guiScalingImageButton(driver, hovered_texture, geom.X, geom.Y));
- setScaleImage(true);
- }
- if (style.isNotDefault(StyleSpec::FGIMG_PRESSED)) {
- video::ITexture *pressed_texture = style.getTexture(StyleSpec::FGIMG_PRESSED, tsrc);
+ video::ITexture *texture = style.getTexture(StyleSpec::FGIMG,
+ getTextureSource());
- setPressedForegroundImage(guiScalingImageButton(driver, pressed_texture, geom.X, geom.Y));
+ setForegroundImage(guiScalingImageButton(driver, texture,
+ AbsoluteRect.getWidth(), AbsoluteRect.getHeight()));
setScaleImage(true);
+ } else {
+ setForegroundImage(nullptr);
}
}
@@ -143,11 +81,12 @@ void GUIButtonImage::setScaleImage(bool scaleImage)
}
GUIButtonImage *GUIButtonImage::addButton(IGUIEnvironment *environment,
- const core::rect<s32> &rectangle, IGUIElement *parent, s32 id,
- const wchar_t *text, const wchar_t *tooltiptext)
+ const core::rect<s32> &rectangle, ISimpleTextureSource *tsrc,
+ IGUIElement *parent, s32 id, const wchar_t *text,
+ const wchar_t *tooltiptext)
{
GUIButtonImage *button = new GUIButtonImage(environment,
- parent ? parent : environment->getRootGUIElement(), id, rectangle);
+ parent ? parent : environment->getRootGUIElement(), id, rectangle, tsrc);
if (text)
button->setText(text);
diff --git a/src/gui/guiButtonImage.h b/src/gui/guiButtonImage.h
index 15901ee5d..59a25b4f0 100644
--- a/src/gui/guiButtonImage.h
+++ b/src/gui/guiButtonImage.h
@@ -17,6 +17,8 @@ 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 "guiButton.h"
#include "IGUIButton.h"
@@ -27,33 +29,23 @@ class GUIButtonImage : public GUIButton
public:
//! constructor
GUIButtonImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent,
- s32 id, core::rect<s32> rectangle, bool noclip = false);
-
- virtual bool OnEvent(const SEvent& event) override;
-
- void setForegroundImage(gui::EGUI_BUTTON_IMAGE_STATE state,
- video::ITexture *image = nullptr,
- const core::rect<s32> &sourceRect = core::rect<s32>(0, 0, 0, 0));
+ s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
+ bool noclip = false);
void setForegroundImage(video::ITexture *image = nullptr);
- void setForegroundImage(video::ITexture *image, const core::rect<s32> &pos);
-
- void setPressedForegroundImage(video::ITexture *image = nullptr);
- void setPressedForegroundImage(video::ITexture *image, const core::rect<s32> &pos);
-
- void setHoveredForegroundImage(video::ITexture *image = nullptr);
- void setHoveredForegroundImage(video::ITexture *image, const core::rect<s32> &pos);
- virtual void setFromStyle(const StyleSpec &style, ISimpleTextureSource *tsrc) override;
+ //! Set element properties from a StyleSpec
+ virtual void setFromStyle(const StyleSpec& style) override;
virtual void setScaleImage(bool scaleImage=true) override;
//! Do not drop returned handle
static GUIButtonImage *addButton(gui::IGUIEnvironment *environment,
- const core::rect<s32> &rectangle, IGUIElement *parent, s32 id,
- const wchar_t *text, const wchar_t *tooltiptext = L"");
+ const core::rect<s32> &rectangle, ISimpleTextureSource *tsrc,
+ IGUIElement *parent, s32 id, const wchar_t *text,
+ const wchar_t *tooltiptext = L"");
private:
- ButtonImage m_foreground_images[gui::EGBIS_COUNT];
+ video::ITexture *m_foreground_image = nullptr;
gui::IGUIImage *m_image;
};
diff --git a/src/gui/guiButtonItemImage.cpp b/src/gui/guiButtonItemImage.cpp
index 5c48b2acd..d8b9042ac 100644
--- a/src/gui/guiButtonItemImage.cpp
+++ b/src/gui/guiButtonItemImage.cpp
@@ -28,9 +28,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
using namespace irr;
using namespace gui;
-GUIButtonItemImage::GUIButtonItemImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent,
- s32 id, core::rect<s32> rectangle, std::string item, Client *client, bool noclip)
- : GUIButton (environment, parent, id, rectangle, noclip)
+GUIButtonItemImage::GUIButtonItemImage(gui::IGUIEnvironment *environment,
+ gui::IGUIElement *parent, s32 id, core::rect<s32> rectangle,
+ ISimpleTextureSource *tsrc, std::string item, Client *client,
+ bool noclip)
+ : GUIButton (environment, parent, id, rectangle, tsrc, noclip)
{
m_image = new GUIItemImage(environment, this, id,
core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()),
@@ -42,12 +44,13 @@ GUIButtonItemImage::GUIButtonItemImage(gui::IGUIEnvironment *environment, gui::I
}
GUIButtonItemImage *GUIButtonItemImage::addButton(IGUIEnvironment *environment,
- const core::rect<s32> &rectangle, IGUIElement *parent, s32 id,
- const wchar_t *text, std::string item, Client *client)
+ const core::rect<s32> &rectangle, ISimpleTextureSource *tsrc,
+ IGUIElement *parent, s32 id, const wchar_t *text, std::string item,
+ Client *client)
{
GUIButtonItemImage *button = new GUIButtonItemImage(environment,
parent ? parent : environment->getRootGUIElement(),
- id, rectangle, item, client);
+ id, rectangle, tsrc, item, client);
if (text)
button->setText(text);
diff --git a/src/gui/guiButtonItemImage.h b/src/gui/guiButtonItemImage.h
index 0a61874da..aad923bda 100644
--- a/src/gui/guiButtonItemImage.h
+++ b/src/gui/guiButtonItemImage.h
@@ -17,6 +17,8 @@ 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 "guiButton.h"
#include "IGUIButton.h"
@@ -30,13 +32,14 @@ class GUIButtonItemImage : public GUIButton
public:
//! constructor
GUIButtonItemImage(gui::IGUIEnvironment *environment, gui::IGUIElement *parent,
- s32 id, core::rect<s32> rectangle, std::string item,
- Client *client, bool noclip = false);
+ s32 id, core::rect<s32> rectangle, ISimpleTextureSource *tsrc,
+ std::string item, Client *client, bool noclip = false);
//! Do not drop returned handle
static GUIButtonItemImage *addButton(gui::IGUIEnvironment *environment,
- const core::rect<s32> &rectangle, IGUIElement *parent, s32 id,
- const wchar_t *text, std::string item, Client *client);
+ const core::rect<s32> &rectangle, ISimpleTextureSource *tsrc,
+ IGUIElement *parent, s32 id, const wchar_t *text, std::string item,
+ Client *client);
private:
std::string m_item_name;
diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp
index e67fae3c6..8de00c12f 100644
--- a/src/gui/guiChatConsole.cpp
+++ b/src/gui/guiChatConsole.cpp
@@ -74,7 +74,9 @@ GUIChatConsole::GUIChatConsole(
m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
}
- m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono);
+ u16 chat_font_size = g_settings->getU16("chat_font_size");
+ m_font = g_fontengine->getFont(chat_font_size != 0 ?
+ chat_font_size : FONT_SIZE_UNSPECIFIED, FM_Mono);
if (!m_font) {
errorstream << "GUIChatConsole: Unable to load mono font" << std::endl;
diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp
index 0d8bdf54e..58ac42740 100644
--- a/src/gui/guiConfirmRegistration.cpp
+++ b/src/gui/guiConfirmRegistration.cpp
@@ -40,10 +40,10 @@ const int ID_message = 266;
GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env,
gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, Client *client,
const std::string &playername, const std::string &password,
- bool *aborted) :
+ bool *aborted, ISimpleTextureSource *tsrc) :
GUIModalMenu(env, parent, id, menumgr),
m_client(client), m_playername(playername), m_password(password),
- m_aborted(aborted)
+ m_aborted(aborted), m_tsrc(tsrc)
{
#ifdef __ANDROID__
m_touchscreen_visible = false;
@@ -130,14 +130,14 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize)
core::rect<s32> 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, this, ID_confirm, text);
+ GUIButton::addButton(Environment, rect2, m_tsrc, this, ID_confirm, text);
delete[] text;
}
{
core::rect<s32> rect2(0, 0, 120 * s, 35 * s);
rect2 = rect2 + v2s32(size.X / 2 + 70 * s, ypos);
text = wgettext("Cancel");
- GUIButton::addButton(Environment, rect2, this, ID_cancel, text);
+ GUIButton::addButton(Environment, rect2, m_tsrc, this, ID_cancel, text);
delete[] text;
}
{
diff --git a/src/gui/guiConfirmRegistration.h b/src/gui/guiConfirmRegistration.h
index 42c07e4ed..d8387201d 100644
--- a/src/gui/guiConfirmRegistration.h
+++ b/src/gui/guiConfirmRegistration.h
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string>
class Client;
+class ISimpleTextureSource;
class GUIConfirmRegistration : public GUIModalMenu
{
@@ -32,7 +33,7 @@ public:
GUIConfirmRegistration(gui::IGUIEnvironment *env, gui::IGUIElement *parent,
s32 id, IMenuManager *menumgr, Client *client,
const std::string &playername, const std::string &password,
- bool *aborted);
+ bool *aborted, ISimpleTextureSource *tsrc);
~GUIConfirmRegistration();
void removeChildren();
@@ -63,4 +64,5 @@ private:
const std::string &m_password;
bool *m_aborted = nullptr;
std::wstring m_pass_confirm = L"";
+ ISimpleTextureSource *m_tsrc;
};
diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp
index 3107d64cd..b40707d01 100644
--- a/src/gui/guiEngine.cpp
+++ b/src/gui/guiEngine.cpp
@@ -144,10 +144,10 @@ GUIEngine::GUIEngine(JoystickController *joystick,
//create soundmanager
MenuMusicFetcher soundfetcher;
#if USE_SOUND
- if (g_settings->getBool("enable_sound"))
+ if (g_settings->getBool("enable_sound") && g_sound_manager_singleton.get())
m_sound_manager = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
#endif
- if(!m_sound_manager)
+ if (!m_sound_manager)
m_sound_manager = &dummySoundManager;
//create topleft header
diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp
index 59cd130ef..37edf3c4b 100644
--- a/src/gui/guiFormSpecMenu.cpp
+++ b/src/gui/guiFormSpecMenu.cpp
@@ -65,6 +65,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "guiInventoryList.h"
#include "guiItemImage.h"
#include "guiScrollBar.h"
+#include "guiScrollContainer.h"
#include "guiTable.h"
#include "intlGUIEditBox.h"
#include "guiHyperText.h"
@@ -143,6 +144,8 @@ GUIFormSpecMenu::~GUIFormSpecMenu()
tooltip_rect_it.first->drop();
for (auto &clickthrough_it : m_clickthrough_elements)
clickthrough_it->drop();
+ for (auto &scroll_container_it : m_scroll_containers)
+ scroll_container_it.second->drop();
delete m_selected_item;
delete m_form_src;
@@ -351,6 +354,102 @@ void GUIFormSpecMenu::parseContainerEnd(parserData* data)
}
}
+void GUIFormSpecMenu::parseScrollContainer(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ';');
+
+ if (parts.size() < 4 ||
+ (parts.size() > 5 && m_formspec_version <= FORMSPEC_API_VERSION)) {
+ errorstream << "Invalid scroll_container start element (" << parts.size()
+ << "): '" << element << "'" << std::endl;
+ return;
+ }
+
+ std::vector<std::string> v_pos = split(parts[0], ',');
+ std::vector<std::string> v_geom = split(parts[1], ',');
+ std::string scrollbar_name = parts[2];
+ std::string orientation = parts[3];
+ f32 scroll_factor = 0.1f;
+ if (parts.size() >= 5 && !parts[4].empty())
+ scroll_factor = stof(parts[4]);
+
+ MY_CHECKPOS("scroll_container", 0);
+ MY_CHECKGEOM("scroll_container", 1);
+
+ v2s32 pos = getRealCoordinateBasePos(v_pos);
+ v2s32 geom = getRealCoordinateGeometry(v_geom);
+
+ if (orientation == "vertical")
+ scroll_factor *= -imgsize.Y;
+ else if (orientation == "horizontal")
+ scroll_factor *= -imgsize.X;
+ else
+ warningstream << "GUIFormSpecMenu::parseScrollContainer(): "
+ << "Invalid scroll_container orientation: " << orientation
+ << std::endl;
+
+ // old parent (at first: this)
+ // ^ is parent of clipper
+ // ^ is parent of mover
+ // ^ is parent of other elements
+
+ // make clipper
+ core::rect<s32> rect_clipper = core::rect<s32>(pos, pos + geom);
+
+ gui::IGUIElement *clipper = new gui::IGUIElement(EGUIET_ELEMENT, Environment,
+ data->current_parent, 0, rect_clipper);
+
+ // make mover
+ FieldSpec spec_mover(
+ "",
+ L"",
+ L"",
+ 258 + m_fields.size()
+ );
+
+ core::rect<s32> rect_mover = core::rect<s32>(0, 0, geom.X, geom.Y);
+
+ GUIScrollContainer *mover = new GUIScrollContainer(Environment,
+ clipper, spec_mover.fid, rect_mover, orientation, scroll_factor);
+
+ data->current_parent = mover;
+
+ m_scroll_containers.emplace_back(scrollbar_name, mover);
+
+ m_fields.push_back(spec_mover);
+
+ clipper->drop();
+
+ // remove interferring offset of normal containers
+ container_stack.push(pos_offset);
+ pos_offset.X = 0.0f;
+ pos_offset.Y = 0.0f;
+}
+
+void GUIFormSpecMenu::parseScrollContainerEnd(parserData *data)
+{
+ if (data->current_parent == this || data->current_parent->getParent() == this ||
+ container_stack.empty()) {
+ errorstream << "Invalid scroll_container end element, "
+ << "no matching scroll_container start element" << std::endl;
+ return;
+ }
+
+ if (pos_offset.getLengthSQ() != 0.0f) {
+ // pos_offset is only set by containers and scroll_containers.
+ // scroll_containers always set it to 0,0 which means that if it is
+ // not 0,0, it is a normal container that was opened last, not a
+ // scroll_container
+ errorstream << "Invalid scroll_container end element, "
+ << "an inner container was left open" << std::endl;
+ return;
+ }
+
+ data->current_parent = data->current_parent->getParent()->getParent();
+ pos_offset = container_stack.top();
+ container_stack.pop();
+}
+
void GUIFormSpecMenu::parseList(parserData *data, const std::string &element)
{
if (m_client == 0) {
@@ -390,38 +489,10 @@ void GUIFormSpecMenu::parseList(parserData *data, const std::string &element)
start_i = stoi(startindex);
if (geom.X < 0 || geom.Y < 0 || start_i < 0) {
- errorstream<< "Invalid list element: '" << element << "'" << std::endl;
+ errorstream << "Invalid list element: '" << element << "'" << std::endl;
return;
}
- // check for the existence of inventory and list
- Inventory *inv = m_invmgr->getInventory(loc);
- if (!inv) {
- warningstream << "GUIFormSpecMenu::parseList(): "
- << "The inventory location "
- << "\"" << loc.dump() << "\" doesn't exist"
- << std::endl;
- return;
- }
- InventoryList *ilist = inv->getList(listname);
- if (!ilist) {
- warningstream << "GUIFormSpecMenu::parseList(): "
- << "The inventory list \"" << listname << "\" "
- << "@ \"" << loc.dump() << "\" doesn't exist"
- << std::endl;
- return;
- }
-
- // trim geom if it is larger than the actual inventory size
- s32 list_size = (s32)ilist->getSize();
- if (list_size < geom.X * geom.Y + start_i) {
- list_size -= MYMAX(start_i, 0);
- geom.Y = list_size / geom.X;
- geom.Y += list_size % geom.X > 0 ? 1 : 0;
- if (geom.Y <= 1)
- geom.X = list_size;
- }
-
if (!data->explicit_size)
warningstream << "invalid use of list without a size[] element" << std::endl;
@@ -443,9 +514,9 @@ void GUIFormSpecMenu::parseList(parserData *data, const std::string &element)
pos.X + (geom.X - 1) * slot_spacing.X + imgsize.X,
pos.Y + (geom.Y - 1) * slot_spacing.Y + imgsize.Y);
- GUIInventoryList *e = new GUIInventoryList(Environment, this, spec.fid,
- rect, m_invmgr, loc, listname, geom, start_i, imgsize, slot_spacing,
- this, data->inventorylist_options, m_font);
+ GUIInventoryList *e = new GUIInventoryList(Environment, data->current_parent,
+ spec.fid, rect, m_invmgr, loc, listname, geom, start_i, imgsize,
+ slot_spacing, this, data->inventorylist_options, m_font);
m_inventorylists.push_back(e);
m_fields.push_back(spec);
@@ -550,10 +621,10 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
spec.ftype = f_CheckBox;
- gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect, this,
- spec.fid, spec.flabel.c_str());
+ gui::IGUICheckBox *e = Environment->addCheckBox(fselected, rect,
+ data->current_parent, spec.fid, spec.flabel.c_str());
- auto style = getStyleForElement("checkbox", name);
+ auto style = getDefaultStyleForElement("checkbox", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
if (spec.fname == data->focused_fieldname) {
@@ -610,10 +681,10 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen
spec.ftype = f_ScrollBar;
spec.send = true;
- GUIScrollBar *e = new GUIScrollBar(Environment, this, spec.fid, rect,
- is_horizontal, true);
+ GUIScrollBar *e = new GUIScrollBar(Environment, data->current_parent,
+ spec.fid, rect, is_horizontal, true);
- auto style = getStyleForElement("scrollbar", name);
+ auto style = getDefaultStyleForElement("scrollbar", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setArrowsVisible(data->scrollbar_options.arrow_visiblity);
@@ -737,10 +808,11 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
1
);
core::rect<s32> rect(pos, pos + geom);
- gui::IGUIImage *e = Environment->addImage(rect, this, spec.fid, 0, true);
+ gui::IGUIImage *e = Environment->addImage(rect, data->current_parent,
+ spec.fid, 0, true);
e->setImage(texture);
e->setScaleImage(true);
- auto style = getStyleForElement("image", spec.fname);
+ auto style = getDefaultStyleForElement("image", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
m_fields.push_back(spec);
@@ -774,9 +846,9 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
L"",
258 + m_fields.size()
);
- gui::IGUIImage *e = Environment->addImage(texture, pos, true, this,
- spec.fid, 0);
- auto style = getStyleForElement("image", spec.fname);
+ gui::IGUIImage *e = Environment->addImage(texture, pos, true,
+ data->current_parent, spec.fid, 0);
+ auto style = getDefaultStyleForElement("image", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
m_fields.push_back(spec);
@@ -841,9 +913,11 @@ void GUIFormSpecMenu::parseAnimatedImage(parserData *data, const std::string &el
if (parts.size() >= 7)
e->setFrameIndex(stoi(parts[6]) - 1);
- auto style = getStyleForElement("animated_image", spec.fname, "image");
+ auto style = getDefaultStyleForElement("animated_image", spec.fname, "image");
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
- e->drop();
+
+ // Animated images should let events through
+ m_clickthrough_elements.push_back(e);
m_fields.push_back(spec);
}
@@ -886,9 +960,9 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen
);
spec.ftype = f_ItemImage;
- GUIItemImage *e = new GUIItemImage(Environment, this, spec.fid,
+ GUIItemImage *e = new GUIItemImage(Environment, data->current_parent, spec.fid,
core::rect<s32>(pos, pos + geom), name, m_font, m_client);
- auto style = getStyleForElement("item_image", spec.fname);
+ auto style = getDefaultStyleForElement("item_image", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
// item images should let events through
@@ -949,10 +1023,11 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
if(type == "button_exit")
spec.is_exit = true;
- GUIButton *e = GUIButton::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str());
+ GUIButton *e = GUIButton::addButton(Environment, rect, m_tsrc,
+ data->current_parent, spec.fid, spec.flabel.c_str());
auto style = getStyleForElement(type, name, (type != "button") ? "button" : "");
- e->setFromStyle(style, m_tsrc);
+ e->setStyles(style);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@@ -1140,7 +1215,8 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
}
//now really show table
- GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc);
+ GUITable *e = new GUITable(Environment, data->current_parent, spec.fid,
+ rect, m_tsrc);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@@ -1155,7 +1231,7 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
if (!str_initial_selection.empty() && str_initial_selection != "0")
e->setSelected(stoi(str_initial_selection));
- auto style = getStyleForElement("table", name);
+ auto style = getDefaultStyleForElement("table", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
m_tables.emplace_back(spec, e);
@@ -1216,7 +1292,8 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element
}
//now really show list
- GUITable *e = new GUITable(Environment, this, spec.fid, rect, m_tsrc);
+ GUITable *e = new GUITable(Environment, data->current_parent, spec.fid,
+ rect, m_tsrc);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@@ -1231,7 +1308,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element
if (!str_initial_selection.empty() && str_initial_selection != "0")
e->setSelected(stoi(str_initial_selection));
- auto style = getStyleForElement("textlist", name);
+ auto style = getDefaultStyleForElement("textlist", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
m_tables.emplace_back(spec, e);
@@ -1292,7 +1369,8 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element
spec.send = true;
//now really show list
- gui::IGUIComboBox *e = Environment->addComboBox(rect, this, spec.fid);
+ gui::IGUIComboBox *e = Environment->addComboBox(rect, data->current_parent,
+ spec.fid);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@@ -1306,7 +1384,7 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element
if (!str_initial_selection.empty())
e->setSelected(stoi(str_initial_selection)-1);
- auto style = getStyleForElement("dropdown", name);
+ auto style = getDefaultStyleForElement("dropdown", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
m_fields.push_back(spec);
@@ -1378,7 +1456,8 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
);
spec.send = true;
- gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid);
+ gui::IGUIEditBox *e = Environment->addEditBox(0, rect, true,
+ data->current_parent, spec.fid);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@@ -1389,12 +1468,12 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
rect.UpperLeftCorner.Y -= font_height;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
- this, 0);
+ data->current_parent, 0);
}
e->setPasswordBox(true,L'*');
- auto style = getStyleForElement("pwdfield", name, "field");
+ auto style = getDefaultStyleForElement("pwdfield", name, "field");
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
@@ -1424,7 +1503,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
if (!is_editable && !is_multiline) {
// spec field id to 0, this stops submit searching for a value that isn't there
gui::StaticText::add(Environment, spec.flabel.c_str(), rect, false, true,
- this, spec.fid);
+ data->current_parent, 0);
return;
}
@@ -1442,19 +1521,19 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
if (use_intl_edit_box && g_settings->getBool("freetype")) {
e = new gui::intlGUIEditBox(spec.fdefault.c_str(), true, Environment,
- this, spec.fid, rect, is_editable, is_multiline);
+ data->current_parent, spec.fid, rect, is_editable, is_multiline);
} else {
if (is_multiline) {
- e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true,
- Environment, this, spec.fid, rect, is_editable, true);
+ e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true, Environment,
+ data->current_parent, spec.fid, rect, is_editable, true);
} else if (is_editable) {
- e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this,
- spec.fid);
+ e = Environment->addEditBox(spec.fdefault.c_str(), rect, true,
+ data->current_parent, spec.fid);
e->grab();
}
}
- auto style = getStyleForElement(is_multiline ? "textarea" : "field", spec.fname);
+ auto style = getDefaultStyleForElement(is_multiline ? "textarea" : "field", spec.fname);
if (e) {
if (is_editable && spec.fname == data->focused_fieldname)
@@ -1490,7 +1569,7 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
rect.UpperLeftCorner.Y -= font_height;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
IGUIElement *t = gui::StaticText::add(Environment, spec.flabel.c_str(),
- rect, false, true, this, 0);
+ rect, false, true, data->current_parent, 0);
if (t)
t->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
@@ -1670,8 +1749,8 @@ void GUIFormSpecMenu::parseHyperText(parserData *data, const std::string &elemen
);
spec.ftype = f_HyperText;
- GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment, this,
- spec.fid, rect, m_client, m_tsrc);
+ GUIHyperText *e = new GUIHyperText(spec.flabel.c_str(), Environment,
+ data->current_parent, spec.fid, rect, m_client, m_tsrc);
e->drop();
m_fields.push_back(spec);
@@ -1749,10 +1828,11 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
4
);
gui::IGUIStaticText *e = gui::StaticText::add(Environment,
- spec.flabel.c_str(), rect, false, false, this, spec.fid);
+ spec.flabel.c_str(), rect, false, false, data->current_parent,
+ spec.fid);
e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER);
- auto style = getStyleForElement("label", spec.fname);
+ auto style = getDefaultStyleForElement("label", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
@@ -1829,10 +1909,10 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen
258 + m_fields.size()
);
gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(),
- rect, false, false, this, spec.fid);
+ rect, false, false, data->current_parent, spec.fid);
e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
- auto style = getStyleForElement("vertlabel", spec.fname, "label");
+ auto style = getDefaultStyleForElement("vertlabel", spec.fname, "label");
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
@@ -1863,17 +1943,8 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
MY_CHECKPOS("imagebutton",0);
MY_CHECKGEOM("imagebutton",1);
- bool noclip = false;
- bool drawborder = true;
std::string pressed_image_name;
- if (parts.size() >= 7) {
- if (parts[5] == "true")
- noclip = true;
- if (parts[6] == "false")
- drawborder = false;
- }
-
if (parts.size() >= 8) {
pressed_image_name = parts[7];
}
@@ -1911,35 +1982,30 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
if (type == "image_button_exit")
spec.is_exit = true;
- GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str());
+ GUIButtonImage *e = GUIButtonImage::addButton(Environment, rect, m_tsrc,
+ data->current_parent, spec.fid, spec.flabel.c_str());
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
}
auto style = getStyleForElement("image_button", spec.fname);
- e->setFromStyle(style, m_tsrc);
- // We explicitly handle these arguments *after* the style properties in
- // order to override them if they are provided
+ // Override style properties with values specified directly in the element
if (!image_name.empty())
- {
- video::ITexture *texture = m_tsrc->getTexture(image_name);
- e->setForegroundImage(guiScalingImageButton(
- Environment->getVideoDriver(), texture, geom.X, geom.Y));
- }
- if (!pressed_image_name.empty()) {
- video::ITexture *pressed_texture = m_tsrc->getTexture(pressed_image_name);
- e->setPressedForegroundImage(guiScalingImageButton(
- Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
- }
- e->setScaleImage(true);
+ style[StyleSpec::STATE_DEFAULT].set(StyleSpec::FGIMG, image_name);
+
+ if (!pressed_image_name.empty())
+ style[StyleSpec::STATE_PRESSED].set(StyleSpec::FGIMG, pressed_image_name);
if (parts.size() >= 7) {
- e->setNotClipped(noclip);
- e->setDrawBorder(drawborder);
+ style[StyleSpec::STATE_DEFAULT].set(StyleSpec::NOCLIP, parts[5]);
+ style[StyleSpec::STATE_DEFAULT].set(StyleSpec::BORDER, parts[6]);
}
+ e->setStyles(style);
+ e->setScaleImage(true);
+
m_fields.push_back(spec);
return;
}
@@ -1961,7 +2027,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
// Width is not here because tabs are the width of the text, and
// there's no reason to change that.
unsigned int i = 0;
- std::vector<std::string> v_geom = {"1", "0.75"}; // Dummy width and default height
+ std::vector<std::string> v_geom = {"1", "1"}; // Dummy width and height
bool auto_width = true;
if (parts.size() == 7) {
i++;
@@ -2005,6 +2071,9 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
pos = getRealCoordinateBasePos(v_pos);
geom = getRealCoordinateGeometry(v_geom);
+ // Set default height
+ if (parts.size() <= 6)
+ geom.Y = m_btn_height * 2;
pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top.
if (auto_width)
geom.X = DesiredRect.getWidth(); // Set automatic width
@@ -2023,8 +2092,8 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
pos.Y+geom.Y);
- gui::IGUITabControl *e = Environment->addTabControl(rect, this,
- show_background, show_border, spec.fid);
+ gui::IGUITabControl *e = Environment->addTabControl(rect,
+ data->current_parent, show_background, show_border, spec.fid);
e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT,
irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT);
e->setTabHeight(geom.Y);
@@ -2033,7 +2102,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
Environment->setFocus(e);
}
- auto style = getStyleForElement("tabheader", name);
+ auto style = getDefaultStyleForElement("tabheader", name);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true));
for (const std::string &button : buttons) {
@@ -2059,7 +2128,6 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element)
{
-
if (m_client == 0) {
warningstream << "invalid use of item_image_button with m_client==0"
<< std::endl;
@@ -2118,10 +2186,12 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
2
);
- GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment, rect, this, spec_btn.fid, spec_btn.flabel.c_str(), item_name, m_client);
+ GUIButtonItemImage *e_btn = GUIButtonItemImage::addButton(Environment,
+ rect, m_tsrc, data->current_parent, spec_btn.fid, spec_btn.flabel.c_str(),
+ item_name, m_client);
auto style = getStyleForElement("item_image_button", spec_btn.fname, "image_button");
- e_btn->setFromStyle(style, m_tsrc);
+ e_btn->setStyles(style);
if (spec_btn.fname == data->focused_fieldname) {
Environment->setFocus(e_btn);
@@ -2175,9 +2245,10 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
core::rect<s32> rect(pos, pos + geom);
- GUIBox *e = new GUIBox(Environment, this, spec.fid, rect, tmp_color);
+ GUIBox *e = new GUIBox(Environment, data->current_parent, spec.fid,
+ rect, tmp_color);
- auto style = getStyleForElement("box", spec.fname);
+ auto style = getDefaultStyleForElement("box", spec.fname);
e->setNotClipped(style.getBool(StyleSpec::NOCLIP, m_formspec_version < 3));
e->drop();
@@ -2327,7 +2398,7 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
core::rect<s32> rect(pos, pos + geom);
gui::IGUIElement *e = new gui::IGUIElement(EGUIET_ELEMENT, Environment,
- this, fieldspec.fid, rect);
+ data->current_parent, fieldspec.fid, rect);
// the element the rect tooltip is bound to should not block mouse-clicks
e->setVisible(false);
@@ -2469,6 +2540,7 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b
StyleSpec spec;
+ // Parse properties
for (size_t i = 1; i < parts.size(); i++) {
size_t equal_pos = parts[i].find('=');
if (equal_pos == std::string::npos) {
@@ -2490,7 +2562,7 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b
<< "'" << std::endl;
property_warned.insert(propname);
}
- return false;
+ continue;
}
spec.set(prop, value);
@@ -2500,16 +2572,92 @@ bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, b
for (size_t sel = 0; sel < selectors.size(); sel++) {
std::string selector = trim(selectors[sel]);
- if (selector.empty()) {
- errorstream << "Invalid style element (Empty selector): '" << element
- << "'" << std::endl;
+ // Copy the style properties to a new StyleSpec
+ // This allows a separate state mask per-selector
+ StyleSpec selector_spec = spec;
+
+ // Parse state information, if it exists
+ bool state_valid = true;
+ size_t state_pos = selector.find(':');
+ if (state_pos != std::string::npos) {
+ std::string state_str = selector.substr(state_pos + 1);
+ selector = selector.substr(0, state_pos);
+
+ if (state_str.empty()) {
+ errorstream << "Invalid style element (Invalid state): '" << element
+ << "'" << std::endl;
+ state_valid = false;
+ } else {
+ std::vector<std::string> states = split(state_str, '+');
+ for (std::string &state : states) {
+ StyleSpec::State converted = StyleSpec::getStateByName(state);
+ if (converted == StyleSpec::STATE_INVALID) {
+ infostream << "Unknown style state " << state <<
+ " in element '" << element << "'" << std::endl;
+ state_valid = false;
+ break;
+ }
+
+ selector_spec.addState(converted);
+ }
+ }
+ }
+
+ if (!state_valid) {
+ // Skip this selector
continue;
}
if (style_type) {
- theme_by_type[selector] |= spec;
+ theme_by_type[selector].push_back(selector_spec);
} else {
- theme_by_name[selector] |= spec;
+ theme_by_name[selector].push_back(selector_spec);
+ }
+
+ // Backwards-compatibility for existing _hovered/_pressed properties
+ if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED)
+ || selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED)
+ || selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) {
+ StyleSpec hover_spec;
+ hover_spec.addState(StyleSpec::STATE_HOVERED);
+
+ if (selector_spec.hasProperty(StyleSpec::BGCOLOR_HOVERED)) {
+ hover_spec.set(StyleSpec::BGCOLOR, selector_spec.get(StyleSpec::BGCOLOR_HOVERED, ""));
+ }
+ if (selector_spec.hasProperty(StyleSpec::BGIMG_HOVERED)) {
+ hover_spec.set(StyleSpec::BGIMG, selector_spec.get(StyleSpec::BGIMG_HOVERED, ""));
+ }
+ if (selector_spec.hasProperty(StyleSpec::FGIMG_HOVERED)) {
+ hover_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_HOVERED, ""));
+ }
+
+ if (style_type) {
+ theme_by_type[selector].push_back(hover_spec);
+ } else {
+ theme_by_name[selector].push_back(hover_spec);
+ }
+ }
+ if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED)
+ || selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED)
+ || selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) {
+ StyleSpec press_spec;
+ press_spec.addState(StyleSpec::STATE_PRESSED);
+
+ if (selector_spec.hasProperty(StyleSpec::BGCOLOR_PRESSED)) {
+ press_spec.set(StyleSpec::BGCOLOR, selector_spec.get(StyleSpec::BGCOLOR_PRESSED, ""));
+ }
+ if (selector_spec.hasProperty(StyleSpec::BGIMG_PRESSED)) {
+ press_spec.set(StyleSpec::BGIMG, selector_spec.get(StyleSpec::BGIMG_PRESSED, ""));
+ }
+ if (selector_spec.hasProperty(StyleSpec::FGIMG_PRESSED)) {
+ press_spec.set(StyleSpec::FGIMG, selector_spec.get(StyleSpec::FGIMG_PRESSED, ""));
+ }
+
+ if (style_type) {
+ theme_by_type[selector].push_back(press_spec);
+ } else {
+ theme_by_name[selector].push_back(press_spec);
+ }
}
}
@@ -2525,24 +2673,12 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
if (parseVersionDirect(element))
return;
- std::vector<std::string> parts = split(element,'[');
-
- // ugly workaround to keep compatibility
- if (parts.size() > 2) {
- if (trim(parts[0]) == "image") {
- for (unsigned int i=2;i< parts.size(); i++) {
- parts[1] += "[" + parts[i];
- }
- }
- else { return; }
- }
-
- if (parts.size() < 2) {
+ size_t pos = element.find('[');
+ if (pos == std::string::npos)
return;
- }
- std::string type = trim(parts[0]);
- std::string description = trim(parts[1]);
+ std::string type = trim(element.substr(0, pos));
+ std::string description = element.substr(pos+1);
if (type == "container") {
parseContainer(data, description);
@@ -2709,6 +2845,16 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
return;
}
+ if (type == "scroll_container") {
+ parseScrollContainer(data, description);
+ return;
+ }
+
+ if (type == "scroll_container_end") {
+ parseScrollContainerEnd(data);
+ return;
+ }
+
// Ignore others
infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
<< std::endl;
@@ -2765,8 +2911,10 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
tooltip_rect_it.first->drop();
for (auto &clickthrough_it : m_clickthrough_elements)
clickthrough_it->drop();
+ for (auto &scroll_container_it : m_scroll_containers)
+ scroll_container_it.second->drop();
- mydata.size= v2s32(100,100);
+ mydata.size = v2s32(100, 100);
mydata.screensize = screensize;
mydata.offset = v2f32(0.5f, 0.5f);
mydata.anchor = v2f32(0.5f, 0.5f);
@@ -2775,6 +2923,9 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
// Base position of contents of form
mydata.basepos = getBasePos();
+ // the parent for the parsed elements
+ mydata.current_parent = this;
+
m_inventorylists.clear();
m_backgrounds.clear();
m_tables.clear();
@@ -2785,6 +2936,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
m_tooltip_rects.clear();
m_inventory_rings.clear();
m_dropdowns.clear();
+ m_scroll_containers.clear();
theme_by_name.clear();
theme_by_type.clear();
m_clickthrough_elements.clear();
@@ -3051,11 +3203,24 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
parseElement(&mydata, elements[i]);
}
- if (!container_stack.empty()) {
+ if (mydata.current_parent != this) {
+ errorstream << "Invalid formspec string: scroll_container was never closed!"
+ << std::endl;
+ } else if (!container_stack.empty()) {
errorstream << "Invalid formspec string: container was never closed!"
<< std::endl;
}
+ // get the scrollbar elements for scroll_containers
+ for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers) {
+ for (const std::pair<FieldSpec, GUIScrollBar *> &b : m_scrollbars) {
+ if (c.first == b.first.fname) {
+ c.second->setScrollBar(b.second);
+ break;
+ }
+ }
+ }
+
// If there are fields without explicit size[], add a "Proceed"
// button and adjust size to fit all the fields.
if (mydata.simple_field_count > 0 && !mydata.explicit_size) {
@@ -3080,7 +3245,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
size.X / 2 - 70 + 140, pos.Y + m_btn_height * 2
);
const wchar_t *text = wgettext("Proceed");
- GUIButton::addButton(Environment, mydata.rect, this, 257, text);
+ GUIButton::addButton(Environment, mydata.rect, m_tsrc, this, 257, text);
delete[] text;
}
}
@@ -3146,28 +3311,24 @@ bool GUIFormSpecMenu::getAndroidUIInput()
if (!hasAndroidUIInput())
return false;
+ // still waiting
+ if (porting::getInputDialogState() == -1)
+ return true;
+
std::string fieldname = m_jni_field_name;
m_jni_field_name.clear();
- for (std::vector<FieldSpec>::iterator iter = m_fields.begin();
- iter != m_fields.end(); ++iter) {
-
- if (iter->fname != fieldname) {
+ for (const FieldSpec &field : m_fields) {
+ if (field.fname != fieldname)
continue;
- }
- IGUIElement *tochange = getElementFromId(iter->fid, true);
- if (tochange == 0) {
- return false;
- }
+ IGUIElement *element = getElementFromId(field.fid, true);
- if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) {
+ if (!element || element->getType() != irr::gui::EGUIET_EDIT_BOX)
return false;
- }
std::string text = porting::getInputDialogValue();
-
- ((gui::IGUIEditBox *)tochange)->setText(utf8_to_wide(text).c_str());
+ ((gui::IGUIEditBox *)element)->setText(utf8_to_wide(text).c_str());
}
return false;
}
@@ -3314,8 +3475,12 @@ void GUIFormSpecMenu::drawMenu()
bool hovered_element_found = false;
if (hovered != NULL) {
- s32 id = hovered->getID();
+ if (m_show_debug) {
+ core::rect<s32> rect = hovered->getAbsoluteClippingRect();
+ driver->draw2DRectangle(0x22FFFF00, rect, &rect);
+ }
+ s32 id = hovered->getID();
u64 delta = 0;
if (id == -1) {
m_old_tooltip_id = id;
@@ -3395,6 +3560,10 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text,
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;
#endif
// Calculate and set the tooltip position
@@ -3837,6 +4006,10 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
(kp == getKeySetting("keymap_screenshot"))) {
m_client->makeScreenshot();
}
+
+ if (event.KeyInput.PressedDown && kp == getKeySetting("keymap_toggle_debug"))
+ m_show_debug = !m_show_debug;
+
if (event.KeyInput.PressedDown &&
(event.KeyInput.Key==KEY_RETURN ||
event.KeyInput.Key==KEY_UP ||
@@ -4344,6 +4517,12 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
}
}
+ if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
+ // move scroll_containers
+ for (const std::pair<std::string, GUIScrollContainer *> &c : m_scroll_containers)
+ c.second->onScrollEvent(event.GUIEvent.Caller);
+ }
+
if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
if (event.GUIEvent.Caller->getID() > 257) {
bool close_on_enter = true;
@@ -4432,25 +4611,46 @@ std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
return L"";
}
-StyleSpec GUIFormSpecMenu::getStyleForElement(const std::string &type,
+StyleSpec GUIFormSpecMenu::getDefaultStyleForElement(const std::string &type,
const std::string &name, const std::string &parent_type) {
- StyleSpec ret;
+ return getStyleForElement(type, name, parent_type)[StyleSpec::STATE_DEFAULT];
+}
+
+std::array<StyleSpec, StyleSpec::NUM_STATES> GUIFormSpecMenu::getStyleForElement(
+ const std::string &type, const std::string &name, const std::string &parent_type)
+{
+ std::array<StyleSpec, StyleSpec::NUM_STATES> ret;
+
+ auto it = theme_by_type.find("*");
+ if (it != theme_by_type.end()) {
+ for (const StyleSpec &spec : it->second)
+ ret[(u32)spec.getState()] |= spec;
+ }
+
+ it = theme_by_name.find("*");
+ if (it != theme_by_name.end()) {
+ for (const StyleSpec &spec : it->second)
+ ret[(u32)spec.getState()] |= spec;
+ }
if (!parent_type.empty()) {
- auto it = theme_by_type.find(parent_type);
+ it = theme_by_type.find(parent_type);
if (it != theme_by_type.end()) {
- ret |= it->second;
+ for (const StyleSpec &spec : it->second)
+ ret[(u32)spec.getState()] |= spec;
}
}
- auto it = theme_by_type.find(type);
+ it = theme_by_type.find(type);
if (it != theme_by_type.end()) {
- ret |= it->second;
+ for (const StyleSpec &spec : it->second)
+ ret[(u32)spec.getState()] |= spec;
}
it = theme_by_name.find(name);
if (it != theme_by_name.end()) {
- ret |= it->second;
+ for (const StyleSpec &spec : it->second)
+ ret[(u32)spec.getState()] |= spec;
}
return ret;
diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h
index 17bfef205..28088416d 100644
--- a/src/gui/guiFormSpecMenu.h
+++ b/src/gui/guiFormSpecMenu.h
@@ -39,6 +39,7 @@ class ISimpleTextureSource;
class Client;
class GUIScrollBar;
class TexturePool;
+class GUIScrollContainer;
typedef enum {
f_Button,
@@ -274,11 +275,13 @@ protected:
v2s32 getRealCoordinateBasePos(const std::vector<std::string> &v_pos);
v2s32 getRealCoordinateGeometry(const std::vector<std::string> &v_geom);
- std::unordered_map<std::string, StyleSpec> theme_by_type;
- std::unordered_map<std::string, StyleSpec> theme_by_name;
+ std::unordered_map<std::string, std::vector<StyleSpec>> theme_by_type;
+ std::unordered_map<std::string, std::vector<StyleSpec>> theme_by_name;
std::unordered_set<std::string> property_warned;
- StyleSpec getStyleForElement(const std::string &type,
+ StyleSpec getDefaultStyleForElement(const std::string &type,
+ const std::string &name="", const std::string &parent_type="");
+ std::array<StyleSpec, StyleSpec::NUM_STATES> getStyleForElement(const std::string &type,
const std::string &name="", const std::string &parent_type="");
v2s32 padding;
@@ -308,6 +311,7 @@ protected:
std::vector<std::pair<FieldSpec, GUIScrollBar *>> m_scrollbars;
std::vector<std::pair<FieldSpec, std::vector<std::string>>> m_dropdowns;
std::vector<gui::IGUIElement *> m_clickthrough_elements;
+ std::vector<std::pair<std::string, GUIScrollContainer *>> m_scroll_containers;
GUIInventoryList::ItemSpec *m_selected_item = nullptr;
u16 m_selected_amount = 0;
@@ -341,6 +345,7 @@ private:
u16 m_formspec_version = 1;
std::string m_focused_element = "";
JoystickController *m_joystick;
+ bool m_show_debug = false;
typedef struct {
bool explicit_size;
@@ -356,6 +361,7 @@ private:
std::string focused_fieldname;
GUITable::TableOptions table_options;
GUITable::TableColumns table_columns;
+ gui::IGUIElement *current_parent = nullptr;
GUIInventoryList::Options inventorylist_options;
@@ -388,6 +394,8 @@ private:
void parseSize(parserData* data, const std::string &element);
void parseContainer(parserData* data, const std::string &element);
void parseContainerEnd(parserData* data);
+ void parseScrollContainer(parserData *data, const std::string &element);
+ void parseScrollContainerEnd(parserData *data);
void parseList(parserData* data, const std::string &element);
void parseListRing(parserData* data, const std::string &element);
void parseCheckbox(parserData* data, const std::string &element);
diff --git a/src/gui/guiHyperText.cpp b/src/gui/guiHyperText.cpp
index e107b5a3e..88931cdf9 100644
--- a/src/gui/guiHyperText.cpp
+++ b/src/gui/guiHyperText.cpp
@@ -917,20 +917,20 @@ void TextDrawer::place(const core::rect<s32> &dest_rect)
// Draw text in a rectangle with a given offset. Items are actually placed in
// relative (to upper left corner) coordinates.
-void TextDrawer::draw(const core::rect<s32> &dest_rect,
+void TextDrawer::draw(const core::rect<s32> &clip_rect,
const core::position2d<s32> &dest_offset)
{
irr::video::IVideoDriver *driver = m_environment->getVideoDriver();
- core::position2d<s32> offset = dest_rect.UpperLeftCorner + dest_offset;
+ core::position2d<s32> offset = dest_offset;
offset.Y += m_voffset;
if (m_text.background_type == ParsedText::BACKGROUND_COLOR)
- driver->draw2DRectangle(m_text.background_color, dest_rect);
+ driver->draw2DRectangle(m_text.background_color, clip_rect);
for (auto &p : m_text.m_paragraphs) {
for (auto &el : p.elements) {
core::rect<s32> rect(el.pos + offset, el.dim);
- if (!rect.isRectCollided(dest_rect))
+ if (!rect.isRectCollided(clip_rect))
continue;
switch (el.type) {
@@ -947,7 +947,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
if (el.type == ParsedText::ELEMENT_TEXT)
el.font->draw(el.text, rect, color, false, true,
- &dest_rect);
+ &clip_rect);
if (el.underline && el.drawwidth) {
s32 linepos = el.pos.Y + offset.Y +
@@ -958,7 +958,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
el.pos.X + offset.X + el.drawwidth,
linepos + (el.baseline >> 3));
- driver->draw2DRectangle(color, linerect, &dest_rect);
+ driver->draw2DRectangle(color, linerect, &clip_rect);
}
} break;
@@ -972,7 +972,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
irr::core::rect<s32>(
core::position2d<s32>(0, 0),
texture->getOriginalSize()),
- &dest_rect, 0, true);
+ &clip_rect, 0, true);
} break;
case ParsedText::ELEMENT_ITEM: {
@@ -982,7 +982,7 @@ void TextDrawer::draw(const core::rect<s32> &dest_rect,
drawItemStack(
m_environment->getVideoDriver(),
- g_fontengine->getFont(), item, rect, &dest_rect,
+ g_fontengine->getFont(), item, rect, &clip_rect,
m_client, IT_ROT_OTHER, el.angle, el.rotation
);
} break;
@@ -1094,6 +1094,7 @@ bool GUIHyperText::OnEvent(const SEvent &event)
m_text_scrollpos.Y = -m_vscrollbar->getPos();
m_drawer.draw(m_display_text_rect, m_text_scrollpos);
checkHover(event.MouseInput.X, event.MouseInput.Y);
+ return true;
} else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
ParsedText::Element *element = getElementAt(
@@ -1151,7 +1152,8 @@ void GUIHyperText::draw()
m_vscrollbar->setPos(0);
m_vscrollbar->setVisible(false);
}
- m_drawer.draw(m_display_text_rect, m_text_scrollpos);
+ m_drawer.draw(AbsoluteClippingRect,
+ m_display_text_rect.UpperLeftCorner + m_text_scrollpos);
// draw children
IGUIElement::draw();
diff --git a/src/gui/guiHyperText.h b/src/gui/guiHyperText.h
index c55f8a705..5b936262e 100644
--- a/src/gui/guiHyperText.h
+++ b/src/gui/guiHyperText.h
@@ -174,7 +174,7 @@ public:
void place(const core::rect<s32> &dest_rect);
inline s32 getHeight() { return m_height; };
- void draw(const core::rect<s32> &dest_rect,
+ void draw(const core::rect<s32> &clip_rect,
const core::position2d<s32> &dest_offset);
ParsedText::Element *getElementAt(core::position2d<s32> pos);
ParsedText::Tag *m_hovertag;
diff --git a/src/gui/guiInventoryList.cpp b/src/gui/guiInventoryList.cpp
index 536471229..dfdb60448 100644
--- a/src/gui/guiInventoryList.cpp
+++ b/src/gui/guiInventoryList.cpp
@@ -47,7 +47,8 @@ GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env,
m_fs_menu(fs_menu),
m_options(options),
m_font(font),
- m_hovered_i(-1)
+ m_hovered_i(-1),
+ m_already_warned(false)
{
}
@@ -58,20 +59,27 @@ void GUIInventoryList::draw()
Inventory *inv = m_invmgr->getInventory(m_inventoryloc);
if (!inv) {
- warningstream << "GUIInventoryList::draw(): "
- << "The inventory location "
- << "\"" << m_inventoryloc.dump() << "\" doesn't exist anymore"
- << std::endl;
+ if (!m_already_warned) {
+ warningstream << "GUIInventoryList::draw(): "
+ << "The inventory location "
+ << "\"" << m_inventoryloc.dump() << "\" doesn't exist"
+ << std::endl;
+ m_already_warned = true;
+ }
return;
}
InventoryList *ilist = inv->getList(m_listname);
if (!ilist) {
- warningstream << "GUIInventoryList::draw(): "
- << "The inventory list \"" << m_listname << "\" @ \""
- << m_inventoryloc.dump() << "\" doesn't exist anymore"
- << std::endl;
+ if (!m_already_warned) {
+ warningstream << "GUIInventoryList::draw(): "
+ << "The inventory list \"" << m_listname << "\" @ \""
+ << m_inventoryloc.dump() << "\" doesn't exist"
+ << std::endl;
+ m_already_warned = true;
+ }
return;
}
+ m_already_warned = false;
video::IVideoDriver *driver = Environment->getVideoDriver();
Client *client = m_fs_menu->getClient();
@@ -80,9 +88,11 @@ void GUIInventoryList::draw()
core::rect<s32> imgrect(0, 0, m_slot_size.X, m_slot_size.Y);
v2s32 base_pos = AbsoluteRect.UpperLeftCorner;
+ const s32 list_size = (s32)ilist->getSize();
+
for (s32 i = 0; i < m_geom.X * m_geom.Y; i++) {
s32 item_i = i + m_start_item_i;
- if (item_i >= (s32)ilist->getSize())
+ if (item_i >= list_size)
break;
v2s32 p((i % m_geom.X) * m_slot_spacing.X,
@@ -192,10 +202,19 @@ bool GUIInventoryList::OnEvent(const SEvent &event)
s32 GUIInventoryList::getItemIndexAtPos(v2s32 p) const
{
+ // no item if no gui element at pointer
if (!IsVisible || AbsoluteClippingRect.getArea() <= 0 ||
!AbsoluteClippingRect.isPointInside(p))
return -1;
+ // there can not be an item if the inventory or the inventorylist does not exist
+ Inventory *inv = m_invmgr->getInventory(m_inventoryloc);
+ if (!inv)
+ return -1;
+ InventoryList *ilist = inv->getList(m_listname);
+ if (!ilist)
+ return -1;
+
core::rect<s32> imgrect(0, 0, m_slot_size.X, m_slot_size.Y);
v2s32 base_pos = AbsoluteRect.UpperLeftCorner;
@@ -210,7 +229,8 @@ s32 GUIInventoryList::getItemIndexAtPos(v2s32 p) const
rect.clipAgainst(AbsoluteClippingRect);
- if (rect.getArea() > 0 && rect.isPointInside(p))
+ if (rect.getArea() > 0 && rect.isPointInside(p) &&
+ i + m_start_item_i < (s32)ilist->getSize())
return i + m_start_item_i;
return -1;
diff --git a/src/gui/guiInventoryList.h b/src/gui/guiInventoryList.h
index fd2c3601b..28e95fbbf 100644
--- a/src/gui/guiInventoryList.h
+++ b/src/gui/guiInventoryList.h
@@ -107,7 +107,7 @@ private:
const InventoryLocation m_inventoryloc;
const std::string m_listname;
- // specifies the width and height of the inventorylist in itemslots
+ // the specified width and height of the shown inventorylist in itemslots
const v2s32 m_geom;
// the first item's index in inventory
const s32 m_start_item_i;
@@ -127,4 +127,7 @@ private:
// the index of the hovered item; -1 if no item is hovered
s32 m_hovered_i;
+
+ // 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 3f270fc7a..da0e25c23 100644
--- a/src/gui/guiKeyChangeMenu.cpp
+++ b/src/gui/guiKeyChangeMenu.cpp
@@ -82,8 +82,10 @@ enum
};
GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
- gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) :
-GUIModalMenu(env, parent, id, menumgr)
+ gui::IGUIElement* parent, s32 id, IMenuManager *menumgr,
+ ISimpleTextureSource *tsrc) :
+ GUIModalMenu(env, parent, id, menumgr),
+ m_tsrc(tsrc)
{
init_keys();
}
@@ -157,7 +159,7 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect += topleft + v2s32(offset.X + 150 * s, offset.Y - 5 * s);
const wchar_t *text = wgettext(k->key.name());
- k->button = GUIButton::addButton(Environment, rect, this, k->id, text);
+ k->button = GUIButton::addButton(Environment, rect, m_tsrc, this, k->id, text);
delete[] text;
}
if ((i + 1) % KMaxButtonPerColumns == 0) {
@@ -217,14 +219,14 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
core::rect<s32> 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, this, GUI_ID_BACK_BUTTON, text);
+ GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_BACK_BUTTON, text);
delete[] text;
}
{
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect += topleft + v2s32(size.X / 2 + 5 * s, size.Y - 40 * s);
const wchar_t *text = wgettext("Cancel");
- GUIButton::addButton(Environment, rect, this, GUI_ID_ABORT_BUTTON, text);
+ GUIButton::addButton(Environment, rect, m_tsrc, this, GUI_ID_ABORT_BUTTON, text);
delete[] text;
}
}
diff --git a/src/gui/guiKeyChangeMenu.h b/src/gui/guiKeyChangeMenu.h
index 528827fd9..1c0f40247 100644
--- a/src/gui/guiKeyChangeMenu.h
+++ b/src/gui/guiKeyChangeMenu.h
@@ -28,6 +28,8 @@
#include <string>
#include <vector>
+class ISimpleTextureSource;
+
struct key_setting
{
int id;
@@ -41,7 +43,7 @@ class GUIKeyChangeMenu : public GUIModalMenu
{
public:
GUIKeyChangeMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
- IMenuManager *menumgr);
+ IMenuManager *menumgr, ISimpleTextureSource *tsrc);
~GUIKeyChangeMenu();
void removeChildren();
@@ -74,4 +76,5 @@ private:
key_setting *active_key = nullptr;
gui::IGUIStaticText *key_used_text = nullptr;
std::vector<key_setting *> key_settings;
+ ISimpleTextureSource *m_tsrc;
};
diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp
index af91ce84c..965a2d6f7 100644
--- a/src/gui/guiPasswordChange.cpp
+++ b/src/gui/guiPasswordChange.cpp
@@ -38,10 +38,12 @@ const int ID_cancel = 261;
GUIPasswordChange::GUIPasswordChange(gui::IGUIEnvironment* env,
gui::IGUIElement* parent, s32 id,
IMenuManager *menumgr,
- Client* client
+ Client* client,
+ ISimpleTextureSource *tsrc
):
GUIModalMenu(env, parent, id, menumgr),
- m_client(client)
+ m_client(client),
+ m_tsrc(tsrc)
{
}
@@ -146,14 +148,14 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize)
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect = rect + v2s32(size.X / 4 + 56 * s, ypos);
text = wgettext("Change");
- GUIButton::addButton(Environment, rect, this, ID_change, text);
+ GUIButton::addButton(Environment, rect, m_tsrc, this, ID_change, text);
delete[] text;
}
{
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
rect = rect + v2s32(size.X / 4 + 185 * s, ypos);
text = wgettext("Cancel");
- GUIButton::addButton(Environment, rect, this, ID_cancel, text);
+ GUIButton::addButton(Environment, rect, m_tsrc, this, ID_cancel, text);
delete[] text;
}
diff --git a/src/gui/guiPasswordChange.h b/src/gui/guiPasswordChange.h
index 58541a399..7141100c0 100644
--- a/src/gui/guiPasswordChange.h
+++ b/src/gui/guiPasswordChange.h
@@ -23,12 +23,14 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <string>
class Client;
+class ISimpleTextureSource;
class GUIPasswordChange : public GUIModalMenu
{
public:
GUIPasswordChange(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
- IMenuManager *menumgr, Client *client);
+ IMenuManager *menumgr, Client *client,
+ ISimpleTextureSource *tsrc);
~GUIPasswordChange();
void removeChildren();
@@ -57,4 +59,5 @@ private:
std::wstring m_oldpass = L"";
std::wstring m_newpass = L"";
std::wstring m_newpass_confirm = L"";
+ ISimpleTextureSource *m_tsrc;
};
diff --git a/src/gui/guiScrollContainer.cpp b/src/gui/guiScrollContainer.cpp
new file mode 100644
index 000000000..88cdc7057
--- /dev/null
+++ b/src/gui/guiScrollContainer.cpp
@@ -0,0 +1,70 @@
+/*
+Minetest
+Copyright (C) 2020 DS
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "guiScrollContainer.h"
+
+GUIScrollContainer::GUIScrollContainer(gui::IGUIEnvironment *env,
+ gui::IGUIElement *parent, s32 id, const core::rect<s32> &rectangle,
+ const std::string &orientation, f32 scrollfactor) :
+ gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
+ m_scrollbar(nullptr), m_scrollfactor(scrollfactor)
+{
+ if (orientation == "vertical")
+ m_orientation = VERTICAL;
+ else if (orientation == "horizontal")
+ m_orientation = HORIZONTAL;
+ else
+ m_orientation = UNDEFINED;
+}
+
+bool GUIScrollContainer::OnEvent(const SEvent &event)
+{
+ if (event.EventType == EET_MOUSE_INPUT_EVENT &&
+ event.MouseInput.Event == EMIE_MOUSE_WHEEL &&
+ !event.MouseInput.isLeftPressed() && m_scrollbar) {
+ Environment->setFocus(m_scrollbar);
+ bool retval = m_scrollbar->OnEvent(event);
+
+ // a hacky fix for updating the hovering and co.
+ IGUIElement *hovered_elem = getElementFromPoint(core::position2d<s32>(
+ event.MouseInput.X, event.MouseInput.Y));
+ SEvent mov_event = event;
+ mov_event.MouseInput.Event = EMIE_MOUSE_MOVED;
+ Environment->postEventFromUser(mov_event);
+ if (hovered_elem)
+ hovered_elem->OnEvent(mov_event);
+
+ return retval;
+ }
+
+ return IGUIElement::OnEvent(event);
+}
+
+void GUIScrollContainer::updateScrolling()
+{
+ s32 pos = m_scrollbar->getPos();
+ core::rect<s32> rect = getRelativePosition();
+
+ if (m_orientation == VERTICAL)
+ rect.UpperLeftCorner.Y = pos * m_scrollfactor;
+ else if (m_orientation == HORIZONTAL)
+ rect.UpperLeftCorner.X = pos * m_scrollfactor;
+
+ setRelativePosition(rect);
+}
diff --git a/src/gui/guiScrollContainer.h b/src/gui/guiScrollContainer.h
new file mode 100644
index 000000000..a0306291e
--- /dev/null
+++ b/src/gui/guiScrollContainer.h
@@ -0,0 +1,60 @@
+/*
+Minetest
+Copyright (C) 2020 DS
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "util/string.h"
+#include "guiScrollBar.h"
+
+class GUIScrollContainer : public gui::IGUIElement
+{
+public:
+ GUIScrollContainer(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ const core::rect<s32> &rectangle, const std::string &orientation,
+ f32 scrollfactor);
+
+ virtual bool OnEvent(const SEvent &event) override;
+
+ inline void onScrollEvent(gui::IGUIElement *caller)
+ {
+ if (caller == m_scrollbar)
+ updateScrolling();
+ }
+
+ inline void setScrollBar(GUIScrollBar *scrollbar)
+ {
+ m_scrollbar = scrollbar;
+ updateScrolling();
+ }
+
+private:
+ enum OrientationEnum
+ {
+ VERTICAL,
+ HORIZONTAL,
+ UNDEFINED
+ };
+
+ GUIScrollBar *m_scrollbar;
+ OrientationEnum m_orientation;
+ f32 m_scrollfactor;
+
+ void updateScrolling();
+};
diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp
index 9428cde83..07b11248c 100644
--- a/src/gui/guiVolumeChange.cpp
+++ b/src/gui/guiVolumeChange.cpp
@@ -38,9 +38,10 @@ const int ID_soundMuteButton = 266;
GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env,
gui::IGUIElement* parent, s32 id,
- IMenuManager *menumgr
+ IMenuManager *menumgr, ISimpleTextureSource *tsrc
):
- GUIModalMenu(env, parent, id, menumgr)
+ GUIModalMenu(env, parent, id, menumgr),
+ m_tsrc(tsrc)
{
}
@@ -104,7 +105,7 @@ void GUIVolumeChange::regenerateGui(v2u32 screensize)
core::rect<s32> rect(0, 0, 80 * s, 30 * s);
rect = rect + v2s32(size.X / 2 - 80 * s / 2, size.Y / 2 + 55 * s);
const wchar_t *text = wgettext("Exit");
- GUIButton::addButton(Environment, rect, this, ID_soundExitButton, text);
+ GUIButton::addButton(Environment, rect, m_tsrc, this, ID_soundExitButton, text);
delete[] text;
}
{
diff --git a/src/gui/guiVolumeChange.h b/src/gui/guiVolumeChange.h
index f4f4e9eea..466e17f9d 100644
--- a/src/gui/guiVolumeChange.h
+++ b/src/gui/guiVolumeChange.h
@@ -23,12 +23,14 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include "modalMenu.h"
#include <string>
+class ISimpleTextureSource;
+
class GUIVolumeChange : public GUIModalMenu
{
public:
GUIVolumeChange(gui::IGUIEnvironment* env,
gui::IGUIElement* parent, s32 id,
- IMenuManager *menumgr);
+ IMenuManager *menumgr, ISimpleTextureSource *tsrc);
~GUIVolumeChange();
void removeChildren();
@@ -46,4 +48,7 @@ public:
protected:
std::wstring getLabelByID(s32 id) { return L""; }
std::string getNameByID(s32 id) { return ""; }
+
+private:
+ ISimpleTextureSource *m_tsrc;
};
diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp
index 8fb6c6f0f..a6fe7ebaf 100644
--- a/src/gui/modalMenu.cpp
+++ b/src/gui/modalMenu.cpp
@@ -28,8 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "touchscreengui.h"
#endif
-// clang-format off
-GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id,
+GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
IMenuManager *menumgr) :
IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
core::rect<s32>(0, 0, 100, 100)),
@@ -153,8 +152,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
if (((gui::IGUIEditBox *)hovered)->isPasswordBox())
type = 3;
- porting::showInputDialog(gettext("ok"), "",
- wide_to_utf8(((gui::IGUIEditBox *)hovered)->getText()), type);
+ porting::showInputDialog(gettext("OK"), "",
+ wide_to_utf8(((gui::IGUIEditBox *)hovered)->getText()), type);
return retval;
}
}
@@ -167,18 +166,17 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
if (!root) {
errorstream << "GUIModalMenu::preprocessEvent"
- << " unable to get root element" << std::endl;
+ << " unable to get root element" << std::endl;
return false;
}
- gui::IGUIElement *hovered = root->getElementFromPoint(
- core::position2d<s32>(event.TouchInput.X, event.TouchInput.Y));
+ gui::IGUIElement *hovered =
+ root->getElementFromPoint(core::position2d<s32>(
+ event.TouchInput.X, event.TouchInput.Y));
translated.MouseInput.X = event.TouchInput.X;
translated.MouseInput.Y = event.TouchInput.Y;
translated.MouseInput.Control = false;
- bool dont_send_event = false;
-
if (event.TouchInput.touchedCount == 1) {
switch (event.TouchInput.Event) {
case ETIE_PRESSED_DOWN:
@@ -205,11 +203,7 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
m_down_pos = v2s32(0, 0);
break;
default:
- dont_send_event = true;
- // this is not supposed to happen
- errorstream << "GUIModalMenu::preprocessEvent"
- << " unexpected usecase Event="
- << event.TouchInput.Event << std::endl;
+ break;
}
} else if ((event.TouchInput.touchedCount == 2) &&
(event.TouchInput.Event == ETIE_PRESSED_DOWN)) {
@@ -219,50 +213,37 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
translated.MouseInput.ButtonStates = EMBSM_LEFT | EMBSM_RIGHT;
translated.MouseInput.X = m_pointer.X;
translated.MouseInput.Y = m_pointer.Y;
- if (hovered) {
+ if (hovered)
hovered->OnEvent(translated);
- }
translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
translated.MouseInput.ButtonStates = EMBSM_LEFT;
- if (hovered) {
+ if (hovered)
hovered->OnEvent(translated);
- }
- dont_send_event = true;
- }
- // ignore unhandled 2 touch events ... accidental moving for example
- else if (event.TouchInput.touchedCount == 2) {
- dont_send_event = true;
- }
- else if (event.TouchInput.touchedCount > 2) {
- errorstream << "GUIModalMenu::preprocessEvent"
- << " to many multitouch events "
- << event.TouchInput.touchedCount << " ignoring them"
- << std::endl;
- }
- if (dont_send_event) {
+ return true;
+ } else {
+ // ignore unhandled 2 touch events (accidental moving for example)
return true;
}
// check if translated event needs to be preprocessed again
- if (preprocessEvent(translated)) {
+ if (preprocessEvent(translated))
return true;
- }
+
if (hovered) {
grab();
bool retval = hovered->OnEvent(translated);
- if (event.TouchInput.Event == ETIE_LEFT_UP) {
+ if (event.TouchInput.Event == ETIE_LEFT_UP)
// reset pointer
m_pointer = v2s32(0, 0);
- }
+
drop();
return retval;
}
}
- // clang-format on
#endif
return false;
}
@@ -271,14 +252,12 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
bool GUIModalMenu::hasAndroidUIInput()
{
// no dialog shown
- if (m_jni_field_name.empty()) {
+ if (m_jni_field_name.empty())
return false;
- }
// still waiting
- if (porting::getInputDialogState() == -1) {
+ if (porting::getInputDialogState() == -1)
return true;
- }
// no value abort dialog processing
if (porting::getInputDialogState() != 0) {
diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp
index 94e331f72..0d64aa618 100644
--- a/src/gui/touchscreengui.cpp
+++ b/src/gui/touchscreengui.cpp
@@ -34,19 +34,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <ISceneCollisionManager.h>
-// Very slow button repeat frequency (in seconds)
-#define SLOW_BUTTON_REPEAT (1.0f)
-
using namespace irr::core;
-const char **touchgui_button_imagenames = (const char *[]) {
+const char **button_imagenames = (const char *[]) {
"jump_btn.png",
"down.png",
"zoom.png",
"aux_btn.png"
};
-const char **touchgui_joystick_imagenames = (const char *[]) {
+const char **joystick_imagenames = (const char *[]) {
"joystick_off.png",
"joystick_bg.png",
"joystick_center.png"
@@ -113,15 +110,17 @@ static irr::EKEY_CODE id2keycode(touch_gui_button_id id)
case range_id:
key = "rangeselect";
break;
+ default:
+ break;
}
- assert(key != "");
+ assert(!key.empty());
return keyname_to_keycode(g_settings->get("keymap_" + key).c_str());
}
TouchScreenGUI *g_touchscreengui;
static void load_button_texture(button_info *btn, const char *path,
- rect<s32> button_rect, ISimpleTextureSource *tsrc, video::IVideoDriver *driver)
+ const rect<s32> &button_rect, ISimpleTextureSource *tsrc, video::IVideoDriver *driver)
{
unsigned int tid;
video::ITexture *texture = guiScalingImageButton(driver,
@@ -141,7 +140,7 @@ static void load_button_texture(button_info *btn, const char *path,
}
btn->guibutton->setDrawBorder(false);
btn->guibutton->setText(L"");
- }
+ }
}
AutoHideButtonBar::AutoHideButtonBar(IrrlichtDevice *device,
@@ -153,8 +152,8 @@ AutoHideButtonBar::AutoHideButtonBar(IrrlichtDevice *device,
}
void AutoHideButtonBar::init(ISimpleTextureSource *tsrc,
- const char *starter_img, int button_id, v2s32 UpperLeft,
- v2s32 LowerRight, autohide_button_bar_dir dir, float timeout)
+ const char *starter_img, int button_id, const v2s32 &UpperLeft,
+ const v2s32 &LowerRight, autohide_button_bar_dir dir, float timeout)
{
m_texturesource = tsrc;
@@ -166,7 +165,7 @@ void AutoHideButtonBar::init(ISimpleTextureSource *tsrc,
irr::core::rect<int> current_button = rect<s32>(UpperLeft.X, UpperLeft.Y,
LowerRight.X, LowerRight.Y);
- m_starter.guibutton = m_guienv->addButton(current_button, 0, button_id, L"", 0);
+ m_starter.guibutton = m_guienv->addButton(current_button, nullptr, button_id, L"", nullptr);
m_starter.guibutton->grab();
m_starter.repeatcounter = -1;
m_starter.keycode = KEY_OEM_8; // use invalid keycode as it's not relevant
@@ -201,16 +200,14 @@ void AutoHideButtonBar::addButton(touch_gui_button_id button_id,
}
int button_size = 0;
- if ((m_dir == AHBB_Dir_Top_Bottom) || (m_dir == AHBB_Dir_Bottom_Top)) {
+ if ((m_dir == AHBB_Dir_Top_Bottom) || (m_dir == AHBB_Dir_Bottom_Top))
button_size = m_lower_right.X - m_upper_left.X;
- } else {
+ else
button_size = m_lower_right.Y - m_upper_left.Y;
- }
irr::core::rect<int> current_button;
if ((m_dir == AHBB_Dir_Right_Left) || (m_dir == AHBB_Dir_Left_Right)) {
-
int x_start = 0;
int x_end = 0;
@@ -227,8 +224,8 @@ void AutoHideButtonBar::addButton(touch_gui_button_id button_id,
current_button = rect<s32>(x_start, m_upper_left.Y, x_end,
m_lower_right.Y);
} else {
- int y_start = 0;
- int y_end = 0;
+ double y_start = 0;
+ double y_end = 0;
if (m_dir == AHBB_Dir_Top_Bottom) {
y_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size())
@@ -240,12 +237,13 @@ void AutoHideButtonBar::addButton(touch_gui_button_id button_id,
y_start = y_end - button_size;
}
- current_button = rect<s32>(m_upper_left.X, y_start, m_lower_right.Y,
- y_end);
+ current_button = rect<s32>(m_upper_left.X, y_start,
+ m_lower_right.Y, y_end);
}
- button_info *btn = new button_info();
- btn->guibutton = m_guienv->addButton(current_button, 0, button_id, caption, 0);
+ auto *btn = new button_info();
+ btn->guibutton = m_guienv->addButton(current_button,
+ nullptr, button_id, caption, nullptr);
btn->guibutton->grab();
btn->guibutton->setVisible(false);
btn->guibutton->setEnabled(false);
@@ -275,26 +273,23 @@ bool AutoHideButtonBar::isButton(const SEvent &event)
{
IGUIElement *rootguielement = m_guienv->getRootGUIElement();
- if (rootguielement == NULL) {
+ if (rootguielement == nullptr)
return false;
- }
gui::IGUIElement *element = rootguielement->getElementFromPoint(
core::position2d<s32>(event.TouchInput.X, event.TouchInput.Y));
- if (element == NULL) {
+ if (element == nullptr)
return false;
- }
if (m_active) {
// check for all buttons in vector
-
- std::vector<button_info *>::iterator iter = m_buttons.begin();
+ auto iter = m_buttons.begin();
while (iter != m_buttons.end()) {
if ((*iter)->guibutton == element) {
- SEvent *translated = new SEvent();
+ auto *translated = new SEvent();
memset(translated, 0, sizeof(SEvent));
translated->EventType = irr::EET_KEY_INPUT_EVENT;
translated->KeyInput.Key = (*iter)->keycode;
@@ -341,7 +336,7 @@ bool AutoHideButtonBar::isButton(const SEvent &event)
m_active = true;
m_timeout = 0;
- std::vector<button_info*>::iterator iter = m_buttons.begin();
+ auto iter = m_buttons.begin();
while (iter != m_buttons.end()) {
(*iter)->guibutton->setVisible(true);
@@ -355,41 +350,13 @@ bool AutoHideButtonBar::isButton(const SEvent &event)
return false;
}
-bool AutoHideButtonBar::isReleaseButton(int eventID)
-{
- std::vector<int>::iterator id = std::find(m_starter.ids.begin(),
- m_starter.ids.end(), eventID);
-
- if (id != m_starter.ids.end()) {
- m_starter.ids.erase(id);
- return true;
- }
-
- std::vector<button_info *>::iterator iter = m_buttons.begin();
-
- while (iter != m_buttons.end()) {
- std::vector<int>::iterator id = std::find((*iter)->ids.begin(),
- (*iter)->ids.end(), eventID);
-
- if (id != (*iter)->ids.end()) {
- (*iter)->ids.erase(id);
- // TODO handle settings button release
- return true;
- }
- ++iter;
- }
-
- return false;
-}
-
void AutoHideButtonBar::step(float dtime)
{
if (m_active) {
m_timeout += dtime;
- if (m_timeout > m_timeout_value) {
+ if (m_timeout > m_timeout_value)
deactivate();
- }
}
}
@@ -401,11 +368,11 @@ void AutoHideButtonBar::deactivate()
}
m_active = false;
- std::vector<button_info *>::iterator iter = m_buttons.begin();
+ auto iter = m_buttons.begin();
while (iter != m_buttons.end()) {
- (*iter)->guibutton->setVisible(false);
- (*iter)->guibutton->setEnabled(false);
+ (*iter)->guibutton->setVisible(false);
+ (*iter)->guibutton->setEnabled(false);
++iter;
}
}
@@ -416,7 +383,7 @@ void AutoHideButtonBar::hide()
m_starter.guibutton->setVisible(false);
m_starter.guibutton->setEnabled(false);
- std::vector<button_info *>::iterator iter = m_buttons.begin();
+ auto iter = m_buttons.begin();
while (iter != m_buttons.end()) {
(*iter)->guibutton->setVisible(false);
@@ -430,7 +397,7 @@ void AutoHideButtonBar::show()
m_visible = true;
if (m_active) {
- std::vector<button_info *>::iterator iter = m_buttons.begin();
+ auto iter = m_buttons.begin();
while (iter != m_buttons.end()) {
(*iter)->guibutton->setVisible(true);
@@ -450,24 +417,26 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver)
m_settingsbar(device, receiver),
m_rarecontrolsbar(device, receiver)
{
- for (unsigned int i=0; i < after_last_element_id; i++) {
- m_buttons[i].guibutton = 0;
- m_buttons[i].repeatcounter = -1;
- m_buttons[i].repeatdelay = BUTTON_REPEAT_DELAY;
+ 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_fixed_joystick = g_settings->getBool("fixed_virtual_joystick");
m_joystick_triggers_special1 = g_settings->getBool("virtual_joystick_triggers_aux");
m_screensize = m_device->getVideoDriver()->getScreenSize();
+ button_size = MYMIN(m_screensize.Y / 4.5f,
+ porting::getDisplayDensity() *
+ g_settings->getFloat("hud_scaling") * 65.0f);
}
-void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect,
- std::wstring caption, bool immediate_release, float repeat_delay)
+void TouchScreenGUI::initButton(touch_gui_button_id id, const rect<s32> &button_rect,
+ const std::wstring &caption, bool immediate_release, float repeat_delay)
{
-
button_info *btn = &m_buttons[id];
- btn->guibutton = m_guienv->addButton(button_rect, 0, id, caption.c_str());
+ btn->guibutton = m_guienv->addButton(button_rect, nullptr, id, caption.c_str());
btn->guibutton->grab();
btn->repeatcounter = -1;
btn->repeatdelay = repeat_delay;
@@ -475,42 +444,29 @@ void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect,
btn->immediate_release = immediate_release;
btn->ids.clear();
- load_button_texture(btn, touchgui_button_imagenames[id], button_rect,
+ load_button_texture(btn, button_imagenames[id], button_rect,
m_texturesource, m_device->getVideoDriver());
}
-button_info *TouchScreenGUI::initJoystickButton(touch_gui_button_id id, rect<s32> button_rect,
- int texture_id, bool visible)
+button_info *TouchScreenGUI::initJoystickButton(touch_gui_button_id id,
+ const rect<s32> &button_rect, int texture_id, bool visible)
{
- button_info *btn = new button_info();
- btn->guibutton = m_guienv->addButton(button_rect, 0, id, L"O");
+ auto *btn = new button_info();
+ btn->guibutton = m_guienv->addButton(button_rect, nullptr, id, L"O");
btn->guibutton->setVisible(visible);
btn->guibutton->grab();
btn->ids.clear();
- load_button_texture(btn, touchgui_joystick_imagenames[texture_id], button_rect,
- m_texturesource, m_device->getVideoDriver());
+ load_button_texture(btn, joystick_imagenames[texture_id],
+ button_rect, m_texturesource, m_device->getVideoDriver());
return btn;
}
-static int getMaxControlPadSize(float density) {
- return 200 * density * g_settings->getFloat("hud_scaling");
-}
-
-int TouchScreenGUI::getGuiButtonSize()
-{
- u32 control_pad_size = MYMIN((2 * m_screensize.Y) / 3,
- getMaxControlPadSize(porting::getDisplayDensity()));
-
- return control_pad_size / 3;
-}
-
void TouchScreenGUI::init(ISimpleTextureSource *tsrc)
{
assert(tsrc);
- u32 button_size = getGuiButtonSize();
m_visible = true;
m_texturesource = tsrc;
@@ -608,35 +564,31 @@ 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");
-
}
touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y)
{
IGUIElement *rootguielement = m_guienv->getRootGUIElement();
- if (rootguielement != NULL) {
+ if (rootguielement != nullptr) {
gui::IGUIElement *element =
rootguielement->getElementFromPoint(core::position2d<s32>(x, y));
- if (element) {
- for (unsigned int i=0; i < after_last_element_id; i++) {
- if (element == m_buttons[i].guibutton) {
+ 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;
}
-touch_gui_button_id TouchScreenGUI::getButtonID(int eventID)
+touch_gui_button_id TouchScreenGUI::getButtonID(size_t eventID)
{
- for (unsigned int i=0; i < after_last_element_id; i++) {
+ for (unsigned int i = 0; i < after_last_element_id; i++) {
button_info *btn = &m_buttons[i];
- std::vector<int>::iterator id =
- std::find(btn->ids.begin(), btn->ids.end(), eventID);
+ auto id = std::find(btn->ids.begin(), btn->ids.end(), eventID);
if (id != btn->ids.end())
return (touch_gui_button_id) i;
@@ -648,55 +600,30 @@ touch_gui_button_id TouchScreenGUI::getButtonID(int eventID)
bool TouchScreenGUI::isHUDButton(const SEvent &event)
{
// check if hud item is pressed
- for (std::map<int, rect<s32> >::iterator iter = m_hud_rects.begin();
- iter != m_hud_rects.end(); ++iter) {
- if (iter->second.isPointInside(
- v2s32(event.TouchInput.X,
- event.TouchInput.Y)
- )) {
- if ( iter->first < 8) {
- SEvent *translated = new SEvent();
- memset(translated, 0, sizeof(SEvent));
- translated->EventType = irr::EET_KEY_INPUT_EVENT;
- translated->KeyInput.Key = (irr::EKEY_CODE) (KEY_KEY_1 + iter->first);
- translated->KeyInput.Control = false;
- translated->KeyInput.Shift = false;
- translated->KeyInput.PressedDown = true;
- m_receiver->OnEvent(*translated);
- m_hud_ids[event.TouchInput.ID] = translated->KeyInput.Key;
- delete translated;
- return true;
- }
+ for (auto &hud_rect : m_hud_rects) {
+ if (hud_rect.second.isPointInside(v2s32(event.TouchInput.X,
+ event.TouchInput.Y))) {
+ auto *translated = new SEvent();
+ memset(translated, 0, sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = (irr::EKEY_CODE) (KEY_KEY_1 + hud_rect.first);
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ translated->KeyInput.PressedDown = true;
+ m_receiver->OnEvent(*translated);
+ m_hud_ids[event.TouchInput.ID] = translated->KeyInput.Key;
+ delete translated;
+ return true;
}
}
return false;
}
-bool TouchScreenGUI::isReleaseHUDButton(int eventID)
-{
- std::map<int, irr::EKEY_CODE>::iterator iter = m_hud_ids.find(eventID);
-
- if (iter != m_hud_ids.end()) {
- SEvent *translated = new SEvent();
- memset(translated, 0, sizeof(SEvent));
- translated->EventType = irr::EET_KEY_INPUT_EVENT;
- translated->KeyInput.Key = iter->second;
- translated->KeyInput.PressedDown = false;
- translated->KeyInput.Control = false;
- translated->KeyInput.Shift = false;
- m_receiver->OnEvent(*translated);
- m_hud_ids.erase(iter);
- delete translated;
- return true;
- }
- return false;
-}
-
void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button,
- int eventID, bool action)
+ size_t eventID, bool action)
{
button_info *btn = &m_buttons[button];
- SEvent *translated = new SEvent();
+ auto *translated = new SEvent();
memset(translated, 0, sizeof(SEvent));
translated->EventType = irr::EET_KEY_INPUT_EVENT;
translated->KeyInput.Key = btn->keycode;
@@ -717,16 +644,16 @@ void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button,
translated->KeyInput.Key = btn->keycode;
m_receiver->OnEvent(*translated);
}
+
// remove event
if ((!action) || (btn->immediate_release)) {
-
- std::vector<int>::iterator pos =
- std::find(btn->ids.begin(), btn->ids.end(), eventID);
+ auto pos = std::find(btn->ids.begin(), btn->ids.end(), eventID);
// has to be in touch list
assert(pos != btn->ids.end());
btn->ids.erase(pos);
- if (btn->ids.size() > 0) { return; }
+ if (!btn->ids.empty())
+ return;
translated->KeyInput.PressedDown = false;
btn->repeatcounter = -1;
@@ -735,30 +662,21 @@ void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button,
delete translated;
}
-
-void TouchScreenGUI::handleReleaseEvent(int evt_id)
+void TouchScreenGUI::handleReleaseEvent(size_t evt_id)
{
touch_gui_button_id button = getButtonID(evt_id);
- // handle button events
+
if (button != after_last_element_id) {
+ // handle button events
handleButtonEvent(button, evt_id, false);
- }
- // handle hud button events
- else if (isReleaseHUDButton(evt_id)) {
- // nothing to do here
- } else if (m_settingsbar.isReleaseButton(evt_id)) {
- // nothing to do here
- } else if (m_rarecontrolsbar.isReleaseButton(evt_id)) {
- // nothing to do here
- }
- // handle the point used for moving view
- else if (evt_id == m_move_id) {
+ } else if (evt_id == m_move_id) {
+ // handle the point used for moving view
m_move_id = -1;
// if this pointer issued a mouse event issue symmetric release here
if (m_move_sent_as_mouse_event) {
- SEvent *translated = new SEvent;
+ auto *translated = new SEvent;
memset(translated, 0, sizeof(SEvent));
translated->EventType = EET_MOUSE_INPUT_EVENT;
translated->MouseInput.X = m_move_downlocation.X;
@@ -769,32 +687,31 @@ void TouchScreenGUI::handleReleaseEvent(int evt_id)
translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
m_receiver->OnEvent(*translated);
delete translated;
- }
- else {
+ } else {
// do double tap detection
doubleTapDetection();
}
}
+
// handle joystick
else if (evt_id == m_joystick_id) {
m_joystick_id = -1;
// reset joystick
- for (unsigned int i = 0; i < 4; i ++)
+ for (unsigned int i = 0; i < 4; i++)
m_joystick_status[i] = false;
applyJoystickStatus();
m_joystick_btn_off->guibutton->setVisible(true);
m_joystick_btn_bg->guibutton->setVisible(false);
m_joystick_btn_center->guibutton->setVisible(false);
- }
- else {
+ } else {
infostream
<< "TouchScreenGUI::translateEvent released unknown button: "
<< evt_id << std::endl;
}
- for (std::vector<id_status>::iterator iter = m_known_ids.begin();
+ for (auto iter = m_known_ids.begin();
iter != m_known_ids.end(); ++iter) {
if (iter->id == evt_id) {
m_known_ids.erase(iter);
@@ -806,27 +723,28 @@ void TouchScreenGUI::handleReleaseEvent(int evt_id)
void TouchScreenGUI::translateEvent(const SEvent &event)
{
if (!m_visible) {
- infostream << "TouchScreenGUI::translateEvent got event but not visible?!" << std::endl;
+ infostream
+ << "TouchScreenGUI::translateEvent got event but not visible!"
+ << std::endl;
return;
}
- if (event.EventType != EET_TOUCH_INPUT_EVENT) {
+ if (event.EventType != EET_TOUCH_INPUT_EVENT)
return;
- }
if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
-
- /* add to own copy of eventlist ...
- * android would provide this information but irrlicht guys don't
+ /*
+ * Add to own copy of event list...
+ * android would provide this information but Irrlicht guys don't
* wanna design a efficient interface
*/
- id_status toadd;
+ id_status toadd{};
toadd.id = event.TouchInput.ID;
toadd.X = event.TouchInput.X;
toadd.Y = event.TouchInput.Y;
m_known_ids.push_back(toadd);
- int eventID = event.TouchInput.ID;
+ size_t eventID = event.TouchInput.ID;
touch_gui_button_id button =
getButtonID(event.TouchInput.X, event.TouchInput.Y);
@@ -846,21 +764,19 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
} else if (m_rarecontrolsbar.isButton(event)) {
m_settingsbar.deactivate();
// already handled in isSettingsBarButton()
- }
- // handle non button events
- else {
+ } else {
+ // handle non button events
m_settingsbar.deactivate();
m_rarecontrolsbar.deactivate();
- u32 button_size = getGuiButtonSize();
- s32 dxj = event.TouchInput.X - button_size * 5 / 2;
- s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5 / 2;
+ 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;
/* 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) ||
- (!m_fixed_joystick && event.TouchInput.X < m_screensize.X / 3)) {
+ (!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_joystick_id == -1) {
m_joystick_id = event.TouchInput.ID;
@@ -871,14 +787,14 @@ 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)
m_joystick_btn_bg->guibutton->setRelativePosition(v2s32(
- event.TouchInput.X - button_size * 3 / 2,
- event.TouchInput.Y - button_size * 3 / 2));
- }
+ 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,
- event.TouchInput.Y - button_size / 2));
+ event.TouchInput.X - button_size / 2.0f,
+ event.TouchInput.Y - button_size / 2.0f));
}
} else {
// If we don't already have a moving point make this the moving one.
@@ -895,17 +811,15 @@ 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) {
- verbosestream << "Up event for pointerid: " << event.TouchInput.ID << std::endl;
+ verbosestream
+ << "Up event for pointerid: " << event.TouchInput.ID << std::endl;
handleReleaseEvent(event.TouchInput.ID);
- }
- else {
+ } else {
assert(event.TouchInput.Event == ETIE_MOVED);
- int move_idx = event.TouchInput.ID;
if (m_pointerpos[event.TouchInput.ID] ==
- v2s32(event.TouchInput.X, event.TouchInput.Y)) {
+ v2s32(event.TouchInput.X, event.TouchInput.Y))
return;
- }
if (m_move_id != -1) {
if ((event.TouchInput.ID == m_move_id) &&
@@ -928,9 +842,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y;
// adapt to similar behaviour as pc screen
- double d = g_settings->getFloat("mouse_sensitivity") * 4;
- double old_yaw = m_camera_yaw_change;
- double old_pitch = m_camera_pitch;
+ 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);
@@ -942,8 +854,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
->getRayFromScreenCoordinates(v2s32(X, Y));
m_pointerpos[event.TouchInput.ID] = v2s32(X, Y);
}
- }
- else if ((event.TouchInput.ID == m_move_id) &&
+ } else if ((event.TouchInput.ID == m_move_id) &&
(m_move_sent_as_mouse_event)) {
m_shootline = m_device
->getSceneManager()
@@ -954,7 +865,6 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
}
if (m_joystick_id != -1 && event.TouchInput.ID == m_joystick_id) {
- u32 button_size = getGuiButtonSize();
s32 X = event.TouchInput.X;
s32 Y = event.TouchInput.Y;
@@ -967,8 +877,8 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
double distance_sq = dx * dx + dy * dy;
- s32 dxj = event.TouchInput.X - button_size * 5 / 2;
- s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5 / 2;
+ 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 ||
@@ -986,8 +896,8 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
angle = fmod(angle + 180 + 22.5, 360);
// reset state before applying
- for (unsigned int i = 0; i < 5; i ++)
- m_joystick_status[i] = false;
+ for (bool & joystick_status : m_joystick_status)
+ joystick_status = false;
if (distance <= m_touchscreen_threshold) {
// do nothing
@@ -1016,8 +926,8 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
if (distance > button_size) {
m_joystick_status[j_special1] = true;
// move joystick "button"
- s32 ndx = (s32) button_size * dx / distance - (s32) button_size / 2;
- s32 ndy = (s32) button_size * dy / distance - (s32) button_size / 2;
+ 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,
@@ -1028,64 +938,54 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
m_pointerpos[event.TouchInput.ID].Y + ndy));
}
} else {
- m_joystick_btn_center->guibutton->setRelativePosition(v2s32(
- X - button_size / 2, Y - button_size / 2));
+ m_joystick_btn_center->guibutton->setRelativePosition(
+ v2s32(X - button_size / 2, Y - button_size / 2));
}
}
}
- if (m_move_id == -1 && m_joystick_id == -1) {
+ if (m_move_id == -1 && m_joystick_id == -1)
handleChangedButton(event);
- }
}
}
void TouchScreenGUI::handleChangedButton(const SEvent &event)
{
for (unsigned int i = 0; i < after_last_element_id; i++) {
-
- if (m_buttons[i].ids.empty()) {
+ if (m_buttons[i].ids.empty())
continue;
- }
- for (std::vector<int>::iterator iter = m_buttons[i].ids.begin();
- iter != m_buttons[i].ids.end(); ++iter) {
+ for (auto iter = m_buttons[i].ids.begin();
+ iter != m_buttons[i].ids.end(); ++iter) {
if (event.TouchInput.ID == *iter) {
-
int current_button_id =
getButtonID(event.TouchInput.X, event.TouchInput.Y);
- if (current_button_id == i) {
+ if (current_button_id == i)
continue;
- }
// remove old button
handleButtonEvent((touch_gui_button_id) i, *iter, false);
- if (current_button_id == after_last_element_id) {
+ if (current_button_id == after_last_element_id)
return;
- }
+
handleButtonEvent((touch_gui_button_id) current_button_id, *iter, true);
return;
-
}
}
}
int current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y);
- if (current_button_id == after_last_element_id) {
+ if (current_button_id == after_last_element_id)
return;
- }
button_info *btn = &m_buttons[current_button_id];
if (std::find(btn->ids.begin(), btn->ids.end(), event.TouchInput.ID)
== btn->ids.end())
- {
handleButtonEvent((touch_gui_button_id) current_button_id,
event.TouchInput.ID, true);
- }
-
}
bool TouchScreenGUI::doubleTapDetection()
@@ -1102,14 +1002,15 @@ bool TouchScreenGUI::doubleTapDetection()
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));
-
+ (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;
- SEvent *translated = new SEvent();
+ auto *translated = new SEvent();
memset(translated, 0, sizeof(SEvent));
translated->EventType = EET_MOUSE_INPUT_EVENT;
translated->MouseInput.X = m_key_events[0].x;
@@ -1129,17 +1030,16 @@ bool TouchScreenGUI::doubleTapDetection()
m_receiver->OnEvent(*translated);
translated->MouseInput.ButtonStates = 0;
- translated->MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
+ translated->MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl;
m_receiver->OnEvent(*translated);
delete translated;
return true;
-
}
void TouchScreenGUI::applyJoystickStatus()
{
- for (unsigned int i = 0; i < 5; i ++) {
+ for (unsigned int i = 0; i < 5; i++) {
if (i == 4 && !m_joystick_triggers_special1)
continue;
@@ -1158,50 +1058,48 @@ void TouchScreenGUI::applyJoystickStatus()
TouchScreenGUI::~TouchScreenGUI()
{
- for (unsigned int i = 0; i < after_last_element_id; i++) {
- button_info *btn = &m_buttons[i];
- if (btn->guibutton) {
- btn->guibutton->drop();
- btn->guibutton = NULL;
+ for (auto &button : m_buttons) {
+ if (button.guibutton) {
+ button.guibutton->drop();
+ button.guibutton = nullptr;
}
}
if (m_joystick_btn_off->guibutton) {
m_joystick_btn_off->guibutton->drop();
- m_joystick_btn_off->guibutton = NULL;
+ m_joystick_btn_off->guibutton = nullptr;
}
if (m_joystick_btn_bg->guibutton) {
m_joystick_btn_bg->guibutton->drop();
- m_joystick_btn_bg->guibutton = NULL;
+ m_joystick_btn_bg->guibutton = nullptr;
}
if (m_joystick_btn_center->guibutton) {
m_joystick_btn_center->guibutton->drop();
- m_joystick_btn_center->guibutton = NULL;
+ m_joystick_btn_center->guibutton = nullptr;
}
}
void TouchScreenGUI::step(float dtime)
{
// simulate keyboard repeats
- for (unsigned int i = 0; i < after_last_element_id; i++) {
- button_info *btn = &m_buttons[i];
-
- if (btn->ids.size() > 0) {
- btn->repeatcounter += dtime;
+ for (auto &button : m_buttons) {
+ if (!button.ids.empty()) {
+ button.repeatcounter += dtime;
// in case we're moving around digging does not happen
if (m_move_id != -1)
m_move_has_really_moved = true;
- if (btn->repeatcounter < btn->repeatdelay) continue;
+ if (button.repeatcounter < button.repeatdelay)
+ continue;
- btn->repeatcounter = 0;
+ button.repeatcounter = 0;
SEvent translated;
memset(&translated, 0, sizeof(SEvent));
translated.EventType = irr::EET_KEY_INPUT_EVENT;
- translated.KeyInput.Key = btn->keycode;
+ translated.KeyInput.Key = button.keycode;
translated.KeyInput.PressedDown = false;
m_receiver->OnEvent(translated);
@@ -1211,7 +1109,12 @@ void TouchScreenGUI::step(float dtime)
}
// joystick
- applyJoystickStatus();
+ for (unsigned int i = 0; i < 4; i++) {
+ if (m_joystick_status[i]) {
+ applyJoystickStatus();
+ break;
+ }
+ }
// if a new placed pointer isn't moved for some time start digging
if ((m_move_id != -1) &&
@@ -1259,22 +1162,18 @@ void TouchScreenGUI::registerHudItem(int index, const rect<s32> &rect)
void TouchScreenGUI::Toggle(bool visible)
{
m_visible = visible;
- for (unsigned int i = 0; i < after_last_element_id; i++) {
- button_info *btn = &m_buttons[i];
- if (btn->guibutton) {
- btn->guibutton->setVisible(visible);
- }
+ for (auto &button : m_buttons) {
+ if (button.guibutton)
+ button.guibutton->setVisible(visible);
}
- if (m_joystick_btn_off->guibutton) {
+ if (m_joystick_btn_off->guibutton)
m_joystick_btn_off->guibutton->setVisible(visible);
- }
// clear all active buttons
if (!visible) {
- while (m_known_ids.size() > 0) {
+ while (!m_known_ids.empty())
handleReleaseEvent(m_known_ids.begin()->id);
- }
m_settingsbar.hide();
m_rarecontrolsbar.hide();
diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h
index 2a3f24a3f..761d33207 100644
--- a/src/gui/touchscreengui.h
+++ b/src/gui/touchscreengui.h
@@ -80,21 +80,22 @@ typedef enum
} autohide_button_bar_dir;
#define MIN_DIG_TIME_MS 500
-#define MAX_TOUCH_COUNT 64
#define BUTTON_REPEAT_DELAY 0.2f
-
#define SETTINGS_BAR_Y_OFFSET 5
#define RARE_CONTROLS_BAR_Y_OFFSET 5
-extern const char **touchgui_button_imagenames;
-extern const char **touchgui_joystick_imagenames;
+// Very slow button repeat frequency
+#define SLOW_BUTTON_REPEAT 1.0f
+
+extern const char **button_imagenames;
+extern const char **joystick_imagenames;
struct button_info
{
float repeatcounter;
float repeatdelay;
irr::EKEY_CODE keycode;
- std::vector<int> ids;
+ std::vector<size_t> ids;
IGUIButton *guibutton = nullptr;
bool immediate_release;
@@ -109,8 +110,8 @@ public:
AutoHideButtonBar(IrrlichtDevice *device, IEventReceiver *receiver);
void init(ISimpleTextureSource *tsrc, const char *starter_img, int button_id,
- v2s32 UpperLeft, v2s32 LowerRight, autohide_button_bar_dir dir,
- float timeout);
+ const v2s32 &UpperLeft, const v2s32 &LowerRight,
+ autohide_button_bar_dir dir, float timeout);
~AutoHideButtonBar();
@@ -125,9 +126,6 @@ public:
// detect settings bar button events
bool isButton(const SEvent &event);
- // handle released hud buttons
- bool isReleaseButton(int eventID);
-
// step handler
void step(float dtime);
@@ -182,7 +180,7 @@ public:
double getPitch() { return m_camera_pitch; }
- /*!
+ /*
* Returns a line which describes what the player is pointing at.
* The starting point and looking direction are significant,
* the line should be scaled to match its length to the actual distance
@@ -206,9 +204,10 @@ private:
IEventReceiver *m_receiver;
ISimpleTextureSource *m_texturesource;
v2u32 m_screensize;
+ s32 button_size;
double m_touchscreen_threshold;
std::map<int, rect<s32>> m_hud_rects;
- std::map<int, irr::EKEY_CODE> m_hud_ids;
+ std::map<size_t, irr::EKEY_CODE> m_hud_ids;
bool m_visible; // is the gui visible
// value in degree
@@ -220,7 +219,7 @@ private:
forward_id, backward_id, left_id, right_id, special1_id};
bool m_joystick_status[5] = {false, false, false, false, false};
- /*!
+ /*
* A line starting at the camera and pointing towards the
* selected object.
* The line ends on the camera's far plane.
@@ -248,23 +247,24 @@ private:
touch_gui_button_id getButtonID(s32 x, s32 y);
// gui button by eventID
- touch_gui_button_id getButtonID(int eventID);
+ touch_gui_button_id getButtonID(size_t eventID);
// check if a button has changed
void handleChangedButton(const SEvent &event);
// initialize a button
- void initButton(touch_gui_button_id id, rect<s32> button_rect,
- std::wstring caption, bool immediate_release,
+ void initButton(touch_gui_button_id id, const rect<s32> &button_rect,
+ const std::wstring &caption, bool immediate_release,
float repeat_delay = BUTTON_REPEAT_DELAY);
// initialize a joystick button
- button_info *initJoystickButton(touch_gui_button_id id, rect<s32> button_rect,
- int texture_id, bool visible = true);
+ button_info *initJoystickButton(touch_gui_button_id id,
+ const rect<s32> &button_rect, int texture_id,
+ bool visible = true);
struct id_status
{
- int id;
+ size_t id;
int X;
int Y;
};
@@ -273,27 +273,21 @@ private:
std::vector<id_status> m_known_ids;
// handle a button event
- void handleButtonEvent(touch_gui_button_id bID, int eventID, bool action);
+ void handleButtonEvent(touch_gui_button_id bID, size_t eventID, bool action);
// handle pressed hud buttons
bool isHUDButton(const SEvent &event);
- // handle released hud buttons
- bool isReleaseHUDButton(int eventID);
-
// handle double taps
bool doubleTapDetection();
// handle release event
- void handleReleaseEvent(int evt_id);
+ void handleReleaseEvent(size_t evt_id);
// apply joystick status
void applyJoystickStatus();
- // get size of regular gui control button
- int getGuiButtonSize();
-
- // doubleclick detection variables
+ // double-click detection variables
struct key_event
{
u64 down_time;
@@ -302,9 +296,9 @@ private:
};
// array for saving last known position of a pointer
- v2s32 m_pointerpos[MAX_TOUCH_COUNT];
+ std::map<size_t, v2s32> m_pointerpos;
- // array for doubletap detection
+ // array for double tap detection
key_event m_key_events[2];
// settings bar
@@ -313,4 +307,5 @@ private:
// rare controls bar
AutoHideButtonBar m_rarecontrolsbar;
};
+
extern TouchScreenGUI *g_touchscreengui;
diff --git a/src/hud.cpp b/src/hud.cpp
index 7711e3a4a..3079b5cd8 100644
--- a/src/hud.cpp
+++ b/src/hud.cpp
@@ -27,6 +27,7 @@ const struct EnumString es_HudElementType[] =
{HUD_ELEM_STATBAR, "statbar"},
{HUD_ELEM_INVENTORY, "inventory"},
{HUD_ELEM_WAYPOINT, "waypoint"},
+ {HUD_ELEM_IMAGE_WAYPOINT, "image_waypoint"},
{0, NULL},
};
@@ -45,6 +46,7 @@ const struct EnumString es_HudElementStat[] =
{HUD_STAT_WORLD_POS, "world_pos"},
{HUD_STAT_SIZE, "size"},
{HUD_STAT_Z_INDEX, "z_index"},
+ {HUD_STAT_TEXT2, "text2"},
{0, NULL},
};
diff --git a/src/hud.h b/src/hud.h
index 23f189dff..e015baec1 100644
--- a/src/hud.h
+++ b/src/hud.h
@@ -18,8 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef HUD_HEADER
-#define HUD_HEADER
+#pragma once
#include "irrlichttypes_extrabloated.h"
#include <string>
@@ -61,6 +60,7 @@ enum HudElementType {
HUD_ELEM_STATBAR = 2,
HUD_ELEM_INVENTORY = 3,
HUD_ELEM_WAYPOINT = 4,
+ HUD_ELEM_IMAGE_WAYPOINT = 5
};
enum HudElementStat {
@@ -76,6 +76,7 @@ enum HudElementStat {
HUD_STAT_WORLD_POS,
HUD_STAT_SIZE,
HUD_STAT_Z_INDEX,
+ HUD_STAT_TEXT2,
};
struct HudElement {
@@ -92,10 +93,10 @@ struct HudElement {
v3f world_pos;
v2s32 size;
s16 z_index = 0;
+ std::string text2;
};
extern const EnumString es_HudElementType[];
extern const EnumString es_HudElementStat[];
extern const EnumString es_HudBuiltinElement[];
-#endif
diff --git a/src/inventory.cpp b/src/inventory.cpp
index 77ecf5876..349ee503d 100644
--- a/src/inventory.cpp
+++ b/src/inventory.cpp
@@ -56,28 +56,31 @@ ItemStack::ItemStack(const std::string &name_, u16 count_,
count = 1;
}
-void ItemStack::serialize(std::ostream &os) const
+void ItemStack::serialize(std::ostream &os, bool serialize_meta) const
{
if (empty())
return;
// Check how many parts of the itemstring are needed
int parts = 1;
- if(count != 1)
- parts = 2;
- if(wear != 0)
- parts = 3;
if (!metadata.empty())
parts = 4;
+ else if (wear != 0)
+ parts = 3;
+ else if (count != 1)
+ parts = 2;
- os<<serializeJsonStringIfNeeded(name);
- if(parts >= 2)
- os<<" "<<count;
- if(parts >= 3)
- os<<" "<<wear;
+ os << serializeJsonStringIfNeeded(name);
+ if (parts >= 2)
+ os << " " << count;
+ if (parts >= 3)
+ os << " " << wear;
if (parts >= 4) {
os << " ";
- metadata.serialize(os);
+ if (serialize_meta)
+ metadata.serialize(os);
+ else
+ os << "<metadata size=" << metadata.size() << ">";
}
}
@@ -240,10 +243,10 @@ void ItemStack::deSerialize(const std::string &str, IItemDefManager *itemdef)
deSerialize(is, itemdef);
}
-std::string ItemStack::getItemString() const
+std::string ItemStack::getItemString(bool include_meta) const
{
std::ostringstream os(std::ios::binary);
- serialize(os);
+ serialize(os, include_meta);
return os.str();
}
diff --git a/src/inventory.h b/src/inventory.h
index 2828d3e5a..67a7859ed 100644
--- a/src/inventory.h
+++ b/src/inventory.h
@@ -40,13 +40,13 @@ struct ItemStack
~ItemStack() = default;
// Serialization
- void serialize(std::ostream &os) const;
+ void serialize(std::ostream &os, bool serialize_meta = true) const;
// Deserialization. Pass itemdef unless you don't want aliases resolved.
void deSerialize(std::istream &is, IItemDefManager *itemdef = NULL);
void deSerialize(const std::string &s, IItemDefManager *itemdef = NULL);
// Returns the string used for inventory
- std::string getItemString() const;
+ std::string getItemString(bool include_meta = true) const;
// Returns the tooltip
std::string getDescription(IItemDefManager *itemdef) const;
diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp
index 5a24f95a4..b6f464901 100644
--- a/src/inventorymanager.cpp
+++ b/src/inventorymanager.cpp
@@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "log.h"
#include "serverenvironment.h"
#include "scripting_server.h"
-#include "serverobject.h"
+#include "server/serveractiveobject.h"
#include "settings.h"
#include "craftdef.h"
#include "rollback_interface.h"
diff --git a/src/itemdef.cpp b/src/itemdef.cpp
index 0d0afeb2b..8e0492827 100644
--- a/src/itemdef.cpp
+++ b/src/itemdef.cpp
@@ -270,17 +270,16 @@ public:
// Convert name according to possible alias
std::string name = getAlias(name_);
// Get the definition
- std::map<std::string, ItemDefinition*>::const_iterator i;
- i = m_item_definitions.find(name);
- if(i == m_item_definitions.end())
+ auto i = m_item_definitions.find(name);
+ if (i == m_item_definitions.cend())
i = m_item_definitions.find("unknown");
- assert(i != m_item_definitions.end());
+ assert(i != m_item_definitions.cend());
return *(i->second);
}
virtual const std::string &getAlias(const std::string &name) const
{
- StringMap::const_iterator it = m_aliases.find(name);
- if (it != m_aliases.end())
+ auto it = m_aliases.find(name);
+ if (it != m_aliases.cend())
return it->second;
return name;
}
@@ -300,8 +299,7 @@ public:
// Convert name according to possible alias
std::string name = getAlias(name_);
// Get the definition
- std::map<std::string, ItemDefinition*>::const_iterator i;
- return m_item_definitions.find(name) != m_item_definitions.end();
+ return m_item_definitions.find(name) != m_item_definitions.cend();
}
#ifndef SERVER
public:
@@ -422,13 +420,30 @@ public:
return get(stack.name).color;
}
#endif
+ void applyTextureOverrides(const std::vector<TextureOverride> &overrides)
+ {
+ infostream << "ItemDefManager::applyTextureOverrides(): Applying "
+ "overrides to textures" << std::endl;
+
+ for (const TextureOverride& texture_override : overrides) {
+ if (m_item_definitions.find(texture_override.id) == m_item_definitions.end()) {
+ continue; // Ignore unknown item
+ }
+
+ ItemDefinition* itemdef = m_item_definitions[texture_override.id];
+
+ if (texture_override.hasTarget(OverrideTarget::INVENTORY))
+ itemdef->inventory_image = texture_override.texture;
+
+ if (texture_override.hasTarget(OverrideTarget::WIELD))
+ itemdef->wield_image = texture_override.texture;
+ }
+ }
void clear()
{
- for(std::map<std::string, ItemDefinition*>::const_iterator
- i = m_item_definitions.begin();
- i != m_item_definitions.end(); ++i)
+ for (auto &i : m_item_definitions)
{
- delete i->second;
+ delete i.second;
}
m_item_definitions.clear();
m_aliases.clear();
@@ -463,7 +478,7 @@ public:
}
virtual void registerItem(const ItemDefinition &def)
{
- verbosestream<<"ItemDefManager: registering \""<<def.name<<"\""<<std::endl;
+ TRACESTREAM(<< "ItemDefManager: registering " << def.name << std::endl);
// Ensure that the "" item (the hand) always has ToolCapabilities
if (def.name.empty())
FATAL_ERROR_IF(!def.tool_capabilities, "Hand does not have ToolCapabilities");
@@ -490,8 +505,8 @@ public:
const std::string &convert_to)
{
if (m_item_definitions.find(name) == m_item_definitions.end()) {
- verbosestream<<"ItemDefManager: setting alias "<<name
- <<" -> "<<convert_to<<std::endl;
+ TRACESTREAM(<< "ItemDefManager: setting alias " << name
+ << " -> " << convert_to << std::endl);
m_aliases[name] = convert_to;
}
}
@@ -501,10 +516,8 @@ public:
u16 count = m_item_definitions.size();
writeU16(os, count);
- for (std::map<std::string, ItemDefinition *>::const_iterator
- it = m_item_definitions.begin();
- it != m_item_definitions.end(); ++it) {
- ItemDefinition *def = it->second;
+ for (const auto &it : m_item_definitions) {
+ ItemDefinition *def = it.second;
// Serialize ItemDefinition and write wrapped in a string
std::ostringstream tmp_os(std::ios::binary);
def->serialize(tmp_os, protocol_version);
@@ -513,11 +526,9 @@ public:
writeU16(os, m_aliases.size());
- for (StringMap::const_iterator
- it = m_aliases.begin();
- it != m_aliases.end(); ++it) {
- os << serializeString(it->first);
- os << serializeString(it->second);
+ for (const auto &it : m_aliases) {
+ os << serializeString(it.first);
+ os << serializeString(it.second);
}
}
void deSerialize(std::istream &is)
diff --git a/src/itemdef.h b/src/itemdef.h
index 45cff582a..f47e6cbe7 100644
--- a/src/itemdef.h
+++ b/src/itemdef.h
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <set>
#include "itemgroup.h"
#include "sound.h"
+#include "texture_override.h" // TextureOverride
class IGameDef;
class Client;
struct ToolCapabilities;
@@ -157,6 +158,10 @@ public:
Client *client) const=0;
#endif
+ // Replace the textures of registered nodes with the ones specified in
+ // the texture pack's override.txt files
+ virtual void applyTextureOverrides(const std::vector<TextureOverride> &overrides)=0;
+
// Remove all registered item and node definitions and aliases
// Then re-add the builtin item definitions
virtual void clear()=0;
diff --git a/src/log.cpp b/src/log.cpp
index 30344b4df..54442c39b 100644
--- a/src/log.cpp
+++ b/src/log.cpp
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "debug.h"
#include "gettime.h"
#include "porting.h"
+#include "settings.h"
#include "config.h"
#include "exceptions.h"
#include "util/numeric.h"
@@ -338,7 +339,80 @@ void FileLogOutput::setFile(const std::string &filename, s64 file_size_max)
"-------------\n" << std::endl;
}
+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 (colored_message) {
+ switch (lev) {
+ case LL_ERROR:
+ // error is red
+ m_stream << "\033[91m";
+ break;
+ case LL_WARNING:
+ // warning is yellow
+ m_stream << "\033[93m";
+ break;
+ case LL_INFO:
+ // info is a bit dark
+ m_stream << "\033[37m";
+ break;
+ case LL_VERBOSE:
+ // verbose is darker than info
+ m_stream << "\033[2m";
+ break;
+ default:
+ // action is white
+ colored_message = false;
+ }
+ }
+ m_stream << line << std::endl;
+
+ if (colored_message) {
+ // reset to white color
+ m_stream << "\033[0m";
+ }
+}
+
+void LogOutputBuffer::updateLogLevel()
+{
+ const std::string &conf_loglev = g_settings->get("chat_log_level");
+ LogLevel log_level = Logger::stringToLevel(conf_loglev);
+ if (log_level == LL_MAX) {
+ warningstream << "Supplied unrecognized chat_log_level; "
+ "showing none." << std::endl;
+ log_level = LL_NONE;
+ }
+
+ m_logger.removeOutput(this);
+ m_logger.addOutputMaxLevel(this, log_level);
+}
+
+void LogOutputBuffer::logRaw(LogLevel lev, const std::string &line)
+{
+ std::string color;
+
+ if (!g_settings->getBool("disable_escape_sequences")) {
+ switch (lev) {
+ case LL_ERROR: // red
+ color = "\x1b(c@#F00)";
+ break;
+ case LL_WARNING: // yellow
+ color = "\x1b(c@#EE0)";
+ break;
+ case LL_INFO: // grey
+ color = "\x1b(c@#BBB)";
+ break;
+ case LL_VERBOSE: // dark grey
+ color = "\x1b(c@#888)";
+ break;
+ default: break;
+ }
+ }
+
+ m_buffer.push(color.append(line));
+}
////
//// *Buffer methods
diff --git a/src/log.h b/src/log.h
index 6350d8a86..856d3479b 100644
--- a/src/log.h
+++ b/src/log.h
@@ -124,39 +124,7 @@ public:
#endif
}
- void 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 (colored_message)
- switch (lev) {
- case LL_ERROR:
- // error is red
- m_stream << "\033[91m";
- break;
- case LL_WARNING:
- // warning is yellow
- m_stream << "\033[93m";
- break;
- case LL_INFO:
- // info is a bit dark
- m_stream << "\033[37m";
- break;
- case LL_VERBOSE:
- // verbose is darker than info
- m_stream << "\033[2m";
- break;
- default:
- // action is white
- colored_message = false;
- }
-
- m_stream << line << std::endl;
-
- if (colored_message)
- // reset to white color
- m_stream << "\033[0m";
- }
+ void logRaw(LogLevel lev, const std::string &line);
private:
std::ostream &m_stream;
@@ -178,23 +146,27 @@ private:
class LogOutputBuffer : public ICombinedLogOutput {
public:
- LogOutputBuffer(Logger &logger, LogLevel lev) :
+ LogOutputBuffer(Logger &logger) :
m_logger(logger)
{
- m_logger.addOutput(this, lev);
- }
+ updateLogLevel();
+ };
- ~LogOutputBuffer()
+ virtual ~LogOutputBuffer()
{
m_logger.removeOutput(this);
}
- void logRaw(LogLevel lev, const std::string &line)
+ void updateLogLevel();
+
+ void logRaw(LogLevel lev, const std::string &line);
+
+ void clear()
{
- m_buffer.push(line);
+ m_buffer = std::queue<std::string>();
}
- bool empty()
+ bool empty() const
{
return m_buffer.empty();
}
diff --git a/src/main.cpp b/src/main.cpp
index 1993f7c24..b3b17c2d1 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -187,7 +187,13 @@ int main(int argc, char *argv[])
#ifndef __ANDROID__
// Run unit tests
if (cmd_args.getFlag("run-unittests")) {
+#if BUILD_UNITTESTS
return run_tests();
+#else
+ errorstream << "Unittest support is not enabled in this binary. "
+ << "If you want to enable it, compile project with BUILD_UNITTESTS=1 flag."
+ << std::endl;
+#endif
}
#endif
@@ -207,9 +213,6 @@ int main(int argc, char *argv[])
sanity_check(!game_params.world_path.empty());
- infostream << "Using commanded world path ["
- << game_params.world_path << "]" << std::endl;
-
if (game_params.is_dedicated_server)
return run_dedicated_server(game_params, cmd_args) ? 0 : 1;
@@ -460,7 +463,6 @@ static bool create_userdata_path()
} else {
success = true;
}
- porting::copyAssets();
#else
// Create user data directory
success = fs::CreateDir(porting::path_user);
@@ -686,8 +688,6 @@ static bool auto_select_world(GameParams *game_params)
// No world was specified; try to select it automatically
// Get information about available worlds
- verbosestream << _("Determining world path") << std::endl;
-
std::vector<WorldSpec> worldspecs = getAvailableWorlds();
std::string world_path;
@@ -708,7 +708,7 @@ static bool auto_select_world(GameParams *game_params)
// This is the ultimate default world path
world_path = porting::path_user + DIR_DELIM + "worlds" +
DIR_DELIM + "world";
- infostream << "Creating default world at ["
+ infostream << "Using default world at ["
<< world_path << "]" << std::endl;
}
@@ -770,7 +770,6 @@ static bool determine_subgame(GameParams *game_params)
assert(game_params->world_path != ""); // Pre-condition
- verbosestream << _("Determining gameid/gamespec") << std::endl;
// If world doesn't exist
if (!game_params->world_path.empty()
&& !getWorldExists(game_params->world_path)) {
@@ -782,7 +781,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 << "Subgame specified in default_game ["
+ errorstream << "Game specified in default_game ["
<< g_settings->get("default_game")
<< "] is invalid." << std::endl;
return false;
@@ -807,7 +806,7 @@ static bool determine_subgame(GameParams *game_params)
}
if (!gamespec.isValid()) {
- errorstream << "Subgame [" << gamespec.id << "] could not be found."
+ errorstream << "Game [" << gamespec.id << "] could not be found."
<< std::endl;
return false;
}
@@ -888,7 +887,6 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
// Create server
Server server(game_params.world_path, game_params.game_spec,
false, bind_addr, true, &iface);
- server.init();
g_term_console.setup(&iface, &kill, admin_nick);
@@ -923,7 +921,6 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings &
// Create server
Server server(game_params.world_path, game_params.game_spec, false,
bind_addr, true);
- server.init();
server.start();
// Run server
diff --git a/src/map.cpp b/src/map.cpp
index 40aba067e..b9ab7c066 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -144,7 +144,7 @@ bool Map::isNodeUnderground(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
MapBlock *block = getBlockNoCreateNoEx(blockpos);
- return block && block->getIsUnderground();
+ return block && block->getIsUnderground();
}
bool Map::isValidPosition(v3s16 p)
@@ -478,6 +478,16 @@ void Map::PrintInfo(std::ostream &out)
#define WATER_DROP_BOOST 4
+const static v3s16 liquid_6dirs[6] = {
+ // order: upper before same level before lower
+ v3s16( 0, 1, 0),
+ v3s16( 0, 0, 1),
+ v3s16( 1, 0, 0),
+ v3s16( 0, 0,-1),
+ v3s16(-1, 0, 0),
+ v3s16( 0,-1, 0)
+};
+
enum NeighborType : u8 {
NEIGHBOR_UPPER,
NEIGHBOR_SAME_LEVEL,
@@ -568,7 +578,7 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks,
switch (liquid_type) {
case LIQUID_SOURCE:
liquid_level = LIQUID_LEVEL_SOURCE;
- liquid_kind = m_nodedef->getId(cf.liquid_alternative_flowing);
+ liquid_kind = cf.liquid_alternative_flowing_id;
break;
case LIQUID_FLOWING:
liquid_level = (n0.param2 & LIQUID_LEVEL_MASK);
@@ -587,7 +597,6 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks,
/*
Collect information about the environment
*/
- const v3s16 *dirs = g_6dirs;
NodeNeighbor sources[6]; // surrounding sources
int num_sources = 0;
NodeNeighbor flows[6]; // surrounding flowing liquid nodes
@@ -601,16 +610,16 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks,
for (u16 i = 0; i < 6; i++) {
NeighborType nt = NEIGHBOR_SAME_LEVEL;
switch (i) {
- case 1:
+ case 0:
nt = NEIGHBOR_UPPER;
break;
- case 4:
+ case 5:
nt = NEIGHBOR_LOWER;
break;
default:
break;
}
- v3s16 npos = p0 + dirs[i];
+ v3s16 npos = p0 + liquid_6dirs[i];
NodeNeighbor nb(getNode(npos), nt, npos);
const ContentFeatures &cfnb = m_nodedef->get(nb.n);
switch (m_nodedef->get(nb.n.getContent()).liquid_type) {
@@ -641,20 +650,24 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks,
case LIQUID_SOURCE:
// if this node is not (yet) of a liquid type, choose the first liquid type we encounter
if (liquid_kind == CONTENT_AIR)
- liquid_kind = m_nodedef->getId(cfnb.liquid_alternative_flowing);
- if (m_nodedef->getId(cfnb.liquid_alternative_flowing) != liquid_kind) {
+ liquid_kind = cfnb.liquid_alternative_flowing_id;
+ if (cfnb.liquid_alternative_flowing_id != liquid_kind) {
neutrals[num_neutrals++] = nb;
} else {
// Do not count bottom source, it will screw things up
- if(dirs[i].Y != -1)
+ if(nt != NEIGHBOR_LOWER)
sources[num_sources++] = nb;
}
break;
case LIQUID_FLOWING:
- // if this node is not (yet) of a liquid type, choose the first liquid type we encounter
- if (liquid_kind == CONTENT_AIR)
- liquid_kind = m_nodedef->getId(cfnb.liquid_alternative_flowing);
- if (m_nodedef->getId(cfnb.liquid_alternative_flowing) != liquid_kind) {
+ if (nb.t != NEIGHBOR_SAME_LEVEL ||
+ (nb.n.param2 & LIQUID_FLOW_DOWN_MASK) != LIQUID_FLOW_DOWN_MASK) {
+ // if this node is not (yet) of a liquid type, choose the first liquid type we encounter
+ // but exclude falling liquids on the same level, they cannot flow here anyway
+ if (liquid_kind == CONTENT_AIR)
+ liquid_kind = cfnb.liquid_alternative_flowing_id;
+ }
+ if (cfnb.liquid_alternative_flowing_id != liquid_kind) {
neutrals[num_neutrals++] = nb;
} else {
flows[num_flows++] = nb;
@@ -680,7 +693,7 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks,
// liquid_kind will be set to either the flowing alternative of the node (if it's a liquid)
// or the flowing alternative of the first of the surrounding sources (if it's air), so
// it's perfectly safe to use liquid_kind here to determine the new node content.
- new_node_content = m_nodedef->getId(m_nodedef->get(liquid_kind).liquid_alternative_source);
+ new_node_content = m_nodedef->get(liquid_kind).liquid_alternative_source_id;
} else if (num_sources >= 1 && sources[0].t != NEIGHBOR_LOWER) {
// liquid_kind is set properly, see above
max_node_level = new_node_level = LIQUID_LEVEL_MAX;
@@ -1187,7 +1200,7 @@ bool Map::isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes)
ServerMap
*/
ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef,
- EmergeManager *emerge):
+ EmergeManager *emerge, MetricsBackend *mb):
Map(dout_server, gamedef),
settings_mgr(g_settings, savedir + DIR_DELIM + "map_meta.txt"),
m_emerge(emerge)
@@ -1221,6 +1234,8 @@ ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef,
m_savedir = savedir;
m_map_saving_enabled = false;
+ m_save_time_counter = mb->addCounter("minetest_core_map_save_time", "Map save time (in nanoseconds)");
+
try {
// If directory exists, check contents and load if possible
if (fs::PathExists(m_savedir)) {
@@ -1777,6 +1792,8 @@ void ServerMap::save(ModifiedState save_level)
return;
}
+ u64 start_time = porting::getTimeNs();
+
if(save_level == MOD_STATE_CLEAN)
infostream<<"ServerMap: Saving whole map, this can take time."
<<std::endl;
@@ -1827,14 +1844,17 @@ void ServerMap::save(ModifiedState save_level)
*/
if(save_level == MOD_STATE_CLEAN
|| block_count != 0) {
- infostream<<"ServerMap: Written: "
- <<block_count<<" block files"
- <<", "<<block_count_all<<" blocks in memory."
- <<std::endl;
+ infostream << "ServerMap: Written: "
+ << block_count << " blocks"
+ << ", " << block_count_all << " blocks in memory."
+ << std::endl;
PrintInfo(infostream); // ServerMap/ClientMap:
infostream<<"Blocks modified by: "<<std::endl;
modprofiler.print(infostream);
}
+
+ auto end_time = porting::getTimeNs();
+ m_save_time_counter->increment(end_time - start_time);
}
void ServerMap::listAllLoadableBlocks(std::vector<v3s16> &dst)
diff --git a/src/map.h b/src/map.h
index da55fb432..4d9847063 100644
--- a/src/map.h
+++ b/src/map.h
@@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "voxel.h"
#include "modifiedstate.h"
#include "util/container.h"
+#include "util/metricsbackend.h"
#include "nodetimer.h"
#include "map_settings_manager.h"
#include "debug.h"
@@ -45,6 +46,7 @@ class NodeMetadata;
class IGameDef;
class IRollbackManager;
class EmergeManager;
+class MetricsBackend;
class ServerEnvironment;
struct BlockMakeData;
@@ -324,7 +326,7 @@ public:
/*
savedir: directory to which map data should be saved
*/
- ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge);
+ ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb);
~ServerMap();
s32 mapType() const
@@ -448,6 +450,8 @@ private:
bool m_map_metadata_changed = true;
MapDatabase *dbase = nullptr;
MapDatabase *dbase_ro = nullptr;
+
+ MetricCounterPtr m_save_time_counter;
};
diff --git a/src/map_settings_manager.cpp b/src/map_settings_manager.cpp
index 7ef4bf12e..4f070e910 100644
--- a/src/map_settings_manager.cpp
+++ b/src/map_settings_manager.cpp
@@ -32,7 +32,6 @@ MapSettingsManager::MapSettingsManager(Settings *user_settings,
m_user_settings(user_settings)
{
assert(m_user_settings != NULL);
- Mapgen::setDefaultSettings(m_map_settings);
}
@@ -180,6 +179,16 @@ MapgenParams *MapSettingsManager::makeMapgenParams()
params->mgtype = mgtype;
+ // Load the mapgen param defaults
+ /* FIXME: Why is it done like this? MapgenParams should just
+ * set the defaults in its constructor instead. */
+ {
+ Settings default_settings;
+ Mapgen::setDefaultSettings(&default_settings);
+ params->MapgenParams::readParams(&default_settings);
+ params->readParams(&default_settings);
+ }
+
// Load the rest of the mapgen params from our active settings
params->MapgenParams::readParams(m_user_settings);
params->MapgenParams::readParams(m_map_settings);
diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp
index a9df4506f..340079821 100644
--- a/src/mapgen/cavegen.cpp
+++ b/src/mapgen/cavegen.cpp
@@ -1,8 +1,8 @@
/*
Minetest
-Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2010-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2018 paramat
+Copyright (C) 2010-2020 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2015-2020 paramat
+Copyright (C) 2010-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
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
@@ -69,7 +69,7 @@ CavesNoiseIntersection::~CavesNoiseIntersection()
void CavesNoiseIntersection::generateCaves(MMVManip *vm,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap)
{
assert(vm);
assert(biomemap);
diff --git a/src/mapgen/cavegen.h b/src/mapgen/cavegen.h
index ff09f9423..d678d365b 100644
--- a/src/mapgen/cavegen.h
+++ b/src/mapgen/cavegen.h
@@ -1,7 +1,7 @@
/*
Minetest
-Copyright (C) 2010-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2018 paramat
+Copyright (C) 2015-2020 paramat
+Copyright (C) 2010-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
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
@@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define VMANIP_FLAG_CAVE VOXELFLAG_CHECKED1
+typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include
+
class GenerateNotifier;
/*
@@ -44,7 +46,7 @@ public:
NoiseParams *np_cave2, s32 seed, float cave_width);
~CavesNoiseIntersection();
- void generateCaves(MMVManip *vm, v3s16 nmin, v3s16 nmax, u8 *biomemap);
+ void generateCaves(MMVManip *vm, v3s16 nmin, v3s16 nmax, biome_t *biomemap);
private:
const NodeDefManager *m_ndef;
diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp
index 79c429ff6..f57529082 100644
--- a/src/mapgen/mapgen.cpp
+++ b/src/mapgen/mapgen.cpp
@@ -28,7 +28,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock.h"
#include "mapnode.h"
#include "map.h"
-#include "content_sao.h"
#include "nodedef.h"
#include "emerge.h"
#include "voxelalgorithms.h"
@@ -107,8 +106,8 @@ STATIC_ASSERT(
//// Mapgen
////
-Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge) :
- gennotify(emerge->gen_notify_on, &emerge->gen_notify_on_deco_ids)
+Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeParams *emerge) :
+ gennotify(emerge->gen_notify_on, emerge->gen_notify_on_deco_ids)
{
id = mapgenid;
water_level = params->water_level;
@@ -157,7 +156,7 @@ const char *Mapgen::getMapgenName(MapgenType mgtype)
Mapgen *Mapgen::createMapgen(MapgenType mgtype, MapgenParams *params,
- EmergeManager *emerge)
+ EmergeParams *emerge)
{
switch (mgtype) {
case MAPGEN_CARPATHIAN:
@@ -586,7 +585,7 @@ void Mapgen::spreadLight(const v3s16 &nmin, const v3s16 &nmax)
//// MapgenBasic
////
-MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge)
+MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeParams *emerge)
: Mapgen(mapgenid, params, emerge)
{
this->m_emerge = emerge;
@@ -629,6 +628,13 @@ MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emer
// Lava falls back to water as both are suitable as cave liquids.
if (c_lava_source == CONTENT_IGNORE)
c_lava_source = c_water_source;
+
+ if (c_stone == CONTENT_IGNORE)
+ errorstream << "Mapgen: Mapgen alias 'mapgen_stone' is invalid!" << std::endl;
+ if (c_water_source == CONTENT_IGNORE)
+ errorstream << "Mapgen: Mapgen alias 'mapgen_water_source' is invalid!" << std::endl;
+ if (c_river_water_source == CONTENT_IGNORE)
+ warningstream << "Mapgen: Mapgen alias 'mapgen_river_water_source' is invalid!" << std::endl;
}
@@ -636,6 +642,8 @@ MapgenBasic::~MapgenBasic()
{
delete biomegen;
delete []heightmap;
+
+ delete m_emerge; // destroying EmergeParams is our responsibility
}
@@ -968,7 +976,7 @@ void MapgenBasic::generateDungeons(s16 max_stone_y)
////
GenerateNotifier::GenerateNotifier(u32 notify_on,
- std::set<u32> *notify_on_deco_ids)
+ const std::set<u32> *notify_on_deco_ids)
{
m_notify_on = notify_on;
m_notify_on_deco_ids = notify_on_deco_ids;
@@ -981,7 +989,8 @@ void GenerateNotifier::setNotifyOn(u32 notify_on)
}
-void GenerateNotifier::setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids)
+void GenerateNotifier::setNotifyOnDecoIds(
+ const std::set<u32> *notify_on_deco_ids)
{
m_notify_on_deco_ids = notify_on_deco_ids;
}
@@ -993,7 +1002,7 @@ bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id)
return false;
if (type == GENNOTIFY_DECORATION &&
- m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->end())
+ m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->cend())
return false;
GenNotifyEvent gne;
diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h
index dc325c791..a92b3b0d0 100644
--- a/src/mapgen/mapgen.h
+++ b/src/mapgen/mapgen.h
@@ -1,8 +1,8 @@
/*
Minetest
-Copyright (C) 2010-2018 celeron55, Perttu Ahola <celeron55@gmail.com>
-Copyright (C) 2013-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2018 paramat
+Copyright (C) 2010-2020 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2015-2020 paramat
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
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
@@ -38,7 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define MG_DECORATIONS 0x20
#define MG_BIOMES 0x40
-typedef u8 biome_t; // copy from mg_biome.h to avoid an unnecessary include
+typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include
class Settings;
class MMVManip;
@@ -51,6 +51,7 @@ class Biome;
class BiomeGen;
struct BiomeParams;
class BiomeManager;
+class EmergeParams;
class EmergeManager;
class MapBlock;
class VoxelManipulator;
@@ -87,10 +88,10 @@ struct GenNotifyEvent {
class GenerateNotifier {
public:
GenerateNotifier() = default;
- GenerateNotifier(u32 notify_on, std::set<u32> *notify_on_deco_ids);
+ GenerateNotifier(u32 notify_on, const std::set<u32> *notify_on_deco_ids);
void setNotifyOn(u32 notify_on);
- void setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids);
+ void setNotifyOnDecoIds(const std::set<u32> *notify_on_deco_ids);
bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0);
void getEvents(std::map<std::string, std::vector<v3s16> > &event_map);
@@ -98,7 +99,7 @@ public:
private:
u32 m_notify_on = 0;
- std::set<u32> *m_notify_on_deco_ids;
+ const std::set<u32> *m_notify_on_deco_ids;
std::list<GenNotifyEvent> m_notify_events;
};
@@ -176,7 +177,7 @@ public:
GenerateNotifier gennotify;
Mapgen() = default;
- Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge);
+ Mapgen(int mapgenid, MapgenParams *params, EmergeParams *emerge);
virtual ~Mapgen() = default;
DISABLE_CLASS_COPY(Mapgen);
@@ -215,7 +216,7 @@ public:
static MapgenType getMapgenType(const std::string &mgname);
static const char *getMapgenName(MapgenType mgtype);
static Mapgen *createMapgen(MapgenType mgtype, MapgenParams *params,
- EmergeManager *emerge);
+ EmergeParams *emerge);
static MapgenParams *createMapgenParams(MapgenType mgtype);
static void getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden);
static void setDefaultSettings(Settings *settings);
@@ -243,7 +244,7 @@ private:
*/
class MapgenBasic : public Mapgen {
public:
- MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge);
+ MapgenBasic(int mapgenid, MapgenParams *params, EmergeParams *emerge);
virtual ~MapgenBasic();
virtual void generateBiomes();
@@ -254,7 +255,7 @@ public:
virtual void generateDungeons(s16 max_stone_y);
protected:
- EmergeManager *m_emerge;
+ EmergeParams *m_emerge;
BiomeManager *m_bmgr;
Noise *noise_filler_depth;
diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp
index 0dc1d33be..feb9b428c 100644
--- a/src/mapgen/mapgen_carpathian.cpp
+++ b/src/mapgen/mapgen_carpathian.cpp
@@ -26,7 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock.h"
#include "mapnode.h"
#include "map.h"
-#include "content_sao.h"
#include "nodedef.h"
#include "voxelalgorithms.h"
//#include "profiler.h" // For TimeTaker
@@ -50,7 +49,7 @@ FlagDesc flagdesc_mapgen_carpathian[] = {
///////////////////////////////////////////////////////////////////////////////
-MapgenCarpathian::MapgenCarpathian(MapgenCarpathianParams *params, EmergeManager *emerge)
+MapgenCarpathian::MapgenCarpathian(MapgenCarpathianParams *params, EmergeParams *emerge)
: MapgenBasic(MAPGEN_CARPATHIAN, params, emerge)
{
base_level = params->base_level;
diff --git a/src/mapgen/mapgen_carpathian.h b/src/mapgen/mapgen_carpathian.h
index acd379958..31b2b91d8 100644
--- a/src/mapgen/mapgen_carpathian.h
+++ b/src/mapgen/mapgen_carpathian.h
@@ -79,7 +79,7 @@ struct MapgenCarpathianParams : public MapgenParams
class MapgenCarpathian : public MapgenBasic
{
public:
- MapgenCarpathian(MapgenCarpathianParams *params, EmergeManager *emerge);
+ MapgenCarpathian(MapgenCarpathianParams *params, EmergeParams *emerge);
~MapgenCarpathian();
virtual MapgenType getType() const { return MAPGEN_CARPATHIAN; }
diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp
index 879435948..369777ad2 100644
--- a/src/mapgen/mapgen_flat.cpp
+++ b/src/mapgen/mapgen_flat.cpp
@@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock.h"
#include "mapnode.h"
#include "map.h"
-#include "content_sao.h"
#include "nodedef.h"
#include "voxelalgorithms.h"
//#include "profiler.h" // For TimeTaker
@@ -48,7 +47,7 @@ FlagDesc flagdesc_mapgen_flat[] = {
///////////////////////////////////////////////////////////////////////////////////////
-MapgenFlat::MapgenFlat(MapgenFlatParams *params, EmergeManager *emerge)
+MapgenFlat::MapgenFlat(MapgenFlatParams *params, EmergeParams *emerge)
: MapgenBasic(MAPGEN_FLAT, params, emerge)
{
spflags = params->spflags;
diff --git a/src/mapgen/mapgen_flat.h b/src/mapgen/mapgen_flat.h
index c314c7605..4902a802c 100644
--- a/src/mapgen/mapgen_flat.h
+++ b/src/mapgen/mapgen_flat.h
@@ -64,7 +64,7 @@ struct MapgenFlatParams : public MapgenParams
class MapgenFlat : public MapgenBasic
{
public:
- MapgenFlat(MapgenFlatParams *params, EmergeManager *emerge);
+ MapgenFlat(MapgenFlatParams *params, EmergeParams *emerge);
~MapgenFlat();
virtual MapgenType getType() const { return MAPGEN_FLAT; }
diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp
index 96febb4f4..cb55bc288 100644
--- a/src/mapgen/mapgen_fractal.cpp
+++ b/src/mapgen/mapgen_fractal.cpp
@@ -26,7 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock.h"
#include "mapnode.h"
#include "map.h"
-#include "content_sao.h"
#include "nodedef.h"
#include "voxelalgorithms.h"
//#include "profiler.h" // For TimeTaker
@@ -48,7 +47,7 @@ FlagDesc flagdesc_mapgen_fractal[] = {
///////////////////////////////////////////////////////////////////////////////////////
-MapgenFractal::MapgenFractal(MapgenFractalParams *params, EmergeManager *emerge)
+MapgenFractal::MapgenFractal(MapgenFractalParams *params, EmergeParams *emerge)
: MapgenBasic(MAPGEN_FRACTAL, params, emerge)
{
spflags = params->spflags;
diff --git a/src/mapgen/mapgen_fractal.h b/src/mapgen/mapgen_fractal.h
index 971dfd822..23af925bc 100644
--- a/src/mapgen/mapgen_fractal.h
+++ b/src/mapgen/mapgen_fractal.h
@@ -72,7 +72,7 @@ struct MapgenFractalParams : public MapgenParams
class MapgenFractal : public MapgenBasic
{
public:
- MapgenFractal(MapgenFractalParams *params, EmergeManager *emerge);
+ MapgenFractal(MapgenFractalParams *params, EmergeParams *emerge);
~MapgenFractal();
virtual MapgenType getType() const { return MAPGEN_FRACTAL; }
diff --git a/src/mapgen/mapgen_singlenode.cpp b/src/mapgen/mapgen_singlenode.cpp
index b64524e1c..cade9e7a8 100644
--- a/src/mapgen/mapgen_singlenode.cpp
+++ b/src/mapgen/mapgen_singlenode.cpp
@@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "emerge.h"
-MapgenSinglenode::MapgenSinglenode(MapgenParams *params, EmergeManager *emerge)
+MapgenSinglenode::MapgenSinglenode(MapgenParams *params, EmergeParams *emerge)
: Mapgen(MAPGEN_SINGLENODE, params, emerge)
{
const NodeDefManager *ndef = emerge->ndef;
diff --git a/src/mapgen/mapgen_singlenode.h b/src/mapgen/mapgen_singlenode.h
index c21089eda..e056d9ab1 100644
--- a/src/mapgen/mapgen_singlenode.h
+++ b/src/mapgen/mapgen_singlenode.h
@@ -38,7 +38,7 @@ public:
content_t c_node;
u8 set_light;
- MapgenSinglenode(MapgenParams *params, EmergeManager *emerge);
+ MapgenSinglenode(MapgenParams *params, EmergeParams *emerge);
~MapgenSinglenode() = default;
virtual MapgenType getType() const { return MAPGEN_SINGLENODE; }
diff --git a/src/mapgen/mapgen_v5.cpp b/src/mapgen/mapgen_v5.cpp
index 447fe8c50..124667e5d 100644
--- a/src/mapgen/mapgen_v5.cpp
+++ b/src/mapgen/mapgen_v5.cpp
@@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock.h"
#include "mapnode.h"
#include "map.h"
-#include "content_sao.h"
#include "nodedef.h"
#include "voxelalgorithms.h"
//#include "profiler.h" // For TimeTaker
@@ -45,7 +44,7 @@ FlagDesc flagdesc_mapgen_v5[] = {
};
-MapgenV5::MapgenV5(MapgenV5Params *params, EmergeManager *emerge)
+MapgenV5::MapgenV5(MapgenV5Params *params, EmergeParams *emerge)
: MapgenBasic(MAPGEN_V5, params, emerge)
{
spflags = params->spflags;
diff --git a/src/mapgen/mapgen_v5.h b/src/mapgen/mapgen_v5.h
index 17bc466f0..cf4ee4899 100644
--- a/src/mapgen/mapgen_v5.h
+++ b/src/mapgen/mapgen_v5.h
@@ -64,7 +64,7 @@ struct MapgenV5Params : public MapgenParams
class MapgenV5 : public MapgenBasic
{
public:
- MapgenV5(MapgenV5Params *params, EmergeManager *emerge);
+ MapgenV5(MapgenV5Params *params, EmergeParams *emerge);
~MapgenV5();
virtual MapgenType getType() const { return MAPGEN_V5; }
diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp
index 653adc8ec..e9692246c 100644
--- a/src/mapgen/mapgen_v6.cpp
+++ b/src/mapgen/mapgen_v6.cpp
@@ -27,8 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock.h"
#include "mapnode.h"
#include "map.h"
-//#include "serverobject.h"
-#include "content_sao.h"
#include "nodedef.h"
#include "voxelalgorithms.h"
//#include "profiler.h" // For TimeTaker
@@ -56,7 +54,7 @@ FlagDesc flagdesc_mapgen_v6[] = {
/////////////////////////////////////////////////////////////////////////////
-MapgenV6::MapgenV6(MapgenV6Params *params, EmergeManager *emerge)
+MapgenV6::MapgenV6(MapgenV6Params *params, EmergeParams *emerge)
: Mapgen(MAPGEN_V6, params, emerge)
{
m_emerge = emerge;
@@ -132,6 +130,21 @@ MapgenV6::MapgenV6(MapgenV6Params *params, EmergeManager *emerge)
c_stair_cobble = c_cobble;
if (c_stair_desert_stone == CONTENT_IGNORE)
c_stair_desert_stone = c_desert_stone;
+
+ if (c_stone == CONTENT_IGNORE)
+ errorstream << "Mapgen v6: Mapgen alias 'mapgen_stone' is invalid!" << std::endl;
+ if (c_dirt == CONTENT_IGNORE)
+ errorstream << "Mapgen v6: Mapgen alias 'mapgen_dirt' is invalid!" << std::endl;
+ if (c_dirt_with_grass == CONTENT_IGNORE)
+ errorstream << "Mapgen v6: Mapgen alias 'mapgen_dirt_with_grass' is invalid!" << std::endl;
+ if (c_sand == CONTENT_IGNORE)
+ errorstream << "Mapgen v6: Mapgen alias 'mapgen_sand' is invalid!" << std::endl;
+ if (c_water_source == CONTENT_IGNORE)
+ errorstream << "Mapgen v6: Mapgen alias 'mapgen_water_source' is invalid!" << std::endl;
+ if (c_lava_source == CONTENT_IGNORE)
+ errorstream << "Mapgen v6: Mapgen alias 'mapgen_lava_source' is invalid!" << std::endl;
+ if (c_cobble == CONTENT_IGNORE)
+ errorstream << "Mapgen v6: Mapgen alias 'mapgen_cobble' is invalid!" << std::endl;
}
@@ -147,6 +160,8 @@ MapgenV6::~MapgenV6()
delete noise_humidity;
delete[] heightmap;
+
+ delete m_emerge; // our responsibility
}
diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h
index d8cdcb26f..ff565edec 100644
--- a/src/mapgen/mapgen_v6.h
+++ b/src/mapgen/mapgen_v6.h
@@ -83,7 +83,7 @@ struct MapgenV6Params : public MapgenParams {
class MapgenV6 : public Mapgen {
public:
- EmergeManager *m_emerge;
+ EmergeParams *m_emerge;
int ystride;
u32 spflags;
@@ -133,7 +133,7 @@ public:
content_t c_stair_cobble;
content_t c_stair_desert_stone;
- MapgenV6(MapgenV6Params *params, EmergeManager *emerge);
+ MapgenV6(MapgenV6Params *params, EmergeParams *emerge);
~MapgenV6();
virtual MapgenType getType() const { return MAPGEN_V6; }
diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp
index 325c4957a..e93dc9140 100644
--- a/src/mapgen/mapgen_v7.cpp
+++ b/src/mapgen/mapgen_v7.cpp
@@ -1,7 +1,7 @@
/*
Minetest
-Copyright (C) 2013-2019 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2014-2019 paramat
+Copyright (C) 2014-2020 paramat
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
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
@@ -26,7 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapblock.h"
#include "mapnode.h"
#include "map.h"
-#include "content_sao.h"
#include "nodedef.h"
#include "voxelalgorithms.h"
//#include "profiler.h" // For TimeTaker
@@ -52,11 +51,17 @@ FlagDesc flagdesc_mapgen_v7[] = {
////////////////////////////////////////////////////////////////////////////////
-MapgenV7::MapgenV7(MapgenV7Params *params, EmergeManager *emerge)
+MapgenV7::MapgenV7(MapgenV7Params *params, EmergeParams *emerge)
: MapgenBasic(MAPGEN_V7, params, emerge)
{
spflags = params->spflags;
mount_zero_level = params->mount_zero_level;
+ floatland_ymin = params->floatland_ymin;
+ floatland_ymax = params->floatland_ymax;
+ floatland_taper = params->floatland_taper;
+ float_taper_exp = params->float_taper_exp;
+ floatland_density = params->floatland_density;
+ floatland_ywater = params->floatland_ywater;
cave_width = params->cave_width;
large_cave_depth = params->large_cave_depth;
@@ -71,6 +76,9 @@ MapgenV7::MapgenV7(MapgenV7Params *params, EmergeManager *emerge)
dungeon_ymin = params->dungeon_ymin;
dungeon_ymax = params->dungeon_ymax;
+ // Allocate floatland noise offset cache
+ this->float_offset_cache = new float[csize.Y + 2];
+
// 2D noise
noise_terrain_base =
new Noise(&params->np_terrain_base, seed, csize.X, csize.Z);
@@ -101,6 +109,12 @@ MapgenV7::MapgenV7(MapgenV7Params *params, EmergeManager *emerge)
new Noise(&params->np_ridge, seed, csize.X, csize.Y + 2, csize.Z);
}
+ if (spflags & MGV7_FLOATLANDS) {
+ // 3D noise, 1 up, 1 down overgeneration
+ noise_floatland =
+ new Noise(&params->np_floatland, seed, csize.X, csize.Y + 2, csize.Z);
+ }
+
// 3D noise, 1 down overgeneration
MapgenBasic::np_cave1 = params->np_cave1;
MapgenBasic::np_cave2 = params->np_cave2;
@@ -127,6 +141,12 @@ MapgenV7::~MapgenV7()
delete noise_ridge_uwater;
delete noise_ridge;
}
+
+ if (spflags & MGV7_FLOATLANDS) {
+ delete noise_floatland;
+ }
+
+ delete []float_offset_cache;
}
@@ -140,6 +160,7 @@ MapgenV7Params::MapgenV7Params():
np_ridge_uwater (0.0, 1.0, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0),
np_mountain (-0.6, 1.0, v3f(250, 350, 250), 5333, 5, 0.63, 2.0),
np_ridge (0.0, 1.0, v3f(100, 100, 100), 6467, 4, 0.75, 2.0),
+ np_floatland (0.0, 0.7, v3f(384, 96, 384), 1009, 4, 0.75, 1.618),
np_cavern (0.0, 1.0, v3f(384, 128, 384), 723, 5, 0.63, 2.0),
np_cave1 (0.0, 12.0, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
np_cave2 (0.0, 12.0, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
@@ -152,6 +173,13 @@ void MapgenV7Params::readParams(const Settings *settings)
{
settings->getFlagStrNoEx("mgv7_spflags", spflags, flagdesc_mapgen_v7);
settings->getS16NoEx("mgv7_mount_zero_level", mount_zero_level);
+ settings->getS16NoEx("mgv7_floatland_ymin", floatland_ymin);
+ settings->getS16NoEx("mgv7_floatland_ymax", floatland_ymax);
+ settings->getS16NoEx("mgv7_floatland_taper", floatland_taper);
+ settings->getFloatNoEx("mgv7_float_taper_exp", float_taper_exp);
+ settings->getFloatNoEx("mgv7_floatland_density", floatland_density);
+ settings->getS16NoEx("mgv7_floatland_ywater", floatland_ywater);
+
settings->getFloatNoEx("mgv7_cave_width", cave_width);
settings->getS16NoEx("mgv7_large_cave_depth", large_cave_depth);
settings->getU16NoEx("mgv7_small_cave_num_min", small_cave_num_min);
@@ -174,6 +202,7 @@ void MapgenV7Params::readParams(const Settings *settings)
settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater);
settings->getNoiseParams("mgv7_np_mountain", np_mountain);
settings->getNoiseParams("mgv7_np_ridge", np_ridge);
+ settings->getNoiseParams("mgv7_np_floatland", np_floatland);
settings->getNoiseParams("mgv7_np_cavern", np_cavern);
settings->getNoiseParams("mgv7_np_cave1", np_cave1);
settings->getNoiseParams("mgv7_np_cave2", np_cave2);
@@ -185,6 +214,13 @@ void MapgenV7Params::writeParams(Settings *settings) const
{
settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7);
settings->setS16("mgv7_mount_zero_level", mount_zero_level);
+ settings->setS16("mgv7_floatland_ymin", floatland_ymin);
+ settings->setS16("mgv7_floatland_ymax", floatland_ymax);
+ settings->setS16("mgv7_floatland_taper", floatland_taper);
+ settings->setFloat("mgv7_float_taper_exp", float_taper_exp);
+ settings->setFloat("mgv7_floatland_density", floatland_density);
+ settings->setS16("mgv7_floatland_ywater", floatland_ywater);
+
settings->setFloat("mgv7_cave_width", cave_width);
settings->setS16("mgv7_large_cave_depth", large_cave_depth);
settings->setU16("mgv7_small_cave_num_min", small_cave_num_min);
@@ -207,6 +243,7 @@ void MapgenV7Params::writeParams(Settings *settings) const
settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater);
settings->setNoiseParams("mgv7_np_mountain", np_mountain);
settings->setNoiseParams("mgv7_np_ridge", np_ridge);
+ settings->setNoiseParams("mgv7_np_floatland", np_floatland);
settings->setNoiseParams("mgv7_np_cavern", np_cavern);
settings->setNoiseParams("mgv7_np_cave1", np_cave1);
settings->setNoiseParams("mgv7_np_cave2", np_cave2);
@@ -358,8 +395,9 @@ void MapgenV7::makeChunk(BlockMakeData *data)
updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
// Calculate lighting
- // TODO disable in and just below floatlands
- bool propagate_shadow = true;
+ // Limit floatland shadows
+ bool propagate_shadow = !((spflags & MGV7_FLOATLANDS) &&
+ node_max.Y >= floatland_ymin - csize.Y * 2 && node_min.Y <= floatland_ymax);
if (flags & MG_LIGHT)
calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
@@ -428,6 +466,12 @@ bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y)
}
+bool MapgenV7::getFloatlandTerrainFromMap(int idx_xyz, float float_offset)
+{
+ return noise_floatland->result[idx_xyz] + floatland_density - float_offset >= 0.0f;
+}
+
+
int MapgenV7::generateTerrain()
{
MapNode n_air(CONTENT_AIR);
@@ -447,6 +491,35 @@ int MapgenV7::generateTerrain()
noise_mountain->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
}
+ //// Floatlands
+ // 'Generate floatlands in this mapchunk' bool for
+ // simplification of condition checks in y-loop.
+ bool gen_floatlands = false;
+ u8 cache_index = 0;
+ // Y values where floatland tapering starts
+ s16 float_taper_ymax = floatland_ymax - floatland_taper;
+ s16 float_taper_ymin = floatland_ymin + floatland_taper;
+
+ if ((spflags & MGV7_FLOATLANDS) &&
+ node_max.Y >= floatland_ymin && node_min.Y <= floatland_ymax) {
+ gen_floatlands = true;
+ // Calculate noise for floatland generation
+ noise_floatland->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+
+ // Cache floatland noise offset values, for floatland tapering
+ for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++, cache_index++) {
+ float float_offset = 0.0f;
+ if (y > float_taper_ymax) {
+ float_offset = std::pow((y - float_taper_ymax) / (float)floatland_taper,
+ float_taper_exp) * 4.0f;
+ } else if (y < float_taper_ymin) {
+ float_offset = std::pow((float_taper_ymin - y) / (float)floatland_taper,
+ float_taper_exp) * 4.0f;
+ }
+ float_offset_cache[cache_index] = float_offset;
+ }
+ }
+
//// Place nodes
const v3s16 &em = vm->m_area.getExtent();
s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
@@ -458,13 +531,15 @@ int MapgenV7::generateTerrain()
if (surface_y > stone_surface_max_y)
stone_surface_max_y = surface_y;
+ cache_index = 0;
u32 vi = vm->m_area.index(x, node_min.Y - 1, z);
u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
for (s16 y = node_min.Y - 1; y <= node_max.Y + 1;
y++,
index3d += ystride,
- VoxelArea::add_y(em, vi, 1)) {
+ VoxelArea::add_y(em, vi, 1),
+ cache_index++) {
if (vm->m_data[vi].getContent() != CONTENT_IGNORE)
continue;
@@ -475,10 +550,18 @@ int MapgenV7::generateTerrain()
vm->m_data[vi] = n_stone; // Mountain terrain
if (y > stone_surface_max_y)
stone_surface_max_y = y;
- } else if (y <= water_level) {
+ } else if (gen_floatlands &&
+ getFloatlandTerrainFromMap(index3d,
+ float_offset_cache[cache_index])) {
+ vm->m_data[vi] = n_stone; // Floatland terrain
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ } else if (y <= water_level) { // Surface water
vm->m_data[vi] = n_water;
+ } else if (gen_floatlands && y >= float_taper_ymax && y <= floatland_ywater) {
+ vm->m_data[vi] = n_water; // Water for solid floatland layer only
} else {
- vm->m_data[vi] = n_air;
+ vm->m_data[vi] = n_air; // Air
}
}
}
@@ -489,8 +572,8 @@ int MapgenV7::generateTerrain()
void MapgenV7::generateRidgeTerrain()
{
- // TODO disable river canyons in floatlands
- if (node_max.Y < water_level - 16)
+ if (node_max.Y < water_level - 16 ||
+ (node_max.Y >= floatland_ymin && node_min.Y <= floatland_ymax))
return;
noise_ridge->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
diff --git a/src/mapgen/mapgen_v7.h b/src/mapgen/mapgen_v7.h
index 0605c5c97..4020cd935 100644
--- a/src/mapgen/mapgen_v7.h
+++ b/src/mapgen/mapgen_v7.h
@@ -1,7 +1,7 @@
/*
Minetest
-Copyright (C) 2013-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2014-2018 paramat
+Copyright (C) 2014-2020 paramat
+Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
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
@@ -36,6 +36,12 @@ extern FlagDesc flagdesc_mapgen_v7[];
struct MapgenV7Params : public MapgenParams {
s16 mount_zero_level = 0;
+ s16 floatland_ymin = 1024;
+ s16 floatland_ymax = 4096;
+ s16 floatland_taper = 256;
+ float float_taper_exp = 2.0f;
+ float floatland_density = -0.6f;
+ s16 floatland_ywater = -31000;
float cave_width = 0.09f;
s16 large_cave_depth = -33;
@@ -59,6 +65,7 @@ struct MapgenV7Params : public MapgenParams {
NoiseParams np_ridge_uwater;
NoiseParams np_mountain;
NoiseParams np_ridge;
+ NoiseParams np_floatland;
NoiseParams np_cavern;
NoiseParams np_cave1;
NoiseParams np_cave2;
@@ -75,7 +82,7 @@ struct MapgenV7Params : public MapgenParams {
class MapgenV7 : public MapgenBasic {
public:
- MapgenV7(MapgenV7Params *params, EmergeManager *emerge);
+ MapgenV7(MapgenV7Params *params, EmergeParams *emerge);
~MapgenV7();
virtual MapgenType getType() const { return MAPGEN_V7; }
@@ -87,12 +94,21 @@ public:
float baseTerrainLevelFromMap(int index);
bool getMountainTerrainAtPoint(s16 x, s16 y, s16 z);
bool getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y);
+ bool getFloatlandTerrainFromMap(int idx_xyz, float float_offset);
int generateTerrain();
void generateRidgeTerrain();
private:
s16 mount_zero_level;
+ s16 floatland_ymin;
+ s16 floatland_ymax;
+ s16 floatland_taper;
+ float float_taper_exp;
+ float floatland_density;
+ s16 floatland_ywater;
+
+ float *float_offset_cache = nullptr;
Noise *noise_terrain_base;
Noise *noise_terrain_alt;
@@ -102,4 +118,5 @@ private:
Noise *noise_ridge_uwater;
Noise *noise_mountain;
Noise *noise_ridge;
+ Noise *noise_floatland;
};
diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp
index ff908b7bb..efcc8ee85 100644
--- a/src/mapgen/mapgen_valleys.cpp
+++ b/src/mapgen/mapgen_valleys.cpp
@@ -54,7 +54,7 @@ FlagDesc flagdesc_mapgen_valleys[] = {
};
-MapgenValleys::MapgenValleys(MapgenValleysParams *params, EmergeManager *emerge)
+MapgenValleys::MapgenValleys(MapgenValleysParams *params, EmergeParams *emerge)
: MapgenBasic(MAPGEN_VALLEYS, params, emerge)
{
// NOTE: MapgenValleys has a hard dependency on BiomeGenOriginal
diff --git a/src/mapgen/mapgen_valleys.h b/src/mapgen/mapgen_valleys.h
index 1aec68842..34a923dfa 100644
--- a/src/mapgen/mapgen_valleys.h
+++ b/src/mapgen/mapgen_valleys.h
@@ -84,7 +84,7 @@ class MapgenValleys : public MapgenBasic {
public:
MapgenValleys(MapgenValleysParams *params,
- EmergeManager *emerge);
+ EmergeParams *emerge);
~MapgenValleys();
virtual MapgenType getType() const { return MAPGEN_VALLEYS; }
diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp
index 345bc8c6a..610c38594 100644
--- a/src/mapgen/mg_biome.cpp
+++ b/src/mapgen/mg_biome.cpp
@@ -78,7 +78,7 @@ void BiomeManager::clear()
EmergeManager *emerge = m_server->getEmergeManager();
// Remove all dangling references in Decorations
- DecorationManager *decomgr = emerge->decomgr;
+ DecorationManager *decomgr = emerge->getWritableDecorationManager();
for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
Decoration *deco = (Decoration *)decomgr->getRaw(i);
deco->biomes.clear();
@@ -92,9 +92,19 @@ void BiomeManager::clear()
}
+BiomeManager *BiomeManager::clone() const
+{
+ auto mgr = new BiomeManager();
+ assert(mgr);
+ ObjDefManager::cloneTo(mgr);
+ mgr->m_server = m_server;
+ return mgr;
+}
+
+
// For BiomeGen type 'BiomeGenOriginal'
float BiomeManager::getHeatAtPosOriginal(v3s16 pos, NoiseParams &np_heat,
- NoiseParams &np_heat_blend, u64 seed)
+ NoiseParams &np_heat_blend, u64 seed) const
{
return
NoisePerlin2D(&np_heat, pos.X, pos.Z, seed) +
@@ -104,7 +114,7 @@ float BiomeManager::getHeatAtPosOriginal(v3s16 pos, NoiseParams &np_heat,
// For BiomeGen type 'BiomeGenOriginal'
float BiomeManager::getHumidityAtPosOriginal(v3s16 pos, NoiseParams &np_humidity,
- NoiseParams &np_humidity_blend, u64 seed)
+ NoiseParams &np_humidity_blend, u64 seed) const
{
return
NoisePerlin2D(&np_humidity, pos.X, pos.Z, seed) +
@@ -113,7 +123,8 @@ float BiomeManager::getHumidityAtPosOriginal(v3s16 pos, NoiseParams &np_humidity
// For BiomeGen type 'BiomeGenOriginal'
-Biome *BiomeManager::getBiomeFromNoiseOriginal(float heat, float humidity, v3s16 pos)
+const Biome *BiomeManager::getBiomeFromNoiseOriginal(float heat,
+ float humidity, v3s16 pos) const
{
Biome *biome_closest = nullptr;
Biome *biome_closest_blend = nullptr;
@@ -143,9 +154,11 @@ Biome *BiomeManager::getBiomeFromNoiseOriginal(float heat, float humidity, v3s16
}
}
- mysrand(pos.Y + (heat + humidity) * 0.9f);
+ const u64 seed = pos.Y + (heat + humidity) * 0.9f;
+ PcgRandom rng(seed);
+
if (biome_closest_blend && dist_min_blend <= dist_min &&
- myrand_range(0, biome_closest_blend->vertical_blend) >=
+ rng.range(0, biome_closest_blend->vertical_blend) >=
pos.Y - biome_closest_blend->max_pos.Y)
return biome_closest_blend;
@@ -308,10 +321,11 @@ Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, v3s16 po
// Carefully tune pseudorandom seed variation to avoid single node dither
// and create larger scale blending patterns similar to horizontal biome
// blend.
- mysrand(pos.Y + (heat + humidity) * 0.9f);
+ const u64 seed = pos.Y + (heat + humidity) * 0.9f;
+ PcgRandom rng(seed);
if (biome_closest_blend && dist_min_blend <= dist_min &&
- myrand_range(0, biome_closest_blend->vertical_blend) >=
+ rng.range(0, biome_closest_blend->vertical_blend) >=
pos.Y - biome_closest_blend->max_pos.Y)
return biome_closest_blend;
@@ -321,6 +335,41 @@ Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, v3s16 po
////////////////////////////////////////////////////////////////////////////////
+ObjDef *Biome::clone() const
+{
+ auto obj = new Biome();
+ ObjDef::cloneTo(obj);
+ NodeResolver::cloneTo(obj);
+
+ obj->flags = flags;
+
+ obj->c_top = c_top;
+ obj->c_filler = c_filler;
+ obj->c_stone = c_stone;
+ obj->c_water_top = c_water_top;
+ obj->c_water = c_water;
+ obj->c_river_water = c_river_water;
+ obj->c_riverbed = c_riverbed;
+ obj->c_dust = c_dust;
+ obj->c_cave_liquid = c_cave_liquid;
+ obj->c_dungeon = c_dungeon;
+ obj->c_dungeon_alt = c_dungeon_alt;
+ obj->c_dungeon_stair = c_dungeon_stair;
+
+ obj->depth_top = depth_top;
+ obj->depth_filler = depth_filler;
+ obj->depth_water_top = depth_water_top;
+ obj->depth_riverbed = depth_riverbed;
+
+ obj->min_pos = min_pos;
+ obj->max_pos = max_pos;
+ obj->heat_point = heat_point;
+ obj->humidity_point = humidity_point;
+ obj->vertical_blend = vertical_blend;
+
+ return obj;
+}
+
void Biome::resolveNodeNames()
{
getIdFromNrBacklog(&c_top, "mapgen_stone", CONTENT_AIR, false);
diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h
index ee148adbc..be4cfea4d 100644
--- a/src/mapgen/mg_biome.h
+++ b/src/mapgen/mg_biome.h
@@ -1,7 +1,7 @@
/*
Minetest
-Copyright (C) 2014-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2014-2018 paramat
+Copyright (C) 2014-2020 paramat
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
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
@@ -32,7 +32,7 @@ class BiomeManager;
//// Biome
////
-typedef u8 biome_t;
+typedef u16 biome_t;
#define BIOME_NONE ((biome_t)0)
@@ -42,6 +42,8 @@ enum BiomeType {
class Biome : public ObjDef, public NodeResolver {
public:
+ ObjDef *clone() const;
+
u32 flags;
content_t c_top;
@@ -88,6 +90,7 @@ struct BiomeParams {
s32 seed;
};
+// WARNING: this class is not thread-safe
class BiomeGen {
public:
virtual ~BiomeGen() = default;
@@ -191,6 +194,8 @@ public:
BiomeManager(Server *server);
virtual ~BiomeManager() = default;
+ BiomeManager *clone() const;
+
const char *getObjectTitle() const
{
return "biome";
@@ -226,12 +231,15 @@ public:
// For BiomeGen type 'BiomeGenOriginal'
float getHeatAtPosOriginal(v3s16 pos, NoiseParams &np_heat,
- NoiseParams &np_heat_blend, u64 seed);
+ NoiseParams &np_heat_blend, u64 seed) const;
float getHumidityAtPosOriginal(v3s16 pos, NoiseParams &np_humidity,
- NoiseParams &np_humidity_blend, u64 seed);
- Biome *getBiomeFromNoiseOriginal(float heat, float humidity, v3s16 pos);
+ NoiseParams &np_humidity_blend, u64 seed) const;
+ const Biome *getBiomeFromNoiseOriginal(float heat, float humidity,
+ v3s16 pos) const;
private:
+ BiomeManager() {};
+
Server *m_server;
};
diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp
index 28dde0209..a4cada396 100644
--- a/src/mapgen/mg_decoration.cpp
+++ b/src/mapgen/mg_decoration.cpp
@@ -67,6 +67,13 @@ size_t DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed,
return nplaced;
}
+DecorationManager *DecorationManager::clone() const
+{
+ auto mgr = new DecorationManager();
+ ObjDefManager::cloneTo(mgr);
+ return mgr;
+}
+
///////////////////////////////////////////////////////////////////////////////
@@ -199,8 +206,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
// All-surfaces decorations
// Check biome of column
if (mg->biomemap && !biomes.empty()) {
- std::unordered_set<u8>::const_iterator iter =
- biomes.find(mg->biomemap[mapindex]);
+ auto iter = biomes.find(mg->biomemap[mapindex]);
if (iter == biomes.end())
continue;
}
@@ -252,8 +258,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
continue;
if (mg->biomemap && !biomes.empty()) {
- std::unordered_set<u8>::const_iterator iter =
- biomes.find(mg->biomemap[mapindex]);
+ auto iter = biomes.find(mg->biomemap[mapindex]);
if (iter == biomes.end())
continue;
}
@@ -269,9 +274,42 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
}
+void Decoration::cloneTo(Decoration *def) const
+{
+ ObjDef::cloneTo(def);
+ def->flags = flags;
+ def->mapseed = mapseed;
+ def->c_place_on = c_place_on;
+ def->sidelen = sidelen;
+ def->y_min = y_min;
+ def->y_max = y_max;
+ def->fill_ratio = fill_ratio;
+ def->np = np;
+ def->c_spawnby = c_spawnby;
+ def->nspawnby = nspawnby;
+ def->place_offset_y = place_offset_y;
+ def->biomes = biomes;
+}
+
+
///////////////////////////////////////////////////////////////////////////////
+ObjDef *DecoSimple::clone() const
+{
+ auto def = new DecoSimple();
+ Decoration::cloneTo(def);
+
+ def->c_decos = c_decos;
+ def->deco_height = deco_height;
+ def->deco_height_max = deco_height_max;
+ def->deco_param2 = deco_param2;
+ def->deco_param2_max = deco_param2_max;
+
+ return def;
+}
+
+
void DecoSimple::resolveNodeNames()
{
Decoration::resolveNodeNames();
@@ -351,6 +389,31 @@ size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling)
///////////////////////////////////////////////////////////////////////////////
+DecoSchematic::~DecoSchematic()
+{
+ if (was_cloned)
+ delete schematic;
+}
+
+
+ObjDef *DecoSchematic::clone() const
+{
+ auto def = new DecoSchematic();
+ Decoration::cloneTo(def);
+ NodeResolver::cloneTo(def);
+
+ def->rotation = rotation;
+ /* FIXME: We do not own this schematic, yet we only have a pointer to it
+ * and not a handle. We are left with no option but to clone it ourselves.
+ * This is a waste of memory and should be replaced with an alternative
+ * approach sometime. */
+ def->schematic = dynamic_cast<Schematic*>(schematic->clone());
+ def->was_cloned = true;
+
+ return def;
+}
+
+
size_t DecoSchematic::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling)
{
// Schematic could have been unloaded but not the decoration
diff --git a/src/mapgen/mg_decoration.h b/src/mapgen/mg_decoration.h
index 03fec04fd..1ea02a527 100644
--- a/src/mapgen/mg_decoration.h
+++ b/src/mapgen/mg_decoration.h
@@ -25,6 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "noise.h"
#include "nodedef.h"
+typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include
+
class Mapgen;
class MMVManip;
class PcgRandom;
@@ -72,12 +74,17 @@ public:
s16 nspawnby;
s16 place_offset_y = 0;
- std::unordered_set<u8> biomes;
+ std::unordered_set<biome_t> biomes;
+
+protected:
+ void cloneTo(Decoration *def) const;
};
class DecoSimple : public Decoration {
public:
+ ObjDef *clone() const;
+
virtual void resolveNodeNames();
virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling);
@@ -91,12 +98,16 @@ public:
class DecoSchematic : public Decoration {
public:
+ ObjDef *clone() const;
+
DecoSchematic() = default;
+ virtual ~DecoSchematic();
virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling);
Rotation rotation;
Schematic *schematic = nullptr;
+ bool was_cloned = false; // see FIXME inside DecoSchemtic::clone()
};
@@ -113,6 +124,8 @@ public:
DecorationManager(IGameDef *gamedef);
virtual ~DecorationManager() = default;
+ DecorationManager *clone() const;
+
const char *getObjectTitle() const
{
return "decoration";
@@ -133,4 +146,7 @@ public:
}
size_t placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+
+private:
+ DecorationManager() {};
};
diff --git a/src/mapgen/mg_ore.cpp b/src/mapgen/mg_ore.cpp
index c36249cb9..b50ed6a32 100644
--- a/src/mapgen/mg_ore.cpp
+++ b/src/mapgen/mg_ore.cpp
@@ -1,7 +1,7 @@
/*
Minetest
-Copyright (C) 2014-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2018 paramat
+Copyright (C) 2015-2020 paramat
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
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
@@ -72,6 +72,14 @@ void OreManager::clear()
}
+OreManager *OreManager::clone() const
+{
+ auto mgr = new OreManager();
+ ObjDefManager::cloneTo(mgr);
+ return mgr;
+}
+
+
///////////////////////////////////////////////////////////////////////////////
@@ -106,11 +114,39 @@ size_t Ore::placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax)
}
+void Ore::cloneTo(Ore *def) const
+{
+ ObjDef::cloneTo(def);
+ NodeResolver::cloneTo(def);
+ def->c_ore = c_ore;
+ def->c_wherein = c_wherein;
+ def->clust_scarcity = clust_scarcity;
+ def->clust_num_ores = clust_num_ores;
+ def->clust_size = clust_size;
+ def->y_min = y_min;
+ def->y_max = y_max;
+ def->ore_param2 = ore_param2;
+ def->flags = flags;
+ def->nthresh = nthresh;
+ def->np = np;
+ def->noise = nullptr; // cannot be shared! so created on demand
+ def->biomes = biomes;
+}
+
+
///////////////////////////////////////////////////////////////////////////////
+ObjDef *OreScatter::clone() const
+{
+ auto def = new OreScatter();
+ Ore::cloneTo(def);
+ return def;
+}
+
+
void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap)
{
PcgRandom pr(blockseed);
MapNode n_ore(c_ore, 0, ore_param2);
@@ -134,7 +170,7 @@ void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed,
if (biomemap && !biomes.empty()) {
u32 index = sizex * (z0 - nmin.Z) + (x0 - nmin.X);
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ auto it = biomes.find(biomemap[index]);
if (it == biomes.end())
continue;
}
@@ -158,8 +194,21 @@ void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed,
///////////////////////////////////////////////////////////////////////////////
+ObjDef *OreSheet::clone() const
+{
+ auto def = new OreSheet();
+ Ore::cloneTo(def);
+
+ def->column_height_max = column_height_max;
+ def->column_height_min = column_height_min;
+ def->column_midpoint_factor = column_midpoint_factor;
+
+ return def;
+}
+
+
void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap)
{
PcgRandom pr(blockseed + 4234);
MapNode n_ore(c_ore, 0, ore_param2);
@@ -188,7 +237,7 @@ void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed,
continue;
if (biomemap && !biomes.empty()) {
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ auto it = biomes.find(biomemap[index]);
if (it == biomes.end())
continue;
}
@@ -221,8 +270,22 @@ OrePuff::~OrePuff()
}
+ObjDef *OrePuff::clone() const
+{
+ auto def = new OrePuff();
+ Ore::cloneTo(def);
+
+ def->np_puff_top = np_puff_top;
+ def->np_puff_bottom = np_puff_bottom;
+ def->noise_puff_top = nullptr; // cannot be shared, on-demand
+ def->noise_puff_bottom = nullptr;
+
+ return def;
+}
+
+
void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap)
{
PcgRandom pr(blockseed + 4234);
MapNode n_ore(c_ore, 0, ore_param2);
@@ -249,7 +312,7 @@ void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed,
continue;
if (biomemap && !biomes.empty()) {
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ auto it = biomes.find(biomemap[index]);
if (it == biomes.end())
continue;
}
@@ -294,8 +357,16 @@ void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed,
///////////////////////////////////////////////////////////////////////////////
+ObjDef *OreBlob::clone() const
+{
+ auto def = new OreBlob();
+ Ore::cloneTo(def);
+ return def;
+}
+
+
void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap)
{
PcgRandom pr(blockseed + 2404);
MapNode n_ore(c_ore, 0, ore_param2);
@@ -317,7 +388,7 @@ void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed,
if (biomemap && !biomes.empty()) {
u32 bmapidx = sizex * (z0 - nmin.Z) + (x0 - nmin.X);
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[bmapidx]);
+ auto it = biomes.find(biomemap[bmapidx]);
if (it == biomes.end())
continue;
}
@@ -366,8 +437,21 @@ OreVein::~OreVein()
}
+ObjDef *OreVein::clone() const
+{
+ auto def = new OreVein();
+ Ore::cloneTo(def);
+
+ def->random_factor = random_factor;
+ def->noise2 = nullptr; // cannot be shared, on-demand
+ def->sizey_prev = sizey_prev;
+
+ return def;
+}
+
+
void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap)
{
PcgRandom pr(blockseed + 520);
MapNode n_ore(c_ore, 0, ore_param2);
@@ -401,7 +485,7 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed,
if (biomemap && !biomes.empty()) {
u32 bmapidx = sizex * (z - nmin.Z) + (x - nmin.X);
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[bmapidx]);
+ auto it = biomes.find(biomemap[bmapidx]);
if (it == biomes.end())
continue;
}
@@ -434,8 +518,21 @@ OreStratum::~OreStratum()
}
+ObjDef *OreStratum::clone() const
+{
+ auto def = new OreStratum();
+ Ore::cloneTo(def);
+
+ def->np_stratum_thickness = np_stratum_thickness;
+ def->noise_stratum_thickness = nullptr; // cannot be shared, on-demand
+ def->stratum_thickness = stratum_thickness;
+
+ return def;
+}
+
+
void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap)
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap)
{
PcgRandom pr(blockseed + 4234);
MapNode n_ore(c_ore, 0, ore_param2);
@@ -463,7 +560,7 @@ void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed,
for (int z = nmin.Z; z <= nmax.Z; z++)
for (int x = nmin.X; x <= nmax.X; x++, index++) {
if (biomemap && !biomes.empty()) {
- std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]);
+ auto it = biomes.find(biomemap[index]);
if (it == biomes.end())
continue;
}
diff --git a/src/mapgen/mg_ore.h b/src/mapgen/mg_ore.h
index d89360c3c..76420fab4 100644
--- a/src/mapgen/mg_ore.h
+++ b/src/mapgen/mg_ore.h
@@ -1,7 +1,7 @@
/*
Minetest
-Copyright (C) 2014-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
-Copyright (C) 2015-2018 paramat
+Copyright (C) 2015-2020 paramat
+Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
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
@@ -25,6 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "noise.h"
#include "nodedef.h"
+typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include
+
class Noise;
class Mapgen;
class MMVManip;
@@ -64,7 +66,7 @@ public:
float nthresh; // threshold for noise at which an ore is placed
NoiseParams np; // noise for distribution of clusters (NULL for uniform scattering)
Noise *noise = nullptr;
- std::unordered_set<u8> biomes;
+ std::unordered_set<biome_t> biomes;
Ore() = default;;
virtual ~Ore();
@@ -73,33 +75,42 @@ public:
size_t placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap) = 0;
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap) = 0;
+
+protected:
+ void cloneTo(Ore *def) const;
};
class OreScatter : public Ore {
public:
static const bool NEEDS_NOISE = false;
+ ObjDef *clone() const;
+
virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap);
};
class OreSheet : public Ore {
public:
static const bool NEEDS_NOISE = true;
+ ObjDef *clone() const;
+
u16 column_height_min;
u16 column_height_max;
float column_midpoint_factor;
virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap);
};
class OrePuff : public Ore {
public:
static const bool NEEDS_NOISE = true;
+ ObjDef *clone() const;
+
NoiseParams np_puff_top;
NoiseParams np_puff_bottom;
Noise *noise_puff_top = nullptr;
@@ -109,21 +120,25 @@ public:
virtual ~OrePuff();
virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap);
};
class OreBlob : public Ore {
public:
static const bool NEEDS_NOISE = true;
+ ObjDef *clone() const;
+
virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap);
};
class OreVein : public Ore {
public:
static const bool NEEDS_NOISE = true;
+ ObjDef *clone() const;
+
float random_factor;
Noise *noise2 = nullptr;
int sizey_prev = 0;
@@ -132,13 +147,15 @@ public:
virtual ~OreVein();
virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap);
};
class OreStratum : public Ore {
public:
static const bool NEEDS_NOISE = false;
+ ObjDef *clone() const;
+
NoiseParams np_stratum_thickness;
Noise *noise_stratum_thickness = nullptr;
u16 stratum_thickness;
@@ -147,7 +164,7 @@ public:
virtual ~OreStratum();
virtual void generate(MMVManip *vm, int mapseed, u32 blockseed,
- v3s16 nmin, v3s16 nmax, u8 *biomemap);
+ v3s16 nmin, v3s16 nmax, biome_t *biomemap);
};
class OreManager : public ObjDefManager {
@@ -155,6 +172,8 @@ public:
OreManager(IGameDef *gamedef);
virtual ~OreManager() = default;
+ OreManager *clone() const;
+
const char *getObjectTitle() const
{
return "ore";
@@ -183,4 +202,7 @@ public:
void clear();
size_t placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax);
+
+private:
+ OreManager() {};
};
diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp
index c1acbfd9d..ba102d997 100644
--- a/src/mapgen/mg_schematic.cpp
+++ b/src/mapgen/mg_schematic.cpp
@@ -43,12 +43,21 @@ SchematicManager::SchematicManager(Server *server) :
}
+SchematicManager *SchematicManager::clone() const
+{
+ auto mgr = new SchematicManager();
+ assert(mgr);
+ ObjDefManager::cloneTo(mgr);
+ return mgr;
+}
+
+
void SchematicManager::clear()
{
EmergeManager *emerge = m_server->getEmergeManager();
// Remove all dangling references in Decorations
- DecorationManager *decomgr = emerge->decomgr;
+ DecorationManager *decomgr = emerge->getWritableDecorationManager();
for (size_t i = 0; i != decomgr->getNumObjects(); i++) {
Decoration *deco = (Decoration *)decomgr->getRaw(i);
@@ -77,6 +86,25 @@ Schematic::~Schematic()
delete []slice_probs;
}
+ObjDef *Schematic::clone() const
+{
+ auto def = new Schematic();
+ ObjDef::cloneTo(def);
+ NodeResolver::cloneTo(def);
+
+ def->c_nodes = c_nodes;
+ def->flags = flags;
+ def->size = size;
+ FATAL_ERROR_IF(!schemdata, "Schematic can only be cloned after loading");
+ u32 nodecount = size.X * size.Y * size.Z;
+ def->schemdata = new MapNode[nodecount];
+ memcpy(def->schemdata, schemdata, sizeof(MapNode) * nodecount);
+ def->slice_probs = new u8[size.Y];
+ memcpy(def->slice_probs, slice_probs, sizeof(u8) * size.Y);
+
+ return def;
+}
+
void Schematic::resolveNodeNames()
{
@@ -93,6 +121,7 @@ void Schematic::resolveNodeNames()
void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place)
{
+ assert(schemdata && slice_probs);
sanity_check(m_ndef != NULL);
int xstride = 1;
@@ -177,7 +206,7 @@ bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags,
Rotation rot, bool force_place)
{
assert(vm != NULL);
- assert(schemdata != NULL);
+ assert(schemdata && slice_probs);
sanity_check(m_ndef != NULL);
//// Determine effective rotation and effective schematic dimensions
@@ -330,7 +359,7 @@ bool Schematic::deserializeFromMts(std::istream *is,
bool Schematic::serializeToMts(std::ostream *os,
- const std::vector<std::string> &names)
+ const std::vector<std::string> &names) const
{
std::ostream &ss = *os;
@@ -354,7 +383,8 @@ bool Schematic::serializeToMts(std::ostream *os,
bool Schematic::serializeToLua(std::ostream *os,
- const std::vector<std::string> &names, bool use_comments, u32 indent_spaces)
+ const std::vector<std::string> &names, bool use_comments,
+ u32 indent_spaces) const
{
std::ostream &ss = *os;
diff --git a/src/mapgen/mg_schematic.h b/src/mapgen/mg_schematic.h
index 371b37557..6b31251b6 100644
--- a/src/mapgen/mg_schematic.h
+++ b/src/mapgen/mg_schematic.h
@@ -95,6 +95,8 @@ public:
Schematic();
virtual ~Schematic();
+ ObjDef *clone() const;
+
virtual void resolveNodeNames();
bool loadSchematicFromFile(const std::string &filename,
@@ -104,9 +106,10 @@ public:
bool getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2);
bool deserializeFromMts(std::istream *is, std::vector<std::string> *names);
- bool serializeToMts(std::ostream *os, const std::vector<std::string> &names);
+ bool serializeToMts(std::ostream *os,
+ const std::vector<std::string> &names) const;
bool serializeToLua(std::ostream *os, const std::vector<std::string> &names,
- bool use_comments, u32 indent_spaces);
+ bool use_comments, u32 indent_spaces) const;
void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place);
bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place);
@@ -128,6 +131,8 @@ public:
SchematicManager(Server *server);
virtual ~SchematicManager() = default;
+ SchematicManager *clone() const;
+
virtual void clear();
const char *getObjectTitle() const
@@ -141,6 +146,8 @@ public:
}
private:
+ SchematicManager() {};
+
Server *m_server;
};
diff --git a/src/mapgen/treegen.cpp b/src/mapgen/treegen.cpp
index 0d8af2851..e7e30c880 100644
--- a/src/mapgen/treegen.cpp
+++ b/src/mapgen/treegen.cpp
@@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/numeric.h"
#include "map.h"
#include "mapblock.h"
-#include "serverenvironment.h"
#include "nodedef.h"
#include "treegen.h"
#include "voxelalgorithms.h"
@@ -44,6 +43,12 @@ void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree,
MapNode treenode(ndef->getId("mapgen_tree"));
MapNode leavesnode(ndef->getId("mapgen_leaves"));
MapNode applenode(ndef->getId("mapgen_apple"));
+ if (treenode == CONTENT_IGNORE)
+ errorstream << "Treegen: Mapgen alias 'mapgen_tree' is invalid!" << std::endl;
+ if (leavesnode == CONTENT_IGNORE)
+ errorstream << "Treegen: Mapgen alias 'mapgen_leaves' is invalid!" << std::endl;
+ if (applenode == CONTENT_IGNORE)
+ errorstream << "Treegen: Mapgen alias 'mapgen_apple' is invalid!" << std::endl;
PseudoRandom pr(seed);
s16 trunk_h = pr.range(4, 5);
@@ -114,10 +119,9 @@ void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree,
// L-System tree LUA spawner
-treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0,
+treegen::error spawn_ltree(ServerMap *map, v3s16 p0,
const NodeDefManager *ndef, const TreeDef &tree_definition)
{
- ServerMap *map = &env->getServerMap();
std::map<v3s16, MapBlock*> modified_blocks;
MMVManip vmanip(map);
v3s16 tree_blockp = getNodeBlockPos(p0);
@@ -144,7 +148,6 @@ treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0,
treegen::error make_ltree(MMVManip &vmanip, v3s16 p0,
const NodeDefManager *ndef, TreeDef tree_definition)
{
- MapNode dirtnode(ndef->getId("mapgen_dirt"));
s32 seed;
if (tree_definition.explicit_seed)
seed = tree_definition.seed + 14002;
@@ -222,43 +225,43 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0,
axiom = temp;
}
- //make sure tree is not floating in the air
+ // Add trunk nodes below a wide trunk to avoid gaps when tree is on sloping ground
if (tree_definition.trunk_type == "double") {
- tree_node_placement(
+ tree_trunk_placement(
vmanip,
v3f(position.X + 1, position.Y - 1, position.Z),
- dirtnode
+ tree_definition
);
- tree_node_placement(
+ tree_trunk_placement(
vmanip,
v3f(position.X, position.Y - 1, position.Z + 1),
- dirtnode
+ tree_definition
);
- tree_node_placement(
+ tree_trunk_placement(
vmanip,
v3f(position.X + 1, position.Y - 1, position.Z + 1),
- dirtnode
+ tree_definition
);
} else if (tree_definition.trunk_type == "crossed") {
- tree_node_placement(
+ tree_trunk_placement(
vmanip,
v3f(position.X + 1, position.Y - 1, position.Z),
- dirtnode
+ tree_definition
);
- tree_node_placement(
+ tree_trunk_placement(
vmanip,
v3f(position.X - 1, position.Y - 1, position.Z),
- dirtnode
+ tree_definition
);
- tree_node_placement(
+ tree_trunk_placement(
vmanip,
v3f(position.X, position.Y - 1, position.Z + 1),
- dirtnode
+ tree_definition
);
- tree_node_placement(
+ tree_trunk_placement(
vmanip,
v3f(position.X, position.Y - 1, position.Z - 1),
- dirtnode
+ tree_definition
);
}
@@ -365,7 +368,7 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0,
!tree_definition.thin_branches)) {
tree_trunk_placement(
vmanip,
- v3f(position.X +1 , position.Y, position.Z),
+ v3f(position.X + 1, position.Y, position.Z),
tree_definition
);
tree_trunk_placement(
@@ -662,6 +665,10 @@ void make_jungletree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef,
c_tree = ndef->getId("mapgen_tree");
if (c_leaves == CONTENT_IGNORE)
c_leaves = ndef->getId("mapgen_leaves");
+ if (c_tree == CONTENT_IGNORE)
+ errorstream << "Treegen: Mapgen alias 'mapgen_jungletree' is invalid!" << std::endl;
+ if (c_leaves == CONTENT_IGNORE)
+ errorstream << "Treegen: Mapgen alias 'mapgen_jungleleaves' is invalid!" << std::endl;
MapNode treenode(c_tree);
MapNode leavesnode(c_leaves);
@@ -765,6 +772,10 @@ void make_pine_tree(MMVManip &vmanip, v3s16 p0, const NodeDefManager *ndef,
c_leaves = ndef->getId("mapgen_leaves");
if (c_snow == CONTENT_IGNORE)
c_snow = CONTENT_AIR;
+ if (c_tree == CONTENT_IGNORE)
+ errorstream << "Treegen: Mapgen alias 'mapgen_pine_tree' is invalid!" << std::endl;
+ if (c_leaves == CONTENT_IGNORE)
+ errorstream << "Treegen: Mapgen alias 'mapgen_pine_needles' is invalid!" << std::endl;
MapNode treenode(c_tree);
MapNode leavesnode(c_leaves);
diff --git a/src/mapgen/treegen.h b/src/mapgen/treegen.h
index 5ab79f428..447baabb3 100644
--- a/src/mapgen/treegen.h
+++ b/src/mapgen/treegen.h
@@ -26,8 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class MMVManip;
class NodeDefManager;
-class ServerEnvironment;
-
+class ServerMap;
namespace treegen {
@@ -73,7 +72,7 @@ namespace treegen {
treegen::error make_ltree(MMVManip &vmanip, v3s16 p0,
const NodeDefManager *ndef, TreeDef tree_definition);
// Spawn L-systems tree from LUA
- treegen::error spawn_ltree (ServerEnvironment *env, v3s16 p0,
+ treegen::error spawn_ltree (ServerMap *map, v3s16 p0,
const NodeDefManager *ndef, const TreeDef &tree_definition);
// L-System tree gen helper functions
diff --git a/src/mapnode.cpp b/src/mapnode.cpp
index bf7e79a71..dcf1f6d6e 100644
--- a/src/mapnode.cpp
+++ b/src/mapnode.cpp
@@ -584,7 +584,7 @@ u8 MapNode::getMaxLevel(const NodeDefManager *nodemgr) const
if( f.liquid_type == LIQUID_FLOWING || f.param_type_2 == CPT2_FLOWINGLIQUID)
return LIQUID_LEVEL_MAX;
if(f.leveled || f.param_type_2 == CPT2_LEVELED)
- return LEVELED_MAX;
+ return f.leveled_max;
return 0;
}
@@ -603,14 +603,15 @@ u8 MapNode::getLevel(const NodeDefManager *nodemgr) const
if (level)
return level;
}
- if (f.leveled > LEVELED_MAX)
- return LEVELED_MAX;
+ // Return static value from nodedef if param2 isn't used for level
+ if (f.leveled > f.leveled_max)
+ return f.leveled_max;
return f.leveled;
}
-u8 MapNode::setLevel(const NodeDefManager *nodemgr, s8 level)
+s8 MapNode::setLevel(const NodeDefManager *nodemgr, s16 level)
{
- u8 rest = 0;
+ s8 rest = 0;
const ContentFeatures &f = nodemgr->get(*this);
if (f.param_type_2 == CPT2_FLOWINGLIQUID
|| f.liquid_type == LIQUID_FLOWING
@@ -621,28 +622,28 @@ u8 MapNode::setLevel(const NodeDefManager *nodemgr, s8 level)
}
if (level >= LIQUID_LEVEL_SOURCE) {
rest = level - LIQUID_LEVEL_SOURCE;
- setContent(nodemgr->getId(f.liquid_alternative_source));
+ setContent(f.liquid_alternative_source_id);
setParam2(0);
} else {
- setContent(nodemgr->getId(f.liquid_alternative_flowing));
+ setContent(f.liquid_alternative_flowing_id);
setParam2((level & LIQUID_LEVEL_MASK) | (getParam2() & ~LIQUID_LEVEL_MASK));
}
} else if (f.param_type_2 == CPT2_LEVELED) {
if (level < 0) { // zero means default for a leveled nodebox
rest = level;
level = 0;
- } else if (level > LEVELED_MAX) {
- rest = level - LEVELED_MAX;
- level = LEVELED_MAX;
+ } else if (level > f.leveled_max) {
+ rest = level - f.leveled_max;
+ level = f.leveled_max;
}
setParam2((level & LEVELED_MASK) | (getParam2() & ~LEVELED_MASK));
}
return rest;
}
-u8 MapNode::addLevel(const NodeDefManager *nodemgr, s8 add)
+s8 MapNode::addLevel(const NodeDefManager *nodemgr, s16 add)
{
- s8 level = getLevel(nodemgr);
+ s16 level = getLevel(nodemgr);
level += add;
return setLevel(nodemgr, level);
}
diff --git a/src/mapnode.h b/src/mapnode.h
index 7a3d30ddc..32ac1b4f6 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -268,12 +268,12 @@ struct MapNode
std::vector<aabb3f> *boxes, u8 neighbors = 0) const;
/*
- Liquid helpers
+ Liquid/leveled helpers
*/
u8 getMaxLevel(const NodeDefManager *nodemgr) const;
u8 getLevel(const NodeDefManager *nodemgr) const;
- u8 setLevel(const NodeDefManager *nodemgr, s8 level = 1);
- u8 addLevel(const NodeDefManager *nodemgr, s8 add = 1);
+ s8 setLevel(const NodeDefManager *nodemgr, s16 level = 1);
+ s8 addLevel(const NodeDefManager *nodemgr, s16 add = 1);
/*
Serialization functions
diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp
index 431455b76..f812a08a1 100644
--- a/src/network/clientopcodes.cpp
+++ b/src/network/clientopcodes.cpp
@@ -68,7 +68,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] =
{ "TOCLIENT_TIME_OF_DAY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_TimeOfDay }, // 0x29
{ "TOCLIENT_CSM_RESTRICTION_FLAGS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CSMRestrictionFlags }, // 0x2A
{ "TOCLIENT_PLAYER_SPEED", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_PlayerSpeed }, // 0x2B
- null_command_handler,
+ { "TOCLIENT_MEDIA_PUSH", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MediaPush }, // 0x2C
null_command_handler,
null_command_handler,
{ "TOCLIENT_CHAT_MESSAGE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ChatMessage }, // 0x2F
@@ -126,6 +126,16 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] =
const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, false };
+/*
+ Channels used for Client -> Server communication
+ 2: Notifications back to the server (e.g. GOTBLOCKS)
+ 1: Init and Authentication
+ 0: everything else
+
+ Packet order is only guaranteed inside a channel, so packets that operate on
+ the same objects are *required* to be in the same channel.
+*/
+
const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] =
{
null_command_factory, // 0x00
@@ -143,7 +153,7 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] =
null_command_factory, // 0x0c
null_command_factory, // 0x0d
null_command_factory, // 0x0e
- null_command_factory, // 0x0F
+ null_command_factory, // 0x0f
null_command_factory, // 0x10
{ "TOSERVER_INIT2", 1, true }, // 0x11
null_command_factory, // 0x12
@@ -186,7 +196,7 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] =
{ "TOSERVER_PLAYERITEM", 0, true }, // 0x37
{ "TOSERVER_RESPAWN", 0, true }, // 0x38
{ "TOSERVER_INTERACT", 0, true }, // 0x39
- { "TOSERVER_REMOVED_SOUNDS", 1, true }, // 0x3a
+ { "TOSERVER_REMOVED_SOUNDS", 2, true }, // 0x3a
{ "TOSERVER_NODEMETA_FIELDS", 0, true }, // 0x3b
{ "TOSERVER_INVENTORY_FIELDS", 0, true }, // 0x3c
null_command_factory, // 0x3d
@@ -194,8 +204,8 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] =
null_command_factory, // 0x3f
{ "TOSERVER_REQUEST_MEDIA", 1, true }, // 0x40
null_command_factory, // 0x41
- { "TOSERVER_BREATH", 0, true }, // 0x42 old TOSERVER_BREATH. Ignored by servers
- { "TOSERVER_CLIENT_READY", 0, true }, // 0x43
+ null_command_factory, // 0x42
+ { "TOSERVER_CLIENT_READY", 1, true }, // 0x43
null_command_factory, // 0x44
null_command_factory, // 0x45
null_command_factory, // 0x46
diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp
index 432fb415e..5934eaf8c 100644
--- a/src/network/clientpackethandler.cpp
+++ b/src/network/clientpackethandler.cpp
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/client.h"
#include "util/base64.h"
+#include "client/camera.h"
#include "chatmessage.h"
#include "client/clientmedia.h"
#include "log.h"
@@ -38,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "script/scripting_client.h"
#include "util/serialize.h"
#include "util/srp.h"
+#include "util/sha1.h"
#include "tileanimation.h"
#include "gettext.h"
#include "skyparams.h"
@@ -386,10 +388,10 @@ void Client::handleCommand_TimeOfDay(NetworkPacket* pkt)
m_env.setTimeOfDaySpeed(time_speed);
m_time_of_day_set = true;
- u32 dr = m_env.getDayNightRatio();
- infostream << "Client: time_of_day=" << time_of_day
- << " time_speed=" << time_speed
- << " dr=" << dr << std::endl;
+ //u32 dr = m_env.getDayNightRatio();
+ //infostream << "Client: time_of_day=" << time_of_day
+ // << " time_speed=" << time_speed
+ // << " dr=" << dr << std::endl;
}
void Client::handleCommand_ChatMessage(NetworkPacket *pkt)
@@ -530,11 +532,21 @@ void Client::handleCommand_Movement(NetworkPacket* pkt)
void Client::handleCommand_Fov(NetworkPacket *pkt)
{
f32 fov;
- bool is_multiplier;
+ bool is_multiplier = false;
+ f32 transition_time = 0.0f;
+
*pkt >> fov >> is_multiplier;
+ // Wrap transition_time extraction within a
+ // try-catch to preserve backwards compat
+ try {
+ *pkt >> transition_time;
+ } catch (PacketError &e) {};
+
LocalPlayer *player = m_env.getLocalPlayer();
- player->setFov({ fov, is_multiplier });
+ assert(player);
+ player->setFov({ fov, is_multiplier, transition_time });
+ m_camera->notifyFovChange();
}
void Client::handleCommand_HP(NetworkPacket *pkt)
@@ -947,114 +959,66 @@ void Client::handleCommand_SpawnParticle(NetworkPacket* pkt)
std::string datastring(pkt->getString(0), pkt->getSize());
std::istringstream is(datastring, std::ios_base::binary);
- v3f pos = readV3F32(is);
- v3f vel = readV3F32(is);
- v3f acc = readV3F32(is);
- float expirationtime = readF32(is);
- float size = readF32(is);
- bool collisiondetection = readU8(is);
- std::string texture = deSerializeLongString(is);
-
- bool vertical = false;
- bool collision_removal = false;
- TileAnimationParams animation;
- animation.type = TAT_NONE;
- u8 glow = 0;
- bool object_collision = false;
- try {
- vertical = readU8(is);
- collision_removal = readU8(is);
- animation.deSerialize(is, m_proto_ver);
- glow = readU8(is);
- object_collision = readU8(is);
- } catch (...) {}
+ ParticleParameters p;
+ p.deSerialize(is, m_proto_ver);
ClientEvent *event = new ClientEvent();
- event->type = CE_SPAWN_PARTICLE;
- event->spawn_particle.pos = new v3f (pos);
- event->spawn_particle.vel = new v3f (vel);
- event->spawn_particle.acc = new v3f (acc);
- event->spawn_particle.expirationtime = expirationtime;
- event->spawn_particle.size = size;
- event->spawn_particle.collisiondetection = collisiondetection;
- event->spawn_particle.collision_removal = collision_removal;
- event->spawn_particle.object_collision = object_collision;
- event->spawn_particle.vertical = vertical;
- event->spawn_particle.texture = new std::string(texture);
- event->spawn_particle.animation = animation;
- event->spawn_particle.glow = glow;
+ event->type = CE_SPAWN_PARTICLE;
+ event->spawn_particle = new ParticleParameters(p);
m_client_event_queue.push(event);
}
void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt)
{
- u16 amount;
- float spawntime;
- v3f minpos;
- v3f maxpos;
- v3f minvel;
- v3f maxvel;
- v3f minacc;
- v3f maxacc;
- float minexptime;
- float maxexptime;
- float minsize;
- float maxsize;
- bool collisiondetection;
- u32 server_id;
-
- *pkt >> amount >> spawntime >> minpos >> maxpos >> minvel >> maxvel
- >> minacc >> maxacc >> minexptime >> maxexptime >> minsize
- >> maxsize >> collisiondetection;
-
- std::string texture = pkt->readLongString();
-
- *pkt >> server_id;
-
- bool vertical = false;
- bool collision_removal = false;
- u16 attached_id = 0;
- TileAnimationParams animation;
- animation.type = TAT_NONE;
- u8 glow = 0;
- bool object_collision = false;
- try {
- *pkt >> vertical;
- *pkt >> collision_removal;
- *pkt >> attached_id;
+ std::string datastring(pkt->getString(0), pkt->getSize());
+ std::istringstream is(datastring, std::ios_base::binary);
- // This is horrible but required (why are there two ways to deserialize pkts?)
- std::string datastring(pkt->getRemainingString(), pkt->getRemainingBytes());
- std::istringstream is(datastring, std::ios_base::binary);
- animation.deSerialize(is, m_proto_ver);
- glow = readU8(is);
- object_collision = readU8(is);
- } catch (...) {}
+ ParticleSpawnerParameters p;
+ u32 server_id;
+ u16 attached_id = 0;
+
+ p.amount = readU16(is);
+ p.time = readF32(is);
+ p.minpos = readV3F32(is);
+ p.maxpos = readV3F32(is);
+ p.minvel = readV3F32(is);
+ p.maxvel = readV3F32(is);
+ p.minacc = readV3F32(is);
+ p.maxacc = readV3F32(is);
+ p.minexptime = readF32(is);
+ p.maxexptime = readF32(is);
+ p.minsize = readF32(is);
+ p.maxsize = readF32(is);
+ p.collisiondetection = readU8(is);
+ p.texture = deSerializeLongString(is);
+
+ server_id = readU32(is);
+
+ p.vertical = readU8(is);
+ p.collision_removal = readU8(is);
+
+ attached_id = readU16(is);
+
+ p.animation.deSerialize(is, m_proto_ver);
+ p.glow = readU8(is);
+ p.object_collision = readU8(is);
+
+ // This is kinda awful
+ do {
+ u16 tmp_param0 = readU16(is);
+ if (is.eof())
+ break;
+ p.node.param0 = tmp_param0;
+ p.node.param2 = readU8(is);
+ p.node_tile = readU8(is);
+ } while (0);
auto event = new ClientEvent();
- event->type = CE_ADD_PARTICLESPAWNER;
- event->add_particlespawner.amount = amount;
- event->add_particlespawner.spawntime = spawntime;
- event->add_particlespawner.minpos = new v3f (minpos);
- event->add_particlespawner.maxpos = new v3f (maxpos);
- event->add_particlespawner.minvel = new v3f (minvel);
- event->add_particlespawner.maxvel = new v3f (maxvel);
- event->add_particlespawner.minacc = new v3f (minacc);
- event->add_particlespawner.maxacc = new v3f (maxacc);
- event->add_particlespawner.minexptime = minexptime;
- event->add_particlespawner.maxexptime = maxexptime;
- event->add_particlespawner.minsize = minsize;
- event->add_particlespawner.maxsize = maxsize;
- event->add_particlespawner.collisiondetection = collisiondetection;
- event->add_particlespawner.collision_removal = collision_removal;
- event->add_particlespawner.object_collision = object_collision;
- event->add_particlespawner.attached_id = attached_id;
- event->add_particlespawner.vertical = vertical;
- event->add_particlespawner.texture = new std::string(texture);
- event->add_particlespawner.id = server_id;
- event->add_particlespawner.animation = animation;
- event->add_particlespawner.glow = glow;
+ event->type = CE_ADD_PARTICLESPAWNER;
+ event->add_particlespawner.p = new ParticleSpawnerParameters(p);
+ event->add_particlespawner.attached_id = attached_id;
+ event->add_particlespawner.id = server_id;
m_client_event_queue.push(event);
}
@@ -1091,22 +1055,16 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt)
v3f world_pos;
v2s32 size;
s16 z_index = 0;
+ std::string text2;
*pkt >> server_id >> type >> pos >> name >> scale >> text >> number >> item
>> dir >> align >> offset;
try {
*pkt >> world_pos;
- }
- catch(SerializationError &e) {};
-
- try {
*pkt >> size;
- } catch(SerializationError &e) {};
-
- try {
*pkt >> z_index;
- }
- catch(PacketError &e) {}
+ *pkt >> text2;
+ } catch(PacketError &e) {};
ClientEvent *event = new ClientEvent();
event->type = CE_HUDADD;
@@ -1124,6 +1082,7 @@ void Client::handleCommand_HudAdd(NetworkPacket* pkt)
event->hudadd.world_pos = new v3f(world_pos);
event->hudadd.size = new v2s32(size);
event->hudadd.z_index = z_index;
+ event->hudadd.text2 = new std::string(text2);
m_client_event_queue.push(event);
}
@@ -1160,7 +1119,7 @@ void Client::handleCommand_HudChange(NetworkPacket* pkt)
if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE ||
stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET)
*pkt >> v2fdata;
- else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT)
+ else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT || stat == HUD_STAT_TEXT2)
*pkt >> sdata;
else if (stat == HUD_STAT_WORLD_POS)
*pkt >> v3fdata;
@@ -1265,9 +1224,9 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt)
// Fix for "regular" skies, as color isn't kept:
if (skybox.type == "regular") {
skybox.sky_color = sky_defaults.getSkyColorDefaults();
- skybox.tint_type = "default";
- skybox.moon_tint = video::SColor(255, 255, 255, 255);
- skybox.sun_tint = video::SColor(255, 255, 255, 255);
+ skybox.fog_tint_type = "default";
+ skybox.fog_moon_tint = video::SColor(255, 255, 255, 255);
+ skybox.fog_sun_tint = video::SColor(255, 255, 255, 255);
}
else {
sun.visible = false;
@@ -1302,7 +1261,7 @@ void Client::handleCommand_HudSetSky(NetworkPacket* pkt)
std::string texture;
*pkt >> skybox.bgcolor >> skybox.type >> skybox.clouds >>
- skybox.sun_tint >> skybox.moon_tint >> skybox.tint_type;
+ skybox.fog_sun_tint >> skybox.fog_moon_tint >> skybox.fog_tint_type;
if (skybox.type == "skybox") {
*pkt >> texture_count;
@@ -1513,6 +1472,51 @@ void Client::handleCommand_PlayerSpeed(NetworkPacket *pkt)
player->addVelocity(added_vel);
}
+void Client::handleCommand_MediaPush(NetworkPacket *pkt)
+{
+ std::string raw_hash, filename, filedata;
+ bool cached;
+
+ *pkt >> raw_hash >> filename >> cached;
+ filedata = pkt->readLongString();
+
+ if (raw_hash.size() != 20 || filedata.empty() || filename.empty() ||
+ !string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) {
+ throw PacketError("Illegal filename, data or hash");
+ }
+
+ verbosestream << "Server pushes media file \"" << filename << "\" with "
+ << filedata.size() << " bytes of data (cached=" << cached
+ << ")" << std::endl;
+
+ if (m_media_pushed_files.count(filename) != 0) {
+ // Silently ignore for synchronization purposes
+ return;
+ }
+
+ // Compute and check checksum of data
+ std::string computed_hash;
+ {
+ SHA1 ctx;
+ ctx.addBytes(filedata.c_str(), filedata.size());
+ unsigned char *buf = ctx.getDigest();
+ computed_hash.assign((char*) buf, 20);
+ free(buf);
+ }
+ if (raw_hash != computed_hash) {
+ verbosestream << "Hash of file data mismatches, ignoring." << std::endl;
+ return;
+ }
+
+ // Actually load media
+ loadMedia(filedata, filename, true);
+ m_media_pushed_files.insert(filename);
+
+ // Cache file for the next time when this client joins the same server
+ if (cached)
+ clientMediaUpdateCache(raw_hash, filedata);
+}
+
/*
* Mod channels
*/
diff --git a/src/network/connection.cpp b/src/network/connection.cpp
index 36124ce3c..3692e45a9 100644
--- a/src/network/connection.cpp
+++ b/src/network/connection.cpp
@@ -924,7 +924,7 @@ UDPPeer::UDPPeer(u16 a_id, Address a_address, Connection* connection) :
Peer(a_address,a_id,connection)
{
for (Channel &channel : channels)
- channel.setWindowSize(g_settings->getU16("max_packets_per_iteration"));
+ channel.setWindowSize(START_RELIABLE_WINDOW_SIZE);
}
bool UDPPeer::getAddress(MTProtocols type,Address& toset)
@@ -975,22 +975,29 @@ void UDPPeer::PutReliableSendCommand(ConnectionCommand &c,
if (m_pending_disconnect)
return;
- if ( channels[c.channelnum].queued_commands.empty() &&
+ Channel &chan = channels[c.channelnum];
+
+ if (chan.queued_commands.empty() &&
/* don't queue more packets then window size */
- (channels[c.channelnum].queued_reliables.size()
- < (channels[c.channelnum].getWindowSize()/2))) {
+ (chan.queued_reliables.size() < chan.getWindowSize() / 2)) {
LOG(dout_con<<m_connection->getDesc()
<<" processing reliable command for peer id: " << c.peer_id
<<" data size: " << c.data.getSize() << std::endl);
if (!processReliableSendCommand(c,max_packet_size)) {
- channels[c.channelnum].queued_commands.push_back(c);
+ chan.queued_commands.push_back(c);
}
}
else {
LOG(dout_con<<m_connection->getDesc()
<<" Queueing reliable command for peer id: " << c.peer_id
<<" data size: " << c.data.getSize() <<std::endl);
- channels[c.channelnum].queued_commands.push_back(c);
+ chan.queued_commands.push_back(c);
+ if (chan.queued_commands.size() >= chan.getWindowSize() / 2) {
+ LOG(derr_con << m_connection->getDesc()
+ << "Possible packet stall to peer id: " << c.peer_id
+ << " queued_commands=" << chan.queued_commands.size()
+ << std::endl);
+ }
}
}
@@ -1001,6 +1008,8 @@ bool UDPPeer::processReliableSendCommand(
if (m_pending_disconnect)
return true;
+ Channel &chan = channels[c.channelnum];
+
u32 chunksize_max = max_packet_size
- BASE_HEADER_SIZE
- RELIABLE_HEADER_SIZE;
@@ -1008,13 +1017,13 @@ bool UDPPeer::processReliableSendCommand(
sanity_check(c.data.getSize() < MAX_RELIABLE_WINDOW_SIZE*512);
std::list<SharedBuffer<u8>> originals;
- u16 split_sequence_number = channels[c.channelnum].readNextSplitSeqNum();
+ u16 split_sequence_number = chan.readNextSplitSeqNum();
if (c.raw) {
originals.emplace_back(c.data);
} else {
makeAutoSplitPacket(c.data, chunksize_max,split_sequence_number, &originals);
- channels[c.channelnum].setNextSplitSeqNum(split_sequence_number);
+ chan.setNextSplitSeqNum(split_sequence_number);
}
bool have_sequence_number = true;
@@ -1023,7 +1032,7 @@ bool UDPPeer::processReliableSendCommand(
volatile u16 initial_sequence_number = 0;
for (SharedBuffer<u8> &original : originals) {
- u16 seqnum = channels[c.channelnum].getOutgoingSequenceNumber(have_sequence_number);
+ u16 seqnum = chan.getOutgoingSequenceNumber(have_sequence_number);
/* oops, we don't have enough sequence numbers to send this packet */
if (!have_sequence_number)
@@ -1055,10 +1064,10 @@ bool UDPPeer::processReliableSendCommand(
// << " channel: " << (c.channelnum&0xFF)
// << " seqnum: " << readU16(&p.data[BASE_HEADER_SIZE+1])
// << std::endl)
- channels[c.channelnum].queued_reliables.push(p);
+ chan.queued_reliables.push(p);
pcount++;
}
- sanity_check(channels[c.channelnum].queued_reliables.size() < 0xFFFF);
+ sanity_check(chan.queued_reliables.size() < 0xFFFF);
return true;
}
@@ -1073,7 +1082,7 @@ bool UDPPeer::processReliableSendCommand(
toadd.pop();
bool successfully_put_back_sequence_number
- = channels[c.channelnum].putBackSequenceNumber(
+ = chan.putBackSequenceNumber(
(initial_sequence_number+toadd.size() % (SEQNUM_MAX+1)));
FATAL_ERROR_IF(!successfully_put_back_sequence_number, "error");
@@ -1081,7 +1090,7 @@ bool UDPPeer::processReliableSendCommand(
// DO NOT REMOVE n_queued! It avoids a deadlock of async locked
// 'log_message_mutex' and 'm_list_mutex'.
- u32 n_queued = channels[c.channelnum].outgoing_reliables_sent.size();
+ u32 n_queued = chan.outgoing_reliables_sent.size();
LOG(dout_con<<m_connection->getDesc()
<< " Windowsize exceeded on reliable sending "
@@ -1164,7 +1173,9 @@ Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout,
m_bc_peerhandler(peerhandler)
{
- m_udpSocket.setTimeoutMs(5);
+ /* Amount of time Receive() will wait for data, this is entirely different
+ * from the connection timeout */
+ m_udpSocket.setTimeoutMs(500);
m_sendThread->setParent(this);
m_receiveThread->setParent(this);
diff --git a/src/network/connection.h b/src/network/connection.h
index 0b12bf701..47b0805ce 100644
--- a/src/network/connection.h
+++ b/src/network/connection.h
@@ -141,7 +141,6 @@ private:
=== NOTES ===
A packet is sent through a channel to a peer with a basic header:
-TODO: Should we have a receiver_peer_id also?
Header (7 bytes):
[0] u32 protocol_id
[4] session_t sender_peer_id
@@ -152,8 +151,7 @@ sender_peer_id:
value 1 (PEER_ID_SERVER) is reserved for server
these constants are defined in constants.h
channel:
- The lower the number, the higher the priority is.
- Only channels 0, 1 and 2 exist.
+ Channel numbers have no intrinsic meaning. Currently only 0, 1, 2 exist.
*/
#define BASE_HEADER_SIZE 7
#define CHANNEL_COUNT 3
@@ -386,12 +384,14 @@ struct ConnectionCommand
}
};
-/* maximum window size to use, 0xFFFF is theoretical maximum don't think about
+/* maximum window size to use, 0xFFFF is theoretical maximum. don't think about
* touching it, the less you're away from it the more likely data corruption
* will occur
*/
#define MAX_RELIABLE_WINDOW_SIZE 0x8000
- /* starting value for window size */
+/* starting value for window size */
+#define START_RELIABLE_WINDOW_SIZE 0x400
+/* minimum value for window size */
#define MIN_RELIABLE_WINDOW_SIZE 0x40
class Channel
@@ -555,15 +555,15 @@ class Peer {
bool isTimedOut(float timeout);
- unsigned int m_increment_packets_remaining = 9;
- unsigned int m_increment_bytes_remaining = 0;
+ unsigned int m_increment_packets_remaining = 0;
virtual u16 getNextSplitSequenceNumber(u8 channel) { return 0; };
virtual void setNextSplitSequenceNumber(u8 channel, u16 seqnum) {};
virtual SharedBuffer<u8> addSplitPacket(u8 channel, const BufferedPacket &toadd,
bool reliable)
{
- fprintf(stderr,"Peer: addSplitPacket called, this is supposed to be never called!\n");
+ errorstream << "Peer::addSplitPacket called,"
+ << " this is supposed to be never called!" << std::endl;
return SharedBuffer<u8>(0);
};
@@ -612,16 +612,16 @@ class Peer {
struct rttstats {
float jitter_min = FLT_MAX;
float jitter_max = 0.0f;
- float jitter_avg = -2.0f;
+ float jitter_avg = -1.0f;
float min_rtt = FLT_MAX;
float max_rtt = 0.0f;
- float avg_rtt = -2.0f;
+ float avg_rtt = -1.0f;
rttstats() = default;
};
rttstats m_rtt;
- float m_last_rtt = -2.0f;
+ float m_last_rtt = -1.0f;
// current usage count
unsigned int m_usage = 0;
diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp
index f8b58c025..9a6617a1c 100644
--- a/src/network/connectionthreads.cpp
+++ b/src/network/connectionthreads.cpp
@@ -73,6 +73,7 @@ ConnectionSendThread::ConnectionSendThread(unsigned int max_packet_size,
m_timeout(timeout),
m_max_data_packets_per_iteration(g_settings->getU16("max_packets_per_iteration"))
{
+ SANITY_CHECK(m_max_data_packets_per_iteration > 1);
}
void *ConnectionSendThread::run()
@@ -107,8 +108,13 @@ void *ConnectionSendThread::run()
curtime = porting::getTimeMs();
float dtime = CALC_DTIME(lasttime, curtime);
- /* first do all the reliable stuff */
+ /* first resend timed-out packets */
runTimeouts(dtime);
+ if (m_iteration_packets_avaialble == 0) {
+ LOG(warningstream << m_connection->getDesc()
+ << " Packet quota used up after re-sending packets, "
+ << "max=" << m_max_data_packets_per_iteration << std::endl);
+ }
/* translate commands to packets */
ConnectionCommand c = m_connection->m_command_queue.pop_frontNoEx(0);
@@ -121,7 +127,7 @@ void *ConnectionSendThread::run()
c = m_connection->m_command_queue.pop_frontNoEx(0);
}
- /* send non reliable packets */
+ /* send queued packets */
sendPackets(dtime);
END_DEBUG_EXCEPTION_HANDLER
@@ -193,7 +199,6 @@ void ConnectionSendThread::runTimeouts(float dtime)
infostream << m_connection->getDesc()
<< "RunTimeouts(): Peer " << peer->id
<< " has timed out."
- << " (source=peer->timeout_counter)"
<< std::endl;
// Add peer to the list
timeouted_peers.push_back(peer->id);
@@ -286,7 +291,7 @@ void ConnectionSendThread::runTimeouts(float dtime)
// Remove timed out peers
for (u16 timeouted_peer : timeouted_peers) {
- LOG(derr_con << m_connection->getDesc()
+ LOG(dout_con << m_connection->getDesc()
<< "RunTimeouts(): Removing peer " << timeouted_peer << std::endl);
m_connection->deletePeer(timeouted_peer, true);
}
@@ -331,11 +336,9 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum,
{
PeerHelper peer = m_connection->getPeerNoEx(peer_id);
if (!peer) {
- LOG(dout_con << m_connection->getDesc()
- << " INFO: dropped packet for non existent peer_id: "
- << peer_id << std::endl);
- FATAL_ERROR_IF(!reliable,
- "Trying to send raw packet reliable but no peer found!");
+ LOG(errorstream << m_connection->getDesc()
+ << " dropped " << (reliable ? "reliable " : "")
+ << "packet for non existent peer_id: " << peer_id << std::endl);
return false;
}
Channel *channel = &(dynamic_cast<UDPPeer *>(&peer)->channels[channelnum]);
@@ -644,6 +647,9 @@ void ConnectionSendThread::sendPackets(float dtime)
std::list<session_t> pendingDisconnect;
std::map<session_t, bool> pending_unreliable;
+ const unsigned int peer_packet_quota = m_iteration_packets_avaialble
+ / MYMAX(peerIds.size(), 1);
+
for (session_t peerId : peerIds) {
PeerHelper peer = m_connection->getPeerNoEx(peerId);
//peer may have been removed
@@ -653,8 +659,7 @@ void ConnectionSendThread::sendPackets(float dtime)
<< std::endl);
continue;
}
- peer->m_increment_packets_remaining =
- m_iteration_packets_avaialble / m_connection->m_peers.size();
+ peer->m_increment_packets_remaining = peer_packet_quota;
UDPPeer *udpPeer = dynamic_cast<UDPPeer *>(&peer);
@@ -751,23 +756,30 @@ void ConnectionSendThread::sendPackets(float dtime)
}
/* send acks immediately */
- if (packet.ack) {
+ if (packet.ack || peer->m_increment_packets_remaining > 0 || stopRequested()) {
rawSendAsPacket(packet.peer_id, packet.channelnum,
packet.data, packet.reliable);
- peer->m_increment_packets_remaining =
- MYMIN(0, peer->m_increment_packets_remaining--);
- } else if (
- (peer->m_increment_packets_remaining > 0) ||
- (stopRequested())) {
- rawSendAsPacket(packet.peer_id, packet.channelnum,
- packet.data, packet.reliable);
- peer->m_increment_packets_remaining--;
+ if (peer->m_increment_packets_remaining > 0)
+ peer->m_increment_packets_remaining--;
} else {
m_outgoing_queue.push(packet);
pending_unreliable[packet.peer_id] = true;
}
}
+ if (peer_packet_quota > 0) {
+ for (session_t peerId : peerIds) {
+ PeerHelper peer = m_connection->getPeerNoEx(peerId);
+ 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);
+ }
+ }
+ }
+
for (session_t peerId : pendingDisconnect) {
if (!pending_unreliable[peerId]) {
m_connection->deletePeer(peerId, false);
@@ -798,6 +810,14 @@ void *ConnectionReceiveThread::run()
ThreadIdentifier);
PROFILE(ThreadIdentifier << "ConnectionReceive: [" << m_connection->getDesc() << "]");
+ // use IPv6 minimum allowed MTU as receive buffer size as this is
+ // theoretical reliable upper boundary of a udp packet for all IPv6 enabled
+ // infrastructure
+ const unsigned int packet_maxsize = 1500;
+ SharedBuffer<u8> packetdata(packet_maxsize);
+
+ bool packet_queued = true;
+
#ifdef DEBUG_CONNECTION_KBPS
u64 curtime = porting::getTimeMs();
u64 lasttime = curtime;
@@ -816,7 +836,7 @@ void *ConnectionReceiveThread::run()
#endif
/* receive packets */
- receive();
+ receive(packetdata, packet_queued);
#ifdef DEBUG_CONNECTION_KBPS
debug_print_timer += dtime;
@@ -878,157 +898,142 @@ void *ConnectionReceiveThread::run()
}
// Receive packets from the network and buffers and create ConnectionEvents
-void ConnectionReceiveThread::receive()
+void ConnectionReceiveThread::receive(SharedBuffer<u8> &packetdata,
+ bool &packet_queued)
{
- // use IPv6 minimum allowed MTU as receive buffer size as this is
- // theoretical reliable upper boundary of a udp packet for all IPv6 enabled
- // infrastructure
- unsigned int packet_maxsize = 1500;
- SharedBuffer<u8> packetdata(packet_maxsize);
-
- bool packet_queued = true;
-
- unsigned int loop_count = 0;
-
- /* first of all read packets from socket */
- /* check for incoming data available */
- while ((loop_count < 10) &&
- (m_connection->m_udpSocket.WaitData(50))) {
- loop_count++;
- try {
- if (packet_queued) {
- bool data_left = true;
- session_t peer_id;
- SharedBuffer<u8> resultdata;
- while (data_left) {
- try {
- data_left = getFromBuffers(peer_id, resultdata);
- if (data_left) {
- ConnectionEvent e;
- e.dataReceived(peer_id, resultdata);
- m_connection->putEvent(e);
- }
- }
- catch (ProcessedSilentlyException &e) {
- /* try reading again */
+ try {
+ // First, see if there any buffered packets we can process now
+ if (packet_queued) {
+ bool data_left = true;
+ session_t peer_id;
+ SharedBuffer<u8> resultdata;
+ while (data_left) {
+ try {
+ data_left = getFromBuffers(peer_id, resultdata);
+ if (data_left) {
+ ConnectionEvent e;
+ e.dataReceived(peer_id, resultdata);
+ m_connection->putEvent(e);
}
}
- packet_queued = false;
- }
-
- Address sender;
- s32 received_size = m_connection->m_udpSocket.Receive(sender, *packetdata,
- packet_maxsize);
-
- if ((received_size < BASE_HEADER_SIZE) ||
- (readU32(&packetdata[0]) != m_connection->GetProtocolID())) {
- LOG(derr_con << m_connection->getDesc()
- << "Receive(): Invalid incoming packet, "
- << "size: " << received_size
- << ", protocol: "
- << ((received_size >= 4) ? readU32(&packetdata[0]) : -1)
- << std::endl);
- continue;
+ catch (ProcessedSilentlyException &e) {
+ /* try reading again */
+ }
}
+ packet_queued = false;
+ }
- session_t peer_id = readPeerId(*packetdata);
- u8 channelnum = readChannel(*packetdata);
+ // Call Receive() to wait for incoming data
+ Address sender;
+ s32 received_size = m_connection->m_udpSocket.Receive(sender,
+ *packetdata, packetdata.getSize());
+ if (received_size < 0)
+ return;
- if (channelnum > CHANNEL_COUNT - 1) {
- LOG(derr_con << m_connection->getDesc()
- << "Receive(): Invalid channel " << (u32)channelnum << std::endl);
- throw InvalidIncomingDataException("Channel doesn't exist");
- }
+ if ((received_size < BASE_HEADER_SIZE) ||
+ (readU32(&packetdata[0]) != m_connection->GetProtocolID())) {
+ LOG(derr_con << m_connection->getDesc()
+ << "Receive(): Invalid incoming packet, "
+ << "size: " << received_size
+ << ", protocol: "
+ << ((received_size >= 4) ? readU32(&packetdata[0]) : -1)
+ << std::endl);
+ return;
+ }
- /* Try to identify peer by sender address (may happen on join) */
- if (peer_id == PEER_ID_INEXISTENT) {
- peer_id = m_connection->lookupPeer(sender);
- // We do not have to remind the peer of its
- // peer id as the CONTROLTYPE_SET_PEER_ID
- // command was sent reliably.
- }
+ session_t peer_id = readPeerId(*packetdata);
+ u8 channelnum = readChannel(*packetdata);
- /* The peer was not found in our lists. Add it. */
- if (peer_id == PEER_ID_INEXISTENT) {
- peer_id = m_connection->createPeer(sender, MTP_MINETEST_RELIABLE_UDP, 0);
- }
+ if (channelnum > CHANNEL_COUNT - 1) {
+ LOG(derr_con << m_connection->getDesc()
+ << "Receive(): Invalid channel " << (u32)channelnum << std::endl);
+ return;
+ }
- PeerHelper peer = m_connection->getPeerNoEx(peer_id);
+ /* Try to identify peer by sender address (may happen on join) */
+ if (peer_id == PEER_ID_INEXISTENT) {
+ peer_id = m_connection->lookupPeer(sender);
+ // We do not have to remind the peer of its
+ // peer id as the CONTROLTYPE_SET_PEER_ID
+ // command was sent reliably.
+ }
- if (!peer) {
- LOG(dout_con << m_connection->getDesc()
- << " got packet from unknown peer_id: "
- << peer_id << " Ignoring." << std::endl);
- continue;
- }
+ /* The peer was not found in our lists. Add it. */
+ if (peer_id == PEER_ID_INEXISTENT) {
+ peer_id = m_connection->createPeer(sender, MTP_MINETEST_RELIABLE_UDP, 0);
+ }
- // Validate peer address
+ PeerHelper peer = m_connection->getPeerNoEx(peer_id);
+ if (!peer) {
+ LOG(dout_con << m_connection->getDesc()
+ << " got packet from unknown peer_id: "
+ << peer_id << " Ignoring." << std::endl);
+ return;
+ }
- Address peer_address;
+ // Validate peer address
- if (peer->getAddress(MTP_UDP, peer_address)) {
- if (peer_address != sender) {
- LOG(derr_con << m_connection->getDesc()
- << m_connection->getDesc()
- << " Peer " << peer_id << " sending from different address."
- " Ignoring." << std::endl);
- continue;
- }
- } else {
-
- bool invalid_address = true;
- if (invalid_address) {
- LOG(derr_con << m_connection->getDesc()
- << m_connection->getDesc()
- << " Peer " << peer_id << " unknown."
- " Ignoring." << std::endl);
- continue;
- }
+ Address peer_address;
+ if (peer->getAddress(MTP_UDP, peer_address)) {
+ if (peer_address != sender) {
+ LOG(derr_con << m_connection->getDesc()
+ << " Peer " << peer_id << " sending from different address."
+ " Ignoring." << std::endl);
+ return;
}
+ } else {
+ LOG(derr_con << m_connection->getDesc()
+ << " Peer " << peer_id << " doesn't have an address?!"
+ " Ignoring." << std::endl);
+ return;
+ }
- peer->ResetTimeout();
-
- Channel *channel = 0;
+ peer->ResetTimeout();
- if (dynamic_cast<UDPPeer *>(&peer) != 0) {
- channel = &(dynamic_cast<UDPPeer *>(&peer)->channels[channelnum]);
- }
+ Channel *channel = nullptr;
+ if (dynamic_cast<UDPPeer *>(&peer)) {
+ channel = &dynamic_cast<UDPPeer *>(&peer)->channels[channelnum];
+ } else {
+ LOG(derr_con << m_connection->getDesc()
+ << "Receive(): peer_id=" << peer_id << " isn't an UDPPeer?!"
+ " Ignoring." << std::endl);
+ return;
+ }
- if (channel != 0) {
- channel->UpdateBytesReceived(received_size);
- }
+ channel->UpdateBytesReceived(received_size);
- // Throw the received packet to channel->processPacket()
+ // Throw the received packet to channel->processPacket()
- // Make a new SharedBuffer from the data without the base headers
- SharedBuffer<u8> strippeddata(received_size - BASE_HEADER_SIZE);
- memcpy(*strippeddata, &packetdata[BASE_HEADER_SIZE],
- strippeddata.getSize());
+ // Make a new SharedBuffer from the data without the base headers
+ SharedBuffer<u8> strippeddata(received_size - BASE_HEADER_SIZE);
+ memcpy(*strippeddata, &packetdata[BASE_HEADER_SIZE],
+ strippeddata.getSize());
- try {
- // Process it (the result is some data with no headers made by us)
- SharedBuffer<u8> resultdata = processPacket
- (channel, strippeddata, peer_id, channelnum, false);
+ try {
+ // Process it (the result is some data with no headers made by us)
+ SharedBuffer<u8> resultdata = processPacket
+ (channel, strippeddata, peer_id, channelnum, false);
- LOG(dout_con << m_connection->getDesc()
- << " ProcessPacket from peer_id: " << peer_id
- << ", channel: " << (u32)channelnum << ", returned "
- << resultdata.getSize() << " bytes" << std::endl);
+ LOG(dout_con << m_connection->getDesc()
+ << " ProcessPacket from peer_id: " << peer_id
+ << ", channel: " << (u32)channelnum << ", returned "
+ << resultdata.getSize() << " bytes" << std::endl);
- ConnectionEvent e;
- e.dataReceived(peer_id, resultdata);
- m_connection->putEvent(e);
- }
- catch (ProcessedSilentlyException &e) {
- }
- catch (ProcessedQueued &e) {
- packet_queued = true;
- }
- }
- catch (InvalidIncomingDataException &e) {
+ ConnectionEvent e;
+ e.dataReceived(peer_id, resultdata);
+ m_connection->putEvent(e);
}
catch (ProcessedSilentlyException &e) {
}
+ catch (ProcessedQueued &e) {
+ // we set it to true anyway (see below)
+ }
+
+ /* Every time we receive a packet it can happen that a previously
+ * buffered packet is now ready to process. */
+ packet_queued = true;
+ }
+ catch (InvalidIncomingDataException &e) {
}
}
@@ -1175,7 +1180,8 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Control(Channel *chan
m_connection->TriggerSend();
} catch (NotFoundException &e) {
LOG(derr_con << m_connection->getDesc()
- << "WARNING: ACKed packet not in outgoing queue" << std::endl);
+ << "WARNING: ACKed packet not in outgoing queue"
+ << " seqnum=" << seqnum << std::endl);
channel->UpdatePacketTooLateCounter();
}
diff --git a/src/network/connectionthreads.h b/src/network/connectionthreads.h
index da4ea92f5..612407c3b 100644
--- a/src/network/connectionthreads.h
+++ b/src/network/connectionthreads.h
@@ -101,7 +101,7 @@ public:
}
private:
- void receive();
+ void receive(SharedBuffer<u8> &packetdata, bool &packet_queued);
// Returns next data from a buffer if possible
// If found, returns true; if not, false.
diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h
index d3799868b..fd683eac9 100644
--- a/src/network/networkprotocol.h
+++ b/src/network/networkprotocol.h
@@ -70,8 +70,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
PROTOCOL_VERSION 14:
Added transfer of player pressed keys to the server
Added new messages for mesh and bone animation, as well as attachments
- GENERIC_CMD_SET_ANIMATION
- GENERIC_CMD_SET_BONE_POSITION
+ AO_CMD_SET_ANIMATION
+ AO_CMD_SET_BONE_POSITION
GENERIC_CMD_SET_ATTACHMENT
PROTOCOL_VERSION 15:
Serialization format changes
@@ -87,7 +87,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
damageGroups added to ToolCapabilities
sound_place added to ItemDefinition
PROTOCOL_VERSION 19:
- GENERIC_CMD_SET_PHYSICS_OVERRIDE
+ AO_CMD_SET_PHYSICS_OVERRIDE
PROTOCOL_VERSION 20:
TOCLIENT_HUDADD
TOCLIENT_HUDRM
@@ -131,7 +131,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Add TOCLIENT_HELLO for presenting server to client after client
presentation
Add TOCLIENT_AUTH_ACCEPT to accept connection from client
- Rename GENERIC_CMD_SET_ATTACHMENT to GENERIC_CMD_ATTACH_TO
+ Rename GENERIC_CMD_SET_ATTACHMENT to AO_CMD_ATTACH_TO
PROTOCOL_VERSION 26:
Add TileDef tileable_horizontal, tileable_vertical flags
PROTOCOL_VERSION 27:
@@ -237,6 +237,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
Formspec elements are drawn in the order of definition
bgcolor[]: use 3 parameters (bgcolor, formspec (now an enum), fbgcolor)
box[] and image[] elements enable clipping by default
+ new element: scroll_container[]
*/
#define FORMSPEC_API_VERSION 3
@@ -322,6 +323,15 @@ enum ToClientCommand
v3f added_vel
*/
+ TOCLIENT_MEDIA_PUSH = 0x2C,
+ /*
+ std::string raw_hash
+ std::string filename
+ bool should_be_cached
+ u32 len
+ char filedata[len]
+ */
+
// (oops, there is some gap here)
TOCLIENT_CHAT_MESSAGE = 0x2F,
@@ -383,8 +393,9 @@ enum ToClientCommand
/*
Sends an FOV override/multiplier to client.
- float fov
+ f32 fov
bool is_multiplier
+ f32 transition_time
*/
TOCLIENT_DEATHSCREEN = 0x37,
@@ -558,10 +569,10 @@ enum ToClientCommand
u32 id
u8 type
v2f1000 pos
- u32 len
+ u16 len
u8[len] name
v2f1000 scale
- u32 len2
+ u16 len2
u8[len2] text
u32 number
u32 item
@@ -571,6 +582,8 @@ enum ToClientCommand
v3f1000 world_pos
v2s32 size
s16 z_index
+ u16 len3
+ u8[len3] text2
*/
TOCLIENT_HUDRM = 0x4a,
@@ -632,9 +645,9 @@ enum ToClientCommand
u8[4] night_sky (ARGB)
u8[4] night_horizon (ARGB)
u8[4] indoors (ARGB)
- u8[4] sun_tint (ARGB)
- u8[4] moon_tint (ARGB)
- std::string tint_type
+ u8[4] fog_sun_tint (ARGB)
+ u8[4] fog_moon_tint (ARGB)
+ std::string fog_tint_type
*/
TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO = 0x50,
diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp
index cca2e56ea..2fc3197c2 100644
--- a/src/network/serveropcodes.cpp
+++ b/src/network/serveropcodes.cpp
@@ -111,6 +111,16 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] =
const static ClientCommandFactory null_command_factory = { "TOCLIENT_NULL", 0, false };
+/*
+ Channels used for Server -> Client communication
+ 2: Bulk data (mapblocks, media, ...)
+ 1: HUD packets
+ 0: everything else
+
+ Packet order is only guaranteed inside a channel, so packets that operate on
+ the same objects are *required* to be in the same channel.
+*/
+
const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
{
null_command_factory, // 0x00
@@ -157,13 +167,13 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
{ "TOCLIENT_TIME_OF_DAY", 0, true }, // 0x29
{ "TOCLIENT_CSM_RESTRICTION_FLAGS", 0, true }, // 0x2A
{ "TOCLIENT_PLAYER_SPEED", 0, true }, // 0x2B
- null_command_factory, // 0x2C
+ { "TOCLIENT_MEDIA_PUSH", 0, true }, // 0x2C (sent over channel 1 too)
null_command_factory, // 0x2D
null_command_factory, // 0x2E
{ "TOCLIENT_CHAT_MESSAGE", 0, true }, // 0x2F
null_command_factory, // 0x30
{ "TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD", 0, true }, // 0x31
- { "TOCLIENT_ACTIVE_OBJECT_MESSAGES", 0, true }, // 0x32 Special packet, sent by 0 (rel) and 1 (unrel) channel
+ { "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
@@ -176,7 +186,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
{ "TOCLIENT_ANNOUNCE_MEDIA", 0, true }, // 0x3C
{ "TOCLIENT_ITEMDEF", 0, true }, // 0x3D
null_command_factory, // 0x3E
- { "TOCLIENT_PLAY_SOUND", 0, true }, // 0x3f
+ { "TOCLIENT_PLAY_SOUND", 0, true }, // 0x3f (may be sent as unrel too)
{ "TOCLIENT_STOP_SOUND", 0, true }, // 0x40
{ "TOCLIENT_PRIVILEGES", 0, true }, // 0x41
{ "TOCLIENT_INVENTORY_FORMSPEC", 0, true }, // 0x42
@@ -188,9 +198,9 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
null_command_factory, // 0x48
{ "TOCLIENT_HUDADD", 1, true }, // 0x49
{ "TOCLIENT_HUDRM", 1, true }, // 0x4a
- { "TOCLIENT_HUDCHANGE", 0, true }, // 0x4b
- { "TOCLIENT_HUD_SET_FLAGS", 0, true }, // 0x4c
- { "TOCLIENT_HUD_SET_PARAM", 0, true }, // 0x4d
+ { "TOCLIENT_HUDCHANGE", 1, true }, // 0x4b
+ { "TOCLIENT_HUD_SET_FLAGS", 1, true }, // 0x4c
+ { "TOCLIENT_HUD_SET_PARAM", 1, true }, // 0x4d
{ "TOCLIENT_BREATH", 0, true }, // 0x4e
{ "TOCLIENT_SET_SKY", 0, true }, // 0x4f
{ "TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO", 0, true }, // 0x50
diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp
index 23bcc867f..b3008bb50 100644
--- a/src/network/serverpackethandler.cpp
+++ b/src/network/serverpackethandler.cpp
@@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "chatmessage.h"
#include "server.h"
#include "log.h"
-#include "content_sao.h"
#include "emerge.h"
#include "mapblock.h"
#include "modchannels.h"
@@ -34,6 +33,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "network/connection.h"
#include "network/networkprotocol.h"
#include "network/serveropcodes.h"
+#include "server/player_sao.h"
+#include "server/serverinventorymgr.h"
#include "util/auth.h"
#include "util/base64.h"
#include "util/pointedthing.h"
@@ -52,11 +53,12 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
if(pkt->getSize() < 1)
return;
- RemoteClient* client = getClient(pkt->getPeerId(), CS_Created);
+ session_t peer_id = pkt->getPeerId();
+ RemoteClient *client = getClient(peer_id, CS_Created);
std::string addr_s;
try {
- Address address = getPeerAddress(pkt->getPeerId());
+ Address address = getPeerAddress(peer_id);
addr_s = address.serializeString();
}
catch (con::PeerNotFoundException &e) {
@@ -66,27 +68,27 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
* respond for some time, your server was overloaded or
* things like that.
*/
- infostream << "Server::ProcessData(): Canceling: peer "
- << pkt->getPeerId() << " not found" << std::endl;
+ infostream << "Server::ProcessData(): Canceling: peer " << peer_id <<
+ " not found" << std::endl;
return;
}
// If net_proto_version is set, this client has already been handled
if (client->getState() > CS_Created) {
- verbosestream << "Server: Ignoring multiple TOSERVER_INITs from "
- << addr_s << " (peer_id=" << pkt->getPeerId() << ")" << std::endl;
+ verbosestream << "Server: Ignoring multiple TOSERVER_INITs from " <<
+ addr_s << " (peer_id=" << peer_id << ")" << std::endl;
return;
}
- verbosestream << "Server: Got TOSERVER_INIT from " << addr_s << " (peer_id="
- << pkt->getPeerId() << ")" << std::endl;
+ verbosestream << "Server: Got TOSERVER_INIT from " << addr_s <<
+ " (peer_id=" << peer_id << ")" << std::endl;
// Do not allow multiple players in simple singleplayer mode.
// This isn't a perfect way to do it, but will suffice for now
if (m_simple_singleplayer_mode && m_clients.getClientIDs().size() > 1) {
- infostream << "Server: Not allowing another client (" << addr_s
- << ") to connect in simple singleplayer mode" << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_SINGLEPLAYER);
+ infostream << "Server: Not allowing another client (" << addr_s <<
+ ") to connect in simple singleplayer mode" << std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_SINGLEPLAYER);
return;
}
@@ -109,11 +111,9 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
depl_serial_v = SER_FMT_VER_INVALID;
if (depl_serial_v == SER_FMT_VER_INVALID) {
- actionstream << "Server: A mismatched client tried to connect from "
- << addr_s << std::endl;
- infostream<<"Server: Cannot negotiate serialization version with "
- << addr_s << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_VERSION);
+ actionstream << "Server: A mismatched client tried to connect from " <<
+ addr_s << " ser_fmt_max=" << (int)client_max << std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_VERSION);
return;
}
@@ -146,9 +146,9 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
net_proto_version != LATEST_PROTOCOL_VERSION) ||
net_proto_version < SERVER_PROTOCOL_VERSION_MIN ||
net_proto_version > SERVER_PROTOCOL_VERSION_MAX) {
- actionstream << "Server: A mismatched client tried to connect from "
- << addr_s << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_VERSION);
+ actionstream << "Server: A mismatched client tried to connect from " <<
+ addr_s << " proto_max=" << (int)max_net_proto_version << std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_VERSION);
return;
}
@@ -159,56 +159,56 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
size_t pns = playerName.size();
if (pns == 0 || pns > PLAYERNAME_SIZE) {
- actionstream << "Server: Player with "
- << ((pns > PLAYERNAME_SIZE) ? "a too long" : "an empty")
- << " name tried to connect from " << addr_s << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_NAME);
+ actionstream << "Server: Player with " <<
+ ((pns > PLAYERNAME_SIZE) ? "a too long" : "an empty") <<
+ " name tried to connect from " << addr_s << std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_NAME);
return;
}
if (!string_allowed(playerName, PLAYERNAME_ALLOWED_CHARS)) {
- actionstream << "Server: Player with an invalid name "
- << "tried to connect from " << addr_s << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_CHARS_IN_NAME);
+ actionstream << "Server: Player with an invalid name tried to connect "
+ "from " << addr_s << std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_CHARS_IN_NAME);
return;
}
- m_clients.setPlayerName(pkt->getPeerId(), playername);
+ m_clients.setPlayerName(peer_id, playername);
//TODO (later) case insensitivity
std::string legacyPlayerNameCasing = playerName;
if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) {
- actionstream << "Server: Player with the name \"singleplayer\" "
- << "tried to connect from " << addr_s << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_NAME);
+ actionstream << "Server: Player with the name \"singleplayer\" tried "
+ "to connect from " << addr_s << std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_NAME);
return;
}
{
std::string reason;
if (m_script->on_prejoinplayer(playername, addr_s, &reason)) {
- actionstream << "Server: Player with the name \"" << playerName << "\" "
- << "tried to connect from " << addr_s << " "
- << "but it was disallowed for the following reason: "
- << reason << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, reason);
+ actionstream << "Server: Player with the name \"" << playerName <<
+ "\" tried to connect from " << addr_s <<
+ " but it was disallowed for the following reason: " << reason <<
+ std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_CUSTOM_STRING, reason);
return;
}
}
- infostream << "Server: New connection: \"" << playerName << "\" from "
- << addr_s << " (peer_id=" << pkt->getPeerId() << ")" << std::endl;
+ infostream << "Server: New connection: \"" << playerName << "\" from " <<
+ addr_s << " (peer_id=" << peer_id << ")" << std::endl;
// Enforce user limit.
// Don't enforce for users that have some admin right or mod permits it.
if (m_clients.isUserLimitReached() &&
playername != g_settings->get("name") &&
!m_script->can_bypass_userlimit(playername, addr_s)) {
- actionstream << "Server: " << playername << " tried to join from "
- << addr_s << ", but there" << " are already max_users="
- << g_settings->getU16("max_users") << " players." << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_TOO_MANY_USERS);
+ actionstream << "Server: " << playername << " tried to join from " <<
+ addr_s << ", but there are already max_users=" <<
+ g_settings->getU16("max_users") << " players." << std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_TOO_MANY_USERS);
return;
}
@@ -228,20 +228,19 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
auth_mechs |= AUTH_MECHANISM_SRP;
client->enc_pwd = encpwd;
} else {
- actionstream << "User " << playername
- << " tried to log in, but password field"
- << " was invalid (unknown mechcode)." << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_SERVER_FAIL);
+ actionstream << "User " << playername << " tried to log in, "
+ "but password field was invalid (unknown mechcode)." <<
+ std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL);
return;
}
} else if (base64_is_valid(encpwd)) {
auth_mechs |= AUTH_MECHANISM_LEGACY_PASSWORD;
client->enc_pwd = encpwd;
} else {
- actionstream << "User " << playername
- << " tried to log in, but password field"
- << " was invalid (invalid base64)." << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_SERVER_FAIL);
+ actionstream << "User " << playername << " tried to log in, but "
+ "password field was invalid (invalid base64)." << std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL);
return;
}
} else {
@@ -264,8 +263,8 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
verbosestream << "Sending TOCLIENT_HELLO with auth method field: "
<< auth_mechs << std::endl;
- NetworkPacket resp_pkt(TOCLIENT_HELLO, 1 + 4
- + legacyPlayerNameCasing.size(), pkt->getPeerId());
+ NetworkPacket resp_pkt(TOCLIENT_HELLO,
+ 1 + 4 + legacyPlayerNameCasing.size(), peer_id);
u16 depl_compress_mode = NETPROTO_COMPRESSION_NONE;
resp_pkt << depl_serial_v << depl_compress_mode << net_proto_version
@@ -276,16 +275,16 @@ void Server::handleCommand_Init(NetworkPacket* pkt)
client->allowed_auth_mechs = auth_mechs;
client->setDeployedCompressionMode(depl_compress_mode);
- m_clients.event(pkt->getPeerId(), CSE_Hello);
+ m_clients.event(peer_id, CSE_Hello);
}
void Server::handleCommand_Init2(NetworkPacket* pkt)
{
- verbosestream << "Server: Got TOSERVER_INIT2 from "
- << pkt->getPeerId() << std::endl;
+ session_t peer_id = pkt->getPeerId();
+ verbosestream << "Server: Got TOSERVER_INIT2 from " << peer_id << std::endl;
- m_clients.event(pkt->getPeerId(), CSE_GotInit2);
- u16 protocol_version = m_clients.getProtocolVersion(pkt->getPeerId());
+ m_clients.event(peer_id, CSE_GotInit2);
+ u16 protocol_version = m_clients.getProtocolVersion(peer_id);
std::string lang;
if (pkt->getSize() > 0)
@@ -295,45 +294,48 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
Send some initialization data
*/
- infostream << "Server: Sending content to "
- << getPlayerName(pkt->getPeerId()) << std::endl;
+ infostream << "Server: Sending content to " << getPlayerName(peer_id) <<
+ std::endl;
// Send item definitions
- SendItemDef(pkt->getPeerId(), m_itemdef, protocol_version);
+ SendItemDef(peer_id, m_itemdef, protocol_version);
// Send node definitions
- SendNodeDef(pkt->getPeerId(), m_nodedef, protocol_version);
+ SendNodeDef(peer_id, m_nodedef, protocol_version);
- m_clients.event(pkt->getPeerId(), CSE_SetDefinitionsSent);
+ m_clients.event(peer_id, CSE_SetDefinitionsSent);
// Send media announcement
- sendMediaAnnouncement(pkt->getPeerId(), lang);
+ sendMediaAnnouncement(peer_id, lang);
+
+ RemoteClient *client = getClient(peer_id, CS_InitDone);
- RemoteClient *client = getClient(pkt->getPeerId(), CS_InitDone);
+ // Keep client language for server translations
+ client->setLangCode(lang);
// Send active objects
{
- PlayerSAO *sao = getPlayerSAO(pkt->getPeerId());
+ PlayerSAO *sao = getPlayerSAO(peer_id);
if (client && sao)
SendActiveObjectRemoveAdd(client, sao);
}
// Send detached inventories
- sendDetachedInventories(pkt->getPeerId(), false);
+ sendDetachedInventories(peer_id, false);
// Send player movement settings
- SendMovement(pkt->getPeerId());
+ SendMovement(peer_id);
// Send time of day
u16 time = m_env->getTimeOfDay();
float time_speed = g_settings->getFloat("time_speed");
- SendTimeOfDay(pkt->getPeerId(), time, time_speed);
+ SendTimeOfDay(peer_id, time, time_speed);
- SendCSMRestrictionFlags(pkt->getPeerId());
+ SendCSMRestrictionFlags(peer_id);
// Warnings about protocol version can be issued here
if (client->net_proto_version < LATEST_PROTOCOL_VERSION) {
- SendChatMessage(pkt->getPeerId(), ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
+ SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
L"# Server: WARNING: YOUR CLIENT'S VERSION MAY NOT BE FULLY COMPATIBLE "
L"WITH THIS SERVER!"));
}
@@ -346,8 +348,9 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt)
*pkt >> numfiles;
- infostream << "Sending " << numfiles << " files to "
- << getPlayerName(pkt->getPeerId()) << std::endl;
+ session_t peer_id = pkt->getPeerId();
+ infostream << "Sending " << numfiles << " files to " <<
+ getPlayerName(peer_id) << std::endl;
verbosestream << "TOSERVER_REQUEST_MEDIA: " << std::endl;
for (u16 i = 0; i < numfiles; i++) {
@@ -360,7 +363,7 @@ void Server::handleCommand_RequestMedia(NetworkPacket* pkt)
<< name << std::endl;
}
- sendRequestedMedia(pkt->getPeerId(), tosend);
+ sendRequestedMedia(peer_id, tosend);
}
void Server::handleCommand_ClientReady(NetworkPacket* pkt)
@@ -370,18 +373,16 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
PlayerSAO* playersao = StageTwoClientInit(peer_id);
if (playersao == NULL) {
- actionstream
- << "TOSERVER_CLIENT_READY stage 2 client init failed for peer_id: "
- << peer_id << std::endl;
+ errorstream << "TOSERVER_CLIENT_READY stage 2 client init failed "
+ "peer_id=" << peer_id << std::endl;
DisconnectPeer(peer_id);
return;
}
if (pkt->getSize() < 8) {
- errorstream
- << "TOSERVER_CLIENT_READY client sent inconsistent data, disconnecting peer_id: "
- << peer_id << std::endl;
+ errorstream << "TOSERVER_CLIENT_READY client sent inconsistent data, "
+ "disconnecting peer_id: " << peer_id << std::endl;
DisconnectPeer(peer_id);
return;
}
@@ -390,9 +391,8 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
std::string full_ver;
*pkt >> major_ver >> minor_ver >> patch_ver >> reserved >> full_ver;
- m_clients.setClientVersion(
- peer_id, major_ver, minor_ver, patch_ver,
- full_ver);
+ m_clients.setClientVersion(peer_id, major_ver, minor_ver, patch_ver,
+ full_ver);
if (pkt->getRemainingBytes() >= 2)
*pkt >> playersao->getPlayer()->formspec_version;
@@ -409,12 +409,15 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
// (u16) 1 + std::string represents a pseudo vector serialization representation
notice_pkt << (u8) PLAYER_LIST_ADD << (u16) 1 << std::string(playersao->getPlayer()->getName());
m_clients.sendToAll(&notice_pkt);
-
m_clients.event(peer_id, CSE_SetClientReady);
- m_script->on_joinplayer(playersao);
+
+ s64 last_login;
+ m_script->getAuth(playersao->getPlayer()->getName(), nullptr, nullptr, &last_login);
+ m_script->on_joinplayer(playersao, last_login);
+
// Send shutdown timer if shutdown has been scheduled
if (m_shutdown_state.isTimerRunning()) {
- SendChatMessage(pkt->getPeerId(), m_shutdown_state.getShutdownTimerMessage());
+ SendChatMessage(peer_id, m_shutdown_state.getShutdownTimerMessage());
}
}
@@ -498,6 +501,7 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao,
player->control.sneak = (keyPressed & 64);
player->control.LMB = (keyPressed & 128);
player->control.RMB = (keyPressed & 256);
+ player->control.zoom = (keyPressed & 512);
if (playersao->checkMovementCheat()) {
// Call callbacks
@@ -508,21 +512,22 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao,
void Server::handleCommand_PlayerPos(NetworkPacket* pkt)
{
- RemotePlayer *player = m_env->getPlayer(pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ RemotePlayer *player = m_env->getPlayer(peer_id);
if (player == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
PlayerSAO *playersao = player->getPlayerSAO();
if (playersao == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player object for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player object for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
@@ -568,22 +573,23 @@ void Server::handleCommand_DeletedBlocks(NetworkPacket* pkt)
void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
{
- RemotePlayer *player = m_env->getPlayer(pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ RemotePlayer *player = m_env->getPlayer(peer_id);
if (player == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
PlayerSAO *playersao = player->getPlayerSAO();
if (playersao == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player object for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player object for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
@@ -619,9 +625,9 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
ma->from_inv.applyCurrentPlayer(player->getName());
ma->to_inv.applyCurrentPlayer(player->getName());
- setInventoryModified(ma->from_inv);
+ m_inventory_mgr->setInventoryModified(ma->from_inv);
if (ma->from_inv != ma->to_inv)
- setInventoryModified(ma->to_inv);
+ m_inventory_mgr->setInventoryModified(ma->to_inv);
bool from_inv_is_current_player =
(ma->from_inv.type == InventoryLocation::PLAYER) &&
@@ -686,7 +692,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
da->from_inv.applyCurrentPlayer(player->getName());
- setInventoryModified(da->from_inv);
+ m_inventory_mgr->setInventoryModified(da->from_inv);
/*
Disable dropping items out of craftpreview
@@ -722,7 +728,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
ca->craft_inv.applyCurrentPlayer(player->getName());
- setInventoryModified(ca->craft_inv);
+ m_inventory_mgr->setInventoryModified(ca->craft_inv);
//bool craft_inv_is_current_player =
// (ca->craft_inv.type == InventoryLocation::PLAYER) &&
@@ -738,7 +744,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
}
// Do the action
- a->apply(this, playersao, this);
+ a->apply(m_inventory_mgr.get(), playersao, this);
// Eat the action
delete a;
}
@@ -761,12 +767,13 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
message += (wchar_t)tmp_wchar;
}
- RemotePlayer *player = m_env->getPlayer(pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ RemotePlayer *player = m_env->getPlayer(peer_id);
if (player == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
@@ -777,8 +784,8 @@ void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
std::wstring answer_to_sender = handleChat(name, wname, message, true, player);
if (!answer_to_sender.empty()) {
// Send the answer to sender
- SendChatMessage(pkt->getPeerId(), ChatMessage(CHATMESSAGE_TYPE_NORMAL,
- answer_to_sender, wname));
+ SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_NORMAL,
+ answer_to_sender, wname));
}
}
@@ -788,22 +795,23 @@ void Server::handleCommand_Damage(NetworkPacket* pkt)
*pkt >> damage;
- RemotePlayer *player = m_env->getPlayer(pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ RemotePlayer *player = m_env->getPlayer(peer_id);
if (player == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
PlayerSAO *playersao = player->getPlayerSAO();
if (playersao == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player object for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player object for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
@@ -825,105 +833,28 @@ void Server::handleCommand_Damage(NetworkPacket* pkt)
}
}
-void Server::handleCommand_Password(NetworkPacket* pkt)
-{
- if (pkt->getSize() != PASSWORD_SIZE * 2)
- return;
-
- std::string oldpwd;
- std::string newpwd;
-
- // Deny for clients using the new protocol
- RemoteClient* client = getClient(pkt->getPeerId(), CS_Created);
- if (client->net_proto_version >= 25) {
- infostream << "Server::handleCommand_Password(): Denying change: "
- << " Client protocol version for peer_id=" << pkt->getPeerId()
- << " too new!" << std::endl;
- return;
- }
-
- for (u16 i = 0; i < PASSWORD_SIZE - 1; i++) {
- char c = pkt->getChar(i);
- if (c == 0)
- break;
- oldpwd += c;
- }
-
- for (u16 i = 0; i < PASSWORD_SIZE - 1; i++) {
- char c = pkt->getChar(PASSWORD_SIZE + i);
- if (c == 0)
- break;
- newpwd += c;
- }
-
- RemotePlayer *player = m_env->getPlayer(pkt->getPeerId());
- if (player == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
- return;
- }
-
- if (!base64_is_valid(newpwd)) {
- infostream<<"Server: " << player->getName() <<
- " supplied invalid password hash" << std::endl;
- // Wrong old password supplied!!
- SendChatMessage(pkt->getPeerId(), ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
- L"Invalid new password hash supplied. Password NOT changed."));
- return;
- }
-
- infostream << "Server: Client requests a password change from "
- << "'" << oldpwd << "' to '" << newpwd << "'" << std::endl;
-
- std::string playername = player->getName();
-
- std::string checkpwd;
- m_script->getAuth(playername, &checkpwd, NULL);
-
- if (oldpwd != checkpwd) {
- infostream << "Server: invalid old password" << std::endl;
- // Wrong old password supplied!!
- SendChatMessage(pkt->getPeerId(), ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
- L"Invalid old password supplied. Password NOT changed."));
- return;
- }
-
- bool success = m_script->setPassword(playername, newpwd);
- if (success) {
- actionstream << player->getName() << " changes password" << std::endl;
- SendChatMessage(pkt->getPeerId(), ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
- L"Password change successful."));
- } else {
- actionstream << player->getName() << " tries to change password but "
- << "it fails" << std::endl;
- SendChatMessage(pkt->getPeerId(), ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
- L"Password change failed or unavailable."));
- }
-}
-
void Server::handleCommand_PlayerItem(NetworkPacket* pkt)
{
if (pkt->getSize() < 2)
return;
- RemotePlayer *player = m_env->getPlayer(pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ RemotePlayer *player = m_env->getPlayer(peer_id);
if (player == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
PlayerSAO *playersao = player->getPlayerSAO();
if (playersao == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player object for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player object for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
@@ -936,12 +867,13 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt)
void Server::handleCommand_Respawn(NetworkPacket* pkt)
{
- RemotePlayer *player = m_env->getPlayer(pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ RemotePlayer *player = m_env->getPlayer(peer_id);
if (player == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
@@ -951,7 +883,7 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt)
if (!playersao->isDead())
return;
- RespawnPlayer(pkt->getPeerId());
+ RespawnPlayer(peer_id);
actionstream << player->getName() << " respawns at "
<< PP(playersao->getBasePosition() / BS) << std::endl;
@@ -973,8 +905,8 @@ bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std:
actionstream << "Player " << player->getName()
<< " tried to access " << what
<< " from too far: "
- << "d=" << d <<", max_d=" << max_d
- << ". ignoring." << std::endl;
+ << "d=" << d << ", max_d=" << max_d
+ << "; ignoring." << std::endl;
// Call callbacks
m_script->on_cheat(player->getPlayerSAO(), "interacted_too_far");
return false;
@@ -1006,31 +938,32 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
verbosestream << "TOSERVER_INTERACT: action=" << (int)action << ", item="
<< item_i << ", pointed=" << pointed.dump() << std::endl;
- RemotePlayer *player = m_env->getPlayer(pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ RemotePlayer *player = m_env->getPlayer(peer_id);
if (player == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
PlayerSAO *playersao = player->getPlayerSAO();
if (playersao == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player object for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player object for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
if (playersao->isDead()) {
- actionstream << "Server: NoCheat: " << player->getName()
+ actionstream << "Server: " << player->getName()
<< " tried to interact while dead; ignoring." << std::endl;
if (pointed.type == POINTEDTHING_NODE) {
// Re-send block to revert change on client-side
- RemoteClient *client = getClient(pkt->getPeerId());
+ RemoteClient *client = getClient(peer_id);
v3s16 blockpos = getNodeBlockPos(pointed.node_undersurface);
client->SetBlockNotSent(blockpos);
}
@@ -1081,7 +1014,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
pointed.dump() << " without 'interact' privilege" << std::endl;
// Re-send block to revert change on client-side
- RemoteClient *client = getClient(pkt->getPeerId());
+ RemoteClient *client = getClient(peer_id);
// Digging completed -> under
if (action == INTERACT_DIGGING_COMPLETED) {
v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
@@ -1109,7 +1042,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
if (!checkInteractDistance(player, d, pointed.dump())) {
// Re-send block to revert change on client-side
- RemoteClient *client = getClient(pkt->getPeerId());
+ RemoteClient *client = getClient(peer_id);
v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
client->SetBlockNotSent(blockpos);
return;
@@ -1132,11 +1065,10 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
n = m_env->getMap().getNode(p_under, &pos_ok);
if (!pos_ok) {
- infostream << "Server: Not punching: Node not found."
- << " Adding block to emerge queue."
- << std::endl;
- m_emerge->enqueueBlockEmerge(pkt->getPeerId(),
- getNodeBlockPos(p_above), false);
+ infostream << "Server: Not punching: Node not found. "
+ "Adding block to emerge queue." << std::endl;
+ m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above),
+ false);
}
if (n.getContent() != CONTENT_IGNORE)
@@ -1202,11 +1134,10 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
bool pos_ok;
MapNode n = m_env->getMap().getNode(p_under, &pos_ok);
if (!pos_ok) {
- infostream << "Server: Not finishing digging: Node not found."
- << " Adding block to emerge queue."
- << std::endl;
- m_emerge->enqueueBlockEmerge(pkt->getPeerId(),
- getNodeBlockPos(p_above), false);
+ infostream << "Server: Not finishing digging: Node not found. "
+ "Adding block to emerge queue." << std::endl;
+ m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above),
+ false);
}
/* Cheat prevention */
@@ -1217,7 +1148,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
playersao->noCheatDigEnd();
// If player didn't start digging this, ignore dig
if (nocheat_p != p_under) {
- infostream << "Server: NoCheat: " << player->getName()
+ infostream << "Server: " << player->getName()
<< " started digging "
<< PP(nocheat_p) << " and completed digging "
<< PP(p_under) << "; not digging." << std::endl;
@@ -1241,9 +1172,9 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
}
// If can't dig, ignore dig
if (!params.diggable) {
- infostream << "Server: NoCheat: " << player->getName()
+ infostream << "Server: " << player->getName()
<< " completed digging " << PP(p_under)
- << ", which is not diggable with tool. not digging."
+ << ", which is not diggable with tool; not digging."
<< std::endl;
is_valid_dig = false;
// Call callbacks
@@ -1267,7 +1198,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
}
// Dig not possible
else {
- infostream << "Server: NoCheat: " << player->getName()
+ infostream << "Server: " << player->getName()
<< " completed digging " << PP(p_under)
<< "too fast; not digging." << std::endl;
is_valid_dig = false;
@@ -1282,7 +1213,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
m_script->node_on_dig(p_under, n, playersao);
v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
- RemoteClient *client = getClient(pkt->getPeerId());
+ RemoteClient *client = getClient(peer_id);
// Send unusual result (that is, node not being removed)
if (m_env->getMap().getNode(p_under).getContent() != CONTENT_AIR) {
// Re-send block to revert change on client-side
@@ -1304,7 +1235,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
// Reset build time counter
if (pointed.type == POINTEDTHING_NODE &&
selected_item.getDefinition(m_itemdef).type == ITEM_NODE)
- getClient(pkt->getPeerId())->m_time_from_building = 0.0;
+ getClient(peer_id)->m_time_from_building = 0.0;
if (pointed.type == POINTEDTHING_OBJECT) {
// Right click object
@@ -1338,7 +1269,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
// If item has node placement prediction, always send the
// blocks to make sure the client knows what exactly happened
- RemoteClient *client = getClient(pkt->getPeerId());
+ RemoteClient *client = getClient(peer_id);
v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
v3s16 blockpos2 = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
if (!selected_item.getDefinition(m_itemdef).node_placement_prediction.empty()) {
@@ -1441,22 +1372,23 @@ void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt)
fields[fieldname] = pkt->readLongString();
}
- RemotePlayer *player = m_env->getPlayer(pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ RemotePlayer *player = m_env->getPlayer(peer_id);
if (player == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
PlayerSAO *playersao = player->getPlayerSAO();
if (playersao == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player object for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player object for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
@@ -1492,22 +1424,23 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt)
fields[fieldname] = pkt->readLongString();
}
- RemotePlayer *player = m_env->getPlayer(pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ RemotePlayer *player = m_env->getPlayer(peer_id);
if (player == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
PlayerSAO *playersao = player->getPlayerSAO();
if (playersao == NULL) {
- errorstream << "Server::ProcessData(): Canceling: "
- "No player object for peer_id=" << pkt->getPeerId()
- << " disconnecting peer!" << std::endl;
- DisconnectPeer(pkt->getPeerId());
+ errorstream <<
+ "Server::ProcessData(): Canceling: No player object for peer_id=" <<
+ peer_id << " disconnecting peer!" << std::endl;
+ DisconnectPeer(peer_id);
return;
}
@@ -1517,7 +1450,7 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt)
}
// verify that we displayed the formspec to the user
- const auto peer_state_iterator = m_formspec_state_data.find(pkt->getPeerId());
+ const auto peer_state_iterator = m_formspec_state_data.find(peer_id);
if (peer_state_iterator != m_formspec_state_data.end()) {
const std::string &server_formspec_name = peer_state_iterator->second;
if (client_formspec_name == server_formspec_name) {
@@ -1543,7 +1476,8 @@ void Server::handleCommand_InventoryFields(NetworkPacket* pkt)
void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
{
- RemoteClient* client = getClient(pkt->getPeerId(), CS_Invalid);
+ session_t peer_id = pkt->getPeerId();
+ RemoteClient *client = getClient(peer_id, CS_Invalid);
ClientState cstate = client->getState();
std::string playername = client->getName();
@@ -1551,7 +1485,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
std::string salt;
std::string verification_key;
- std::string addr_s = getPeerAddress(pkt->getPeerId()).serializeString();
+ std::string addr_s = getPeerAddress(peer_id).serializeString();
u8 is_empty;
*pkt >> salt >> verification_key >> is_empty;
@@ -1565,7 +1499,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
actionstream << "Server: Client from " << addr_s
<< " tried to set password without being "
<< "authenticated, or the username being new." << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA);
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
return;
}
@@ -1574,7 +1508,7 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
is_empty == 1) {
actionstream << "Server: " << playername
<< " supplied empty password from " << addr_s << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_EMPTY_PASSWORD);
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_EMPTY_PASSWORD);
return;
}
@@ -1582,8 +1516,9 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
initial_ver_key = encode_srp_verifier(verification_key, salt);
m_script->createAuth(playername, initial_ver_key);
+ m_script->on_authplayer(playername, addr_s, true);
- acceptAuth(pkt->getPeerId(), false);
+ acceptAuth(peer_id, false);
} else {
if (cstate < CS_SudoMode) {
infostream << "Server::ProcessData(): Ignoring TOSERVER_FIRST_SRP from "
@@ -1591,48 +1526,48 @@ void Server::handleCommand_FirstSrp(NetworkPacket* pkt)
<< std::endl;
return;
}
- m_clients.event(pkt->getPeerId(), CSE_SudoLeave);
+ m_clients.event(peer_id, CSE_SudoLeave);
std::string pw_db_field = encode_srp_verifier(verification_key, salt);
bool success = m_script->setPassword(playername, pw_db_field);
if (success) {
actionstream << playername << " changes password" << std::endl;
- SendChatMessage(pkt->getPeerId(), ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
- L"Password change successful."));
+ SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
+ L"Password change successful."));
} else {
- actionstream << playername << " tries to change password but "
- << "it fails" << std::endl;
- SendChatMessage(pkt->getPeerId(), ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
- L"Password change failed or unavailable."));
+ actionstream << playername <<
+ " tries to change password but it fails" << std::endl;
+ SendChatMessage(peer_id, ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
+ L"Password change failed or unavailable."));
}
}
}
void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
{
- RemoteClient* client = getClient(pkt->getPeerId(), CS_Invalid);
+ session_t peer_id = pkt->getPeerId();
+ 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(pkt->getPeerId()).serializeString()
- << ". Ignoring." << std::endl;
+ actionstream << "Server: got SRP _A packet in wrong state " << cstate <<
+ " from " << getPeerAddress(peer_id).serializeString() <<
+ ". Ignoring." << std::endl;
return;
}
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 " << getPeerAddress(pkt->getPeerId()).serializeString()
- << " (wantSudo=" << wantSudo << "). Ignoring." << std::endl;
+ actionstream << "Server: got SRP _A packet, while auth is already "
+ "going on with mech " << client->chosen_mech << " from " <<
+ getPeerAddress(peer_id).serializeString() <<
+ " (wantSudo=" << wantSudo << "). Ignoring." << std::endl;
if (wantSudo) {
- DenySudoAccess(pkt->getPeerId());
+ DenySudoAccess(peer_id);
return;
}
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA);
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
return;
}
@@ -1649,19 +1584,19 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
if (wantSudo) {
if (!client->isSudoMechAllowed(chosen)) {
- actionstream << "Server: Player \"" << client->getName()
- << "\" at " << getPeerAddress(pkt->getPeerId()).serializeString()
- << " tried to change password using unallowed mech "
- << chosen << "." << std::endl;
- DenySudoAccess(pkt->getPeerId());
+ actionstream << "Server: Player \"" << client->getName() <<
+ "\" at " << getPeerAddress(peer_id).serializeString() <<
+ " tried to change password using unallowed mech " << chosen <<
+ "." << std::endl;
+ DenySudoAccess(peer_id);
return;
}
} else {
if (!client->isMechAllowed(chosen)) {
- actionstream << "Server: Client tried to authenticate from "
- << getPeerAddress(pkt->getPeerId()).serializeString()
- << " using unallowed mech " << chosen << "." << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA);
+ actionstream << "Server: Client tried to authenticate from " <<
+ getPeerAddress(peer_id).serializeString() <<
+ " using unallowed mech " << chosen << "." << std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
return;
}
}
@@ -1677,10 +1612,10 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
&verifier, &salt);
} else if (!decode_srp_verifier_and_salt(client->enc_pwd, &verifier, &salt)) {
// Non-base64 errors should have been catched in the init handler
- actionstream << "Server: User " << client->getName()
- << " tried to log in, but srp verifier field"
- << " was invalid (most likely invalid base64)." << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_SERVER_FAIL);
+ actionstream << "Server: User " << client->getName() <<
+ " tried to log in, but srp verifier field was invalid (most likely "
+ "invalid base64)." << std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL);
return;
}
@@ -1700,23 +1635,26 @@ void Server::handleCommand_SrpBytesA(NetworkPacket* pkt)
<< " tried to log in, SRP-6a safety check violated in _A handler."
<< std::endl;
if (wantSudo) {
- DenySudoAccess(pkt->getPeerId());
+ DenySudoAccess(peer_id);
return;
}
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA);
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
return;
}
- NetworkPacket resp_pkt(TOCLIENT_SRP_BYTES_S_B, 0, pkt->getPeerId());
+ NetworkPacket resp_pkt(TOCLIENT_SRP_BYTES_S_B, 0, peer_id);
resp_pkt << salt << std::string(bytes_B, len_B);
Send(&resp_pkt);
}
void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
{
- RemoteClient* client = getClient(pkt->getPeerId(), CS_Invalid);
+ 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();
bool wantSudo = (cstate == CS_Active);
@@ -1724,8 +1662,7 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) {
actionstream << "Server: got SRP _M packet in wrong state "
- << cstate << " from "
- << getPeerAddress(pkt->getPeerId()).serializeString()
+ << cstate << " from " << addr_s
<< ". Ignoring." << std::endl;
return;
}
@@ -1733,15 +1670,14 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
if (client->chosen_mech != AUTH_MECHANISM_SRP &&
client->chosen_mech != AUTH_MECHANISM_LEGACY_PASSWORD) {
actionstream << "Server: got SRP _M packet, while auth"
- << "is going on with mech " << client->chosen_mech
- << " from " << getPeerAddress(pkt->getPeerId()).serializeString()
- << " (wantSudo=" << wantSudo << "). Denying." << std::endl;
+ << "is going on with mech " << client->chosen_mech << " from "
+ << addr_s << " (wantSudo=" << wantSudo << "). Denying." << std::endl;
if (wantSudo) {
- DenySudoAccess(pkt->getPeerId());
+ DenySudoAccess(peer_id);
return;
}
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA);
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
return;
}
@@ -1750,10 +1686,9 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
if (srp_verifier_get_session_key_length((SRPVerifier *) client->auth_data)
!= bytes_M.size()) {
- actionstream << "Server: User " << client->getName()
- << " at " << getPeerAddress(pkt->getPeerId()).serializeString()
+ actionstream << "Server: User " << playername << " at " << addr_s
<< " sent bytes_M with invalid length " << bytes_M.size() << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA);
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_UNEXPECTED_DATA);
return;
}
@@ -1764,39 +1699,36 @@ void Server::handleCommand_SrpBytesM(NetworkPacket* pkt)
if (!bytes_HAMK) {
if (wantSudo) {
- actionstream << "Server: User " << client->getName()
- << " at " << getPeerAddress(pkt->getPeerId()).serializeString()
+ actionstream << "Server: User " << playername << " at " << addr_s
<< " tried to change their password, but supplied wrong"
<< " (SRP) password for authentication." << std::endl;
- DenySudoAccess(pkt->getPeerId());
+ DenySudoAccess(peer_id);
return;
}
- std::string ip = getPeerAddress(pkt->getPeerId()).serializeString();
- actionstream << "Server: User " << client->getName()
- << " at " << ip
- << " supplied wrong password (auth mechanism: SRP)."
- << std::endl;
- m_script->on_auth_failure(client->getName(), ip);
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_PASSWORD);
+ actionstream << "Server: User " << playername << " at " << addr_s
+ << " supplied wrong password (auth mechanism: SRP)." << std::endl;
+ m_script->on_authplayer(playername, addr_s, false);
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_WRONG_PASSWORD);
return;
}
if (client->create_player_on_auth_success) {
- std::string playername = client->getName();
m_script->createAuth(playername, client->enc_pwd);
std::string checkpwd; // not used, but needed for passing something
if (!m_script->getAuth(playername, &checkpwd, NULL)) {
- actionstream << "Server: " << playername << " cannot be authenticated"
- << " (auth handler does not work?)" << std::endl;
- DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_SERVER_FAIL);
+ actionstream << "Server: " << playername <<
+ " cannot be authenticated (auth handler does not work?)" <<
+ std::endl;
+ DenyAccess(peer_id, SERVER_ACCESSDENIED_SERVER_FAIL);
return;
}
client->create_player_on_auth_success = false;
}
- acceptAuth(pkt->getPeerId(), wantSudo);
+ m_script->on_authplayer(playername, addr_s, true);
+ acceptAuth(peer_id, wantSudo);
}
/*
@@ -1808,20 +1740,21 @@ void Server::handleCommand_ModChannelJoin(NetworkPacket *pkt)
std::string channel_name;
*pkt >> channel_name;
- NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL, 1 + 2 + channel_name.size(),
- pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL,
+ 1 + 2 + channel_name.size(), peer_id);
// Send signal to client to notify join succeed or not
if (g_settings->getBool("enable_mod_channels") &&
- m_modchannel_mgr->joinChannel(channel_name, pkt->getPeerId())) {
+ m_modchannel_mgr->joinChannel(channel_name, peer_id)) {
resp_pkt << (u8) MODCHANNEL_SIGNAL_JOIN_OK;
- infostream << "Peer " << pkt->getPeerId() << " joined channel " << channel_name
- << std::endl;
+ infostream << "Peer " << peer_id << " joined channel " <<
+ channel_name << std::endl;
}
else {
resp_pkt << (u8)MODCHANNEL_SIGNAL_JOIN_FAILURE;
- infostream << "Peer " << pkt->getPeerId() << " tried to join channel "
- << channel_name << ", but was already registered." << std::endl;
+ infostream << "Peer " << peer_id << " tried to join channel " <<
+ channel_name << ", but was already registered." << std::endl;
}
resp_pkt << channel_name;
Send(&resp_pkt);
@@ -1832,19 +1765,20 @@ void Server::handleCommand_ModChannelLeave(NetworkPacket *pkt)
std::string channel_name;
*pkt >> channel_name;
- NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL, 1 + 2 + channel_name.size(),
- pkt->getPeerId());
+ session_t peer_id = pkt->getPeerId();
+ NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL,
+ 1 + 2 + channel_name.size(), peer_id);
// Send signal to client to notify join succeed or not
if (g_settings->getBool("enable_mod_channels") &&
- m_modchannel_mgr->leaveChannel(channel_name, pkt->getPeerId())) {
+ m_modchannel_mgr->leaveChannel(channel_name, peer_id)) {
resp_pkt << (u8)MODCHANNEL_SIGNAL_LEAVE_OK;
- infostream << "Peer " << pkt->getPeerId() << " left channel " << channel_name
- << std::endl;
+ infostream << "Peer " << peer_id << " left channel " << channel_name <<
+ std::endl;
} else {
resp_pkt << (u8) MODCHANNEL_SIGNAL_LEAVE_FAILURE;
- infostream << "Peer " << pkt->getPeerId() << " left channel " << channel_name
- << ", but was not registered." << std::endl;
+ infostream << "Peer " << peer_id << " left channel " << channel_name <<
+ ", but was not registered." << std::endl;
}
resp_pkt << channel_name;
Send(&resp_pkt);
@@ -1855,8 +1789,10 @@ void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt)
std::string channel_name, channel_msg;
*pkt >> channel_name >> channel_msg;
- verbosestream << "Mod channel message received from peer " << pkt->getPeerId()
- << " on channel " << channel_name << " message: " << channel_msg << std::endl;
+ session_t peer_id = pkt->getPeerId();
+ verbosestream << "Mod channel message received from peer " << peer_id <<
+ " on channel " << channel_name << " message: " << channel_msg <<
+ std::endl;
// If mod channels are not enabled, discard message
if (!g_settings->getBool("enable_mod_channels")) {
@@ -1865,8 +1801,8 @@ void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt)
// If channel not registered, signal it and ignore message
if (!m_modchannel_mgr->channelRegistered(channel_name)) {
- NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL, 1 + 2 + channel_name.size(),
- pkt->getPeerId());
+ NetworkPacket resp_pkt(TOCLIENT_MODCHANNEL_SIGNAL,
+ 1 + 2 + channel_name.size(), peer_id);
resp_pkt << (u8)MODCHANNEL_SIGNAL_CHANNEL_NOT_REGISTERED << channel_name;
Send(&resp_pkt);
return;
@@ -1874,5 +1810,5 @@ void Server::handleCommand_ModChannelMsg(NetworkPacket *pkt)
// @TODO: filter, rate limit
- broadcastModChannelMessage(channel_name, channel_msg, pkt->getPeerId());
+ broadcastModChannelMessage(channel_name, channel_msg, peer_id);
}
diff --git a/src/nodedef.cpp b/src/nodedef.cpp
index 977a4533d..a84338752 100644
--- a/src/nodedef.cpp
+++ b/src/nodedef.cpp
@@ -368,9 +368,12 @@ void ContentFeatures::reset()
floodable = false;
rightclickable = true;
leveled = 0;
+ leveled_max = LEVELED_MAX;
liquid_type = LIQUID_NONE;
liquid_alternative_flowing = "";
+ liquid_alternative_flowing_id = CONTENT_IGNORE;
liquid_alternative_source = "";
+ liquid_alternative_source_id = CONTENT_IGNORE;
liquid_viscosity = 0;
liquid_renewable = true;
liquid_range = LIQUID_LEVEL_MAX+1;
@@ -478,6 +481,7 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
writeU8(os, legacy_wallmounted);
os << serializeString(node_dig_prediction);
+ writeU8(os, leveled_max);
}
void ContentFeatures::correctAlpha(TileDef *tiles, int length)
@@ -586,6 +590,10 @@ void ContentFeatures::deSerialize(std::istream &is)
try {
node_dig_prediction = deSerializeString(is);
+ u8 tmp_leveled_max = readU8(is);
+ if (is.eof()) /* readU8 doesn't throw exceptions so we have to do this */
+ throw SerializationError("");
+ leveled_max = tmp_leveled_max;
} catch(SerializationError &e) {};
}
@@ -600,8 +608,9 @@ static void fillTileAttribs(ITextureSource *tsrc, TileLayer *layer,
layer->material_type = material_type;
bool has_scale = tiledef.scale > 0;
- if (((tsettings.autoscale_mode == AUTOSCALE_ENABLE) && !has_scale) ||
- (tsettings.autoscale_mode == AUTOSCALE_FORCE)) {
+ bool use_autoscale = tsettings.autoscale_mode == AUTOSCALE_FORCE ||
+ (tsettings.autoscale_mode == AUTOSCALE_ENABLE && !has_scale);
+ if (use_autoscale && layer->texture) {
auto texture_size = layer->texture->getOriginalSize();
float base_size = tsettings.node_texture_size;
float size = std::fmin(texture_size.Width, texture_size.Height);
@@ -1304,60 +1313,35 @@ void NodeDefManager::updateAliases(IItemDefManager *idef)
}
}
-void NodeDefManager::applyTextureOverrides(const std::string &override_filepath)
+void NodeDefManager::applyTextureOverrides(const std::vector<TextureOverride> &overrides)
{
infostream << "NodeDefManager::applyTextureOverrides(): Applying "
- "overrides to textures from " << override_filepath << std::endl;
-
- std::ifstream infile(override_filepath.c_str());
- std::string line;
- int line_c = 0;
- while (std::getline(infile, line)) {
- line_c++;
- // Also trim '\r' on DOS-style files
- line = trim(line);
- if (line.empty())
- continue;
-
- std::vector<std::string> splitted = str_split(line, ' ');
- if (splitted.size() != 3) {
- errorstream << override_filepath
- << ":" << line_c << " Could not apply texture override \""
- << line << "\": Syntax error" << std::endl;
- continue;
- }
+ "overrides to textures" << std::endl;
+ for (const TextureOverride& texture_override : overrides) {
content_t id;
- if (!getId(splitted[0], id))
+ if (!getId(texture_override.id, id))
continue; // Ignore unknown node
ContentFeatures &nodedef = m_content_features[id];
- if (splitted[1] == "top")
- nodedef.tiledef[0].name = splitted[2];
- else if (splitted[1] == "bottom")
- nodedef.tiledef[1].name = splitted[2];
- else if (splitted[1] == "right")
- nodedef.tiledef[2].name = splitted[2];
- else if (splitted[1] == "left")
- nodedef.tiledef[3].name = splitted[2];
- else if (splitted[1] == "back")
- nodedef.tiledef[4].name = splitted[2];
- else if (splitted[1] == "front")
- nodedef.tiledef[5].name = splitted[2];
- else if (splitted[1] == "all" || splitted[1] == "*")
- for (TileDef &i : nodedef.tiledef)
- i.name = splitted[2];
- else if (splitted[1] == "sides")
- for (int i = 2; i < 6; i++)
- nodedef.tiledef[i].name = splitted[2];
- else {
- errorstream << override_filepath
- << ":" << line_c << " Could not apply texture override \""
- << line << "\": Unknown node side \""
- << splitted[1] << "\"" << std::endl;
- continue;
- }
+ if (texture_override.hasTarget(OverrideTarget::TOP))
+ nodedef.tiledef[0].name = texture_override.texture;
+
+ if (texture_override.hasTarget(OverrideTarget::BOTTOM))
+ nodedef.tiledef[1].name = texture_override.texture;
+
+ if (texture_override.hasTarget(OverrideTarget::RIGHT))
+ nodedef.tiledef[2].name = texture_override.texture;
+
+ if (texture_override.hasTarget(OverrideTarget::LEFT))
+ nodedef.tiledef[3].name = texture_override.texture;
+
+ if (texture_override.hasTarget(OverrideTarget::BACK))
+ nodedef.tiledef[4].name = texture_override.texture;
+
+ if (texture_override.hasTarget(OverrideTarget::FRONT))
+ nodedef.tiledef[5].name = texture_override.texture;
}
}
@@ -1458,11 +1442,15 @@ void NodeDefManager::deSerialize(std::istream &is)
m_content_features.resize((u32)(i) + 1);
m_content_features[i] = f;
addNameIdMapping(i, f.name);
- verbosestream << "deserialized " << f.name << std::endl;
+ TRACESTREAM(<< "NodeDef: deserialized " << f.name << std::endl);
getNodeBoxUnion(f.selection_box, f, &m_selection_box_union);
fixSelectionBoxIntUnion();
}
+
+ // Since liquid_alternative_flowing_id and liquid_alternative_source_id
+ // are not sent, resolve them client-side too.
+ resolveCrossrefs();
}
@@ -1523,15 +1511,28 @@ void NodeDefManager::resetNodeResolveState()
m_pending_resolve_callbacks.clear();
}
-void NodeDefManager::mapNodeboxConnections()
+static void removeDupes(std::vector<content_t> &list)
+{
+ std::sort(list.begin(), list.end());
+ auto new_end = std::unique(list.begin(), list.end());
+ list.erase(new_end, list.end());
+}
+
+void NodeDefManager::resolveCrossrefs()
{
for (ContentFeatures &f : m_content_features) {
+ if (f.liquid_type != LIQUID_NONE) {
+ f.liquid_alternative_flowing_id = getId(f.liquid_alternative_flowing);
+ f.liquid_alternative_source_id = getId(f.liquid_alternative_source);
+ continue;
+ }
if (f.drawtype != NDT_NODEBOX || f.node_box.type != NODEBOX_CONNECTED)
continue;
for (const std::string &name : f.connects_to) {
getIds(name, f.connects_to_ids);
}
+ removeDupes(f.connects_to_ids);
}
}
@@ -1597,6 +1598,18 @@ NodeResolver::~NodeResolver()
}
+void NodeResolver::cloneTo(NodeResolver *res) const
+{
+ FATAL_ERROR_IF(!m_resolve_done, "NodeResolver can only be cloned"
+ " after resolving has completed");
+ /* We don't actually do anything significant. Since the node resolving has
+ * already completed, the class that called us will already have the
+ * resolved IDs in its data structures (which it copies on its own) */
+ res->m_ndef = m_ndef;
+ res->m_resolve_done = true;
+}
+
+
void NodeResolver::nodeResolveInternal()
{
m_nodenames_idx = 0;
diff --git a/src/nodedef.h b/src/nodedef.h
index 1a12aae93..0992001e1 100644
--- a/src/nodedef.h
+++ b/src/nodedef.h
@@ -33,6 +33,7 @@ class Client;
#include "itemgroup.h"
#include "sound.h" // SimpleSoundSpec
#include "constants.h" // BS
+#include "texture_override.h" // TextureOverride
#include "tileanimation.h"
// PROTOCOL_VERSION >= 37
@@ -325,8 +326,10 @@ struct ContentFeatures
std::vector<content_t> connects_to_ids;
// Post effect color, drawn when the camera is inside the node.
video::SColor post_effect_color;
- // Flowing liquid or snow, value = default level
+ // Flowing liquid or leveled nodebox, value = default level
u8 leveled;
+ // Maximum value for leveled nodes
+ u8 leveled_max;
// --- LIGHTING-RELATED ---
@@ -365,8 +368,10 @@ struct ContentFeatures
enum LiquidType liquid_type;
// If the content is liquid, this is the flowing version of the liquid.
std::string liquid_alternative_flowing;
+ content_t liquid_alternative_flowing_id;
// If the content is liquid, this is the source version of the liquid.
std::string liquid_alternative_source;
+ content_t liquid_alternative_source_id;
// Viscosity for fluid flow, ranging from 1 to 7, with
// 1 giving almost instantaneous propagation and 7 being
// the slowest possible
@@ -425,7 +430,7 @@ struct ContentFeatures
}
bool sameLiquid(const ContentFeatures &f) const{
if(!isLiquid() || !f.isLiquid()) return false;
- return (liquid_alternative_flowing == f.liquid_alternative_flowing);
+ return (liquid_alternative_flowing_id == f.liquid_alternative_flowing_id);
}
int getGroup(const std::string &group) const
@@ -583,15 +588,12 @@ public:
void updateAliases(IItemDefManager *idef);
/*!
- * Reads the used texture pack's override.txt, and replaces the textures
- * of registered nodes with the ones specified there.
+ * Replaces the textures of registered nodes with the ones specified in
+ * the texturepack's override.txt file
*
- * Format of the input file: in each line
- * `node_name top|bottom|right|left|front|back|all|*|sides texture_name.png`
- *
- * @param override_filepath path to 'texturepack/override.txt'
+ * @param overrides the texture overrides
*/
- void applyTextureOverrides(const std::string &override_filepath);
+ void applyTextureOverrides(const std::vector<TextureOverride> &overrides);
/*!
* Only the client uses this. Loads textures and shaders required for
@@ -641,10 +643,11 @@ public:
void resetNodeResolveState();
/*!
- * Resolves the IDs to which connecting nodes connect from names.
+ * Resolves (caches the IDs) cross-references between nodes,
+ * like liquid alternatives.
* Must be called after node registration has finished!
*/
- void mapNodeboxConnections();
+ void resolveCrossrefs();
private:
/*!
@@ -741,6 +744,9 @@ public:
virtual ~NodeResolver();
virtual void resolveNodeNames() = 0;
+ // required because this class is used as mixin for ObjDef
+ void cloneTo(NodeResolver *res) const;
+
bool getIdFromNrBacklog(content_t *result_out,
const std::string &node_alt, content_t c_fallback,
bool error_on_fallback = true);
diff --git a/src/noise.cpp b/src/noise.cpp
index 9c91a6df4..5a1d989cb 100644
--- a/src/noise.cpp
+++ b/src/noise.cpp
@@ -46,11 +46,6 @@ typedef float (*Interp3dFxn)(
float v001, float v101, float v011, float v111,
float x, float y, float z);
-float cos_lookup[16] = {
- 1.0f, 0.9238f, 0.7071f, 0.3826f, .0f, -0.3826f, -0.7071f, -0.9238f,
- 1.0f, -0.9238f, -0.7071f, -0.3826f, .0f, 0.3826f, 0.7071f, 0.9238f
-};
-
FlagDesc flagdesc_noiseparams[] = {
{"defaults", NOISE_FLAG_DEFAULTS},
{"eased", NOISE_FLAG_EASED},
diff --git a/src/objdef.cpp b/src/objdef.cpp
index 08d6844fc..482544d37 100644
--- a/src/objdef.cpp
+++ b/src/objdef.cpp
@@ -182,3 +182,22 @@ bool ObjDefManager::decodeHandle(ObjDefHandle handle, u32 *index,
*uid = get_bits(handle, 24, 7);
return true;
}
+
+// Cloning
+
+void ObjDef::cloneTo(ObjDef *def) const
+{
+ def->index = index;
+ def->uid = uid;
+ def->handle = handle;
+ def->name = name;
+}
+
+void ObjDefManager::cloneTo(ObjDefManager *mgr) const
+{
+ mgr->m_ndef = m_ndef;
+ mgr->m_objects.reserve(m_objects.size());
+ for (const auto &obj : m_objects)
+ mgr->m_objects.push_back(obj->clone());
+ mgr->m_objtype = m_objtype;
+}
diff --git a/src/objdef.h b/src/objdef.h
index 9ab3df977..e40324a88 100644
--- a/src/objdef.h
+++ b/src/objdef.h
@@ -45,21 +45,36 @@ class ObjDef {
public:
virtual ~ObjDef() = default;
+ // Only implemented by child classes (leafs in class hierarchy)
+ // Should create new object of its own type, call cloneTo() of parent class
+ // and copy its own instance variables over
+ virtual ObjDef *clone() const = 0;
+
u32 index;
u32 uid;
ObjDefHandle handle;
std::string name;
+
+protected:
+ // Only implemented by classes that have children themselves
+ // by copying the defintion and changing that argument type (!!!)
+ // Should defer to parent class cloneTo() if applicable and then copy
+ // over its own properties
+ void cloneTo(ObjDef *def) const;
};
// WARNING: Ownership of ObjDefs is transferred to the ObjDefManager it is
// added/set to. Note that ObjDefs managed by ObjDefManager are NOT refcounted,
// so the same ObjDef instance must not be referenced multiple
+// TODO: const correctness for getter methods
class ObjDefManager {
public:
ObjDefManager(IGameDef *gamedef, ObjDefType type);
virtual ~ObjDefManager();
DISABLE_CLASS_COPY(ObjDefManager);
+ // T *clone() const; // implemented in child class with correct type
+
virtual const char *getObjectTitle() const { return "ObjDef"; }
virtual void clear();
@@ -88,6 +103,10 @@ public:
ObjDefType *type, u32 *uid);
protected:
+ ObjDefManager() {};
+ // Helper for child classes to implement clone()
+ void cloneTo(ObjDefManager *mgr) const;
+
const NodeDefManager *m_ndef;
std::vector<ObjDef *> m_objects;
ObjDefType m_objtype;
diff --git a/src/object_properties.cpp b/src/object_properties.cpp
index 4cf180b18..8d51bcbfa 100644
--- a/src/object_properties.cpp
+++ b/src/object_properties.cpp
@@ -68,6 +68,8 @@ std::string ObjectProperties::dump()
os << ", eye_height=" << eye_height;
os << ", zoom_fov=" << zoom_fov;
os << ", use_texture_alpha=" << use_texture_alpha;
+ os << ", damage_texture_modifier=" << damage_texture_modifier;
+ os << ", shaded=" << shaded;
return os.str();
}
@@ -114,6 +116,8 @@ void ObjectProperties::serialize(std::ostream &os) const
writeF32(os, eye_height);
writeF32(os, zoom_fov);
writeU8(os, use_texture_alpha);
+ os << serializeString(damage_texture_modifier);
+ writeU8(os, shaded);
// Add stuff only at the bottom.
// Never remove anything, because we don't want new versions of this
@@ -166,4 +170,11 @@ void ObjectProperties::deSerialize(std::istream &is)
eye_height = readF32(is);
zoom_fov = readF32(is);
use_texture_alpha = readU8(is);
+ try {
+ damage_texture_modifier = deSerializeString(is);
+ u8 tmp = readU8(is);
+ if (is.eof())
+ throw SerializationError("");
+ shaded = tmp;
+ } catch (SerializationError &e) {}
}
diff --git a/src/object_properties.h b/src/object_properties.h
index 3895f3379..f010c1daf 100644
--- a/src/object_properties.h
+++ b/src/object_properties.h
@@ -39,6 +39,7 @@ struct ObjectProperties
std::string mesh = "";
v3f visual_size = v3f(1, 1, 1);
std::vector<std::string> textures;
+ std::string damage_texture_modifier = "^[brighten";
std::vector<video::SColor> colors;
v2s16 spritediv = v2s16(1, 1);
v2s16 initial_sprite_basepos;
@@ -60,6 +61,7 @@ struct ObjectProperties
float eye_height = 1.625f;
float zoom_fov = 0.0f;
bool use_texture_alpha = false;
+ bool shaded = true;
ObjectProperties();
std::string dump();
diff --git a/src/particles.cpp b/src/particles.cpp
new file mode 100644
index 000000000..fd81238dc
--- /dev/null
+++ b/src/particles.cpp
@@ -0,0 +1,63 @@
+/*
+Minetest
+Copyright (C) 2020 sfan5 <sfan5@live.de>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "particles.h"
+#include "util/serialize.h"
+
+void ParticleParameters::serialize(std::ostream &os, u16 protocol_ver) const
+{
+ writeV3F32(os, pos);
+ writeV3F32(os, vel);
+ writeV3F32(os, acc);
+ writeF32(os, expirationtime);
+ writeF32(os, size);
+ writeU8(os, collisiondetection);
+ os << serializeLongString(texture);
+ writeU8(os, vertical);
+ writeU8(os, collision_removal);
+ animation.serialize(os, 6); /* NOT the protocol ver */
+ writeU8(os, glow);
+ writeU8(os, object_collision);
+ writeU16(os, node.param0);
+ writeU8(os, node.param2);
+ writeU8(os, node_tile);
+}
+
+void ParticleParameters::deSerialize(std::istream &is, u16 protocol_ver)
+{
+ pos = readV3F32(is);
+ vel = readV3F32(is);
+ acc = readV3F32(is);
+ expirationtime = readF32(is);
+ size = readF32(is);
+ collisiondetection = readU8(is);
+ texture = deSerializeLongString(is);
+ vertical = readU8(is);
+ collision_removal = readU8(is);
+ animation.deSerialize(is, 6); /* NOT the protocol ver */
+ glow = readU8(is);
+ object_collision = readU8(is);
+ // This is kinda awful
+ u16 tmp_param0 = readU16(is);
+ if (is.eof())
+ return;
+ node.param0 = tmp_param0;
+ node.param2 = readU8(is);
+ node_tile = readU8(is);
+}
diff --git a/src/particles.h b/src/particles.h
new file mode 100644
index 000000000..6f518b771
--- /dev/null
+++ b/src/particles.h
@@ -0,0 +1,79 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include <string>
+#include "irrlichttypes_bloated.h"
+#include "tileanimation.h"
+#include "mapnode.h"
+
+// This file defines the particle-related structures that both the server and
+// client need. The ParticleManager and rendering is in client/particles.h
+
+struct CommonParticleParams {
+ bool collisiondetection = false;
+ bool collision_removal = false;
+ bool object_collision = false;
+ bool vertical = false;
+ std::string texture;
+ struct TileAnimationParams animation;
+ u8 glow = 0;
+ MapNode node;
+ u8 node_tile = 0;
+
+ CommonParticleParams() {
+ animation.type = TAT_NONE;
+ node.setContent(CONTENT_IGNORE);
+ }
+
+ /* This helper is useful for copying params from
+ * ParticleSpawnerParameters to ParticleParameters */
+ inline void copyCommon(CommonParticleParams &to) const {
+ to.collisiondetection = collisiondetection;
+ to.collision_removal = collision_removal;
+ to.object_collision = object_collision;
+ to.vertical = vertical;
+ to.texture = texture;
+ to.animation = animation;
+ to.glow = glow;
+ to.node = node;
+ to.node_tile = node_tile;
+ }
+};
+
+struct ParticleParameters : CommonParticleParams {
+ v3f pos;
+ v3f vel;
+ v3f acc;
+ f32 expirationtime = 1;
+ f32 size = 1;
+
+ void serialize(std::ostream &os, u16 protocol_ver) const;
+ void deSerialize(std::istream &is, u16 protocol_ver);
+};
+
+struct ParticleSpawnerParameters : CommonParticleParams {
+ u16 amount = 1;
+ v3f minpos, maxpos, minvel, maxvel, minacc, maxacc;
+ f32 time = 1;
+ f32 minexptime = 1, maxexptime = 1, minsize = 1, maxsize = 1;
+
+ // For historical reasons no (de-)serialization methods here
+};
diff --git a/src/pathfinder.cpp b/src/pathfinder.cpp
index 8195bd643..3f0b98c10 100644
--- a/src/pathfinder.cpp
+++ b/src/pathfinder.cpp
@@ -23,8 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
/******************************************************************************/
#include "pathfinder.h"
-#include "serverenvironment.h"
-#include "server.h"
+#include "map.h"
#include "nodedef.h"
//#define PATHFINDER_DEBUG
@@ -180,10 +179,8 @@ private:
class Pathfinder {
public:
- /**
- * default constructor
- */
- Pathfinder() = default;
+ Pathfinder() = delete;
+ Pathfinder(Map *map, const NodeDefManager *ndef) : m_map(map), m_ndef(ndef) {}
~Pathfinder();
@@ -197,8 +194,7 @@ public:
* @param max_drop maximum number of blocks a path may drop
* @param algo Algorithm to use for finding a path
*/
- std::vector<v3s16> getPath(ServerEnvironment *env,
- v3s16 source,
+ std::vector<v3s16> getPath(v3s16 source,
v3s16 destination,
unsigned int searchdistance,
unsigned int max_jump,
@@ -328,7 +324,9 @@ private:
friend class GridNodeContainer;
GridNodeContainer *m_nodes_container = nullptr;
- ServerEnvironment *m_env = 0; /**< minetest environment pointer */
+ Map *m_map = nullptr;
+
+ const NodeDefManager *m_ndef = nullptr;
friend class PathfinderCompareHeuristic;
@@ -410,18 +408,15 @@ class PathfinderCompareHeuristic
/* implementation */
/******************************************************************************/
-std::vector<v3s16> get_path(ServerEnvironment* env,
- v3s16 source,
- v3s16 destination,
- unsigned int searchdistance,
- unsigned int max_jump,
- unsigned int max_drop,
- PathAlgorithm algo)
+std::vector<v3s16> get_path(Map* map, const NodeDefManager *ndef,
+ v3s16 source,
+ v3s16 destination,
+ unsigned int searchdistance,
+ unsigned int max_jump,
+ unsigned int max_drop,
+ PathAlgorithm algo)
{
- Pathfinder searchclass;
-
- return searchclass.getPath(env,
- source, destination,
+ return Pathfinder(map, ndef).getPath(source, destination,
searchdistance, max_jump, max_drop, algo);
}
@@ -521,13 +516,13 @@ void PathGridnode::setCost(v3s16 dir, const PathCost &cost)
void GridNodeContainer::initNode(v3s16 ipos, PathGridnode *p_node)
{
- const NodeDefManager *ndef = m_pathf->m_env->getGameDef()->ndef();
+ const NodeDefManager *ndef = m_pathf->m_ndef;
PathGridnode &elem = *p_node;
v3s16 realpos = m_pathf->getRealPos(ipos);
- MapNode current = m_pathf->m_env->getMap().getNode(realpos);
- MapNode below = m_pathf->m_env->getMap().getNode(realpos + v3s16(0, -1, 0));
+ MapNode current = m_pathf->m_map->getNode(realpos);
+ MapNode below = m_pathf->m_map->getNode(realpos + v3s16(0, -1, 0));
if ((current.param0 == CONTENT_IGNORE) ||
@@ -610,8 +605,7 @@ PathGridnode &MapGridNodeContainer::access(v3s16 p)
/******************************************************************************/
-std::vector<v3s16> Pathfinder::getPath(ServerEnvironment *env,
- v3s16 source,
+std::vector<v3s16> Pathfinder::getPath(v3s16 source,
v3s16 destination,
unsigned int searchdistance,
unsigned int max_jump,
@@ -624,15 +618,8 @@ std::vector<v3s16> Pathfinder::getPath(ServerEnvironment *env,
#endif
std::vector<v3s16> retval;
- //check parameters
- if (env == 0) {
- ERROR_TARGET << "Missing environment pointer" << std::endl;
- return retval;
- }
-
//initialization
m_searchdistance = searchdistance;
- m_env = env;
m_maxjump = max_jump;
m_maxdrop = max_drop;
m_start = source;
@@ -681,15 +668,14 @@ std::vector<v3s16> Pathfinder::getPath(ServerEnvironment *env,
#endif
//fail if source or destination is walkable
- const NodeDefManager *ndef = m_env->getGameDef()->ndef();
- MapNode node_at_pos = m_env->getMap().getNode(destination);
- if (ndef->get(node_at_pos).walkable) {
+ MapNode node_at_pos = m_map->getNode(destination);
+ if (m_ndef->get(node_at_pos).walkable) {
VERBOSE_TARGET << "Destination is walkable. " <<
"Pos: " << PP(destination) << std::endl;
return retval;
}
- node_at_pos = m_env->getMap().getNode(source);
- if (ndef->get(node_at_pos).walkable) {
+ node_at_pos = m_map->getNode(source);
+ if (m_ndef->get(node_at_pos).walkable) {
VERBOSE_TARGET << "Source is walkable. " <<
"Pos: " << PP(source) << std::endl;
return retval;
@@ -843,7 +829,6 @@ v3s16 Pathfinder::getRealPos(v3s16 ipos)
/******************************************************************************/
PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir)
{
- const NodeDefManager *ndef = m_env->getGameDef()->ndef();
PathCost retval;
retval.updated = true;
@@ -857,7 +842,7 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir)
return retval;
}
- MapNode node_at_pos2 = m_env->getMap().getNode(pos2);
+ MapNode node_at_pos2 = m_map->getNode(pos2);
//did we get information about node?
if (node_at_pos2.param0 == CONTENT_IGNORE ) {
@@ -866,9 +851,9 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir)
return retval;
}
- if (!ndef->get(node_at_pos2).walkable) {
+ if (!m_ndef->get(node_at_pos2).walkable) {
MapNode node_below_pos2 =
- m_env->getMap().getNode(pos2 + v3s16(0, -1, 0));
+ m_map->getNode(pos2 + v3s16(0, -1, 0));
//did we get information about node?
if (node_below_pos2.param0 == CONTENT_IGNORE ) {
@@ -878,7 +863,7 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir)
}
//test if the same-height neighbor is suitable
- if (ndef->get(node_below_pos2).walkable) {
+ if (m_ndef->get(node_below_pos2).walkable) {
//SUCCESS!
retval.valid = true;
retval.value = 1;
@@ -889,19 +874,19 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir)
else {
//test if we can fall a couple of nodes (m_maxdrop)
v3s16 testpos = pos2 + v3s16(0, -1, 0);
- MapNode node_at_pos = m_env->getMap().getNode(testpos);
+ MapNode node_at_pos = m_map->getNode(testpos);
while ((node_at_pos.param0 != CONTENT_IGNORE) &&
- (!ndef->get(node_at_pos).walkable) &&
+ (!m_ndef->get(node_at_pos).walkable) &&
(testpos.Y > m_limits.MinEdge.Y)) {
testpos += v3s16(0, -1, 0);
- node_at_pos = m_env->getMap().getNode(testpos);
+ node_at_pos = m_map->getNode(testpos);
}
//did we find surface?
if ((testpos.Y >= m_limits.MinEdge.Y) &&
(node_at_pos.param0 != CONTENT_IGNORE) &&
- (ndef->get(node_at_pos).walkable)) {
+ (m_ndef->get(node_at_pos).walkable)) {
if ((pos2.Y - testpos.Y - 1) <= m_maxdrop) {
//SUCCESS!
retval.valid = true;
@@ -927,34 +912,34 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir)
v3s16 targetpos = pos2; // position for jump target
v3s16 jumppos = pos; // position for checking if jumping space is free
- MapNode node_target = m_env->getMap().getNode(targetpos);
- MapNode node_jump = m_env->getMap().getNode(jumppos);
+ MapNode node_target = m_map->getNode(targetpos);
+ MapNode node_jump = m_map->getNode(jumppos);
bool headbanger = false; // true if anything blocks jumppath
while ((node_target.param0 != CONTENT_IGNORE) &&
- (ndef->get(node_target).walkable) &&
+ (m_ndef->get(node_target).walkable) &&
(targetpos.Y < m_limits.MaxEdge.Y)) {
//if the jump would hit any solid node, discard
if ((node_jump.param0 == CONTENT_IGNORE) ||
- (ndef->get(node_jump).walkable)) {
+ (m_ndef->get(node_jump).walkable)) {
headbanger = true;
break;
}
targetpos += v3s16(0, 1, 0);
jumppos += v3s16(0, 1, 0);
- node_target = m_env->getMap().getNode(targetpos);
- node_jump = m_env->getMap().getNode(jumppos);
+ node_target = m_map->getNode(targetpos);
+ node_jump = m_map->getNode(jumppos);
}
//check headbanger one last time
if ((node_jump.param0 == CONTENT_IGNORE) ||
- (ndef->get(node_jump).walkable)) {
+ (m_ndef->get(node_jump).walkable)) {
headbanger = true;
}
//did we find surface without banging our head?
if ((!headbanger) && (targetpos.Y <= m_limits.MaxEdge.Y) &&
- (!ndef->get(node_target).walkable)) {
+ (!m_ndef->get(node_target).walkable)) {
if (targetpos.Y - pos2.Y <= m_maxjump) {
//SUCCESS!
@@ -1254,21 +1239,20 @@ v3s16 Pathfinder::walkDownwards(v3s16 pos, unsigned int max_down) {
if (max_down == 0)
return pos;
v3s16 testpos = v3s16(pos);
- MapNode node_at_pos = m_env->getMap().getNode(testpos);
- const NodeDefManager *ndef = m_env->getGameDef()->ndef();
+ MapNode node_at_pos = m_map->getNode(testpos);
unsigned int down = 0;
while ((node_at_pos.param0 != CONTENT_IGNORE) &&
- (!ndef->get(node_at_pos).walkable) &&
+ (!m_ndef->get(node_at_pos).walkable) &&
(testpos.Y > m_limits.MinEdge.Y) &&
(down <= max_down)) {
testpos += v3s16(0, -1, 0);
down++;
- node_at_pos = m_env->getMap().getNode(testpos);
+ node_at_pos = m_map->getNode(testpos);
}
//did we find surface?
if ((testpos.Y >= m_limits.MinEdge.Y) &&
(node_at_pos.param0 != CONTENT_IGNORE) &&
- (ndef->get(node_at_pos).walkable)) {
+ (m_ndef->get(node_at_pos).walkable)) {
if (down == 0) {
pos = testpos;
} else if ((down - 1) <= max_down) {
diff --git a/src/pathfinder.h b/src/pathfinder.h
index 70f3d6bbc..526aa0ee8 100644
--- a/src/pathfinder.h
+++ b/src/pathfinder.h
@@ -29,7 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
/* Forward declarations */
/******************************************************************************/
-class ServerEnvironment;
+class NodeDefManager;
+class Map;
/******************************************************************************/
/* Typedefs and macros */
@@ -54,10 +55,10 @@ typedef enum {
/******************************************************************************/
/** c wrapper function to use from scriptapi */
-std::vector<v3s16> get_path(ServerEnvironment *env,
- v3s16 source,
- v3s16 destination,
- unsigned int searchdistance,
- unsigned int max_jump,
- unsigned int max_drop,
- PathAlgorithm algo);
+std::vector<v3s16> get_path(Map *map, const NodeDefManager *ndef,
+ v3s16 source,
+ v3s16 destination,
+ unsigned int searchdistance,
+ unsigned int max_jump,
+ unsigned int max_drop,
+ PathAlgorithm algo);
diff --git a/src/player.h b/src/player.h
index de7f427e9..3bc7762fa 100644
--- a/src/player.h
+++ b/src/player.h
@@ -35,7 +35,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
struct PlayerFovSpec
{
f32 fov;
+
+ // Whether to multiply the client's FOV or to override it
bool is_multiplier;
+
+ // The time to be take to trasition to the new FOV value.
+ // Transition is instantaneous if omitted. Omitted by default.
+ f32 transition_time;
};
struct PlayerControl
@@ -186,12 +192,12 @@ public:
void setFov(const PlayerFovSpec &spec)
{
- m_fov_spec = spec;
+ m_fov_override_spec = spec;
}
const PlayerFovSpec &getFov() const
{
- return m_fov_spec;
+ return m_fov_override_spec;
}
u32 keyPressed = 0;
@@ -208,7 +214,7 @@ protected:
char m_name[PLAYERNAME_SIZE];
v3f m_speed;
u16 m_wield_index = 0;
- PlayerFovSpec m_fov_spec = { 0.0f, false };
+ PlayerFovSpec m_fov_override_spec = { 0.0f, false, 0.0f };
std::vector<HudElement *> hud;
private:
diff --git a/src/porting.cpp b/src/porting.cpp
index 76fd592d6..d902d3737 100644
--- a/src/porting.cpp
+++ b/src/porting.cpp
@@ -28,27 +28,39 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
#include <sys/types.h>
#include <sys/sysctl.h>
+ extern char **environ;
#elif defined(_WIN32)
#include <windows.h>
#include <wincrypt.h>
#include <algorithm>
#include <shlwapi.h>
+ #include <shellapi.h>
#endif
#if !defined(_WIN32)
#include <unistd.h>
#include <sys/utsname.h>
+ #if !defined(__ANDROID__)
+ #include <spawn.h>
+ #endif
#endif
#if defined(__hpux)
#define _PSTAT64
#include <sys/pstat.h>
#endif
+#if defined(__ANDROID__)
+ #include "porting_android.h"
+#endif
+#if defined(__APPLE__)
+ // For _NSGetEnviron()
+ // Related: https://gitlab.haskell.org/ghc/ghc/issues/2458
+ #include <crt_externs.h>
+#endif
#include "config.h"
#include "debug.h"
#include "filesys.h"
#include "log.h"
#include "util/string.h"
-#include "settings.h"
#include <list>
#include <cstdarg>
#include <cstdio>
@@ -598,18 +610,18 @@ void initializePaths()
#if USE_GETTEXT
bool found_localedir = false;
# ifdef STATIC_LOCALEDIR
- if (STATIC_LOCALEDIR[0] && fs::PathExists(STATIC_LOCALEDIR)) {
+ /* STATIC_LOCALEDIR may be a generalized path such as /usr/share/locale that
+ * doesn't necessarily contain our locale files, so check data path first. */
+ path_locale = getDataPath("locale");
+ if (fs::PathExists(path_locale)) {
+ found_localedir = true;
+ infostream << "Using in-place locale directory " << path_locale
+ << " even though a static one was provided." << std::endl;
+ } else if (STATIC_LOCALEDIR[0] && fs::PathExists(STATIC_LOCALEDIR)) {
found_localedir = true;
path_locale = STATIC_LOCALEDIR;
- infostream << "Using locale directory " << STATIC_LOCALEDIR << std::endl;
- } else {
- path_locale = getDataPath("locale");
- if (fs::PathExists(path_locale)) {
- found_localedir = true;
- infostream << "Using in-place locale directory " << path_locale
- << " even though a static one was provided "
- << "(RUN_IN_PLACE or CUSTOM_LOCALEDIR)." << std::endl;
- }
+ infostream << "Using static locale directory " << STATIC_LOCALEDIR
+ << std::endl;
}
# else
path_locale = getDataPath("locale");
@@ -697,6 +709,29 @@ int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...)
return c;
}
+bool openURL(const std::string &url)
+{
+ if ((url.substr(0, 7) != "http://" && url.substr(0, 8) != "https://") ||
+ url.find_first_of("\r\n") != std::string::npos) {
+ errorstream << "Invalid url: " << url << std::endl;
+ return false;
+ }
+
+#if defined(_WIN32)
+ return (intptr_t)ShellExecuteA(NULL, NULL, url.c_str(), NULL, NULL, SW_SHOWNORMAL) > 32;
+#elif defined(__ANDROID__)
+ openURLAndroid(url);
+ return true;
+#elif defined(__APPLE__)
+ const char *argv[] = {"open", url.c_str(), NULL};
+ return posix_spawnp(NULL, "open", NULL, NULL, (char**)argv,
+ (*_NSGetEnviron())) == 0;
+#else
+ const char *argv[] = {"xdg-open", url.c_str(), NULL};
+ return posix_spawnp(NULL, "xdg-open", NULL, NULL, (char**)argv, environ) == 0;
+#endif
+}
+
// Load performance counter frequency only once at startup
#ifdef _WIN32
diff --git a/src/porting.h b/src/porting.h
index 4d30a5970..f50f0a950 100644
--- a/src/porting.h
+++ b/src/porting.h
@@ -329,6 +329,9 @@ bool secure_rand_fill_buf(void *buf, size_t len);
void attachOrCreateConsole();
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...);
+
+bool openURL(const std::string &url);
+
} // namespace porting
#ifdef __ANDROID__
diff --git a/src/porting_android.cpp b/src/porting_android.cpp
index 7c74f7b5b..41b521ec2 100644
--- a/src/porting_android.cpp
+++ b/src/porting_android.cpp
@@ -47,8 +47,7 @@ void android_main(android_app *app)
Thread::setName("Main");
try {
- app_dummy();
- char *argv[] = {strdup(PROJECT_NAME), NULL};
+ char *argv[] = {strdup(PROJECT_NAME), nullptr};
main(ARRLEN(argv) - 1, argv);
free(argv[0]);
} catch (std::exception &e) {
@@ -64,96 +63,73 @@ void android_main(android_app *app)
exit(retval);
}
-/* handler for finished message box input */
-/* Intentionally NOT in namespace porting */
-/* TODO this doesn't work as expected, no idea why but there's a workaround */
-/* for it right now */
+/**
+ * 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_MtNativeActivity_putMessageBoxResult(
- JNIEnv * env, jclass thiz, jstring text)
+ JNIEXPORT void JNICALL Java_net_minetest_minetest_GameActivity_putMessageBoxResult(
+ JNIEnv *env, jclass thiz, jstring text)
{
- errorstream << "Java_net_minetest_MtNativeActivity_putMessageBoxResult got: "
- << std::string((const char*)env->GetStringChars(text,0))
- << std::endl;
+ errorstream <<
+ "Java_net_minetest_minetest_GameActivity_putMessageBoxResult got: " <<
+ std::string((const char*) env->GetStringChars(text, nullptr)) << std::endl;
}
}
namespace porting {
-
-std::string path_storage = DIR_DELIM "sdcard" DIR_DELIM;
-
-android_app* app_global;
-JNIEnv* jnienv;
+android_app *app_global;
+JNIEnv *jnienv;
jclass nativeActivity;
-jclass findClass(std::string classname)
+jclass findClass(const std::string &classname)
{
- if (jnienv == 0) {
- return 0;
- }
+ if (jnienv == nullptr)
+ return nullptr;
jclass nativeactivity = jnienv->FindClass("android/app/NativeActivity");
- jmethodID getClassLoader =
- jnienv->GetMethodID(nativeactivity,"getClassLoader",
- "()Ljava/lang/ClassLoader;");
- jobject cls =
- jnienv->CallObjectMethod(app_global->activity->clazz, getClassLoader);
+ jmethodID getClassLoader = jnienv->GetMethodID(
+ nativeactivity, "getClassLoader", "()Ljava/lang/ClassLoader;");
+ jobject cls = jnienv->CallObjectMethod(
+ app_global->activity->clazz, getClassLoader);
jclass classLoader = jnienv->FindClass("java/lang/ClassLoader");
- jmethodID findClass =
- jnienv->GetMethodID(classLoader, "loadClass",
+ jmethodID findClass = jnienv->GetMethodID(classLoader, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
- jstring strClassName =
- jnienv->NewStringUTF(classname.c_str());
+ jstring strClassName = jnienv->NewStringUTF(classname.c_str());
return (jclass) jnienv->CallObjectMethod(cls, findClass, strClassName);
}
-void copyAssets()
-{
- jmethodID assetcopy = jnienv->GetMethodID(nativeActivity,"copyAssets","()V");
-
- if (assetcopy == 0) {
- assert("porting::copyAssets unable to find copy assets method" == 0);
- }
-
- jnienv->CallVoidMethod(app_global->activity->clazz, assetcopy);
-}
-
void initAndroid()
{
- porting::jnienv = NULL;
+ 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 = NULL;
-#ifdef NDEBUG
- // This is a ugly hack as arm v7a non debuggable builds crash without this
- // printf ... if someone finds out why please fix it!
- infostream << "Attaching native thread. " << std::endl;
-#endif
- if ( jvm->AttachCurrentThread(&porting::jnienv, &lJavaVMAttachArgs) == JNI_ERR) {
+ lJavaVMAttachArgs.group = nullptr;
+
+ 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/MtNativeActivity");
- if (nativeActivity == 0) {
+ nativeActivity = findClass("net/minetest/minetest/GameActivity");
+ if (nativeActivity == nullptr)
errorstream <<
"porting::initAndroid unable to find java native activity class" <<
std::endl;
- }
#ifdef GPROF
- /* in the start-up code */
+ // in the start-up code
__android_log_print(ANDROID_LOG_ERROR, PROJECT_NAME_C,
"Initializing GPROF profiler");
- monstartup("libminetest.so");
+ monstartup("libMinetest.so");
#endif
}
void cleanupAndroid()
{
-
#ifdef GPROF
errorstream << "Shutting down GPROF profiler" << std::endl;
setenv("CPUPROFILE", (path_user + DIR_DELIM + "gmon.out").c_str(), 1);
@@ -168,7 +144,7 @@ static std::string javaStringToUTF8(jstring js)
{
std::string str;
// Get string as a UTF-8 c-string
- const char *c_str = jnienv->GetStringUTFChars(js, NULL);
+ const char *c_str = jnienv->GetStringUTFChars(js, nullptr);
// Save it
str = c_str;
// And free the c-string
@@ -177,17 +153,15 @@ static std::string javaStringToUTF8(jstring js)
}
// Calls static method if obj is NULL
-static std::string getAndroidPath(jclass cls, jobject obj, jclass cls_File,
- jmethodID mt_getAbsPath, const char *getter)
+static std::string getAndroidPath(
+ jclass cls, jobject obj, jmethodID mt_getAbsPath, const char *getter)
{
// Get getter method
jmethodID mt_getter;
if (obj)
- mt_getter = jnienv->GetMethodID(cls, getter,
- "()Ljava/io/File;");
+ mt_getter = jnienv->GetMethodID(cls, getter, "()Ljava/io/File;");
else
- mt_getter = jnienv->GetStaticMethodID(cls, getter,
- "()Ljava/io/File;");
+ mt_getter = jnienv->GetStaticMethodID(cls, getter, "()Ljava/io/File;");
// Call getter
jobject ob_file;
@@ -197,8 +171,7 @@ static std::string getAndroidPath(jclass cls, jobject obj, jclass cls_File,
ob_file = jnienv->CallStaticObjectMethod(cls, mt_getter);
// Call getAbsolutePath
- jstring js_path = (jstring) jnienv->CallObjectMethod(ob_file,
- mt_getAbsPath);
+ auto js_path = (jstring) jnienv->CallObjectMethod(ob_file, mt_getAbsPath);
return javaStringToUTF8(js_path);
}
@@ -212,26 +185,24 @@ void initializePathsAndroid()
// 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");
- path_cache = getAndroidPath(nativeActivity, app_global->activity->clazz,
- cls_File, mt_getAbsPath, "getCacheDir");
- path_storage = getAndroidPath(cls_Env, NULL, cls_File, mt_getAbsPath,
- "getExternalStorageDirectory");
path_user = path_storage + DIR_DELIM + PROJECT_NAME_C;
path_share = path_storage + DIR_DELIM + PROJECT_NAME_C;
-
+ path_cache = getAndroidPath(nativeActivity,
+ app_global->activity->clazz, mt_getAbsPath, "getCacheDir");
migrateCachePath();
}
-void showInputDialog(const std::string& acceptButton, const std::string& hint,
- const std::string& current, int editType)
+void showInputDialog(const std::string &acceptButton, const std::string &hint,
+ const std::string &current, int editType)
{
- jmethodID showdialog = jnienv->GetMethodID(nativeActivity,"showDialog",
+ jmethodID showdialog = jnienv->GetMethodID(nativeActivity, "showDialog",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V");
- if (showdialog == 0) {
- assert("porting::showInputDialog unable to find java show dialog method" == 0);
- }
+ FATAL_ERROR_IF(showdialog == nullptr,
+ "porting::showInputDialog unable to find java show dialog method");
jstring jacceptButton = jnienv->NewStringUTF(acceptButton.c_str());
jstring jhint = jnienv->NewStringUTF(hint.c_str());
@@ -242,14 +213,25 @@ void showInputDialog(const std::string& acceptButton, const std::string& hint,
jacceptButton, jhint, jcurrent, jeditType);
}
+void openURLAndroid(const std::string &url)
+{
+ jmethodID url_open = jnienv->GetMethodID(nativeActivity, "openURL",
+ "(Ljava/lang/String;)V");
+
+ FATAL_ERROR_IF(url_open == nullptr,
+ "porting::openURLAndroid unable to find java openURL method");
+
+ jstring jurl = jnienv->NewStringUTF(url.c_str());
+ jnienv->CallVoidMethod(app_global->activity->clazz, url_open, jurl);
+}
+
int getInputDialogState()
{
jmethodID dialogstate = jnienv->GetMethodID(nativeActivity,
"getDialogState", "()I");
- if (dialogstate == 0) {
- assert("porting::getInputDialogState unable to find java dialog state method" == 0);
- }
+ FATAL_ERROR_IF(dialogstate == nullptr,
+ "porting::getInputDialogState unable to find java dialog state method");
return jnienv->CallIntMethod(app_global->activity->clazz, dialogstate);
}
@@ -259,14 +241,13 @@ std::string getInputDialogValue()
jmethodID dialogvalue = jnienv->GetMethodID(nativeActivity,
"getDialogValue", "()Ljava/lang/String;");
- if (dialogvalue == 0) {
- assert("porting::getInputDialogValue unable to find java dialog value method" == 0);
- }
+ FATAL_ERROR_IF(dialogvalue == nullptr,
+ "porting::getInputDialogValue unable to find java dialog value method");
jobject result = jnienv->CallObjectMethod(app_global->activity->clazz,
dialogvalue);
- const char* javachars = jnienv->GetStringUTFChars((jstring) result,0);
+ const char *javachars = jnienv->GetStringUTFChars((jstring) result, nullptr);
std::string text(javachars);
jnienv->ReleaseStringUTFChars((jstring) result, javachars);
@@ -280,12 +261,11 @@ float getDisplayDensity()
static float value = 0;
if (firstrun) {
- jmethodID getDensity = jnienv->GetMethodID(nativeActivity, "getDensity",
- "()F");
+ jmethodID getDensity = jnienv->GetMethodID(nativeActivity,
+ "getDensity", "()F");
- if (getDensity == 0) {
- assert("porting::getDisplayDensity unable to find java getDensity method" == 0);
- }
+ FATAL_ERROR_IF(getDensity == nullptr,
+ "porting::getDisplayDensity unable to find java getDensity method");
value = jnienv->CallFloatMethod(app_global->activity->clazz, getDensity);
firstrun = false;
@@ -302,9 +282,8 @@ v2u32 getDisplaySize()
jmethodID getDisplayWidth = jnienv->GetMethodID(nativeActivity,
"getDisplayWidth", "()I");
- if (getDisplayWidth == 0) {
- assert("porting::getDisplayWidth unable to find java getDisplayWidth method" == 0);
- }
+ FATAL_ERROR_IF(getDisplayWidth == nullptr,
+ "porting::getDisplayWidth unable to find java getDisplayWidth method");
retval.X = jnienv->CallIntMethod(app_global->activity->clazz,
getDisplayWidth);
@@ -312,9 +291,8 @@ v2u32 getDisplaySize()
jmethodID getDisplayHeight = jnienv->GetMethodID(nativeActivity,
"getDisplayHeight", "()I");
- if (getDisplayHeight == 0) {
- assert("porting::getDisplayHeight unable to find java getDisplayHeight method" == 0);
- }
+ FATAL_ERROR_IF(getDisplayHeight == nullptr,
+ "porting::getDisplayHeight unable to find java getDisplayHeight method");
retval.Y = jnienv->CallIntMethod(app_global->activity->clazz,
getDisplayHeight);
diff --git a/src/porting_android.h b/src/porting_android.h
index 0b3f2575e..6eb054041 100644
--- a/src/porting_android.h
+++ b/src/porting_android.h
@@ -30,16 +30,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string>
namespace porting {
-/** java app **/
+// java app
extern android_app *app_global;
-/** java <-> c++ interaction interface **/
+// java <-> c++ interaction interface
extern JNIEnv *jnienv;
-/**
- * do initialization required on android only
- */
+// do initialization required on android only
void initAndroid();
+
void cleanupAndroid();
/**
@@ -49,11 +48,6 @@ void cleanupAndroid();
void initializePathsAndroid();
/**
- * use java function to copy media from assets to external storage
- */
-void copyAssets();
-
-/**
* show text input dialog in java
* @param acceptButton text to display on accept button
* @param hint hint to show
@@ -61,8 +55,10 @@ void copyAssets();
* @param editType type of texfield
* (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& current, int editType);
+void showInputDialog(const std::string &acceptButton,
+ const std::string &hint, const std::string &current, int editType);
+
+void openURLAndroid(const std::string &url);
/**
* WORKAROUND for not working callbacks from java -> c++
@@ -80,5 +76,4 @@ std::string getInputDialogValue();
float getDisplayDensity();
v2u32 getDisplaySize();
#endif
-
}
diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp
index 1a8fec68c..bef60c792 100644
--- a/src/remoteplayer.cpp
+++ b/src/remoteplayer.cpp
@@ -20,13 +20,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "remoteplayer.h"
#include <json/json.h>
-#include "content_sao.h"
#include "filesys.h"
#include "gamedef.h"
#include "porting.h" // strlcpy
#include "server.h"
#include "settings.h"
#include "convert_json.h"
+#include "server/player_sao.h"
/*
RemotePlayer
@@ -74,9 +74,9 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef):
m_skybox_params.sky_color = sky_defaults.getSkyColorDefaults();
m_skybox_params.type = "regular";
m_skybox_params.clouds = true;
- m_skybox_params.sun_tint = video::SColor(255, 244, 125, 29);
- m_skybox_params.moon_tint = video::SColorf(0.5, 0.6, 0.8, 1).toSColor();
- m_skybox_params.tint_type = "default";
+ m_skybox_params.fog_sun_tint = video::SColor(255, 244, 125, 29);
+ m_skybox_params.fog_moon_tint = video::SColorf(0.5, 0.6, 0.8, 1).toSColor();
+ m_skybox_params.fog_tint_type = "default";
m_sun_params = sky_defaults.getSunDefaults();
m_moon_params = sky_defaults.getMoonDefaults();
diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp
index accbb1a87..3dfd7ce61 100644
--- a/src/script/common/c_content.cpp
+++ b/src/script/common/c_content.cpp
@@ -21,7 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "common/c_types.h"
#include "nodedef.h"
#include "object_properties.h"
-#include "content_sao.h"
+#include "collision.h"
#include "cpp_api/s_node.h"
#include "lua_api/l_object.h"
#include "lua_api/l_item.h"
@@ -29,10 +29,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "server.h"
#include "log.h"
#include "tool.h"
-#include "serverobject.h"
#include "porting.h"
#include "mapgen/mg_schematic.h"
#include "noise.h"
+#include "server/player_sao.h"
#include "util/pointedthing.h"
#include "debug.h" // For FATAL_ERROR
#include <json/json.h>
@@ -103,7 +103,8 @@ void read_item_definition(lua_State* L, int index,
lua_pop(L, 1);
lua_getfield(L, index, "sounds");
- if(lua_istable(L, -1)){
+ if (!lua_isnil(L, -1)) {
+ luaL_checktype(L, -1, LUA_TTABLE);
lua_getfield(L, -1, "place");
read_soundspec(L, -1, def.sound_place);
lua_pop(L, 1);
@@ -163,7 +164,7 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i)
lua_setfield(L, -2, "usable");
lua_pushboolean(L, i.liquids_pointable);
lua_setfield(L, -2, "liquids_pointable");
- if (i.type == ITEM_TOOL) {
+ if (i.tool_capabilities) {
push_tool_capabilities(L, *i.tool_capabilities);
lua_setfield(L, -2, "tool_capabilities");
}
@@ -183,9 +184,11 @@ void read_object_properties(lua_State *L, int index,
{
if(index < 0)
index = lua_gettop(L) + 1 + index;
- if(!lua_istable(L, index))
+ if (lua_isnil(L, index))
return;
+ luaL_checktype(L, -1, LUA_TTABLE);
+
int hp_max = 0;
if (getintfield(L, -1, "hp_max", hp_max)) {
prop->hp_max = (u16)rangelim(hp_max, 0, U16_MAX);
@@ -324,6 +327,9 @@ void read_object_properties(lua_State *L, int index,
getfloatfield(L, -1, "zoom_fov", prop->zoom_fov);
getboolfield(L, -1, "use_texture_alpha", prop->use_texture_alpha);
+ getboolfield(L, -1, "shaded", prop->shaded);
+
+ getstringfield(L, -1, "damage_texture_modifier", prop->damage_texture_modifier);
}
/******************************************************************************/
@@ -351,7 +357,7 @@ void push_object_properties(lua_State *L, ObjectProperties *prop)
push_v3f(L, prop->visual_size);
lua_setfield(L, -2, "visual_size");
- lua_newtable(L);
+ lua_createtable(L, prop->textures.size(), 0);
u16 i = 1;
for (const std::string &texture : prop->textures) {
lua_pushlstring(L, texture.c_str(), texture.size());
@@ -359,7 +365,7 @@ void push_object_properties(lua_State *L, ObjectProperties *prop)
}
lua_setfield(L, -2, "textures");
- lua_newtable(L);
+ lua_createtable(L, prop->colors.size(), 0);
i = 1;
for (const video::SColor &color : prop->colors) {
push_ARGB8(L, color);
@@ -406,6 +412,10 @@ void push_object_properties(lua_State *L, ObjectProperties *prop)
lua_setfield(L, -2, "zoom_fov");
lua_pushboolean(L, prop->use_texture_alpha);
lua_setfield(L, -2, "use_texture_alpha");
+ lua_pushboolean(L, prop->shaded);
+ lua_setfield(L, -2, "shaded");
+ lua_pushlstring(L, prop->damage_texture_modifier.c_str(), prop->damage_texture_modifier.size());
+ lua_setfield(L, -2, "damage_texture_modifier");
}
/******************************************************************************/
@@ -687,6 +697,8 @@ ContentFeatures read_content_features(lua_State *L, int index)
f.liquid_range = getintfield_default(L, index,
"liquid_range", f.liquid_range);
f.leveled = getintfield_default(L, index, "leveled", f.leveled);
+ f.leveled_max = getintfield_default(L, index,
+ "leveled_max", f.leveled_max);
getboolfield(L, index, "liquid_renewable", f.liquid_renewable);
f.drowning = getintfield_default(L, index,
@@ -841,7 +853,7 @@ void push_content_features(lua_State *L, const ContentFeatures &c)
lua_pushnumber(L, c.connect_sides);
lua_setfield(L, -2, "connect_sides");
- lua_newtable(L);
+ lua_createtable(L, c.connects_to.size(), 0);
u16 i = 1;
for (const std::string &it : c.connects_to) {
lua_pushlstring(L, it.c_str(), it.size());
@@ -853,6 +865,8 @@ void push_content_features(lua_State *L, const ContentFeatures &c)
lua_setfield(L, -2, "post_effect_color");
lua_pushnumber(L, c.leveled);
lua_setfield(L, -2, "leveled");
+ lua_pushnumber(L, c.leveled_max);
+ lua_setfield(L, -2, "leveled_max");
lua_pushboolean(L, c.sunlight_propagates);
lua_setfield(L, -2, "sunlight_propagates");
lua_pushnumber(L, c.light_source);
@@ -964,7 +978,7 @@ void push_nodebox(lua_State *L, const NodeBox &box)
void push_box(lua_State *L, const std::vector<aabb3f> &box)
{
- lua_newtable(L);
+ lua_createtable(L, box.size(), 0);
u8 i = 1;
for (const aabb3f &it : box) {
push_aabb3f(L, it);
@@ -1028,20 +1042,22 @@ void read_soundspec(lua_State *L, int index, SimpleSoundSpec &spec)
{
if(index < 0)
index = lua_gettop(L) + 1 + index;
- if(lua_isnil(L, index)){
- } else if(lua_istable(L, index)){
+ if (lua_isnil(L, index))
+ return;
+
+ if (lua_istable(L, index)) {
getstringfield(L, index, "name", spec.name);
getfloatfield(L, index, "gain", spec.gain);
getfloatfield(L, index, "fade", spec.fade);
getfloatfield(L, index, "pitch", spec.pitch);
- } else if(lua_isstring(L, index)){
+ } else if (lua_isstring(L, index)) {
spec.name = lua_tostring(L, index);
}
}
void push_soundspec(lua_State *L, const SimpleSoundSpec &spec)
{
- lua_newtable(L);
+ lua_createtable(L, 0, 3);
lua_pushstring(L, spec.name.c_str());
lua_setfield(L, -2, "name");
lua_pushnumber(L, spec.gain);
@@ -1056,9 +1072,13 @@ void push_soundspec(lua_State *L, const SimpleSoundSpec &spec)
NodeBox read_nodebox(lua_State *L, int index)
{
NodeBox nodebox;
- if(lua_istable(L, -1)){
- nodebox.type = (NodeBoxType)getenumfield(L, index, "type",
- ScriptApiNode::es_NodeBoxType, NODEBOX_REGULAR);
+ if (lua_isnil(L, -1))
+ return nodebox;
+
+ luaL_checktype(L, -1, LUA_TTABLE);
+
+ nodebox.type = (NodeBoxType)getenumfield(L, index, "type",
+ ScriptApiNode::es_NodeBoxType, NODEBOX_REGULAR);
#define NODEBOXREAD(n, s){ \
lua_getfield(L, index, (s)); \
@@ -1068,30 +1088,30 @@ NodeBox read_nodebox(lua_State *L, int index)
}
#define NODEBOXREADVEC(n, s) \
- lua_getfield(L, index, (s)); \
- if (lua_istable(L, -1)) \
- (n) = read_aabb3f_vector(L, -1, BS); \
- lua_pop(L, 1);
+ lua_getfield(L, index, (s)); \
+ if (lua_istable(L, -1)) \
+ (n) = read_aabb3f_vector(L, -1, BS); \
+ lua_pop(L, 1);
+
+ NODEBOXREADVEC(nodebox.fixed, "fixed");
+ NODEBOXREAD(nodebox.wall_top, "wall_top");
+ NODEBOXREAD(nodebox.wall_bottom, "wall_bottom");
+ NODEBOXREAD(nodebox.wall_side, "wall_side");
+ NODEBOXREADVEC(nodebox.connect_top, "connect_top");
+ NODEBOXREADVEC(nodebox.connect_bottom, "connect_bottom");
+ NODEBOXREADVEC(nodebox.connect_front, "connect_front");
+ NODEBOXREADVEC(nodebox.connect_left, "connect_left");
+ NODEBOXREADVEC(nodebox.connect_back, "connect_back");
+ NODEBOXREADVEC(nodebox.connect_right, "connect_right");
+ NODEBOXREADVEC(nodebox.disconnected_top, "disconnected_top");
+ NODEBOXREADVEC(nodebox.disconnected_bottom, "disconnected_bottom");
+ NODEBOXREADVEC(nodebox.disconnected_front, "disconnected_front");
+ NODEBOXREADVEC(nodebox.disconnected_left, "disconnected_left");
+ NODEBOXREADVEC(nodebox.disconnected_back, "disconnected_back");
+ NODEBOXREADVEC(nodebox.disconnected_right, "disconnected_right");
+ NODEBOXREADVEC(nodebox.disconnected, "disconnected");
+ NODEBOXREADVEC(nodebox.disconnected_sides, "disconnected_sides");
- NODEBOXREADVEC(nodebox.fixed, "fixed");
- NODEBOXREAD(nodebox.wall_top, "wall_top");
- NODEBOXREAD(nodebox.wall_bottom, "wall_bottom");
- NODEBOXREAD(nodebox.wall_side, "wall_side");
- NODEBOXREADVEC(nodebox.connect_top, "connect_top");
- NODEBOXREADVEC(nodebox.connect_bottom, "connect_bottom");
- NODEBOXREADVEC(nodebox.connect_front, "connect_front");
- NODEBOXREADVEC(nodebox.connect_left, "connect_left");
- NODEBOXREADVEC(nodebox.connect_back, "connect_back");
- NODEBOXREADVEC(nodebox.connect_right, "connect_right");
- NODEBOXREADVEC(nodebox.disconnected_top, "disconnected_top");
- NODEBOXREADVEC(nodebox.disconnected_bottom, "disconnected_bottom");
- NODEBOXREADVEC(nodebox.disconnected_front, "disconnected_front");
- NODEBOXREADVEC(nodebox.disconnected_left, "disconnected_left");
- NODEBOXREADVEC(nodebox.disconnected_back, "disconnected_back");
- NODEBOXREADVEC(nodebox.disconnected_right, "disconnected_right");
- NODEBOXREADVEC(nodebox.disconnected, "disconnected");
- NODEBOXREADVEC(nodebox.disconnected_sides, "disconnected_sides");
- }
return nodebox;
}
@@ -1126,12 +1146,12 @@ MapNode readnode(lua_State *L, int index, const NodeDefManager *ndef)
/******************************************************************************/
void pushnode(lua_State *L, const MapNode &n, const NodeDefManager *ndef)
{
- lua_newtable(L);
+ lua_createtable(L, 0, 3);
lua_pushstring(L, ndef->get(n).name.c_str());
lua_setfield(L, -2, "name");
- lua_pushnumber(L, n.getParam1());
+ lua_pushinteger(L, n.getParam1());
lua_setfield(L, -2, "param1");
- lua_pushnumber(L, n.getParam2());
+ lua_pushinteger(L, n.getParam2());
lua_setfield(L, -2, "param2");
}
@@ -1164,7 +1184,7 @@ bool string_to_enum(const EnumString *spec, int &result,
{
const EnumString *esp = spec;
while(esp->str){
- if(str == std::string(esp->str)){
+ if (!strcmp(str.c_str(), esp->str)) {
result = esp->num;
return true;
}
@@ -1439,7 +1459,7 @@ ToolCapabilities read_tool_capabilities(
/******************************************************************************/
void push_dig_params(lua_State *L,const DigParams &params)
{
- lua_newtable(L);
+ lua_createtable(L, 0, 3);
setboolfield(L, -1, "diggable", params.diggable);
setfloatfield(L, -1, "time", params.time);
setintfield(L, -1, "wear", params.wear);
@@ -1448,7 +1468,7 @@ void push_dig_params(lua_State *L,const DigParams &params)
/******************************************************************************/
void push_hit_params(lua_State *L,const HitParams &params)
{
- lua_newtable(L);
+ lua_createtable(L, 0, 3);
setintfield(L, -1, "hp", params.hp);
setintfield(L, -1, "wear", params.wear);
}
@@ -1520,8 +1540,11 @@ void push_flags_string(lua_State *L, FlagDesc *flagdesc, u32 flags, u32 flagmask
/******************************************************************************/
void read_groups(lua_State *L, int index, ItemGroupList &result)
{
- if (!lua_istable(L,index))
+ if (lua_isnil(L, index))
return;
+
+ luaL_checktype(L, index, LUA_TTABLE);
+
result.clear();
lua_pushnil(L);
if (index < 0)
@@ -1541,9 +1564,9 @@ void read_groups(lua_State *L, int index, ItemGroupList &result)
/******************************************************************************/
void push_groups(lua_State *L, const ItemGroupList &groups)
{
- lua_newtable(L);
+ lua_createtable(L, 0, groups.size());
for (const auto &group : groups) {
- lua_pushnumber(L, group.second);
+ lua_pushinteger(L, group.second);
lua_setfield(L, -2, group.first.c_str());
}
}
@@ -1588,7 +1611,7 @@ void luaentity_get(lua_State *L, u16 id)
lua_getglobal(L, "core");
lua_getfield(L, -1, "luaentities");
luaL_checktype(L, -1, LUA_TTABLE);
- lua_pushnumber(L, id);
+ lua_pushinteger(L, id);
lua_gettable(L, -2);
lua_remove(L, -2); // Remove luaentities
lua_remove(L, -2); // Remove core
@@ -1672,10 +1695,10 @@ static bool push_json_value_helper(lua_State *L, const Json::Value &value,
lua_pushvalue(L, nullindex);
break;
case Json::intValue:
- lua_pushinteger(L, value.asInt());
+ lua_pushinteger(L, value.asLargestInt());
break;
case Json::uintValue:
- lua_pushinteger(L, value.asUInt());
+ lua_pushinteger(L, value.asLargestUInt());
break;
case Json::realValue:
lua_pushnumber(L, value.asDouble());
@@ -1690,7 +1713,7 @@ static bool push_json_value_helper(lua_State *L, const Json::Value &value,
lua_pushboolean(L, value.asInt());
break;
case Json::arrayValue:
- lua_newtable(L);
+ lua_createtable(L, value.size(), 0);
for (Json::Value::const_iterator it = value.begin();
it != value.end(); ++it) {
push_json_value_helper(L, *it, nullindex);
@@ -1698,10 +1721,10 @@ static bool push_json_value_helper(lua_State *L, const Json::Value &value,
}
break;
case Json::objectValue:
- lua_newtable(L);
+ lua_createtable(L, 0, value.size());
for (Json::Value::const_iterator it = value.begin();
it != value.end(); ++it) {
-#ifndef JSONCPP_STRING
+#if !defined(JSONCPP_STRING) && (JSONCPP_VERSION_MAJOR < 1 || JSONCPP_VERSION_MINOR < 9)
const char *str = it.memberName();
lua_pushstring(L, str ? str : "");
#else
@@ -1825,7 +1848,7 @@ void push_objectRef(lua_State *L, const u16 id)
lua_getglobal(L, "core");
lua_getfield(L, -1, "object_refs");
luaL_checktype(L, -1, LUA_TTABLE);
- lua_pushnumber(L, id);
+ lua_pushinteger(L, id);
lua_gettable(L, -2);
lua_remove(L, -2); // object_refs
lua_remove(L, -2); // core
@@ -1851,10 +1874,15 @@ void read_hud_element(lua_State *L, HudElement *elem)
elem->name = getstringfield_default(L, 2, "name", "");
elem->text = getstringfield_default(L, 2, "text", "");
elem->number = getintfield_default(L, 2, "number", 0);
- elem->item = getintfield_default(L, 2, "item", 0);
+ if (elem->type == HUD_ELEM_WAYPOINT)
+ // waypoints reuse the item field to store precision, item = precision + 1
+ elem->item = getintfield_default(L, 2, "precision", -1) + 1;
+ else
+ elem->item = getintfield_default(L, 2, "item", 0);
elem->dir = getintfield_default(L, 2, "direction", 0);
elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX,
getintfield_default(L, 2, "z_index", 0)));
+ elem->text2 = getstringfield_default(L, 2, "text2", "");
// Deprecated, only for compatibility's sake
if (elem->dir == 0)
@@ -1923,6 +1951,9 @@ void push_hud_element(lua_State *L, HudElement *elem)
lua_pushnumber(L, elem->z_index);
lua_setfield(L, -2, "z_index");
+
+ lua_pushstring(L, elem->text2.c_str());
+ lua_setfield(L, -2, "text2");
}
HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value)
@@ -1984,6 +2015,66 @@ HudElementStat read_hud_change(lua_State *L, HudElement *elem, void **value)
elem->z_index = MYMAX(S16_MIN, MYMIN(S16_MAX, luaL_checknumber(L, 4)));
*value = &elem->z_index;
break;
+ case HUD_STAT_TEXT2:
+ elem->text2 = luaL_checkstring(L, 4);
+ *value = &elem->text2;
+ break;
}
return stat;
}
+
+/******************************************************************************/
+
+// Indices must match values in `enum CollisionType` exactly!!
+static const char *collision_type_str[] = {
+ "node",
+ "object",
+};
+
+// Indices must match values in `enum CollisionAxis` exactly!!
+static const char *collision_axis_str[] = {
+ "x",
+ "y",
+ "z",
+};
+
+void push_collision_move_result(lua_State *L, const collisionMoveResult &res)
+{
+ lua_createtable(L, 0, 4);
+
+ setboolfield(L, -1, "touching_ground", res.touching_ground);
+ setboolfield(L, -1, "collides", res.collides);
+ setboolfield(L, -1, "standing_on_object", res.standing_on_object);
+
+ /* collisions */
+ lua_createtable(L, res.collisions.size(), 0);
+ int i = 1;
+ for (const auto &c : res.collisions) {
+ lua_createtable(L, 0, 5);
+
+ lua_pushstring(L, collision_type_str[c.type]);
+ lua_setfield(L, -2, "type");
+
+ assert(c.axis != COLLISION_AXIS_NONE);
+ lua_pushstring(L, collision_axis_str[c.axis]);
+ lua_setfield(L, -2, "axis");
+
+ if (c.type == COLLISION_NODE) {
+ push_v3s16(L, c.node_p);
+ lua_setfield(L, -2, "node_pos");
+ } else if (c.type == COLLISION_OBJECT) {
+ push_objectRef(L, c.object->getId());
+ lua_setfield(L, -2, "object");
+ }
+
+ push_v3f(L, c.old_speed / BS);
+ lua_setfield(L, -2, "old_velocity");
+
+ push_v3f(L, c.new_speed / BS);
+ lua_setfield(L, -2, "new_velocity");
+
+ lua_rawseti(L, -2, i++);
+ }
+ lua_setfield(L, -2, "collisions");
+ /**/
+}
diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h
index 9e755682f..8f32e58eb 100644
--- a/src/script/common/c_content.h
+++ b/src/script/common/c_content.h
@@ -63,7 +63,9 @@ struct EnumString;
struct NoiseParams;
class Schematic;
class ServerActiveObject;
+struct collisionMoveResult;
+extern struct EnumString es_TileAnimationType[];
ContentFeatures read_content_features (lua_State *L, int index);
void push_content_features (lua_State *L,
@@ -196,4 +198,4 @@ void push_hud_element (lua_State *L, HudElement *elem);
HudElementStat read_hud_change (lua_State *L, HudElement *elem, void **value);
-extern struct EnumString es_TileAnimationType[];
+void push_collision_move_result(lua_State *L, const collisionMoveResult &res);
diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp
index 3c2f75641..eb6ab5331 100644
--- a/src/script/common/c_converter.cpp
+++ b/src/script/common/c_converter.cpp
@@ -28,15 +28,15 @@ extern "C" {
#include "common/c_converter.h"
#include "common/c_internal.h"
#include "constants.h"
+#include <set>
#define CHECK_TYPE(index, name, type) { \
int t = lua_type(L, (index)); \
if (t != (type)) { \
- std::string traceback = script_get_backtrace(L); \
throw LuaError(std::string("Invalid ") + (name) + \
" (expected " + lua_typename(L, (type)) + \
- " got " + lua_typename(L, t) + ").\n" + traceback); \
+ " got " + lua_typename(L, t) + ")."); \
} \
}
#define CHECK_POS_COORD(name) CHECK_TYPE(-1, "position coordinate '" name "'", LUA_TNUMBER)
@@ -62,7 +62,7 @@ void push_float_string(lua_State *L, float value)
void push_v3f(lua_State *L, v3f p)
{
- lua_newtable(L);
+ lua_createtable(L, 0, 3);
lua_pushnumber(L, p.X);
lua_setfield(L, -2, "x");
lua_pushnumber(L, p.Y);
@@ -73,7 +73,7 @@ void push_v3f(lua_State *L, v3f p)
void push_v2f(lua_State *L, v2f p)
{
- lua_newtable(L);
+ lua_createtable(L, 0, 2);
lua_pushnumber(L, p.X);
lua_setfield(L, -2, "x");
lua_pushnumber(L, p.Y);
@@ -82,7 +82,7 @@ void push_v2f(lua_State *L, v2f p)
void push_v3_float_string(lua_State *L, v3f p)
{
- lua_newtable(L);
+ lua_createtable(L, 0, 3);
push_float_string(L, p.X);
lua_setfield(L, -2, "x");
push_float_string(L, p.Y);
@@ -93,7 +93,7 @@ void push_v3_float_string(lua_State *L, v3f p)
void push_v2_float_string(lua_State *L, v2f p)
{
- lua_newtable(L);
+ lua_createtable(L, 0, 2);
push_float_string(L, p.X);
lua_setfield(L, -2, "x");
push_float_string(L, p.Y);
@@ -115,19 +115,19 @@ v2s16 read_v2s16(lua_State *L, int index)
void push_v2s16(lua_State *L, v2s16 p)
{
- lua_newtable(L);
- lua_pushnumber(L, p.X);
+ lua_createtable(L, 0, 2);
+ lua_pushinteger(L, p.X);
lua_setfield(L, -2, "x");
- lua_pushnumber(L, p.Y);
+ lua_pushinteger(L, p.Y);
lua_setfield(L, -2, "y");
}
void push_v2s32(lua_State *L, v2s32 p)
{
- lua_newtable(L);
- lua_pushnumber(L, p.X);
+ lua_createtable(L, 0, 2);
+ lua_pushinteger(L, p.X);
lua_setfield(L, -2, "x");
- lua_pushnumber(L, p.Y);
+ lua_pushinteger(L, p.Y);
lua_setfield(L, -2, "y");
}
@@ -250,14 +250,14 @@ v3d check_v3d(lua_State *L, int index)
void push_ARGB8(lua_State *L, video::SColor color)
{
- lua_newtable(L);
- lua_pushnumber(L, color.getAlpha());
+ lua_createtable(L, 0, 4);
+ lua_pushinteger(L, color.getAlpha());
lua_setfield(L, -2, "a");
- lua_pushnumber(L, color.getRed());
+ lua_pushinteger(L, color.getRed());
lua_setfield(L, -2, "r");
- lua_pushnumber(L, color.getGreen());
+ lua_pushinteger(L, color.getGreen());
lua_setfield(L, -2, "g");
- lua_pushnumber(L, color.getBlue());
+ lua_pushinteger(L, color.getBlue());
lua_setfield(L, -2, "b");
}
@@ -274,12 +274,12 @@ v3f checkFloatPos(lua_State *L, int index)
void push_v3s16(lua_State *L, v3s16 p)
{
- lua_newtable(L);
- lua_pushnumber(L, p.X);
+ lua_createtable(L, 0, 3);
+ lua_pushinteger(L, p.X);
lua_setfield(L, -2, "x");
- lua_pushnumber(L, p.Y);
+ lua_pushinteger(L, p.Y);
lua_setfield(L, -2, "y");
- lua_pushnumber(L, p.Z);
+ lua_pushinteger(L, p.Z);
lua_setfield(L, -2, "z");
}
@@ -386,7 +386,7 @@ aabb3f read_aabb3f(lua_State *L, int index, f32 scale)
void push_aabb3f(lua_State *L, aabb3f box)
{
- lua_newtable(L);
+ lua_createtable(L, 6, 0);
lua_pushnumber(L, box.MinEdge.X);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, box.MinEdge.Y);
@@ -457,12 +457,55 @@ size_t read_stringlist(lua_State *L, int index, std::vector<std::string> *result
Table field getters
*/
+#if defined(__MINGW32__) && !defined(__MINGW64__)
+/* MinGW 32-bit somehow crashes in the std::set destructor when this
+ * variable is thread-local, so just don't do that. */
+static std::set<u64> warned_msgs;
+#endif
+
+bool check_field_or_nil(lua_State *L, int index, int type, const char *fieldname)
+{
+#if !defined(__MINGW32__) || defined(__MINGW64__)
+ thread_local std::set<u64> warned_msgs;
+#endif
+
+ int t = lua_type(L, index);
+ if (t == LUA_TNIL)
+ return false;
+
+ if (t == type)
+ return true;
+
+ // Check coercion types
+ if (type == LUA_TNUMBER) {
+ if (lua_isnumber(L, index))
+ return true;
+ } else if (type == LUA_TSTRING) {
+ if (lua_isstring(L, index))
+ return true;
+ }
+
+ // Types mismatch. Log unique line.
+ std::string backtrace = std::string("Invalid field ") + fieldname +
+ " (expected " + lua_typename(L, type) +
+ " got " + lua_typename(L, t) + ").\n" + script_get_backtrace(L);
+
+ u64 hash = murmur_hash_64_ua(backtrace.data(), backtrace.length(), 0xBADBABE);
+ if (warned_msgs.find(hash) == warned_msgs.end()) {
+ errorstream << backtrace << std::endl;
+ warned_msgs.insert(hash);
+ }
+
+ return false;
+}
+
bool getstringfield(lua_State *L, int table,
const char *fieldname, std::string &result)
{
lua_getfield(L, table, fieldname);
bool got = false;
- if(lua_isstring(L, -1)){
+
+ if (check_field_or_nil(L, -1, LUA_TSTRING, fieldname)) {
size_t len = 0;
const char *ptr = lua_tolstring(L, -1, &len);
if (ptr) {
@@ -479,7 +522,8 @@ bool getfloatfield(lua_State *L, int table,
{
lua_getfield(L, table, fieldname);
bool got = false;
- if(lua_isnumber(L, -1)){
+
+ if (check_field_or_nil(L, -1, LUA_TNUMBER, fieldname)) {
result = lua_tonumber(L, -1);
got = true;
}
@@ -492,7 +536,8 @@ bool getboolfield(lua_State *L, int table,
{
lua_getfield(L, table, fieldname);
bool got = false;
- if(lua_isboolean(L, -1)){
+
+ if (check_field_or_nil(L, -1, LUA_TBOOLEAN, fieldname)){
result = lua_toboolean(L, -1);
got = true;
}
@@ -511,17 +556,6 @@ size_t getstringlistfield(lua_State *L, int table, const char *fieldname,
return num_strings_read;
}
-std::string checkstringfield(lua_State *L, int table,
- const char *fieldname)
-{
- lua_getfield(L, table, fieldname);
- CHECK_TYPE(-1, std::string("field \"") + fieldname + '"', LUA_TSTRING);
- size_t len;
- const char *s = lua_tolstring(L, -1, &len);
- lua_pop(L, 1);
- return std::string(s, len);
-}
-
std::string getstringfield_default(lua_State *L, int table,
const char *fieldname, const std::string &default_)
{
diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h
index 9620bf75a..a4a7079fd 100644
--- a/src/script/common/c_converter.h
+++ b/src/script/common/c_converter.h
@@ -45,13 +45,15 @@ float getfloatfield_default(lua_State *L, int table,
int getintfield_default(lua_State *L, int table,
const char *fieldname, int default_);
+bool check_field_or_nil(lua_State *L, int index, int type, const char *fieldname);
+
template<typename T>
bool getintfield(lua_State *L, int table,
const char *fieldname, T &result)
{
lua_getfield(L, table, fieldname);
bool got = false;
- if (lua_isnumber(L, -1)){
+ if (check_field_or_nil(L, -1, LUA_TNUMBER, fieldname)){
result = lua_tointeger(L, -1);
got = true;
}
@@ -87,8 +89,6 @@ bool getboolfield(lua_State *L, int table,
const char *fieldname, bool &result);
bool getfloatfield(lua_State *L, int table,
const char *fieldname, float &result);
-std::string checkstringfield(lua_State *L, int table,
- const char *fieldname);
void setstringfield(lua_State *L, int table,
const char *fieldname, const std::string &value);
diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp
index b19af9f82..6df1f8b7b 100644
--- a/src/script/common/c_internal.cpp
+++ b/src/script/common/c_internal.cpp
@@ -157,9 +157,9 @@ static void script_log(lua_State *L, const std::string &message,
void log_deprecated(lua_State *L, const std::string &message, int stack_depth)
{
- static bool configured = false;
- static bool do_log = false;
- static bool do_error = false;
+ static thread_local bool configured = false;
+ static thread_local bool do_log = false;
+ static thread_local bool do_error = false;
// Only read settings on first call
if (!configured) {
@@ -167,9 +167,10 @@ void log_deprecated(lua_State *L, const std::string &message, int stack_depth)
if (value == "log") {
do_log = true;
} else if (value == "error") {
- do_log = true;
+ do_log = true;
do_error = true;
}
+ configured = true;
}
if (do_log)
diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h
index d8cf3fe76..442546332 100644
--- a/src/script/common/c_internal.h
+++ b/src/script/common/c_internal.h
@@ -31,6 +31,7 @@ extern "C" {
#include <lauxlib.h>
}
+#include "config.h"
#include "common/c_types.h"
@@ -54,6 +55,15 @@ extern "C" {
#define CUSTOM_RIDX_CURRENT_MOD_NAME (CUSTOM_RIDX_BASE + 2)
#define CUSTOM_RIDX_BACKTRACE (CUSTOM_RIDX_BASE + 3)
+// Determine if CUSTOM_RIDX_SCRIPTAPI will hold a light or full userdata
+#if defined(__aarch64__) && USE_LUAJIT
+/* LuaJIT has a 47-bit limit for lightuserdata on this platform and we cannot
+ * assume that the ScriptApi class was allocated at a fitting address. */
+#define INDIRECT_SCRIPTAPI_RIDX 1
+#else
+#define INDIRECT_SCRIPTAPI_RIDX 0
+#endif
+
// Pushes the error handler onto the stack and returns its index
#define PUSH_ERROR_HANDLER(L) \
(lua_rawgeti((L), LUA_REGISTRYINDEX, CUSTOM_RIDX_BACKTRACE), lua_gettop((L)))
diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp
index ecb1ba39b..f965975a3 100644
--- a/src/script/cpp_api/s_base.cpp
+++ b/src/script/cpp_api/s_base.cpp
@@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "cpp_api/s_security.h"
#include "lua_api/l_object.h"
#include "common/c_converter.h"
-#include "serverobject.h"
+#include "server/player_sao.h"
#include "filesys.h"
#include "content/mods.h"
#include "porting.h"
@@ -43,7 +43,6 @@ extern "C" {
#include <cstdio>
#include <cstdarg>
#include "script/common/c_content.h"
-#include "content_sao.h"
#include <sstream>
@@ -90,7 +89,11 @@ ScriptApiBase::ScriptApiBase(ScriptingType type):
luaL_openlibs(m_luastack);
// Make the ScriptApiBase* accessible to ModApiBase
+#if INDIRECT_SCRIPTAPI_RIDX
+ *(void **)(lua_newuserdata(m_luastack, sizeof(void *))) = this;
+#else
lua_pushlightuserdata(m_luastack, this);
+#endif
lua_rawseti(m_luastack, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI);
// Add and save an error handler
@@ -184,7 +187,9 @@ void ScriptApiBase::loadScript(const std::string &script_path)
}
ok = ok && !lua_pcall(L, 0, 0, error_handler);
if (!ok) {
- std::string error_msg = readParam<std::string>(L, -1);
+ const char *error_msg = lua_tostring(L, -1);
+ if (!error_msg)
+ error_msg = "(error object is not a string)";
lua_pop(L, 2); // Pop error message and error handler
throw ModError("Failed to load and run script from " +
script_path + ":\n" + error_msg);
@@ -216,7 +221,9 @@ void ScriptApiBase::loadModFromMemory(const std::string &mod_name)
if (ok)
ok = !lua_pcall(L, 0, 0, error_handler);
if (!ok) {
- std::string error_msg = luaL_checkstring(L, -1);
+ const char *error_msg = lua_tostring(L, -1);
+ if (!error_msg)
+ error_msg = "(error object is not a string)";
lua_pop(L, 2); // Pop error message and error handler
throw ModError("Failed to load and run mod \"" +
mod_name + "\":\n" + error_msg);
diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h
index 697e5f556..86f7f7bac 100644
--- a/src/script/cpp_api/s_base.h
+++ b/src/script/cpp_api/s_base.h
@@ -136,8 +136,10 @@ protected:
Environment* getEnv() { return m_environment; }
void setEnv(Environment* env) { m_environment = env; }
+#ifndef SERVER
GUIEngine* getGuiEngine() { return m_guiengine; }
void setGuiEngine(GUIEngine* guiengine) { m_guiengine = guiengine; }
+#endif
void objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj);
@@ -158,6 +160,8 @@ private:
IGameDef *m_gamedef = nullptr;
Environment *m_environment = nullptr;
+#ifndef SERVER
GUIEngine *m_guiengine = nullptr;
+#endif
ScriptingType m_type;
};
diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp
index 26c7e8cd4..ea9320051 100644
--- a/src/script/cpp_api/s_entity.cpp
+++ b/src/script/cpp_api/s_entity.cpp
@@ -178,12 +178,11 @@ void ScriptApiEntity::luaentity_GetProperties(u16 id,
lua_pop(L, 1);
}
-void ScriptApiEntity::luaentity_Step(u16 id, float dtime)
+void ScriptApiEntity::luaentity_Step(u16 id, float dtime,
+ const collisionMoveResult *moveresult)
{
SCRIPTAPI_PRECHECKHEADER
- //infostream<<"scriptapi_luaentity_step: id="<<id<<std::endl;
-
int error_handler = PUSH_ERROR_HANDLER(L);
// Get core.luaentities[id]
@@ -199,9 +198,14 @@ void ScriptApiEntity::luaentity_Step(u16 id, float dtime)
luaL_checktype(L, -1, LUA_TFUNCTION);
lua_pushvalue(L, object); // self
lua_pushnumber(L, dtime); // dtime
+ /* moveresult */
+ if (moveresult)
+ push_collision_move_result(L, *moveresult);
+ else
+ lua_pushnil(L);
setOriginFromTable(object);
- PCALL_RES(lua_pcall(L, 2, 0, error_handler));
+ PCALL_RES(lua_pcall(L, 3, 0, error_handler));
lua_pop(L, 2); // Pop object and error handler
}
diff --git a/src/script/cpp_api/s_entity.h b/src/script/cpp_api/s_entity.h
index cc08c46e8..b5f7a6586 100644
--- a/src/script/cpp_api/s_entity.h
+++ b/src/script/cpp_api/s_entity.h
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
struct ObjectProperties;
struct ToolCapabilities;
+struct collisionMoveResult;
class ScriptApiEntity
: virtual public ScriptApiBase
@@ -36,7 +37,8 @@ public:
std::string luaentity_GetStaticdata(u16 id);
void luaentity_GetProperties(u16 id,
ServerActiveObject *self, ObjectProperties *prop);
- void luaentity_Step(u16 id, float dtime);
+ void luaentity_Step(u16 id, float dtime,
+ const collisionMoveResult *moveresult);
bool luaentity_Punch(u16 id,
ServerActiveObject *puncher, float time_from_last_punch,
const ToolCapabilities *toolcap, v3f dir, s16 damage);
diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp
index ab3b5fe46..8da5debaa 100644
--- a/src/script/cpp_api/s_env.cpp
+++ b/src/script/cpp_api/s_env.cpp
@@ -86,7 +86,7 @@ void ScriptApiEnv::player_event(ServerActiveObject *player, const std::string &t
void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env)
{
SCRIPTAPI_PRECHECKHEADER
- verbosestream << "scriptapi_add_environment" << std::endl;
+ verbosestream << "ScriptApiEnv: Environment initialized" << std::endl;
setEnv(env);
/*
diff --git a/src/script/cpp_api/s_node.cpp b/src/script/cpp_api/s_node.cpp
index d93a4c3ad..e0f9bcd78 100644
--- a/src/script/cpp_api/s_node.cpp
+++ b/src/script/cpp_api/s_node.cpp
@@ -94,7 +94,7 @@ struct EnumString ScriptApiNode::es_NodeBoxType[] =
};
bool ScriptApiNode::node_on_punch(v3s16 p, MapNode node,
- ServerActiveObject *puncher, PointedThing pointed)
+ ServerActiveObject *puncher, const PointedThing &pointed)
{
SCRIPTAPI_PRECHECKHEADER
diff --git a/src/script/cpp_api/s_node.h b/src/script/cpp_api/s_node.h
index e7c0c01d1..81b44f0f0 100644
--- a/src/script/cpp_api/s_node.h
+++ b/src/script/cpp_api/s_node.h
@@ -36,7 +36,7 @@ public:
virtual ~ScriptApiNode() = default;
bool node_on_punch(v3s16 p, MapNode node,
- ServerActiveObject *puncher, PointedThing pointed);
+ ServerActiveObject *puncher, const PointedThing &pointed);
bool node_on_dig(v3s16 p, MapNode node,
ServerActiveObject *digger);
void node_on_construct(v3s16 p, MapNode node);
diff --git a/src/script/cpp_api/s_player.cpp b/src/script/cpp_api/s_player.cpp
index df67ea00c..712120c61 100644
--- a/src/script/cpp_api/s_player.cpp
+++ b/src/script/cpp_api/s_player.cpp
@@ -147,7 +147,7 @@ bool ScriptApiPlayer::can_bypass_userlimit(const std::string &name, const std::s
return lua_toboolean(L, -1);
}
-void ScriptApiPlayer::on_joinplayer(ServerActiveObject *player)
+void ScriptApiPlayer::on_joinplayer(ServerActiveObject *player, s64 last_login)
{
SCRIPTAPI_PRECHECKHEADER
@@ -156,7 +156,11 @@ void ScriptApiPlayer::on_joinplayer(ServerActiveObject *player)
lua_getfield(L, -1, "registered_on_joinplayers");
// Call callbacks
objectrefGetOrCreate(L, player);
- runCallbacks(1, RUN_CALLBACKS_MODE_FIRST);
+ if (last_login != -1)
+ lua_pushinteger(L, last_login);
+ else
+ lua_pushnil(L);
+ runCallbacks(2, RUN_CALLBACKS_MODE_FIRST);
}
void ScriptApiPlayer::on_leaveplayer(ServerActiveObject *player,
@@ -216,16 +220,19 @@ void ScriptApiPlayer::on_playerReceiveFields(ServerActiveObject *player,
runCallbacks(3, RUN_CALLBACKS_MODE_OR_SC);
}
-void ScriptApiPlayer::on_auth_failure(const std::string &name, const std::string &ip)
+void ScriptApiPlayer::on_authplayer(const std::string &name, const std::string &ip, bool is_success)
{
SCRIPTAPI_PRECHECKHEADER
- // Get core.registered_on_auth_failure
+ // Get core.registered_on_authplayers
lua_getglobal(L, "core");
- lua_getfield(L, -1, "registered_on_auth_fail");
+ lua_getfield(L, -1, "registered_on_authplayers");
+
+ // Call callbacks
lua_pushstring(L, name.c_str());
lua_pushstring(L, ip.c_str());
- runCallbacks(2, RUN_CALLBACKS_MODE_FIRST);
+ lua_pushboolean(L, is_success);
+ runCallbacks(3, RUN_CALLBACKS_MODE_FIRST);
}
void ScriptApiPlayer::pushMoveArguments(
diff --git a/src/script/cpp_api/s_player.h b/src/script/cpp_api/s_player.h
index cf24ddc73..a337f975b 100644
--- a/src/script/cpp_api/s_player.h
+++ b/src/script/cpp_api/s_player.h
@@ -28,6 +28,7 @@ struct InventoryLocation;
struct ItemStack;
struct ToolCapabilities;
struct PlayerHPChangeReason;
+class ServerActiveObject;
class ScriptApiPlayer : virtual public ScriptApiBase
{
@@ -40,7 +41,7 @@ public:
bool on_prejoinplayer(const std::string &name, const std::string &ip,
std::string *reason);
bool can_bypass_userlimit(const std::string &name, const std::string &ip);
- void on_joinplayer(ServerActiveObject *player);
+ void on_joinplayer(ServerActiveObject *player, s64 last_login);
void on_leaveplayer(ServerActiveObject *player, bool timeout);
void on_cheat(ServerActiveObject *player, const std::string &cheat_type);
bool on_punchplayer(ServerActiveObject *player, ServerActiveObject *hitter,
@@ -50,7 +51,7 @@ public:
const PlayerHPChangeReason &reason);
void on_playerReceiveFields(ServerActiveObject *player,
const std::string &formname, const StringMap &fields);
- void on_auth_failure(const std::string &name, const std::string &ip);
+ void on_authplayer(const std::string &name, const std::string &ip, bool is_success);
// Player inventory callbacks
// Return number of accepted items to be moved
diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp
index b5abcfb5d..2afa3a191 100644
--- a/src/script/cpp_api/s_security.cpp
+++ b/src/script/cpp_api/s_security.cpp
@@ -499,7 +499,12 @@ bool ScriptApiSecurity::checkPath(lua_State *L, const char *path,
// Get server from registry
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI);
- ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1);
+ ScriptApiBase *script;
+#if INDIRECT_SCRIPTAPI_RIDX
+ script = (ScriptApiBase *) *(void**)(lua_touserdata(L, -1));
+#else
+ script = (ScriptApiBase *) lua_touserdata(L, -1);
+#endif
lua_pop(L, 1);
const IGameDef *gamedef = script->getGameDef();
if (!gamedef)
diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp
index 1ce2f9d45..96cb28b28 100644
--- a/src/script/cpp_api/s_server.cpp
+++ b/src/script/cpp_api/s_server.cpp
@@ -23,7 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
bool ScriptApiServer::getAuth(const std::string &playername,
std::string *dst_password,
- std::set<std::string> *dst_privs)
+ std::set<std::string> *dst_privs,
+ s64 *dst_last_login)
{
SCRIPTAPI_PRECHECKHEADER
@@ -43,8 +44,7 @@ bool ScriptApiServer::getAuth(const std::string &playername,
luaL_checktype(L, -1, LUA_TTABLE);
std::string password;
- bool found = getstringfield(L, -1, "password", password);
- if (!found)
+ if (!getstringfield(L, -1, "password", password))
throw LuaError("Authentication handler didn't return password");
if (dst_password)
*dst_password = password;
@@ -54,7 +54,13 @@ bool ScriptApiServer::getAuth(const std::string &playername,
throw LuaError("Authentication handler didn't return privilege table");
if (dst_privs)
readPrivileges(-1, *dst_privs);
- lua_pop(L, 1);
+ lua_pop(L, 1); // Remove key from privs table
+
+ s64 last_login;
+ if(!getintfield(L, -1, "last_login", last_login))
+ throw LuaError("Authentication handler didn't return last_login");
+ if (dst_last_login)
+ *dst_last_login = (s64)last_login;
return true;
}
diff --git a/src/script/cpp_api/s_server.h b/src/script/cpp_api/s_server.h
index a4cede84d..d8639cba7 100644
--- a/src/script/cpp_api/s_server.h
+++ b/src/script/cpp_api/s_server.h
@@ -43,7 +43,8 @@ public:
/* auth */
bool getAuth(const std::string &playername,
std::string *dst_password,
- std::set<std::string> *dst_privs);
+ std::set<std::string> *dst_privs,
+ s64 *dst_last_login = nullptr);
void createAuth(const std::string &playername,
const std::string &password);
bool setPassword(const std::string &playername,
diff --git a/src/script/lua_api/l_base.cpp b/src/script/lua_api/l_base.cpp
index 8486fc7bc..2bee09436 100644
--- a/src/script/lua_api/l_base.cpp
+++ b/src/script/lua_api/l_base.cpp
@@ -30,7 +30,12 @@ ScriptApiBase *ModApiBase::getScriptApiBase(lua_State *L)
{
// Get server from registry
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_SCRIPTAPI);
- ScriptApiBase *sapi_ptr = (ScriptApiBase*) lua_touserdata(L, -1);
+ ScriptApiBase *sapi_ptr;
+#if INDIRECT_SCRIPTAPI_RIDX
+ sapi_ptr = (ScriptApiBase*) *(void**)(lua_touserdata(L, -1));
+#else
+ sapi_ptr = (ScriptApiBase*) lua_touserdata(L, -1);
+#endif
lua_pop(L, 1);
return sapi_ptr;
}
@@ -40,6 +45,11 @@ Server *ModApiBase::getServer(lua_State *L)
return getScriptApiBase(L)->getServer();
}
+ServerInventoryManager *ModApiBase::getServerInventoryMgr(lua_State *L)
+{
+ return getScriptApiBase(L)->getServer()->getInventoryMgr();
+}
+
#ifndef SERVER
Client *ModApiBase::getClient(lua_State *L)
{
@@ -57,10 +67,12 @@ Environment *ModApiBase::getEnv(lua_State *L)
return getScriptApiBase(L)->getEnv();
}
+#ifndef SERVER
GUIEngine *ModApiBase::getGuiEngine(lua_State *L)
{
return getScriptApiBase(L)->getGuiEngine();
}
+#endif
std::string ModApiBase::getCurrentModPath(lua_State *L)
{
diff --git a/src/script/lua_api/l_base.h b/src/script/lua_api/l_base.h
index b46b5b567..65fce8481 100644
--- a/src/script/lua_api/l_base.h
+++ b/src/script/lua_api/l_base.h
@@ -32,26 +32,29 @@ extern "C" {
#ifndef SERVER
class Client;
+class GUIEngine;
#endif
class ScriptApiBase;
class Server;
class Environment;
-class GUIEngine;
+class ServerInventoryManager;
class ModApiBase : protected LuaHelper {
public:
static ScriptApiBase* getScriptApiBase(lua_State *L);
static Server* getServer(lua_State *L);
+ static ServerInventoryManager *getServerInventoryMgr(lua_State *L);
#ifndef SERVER
static Client* getClient(lua_State *L);
+ static GUIEngine* getGuiEngine(lua_State *L);
#endif // !SERVER
static IGameDef* getGameDef(lua_State *L);
static Environment* getEnv(lua_State *L);
- static GUIEngine* getGuiEngine(lua_State *L);
+
// When we are not loading the mod, this function returns "."
static std::string getCurrentModPath(lua_State *L);
diff --git a/src/script/lua_api/l_camera.cpp b/src/script/lua_api/l_camera.cpp
index 9c1470284..bfa60be67 100644
--- a/src/script/lua_api/l_camera.cpp
+++ b/src/script/lua_api/l_camera.cpp
@@ -51,6 +51,7 @@ void LuaCamera::create(lua_State *L, Camera *m)
lua_setfield(L, objectstable, "camera");
}
+// set_camera_mode(self, mode)
int LuaCamera::l_set_camera_mode(lua_State *L)
{
Camera *camera = getobject(L, 1);
@@ -67,17 +68,19 @@ int LuaCamera::l_set_camera_mode(lua_State *L)
return 0;
}
+// get_camera_mode(self)
int LuaCamera::l_get_camera_mode(lua_State *L)
{
Camera *camera = getobject(L, 1);
if (!camera)
return 0;
- lua_pushnumber(L, (int)camera->getCameraMode());
+ lua_pushinteger(L, (int)camera->getCameraMode());
return 1;
}
+// get_fov(self)
int LuaCamera::l_get_fov(lua_State *L)
{
Camera *camera = getobject(L, 1);
@@ -85,9 +88,9 @@ int LuaCamera::l_get_fov(lua_State *L)
return 0;
lua_newtable(L);
- lua_pushnumber(L, camera->getFovX() * core::DEGTORAD);
+ lua_pushnumber(L, camera->getFovX() * core::RADTODEG);
lua_setfield(L, -2, "x");
- lua_pushnumber(L, camera->getFovY() * core::DEGTORAD);
+ lua_pushnumber(L, camera->getFovY() * core::RADTODEG);
lua_setfield(L, -2, "y");
lua_pushnumber(L, camera->getCameraNode()->getFOV() * core::RADTODEG);
lua_setfield(L, -2, "actual");
@@ -96,16 +99,18 @@ int LuaCamera::l_get_fov(lua_State *L)
return 1;
}
+// get_pos(self)
int LuaCamera::l_get_pos(lua_State *L)
{
Camera *camera = getobject(L, 1);
if (!camera)
return 0;
- push_v3f(L, camera->getPosition());
+ push_v3f(L, camera->getPosition() / BS);
return 1;
}
+// get_offset(self)
int LuaCamera::l_get_offset(lua_State *L)
{
LocalPlayer *player = getClient(L)->getEnv().getLocalPlayer();
@@ -115,38 +120,40 @@ int LuaCamera::l_get_offset(lua_State *L)
return 1;
}
+// get_look_dir(self)
int LuaCamera::l_get_look_dir(lua_State *L)
{
- LocalPlayer *player = getClient(L)->getEnv().getLocalPlayer();
- sanity_check(player);
-
- float pitch = -1.0 * player->getPitch() * core::DEGTORAD;
- float yaw = (player->getYaw() + 90.) * core::DEGTORAD;
- v3f v(std::cos(pitch) * std::cos(yaw), std::sin(pitch),
- std::cos(pitch) * std::sin(yaw));
+ Camera *camera = getobject(L, 1);
+ if (!camera)
+ return 0;
- push_v3f(L, v);
+ push_v3f(L, camera->getDirection());
return 1;
}
+// get_look_horizontal(self)
+// FIXME: wouldn't localplayer be a better place for this?
int LuaCamera::l_get_look_horizontal(lua_State *L)
{
LocalPlayer *player = getClient(L)->getEnv().getLocalPlayer();
sanity_check(player);
- lua_pushnumber(L, (player->getYaw() + 90.) * core::DEGTORAD);
+ lua_pushnumber(L, (player->getYaw() + 90.f) * core::DEGTORAD);
return 1;
}
+// get_look_vertical(self)
+// FIXME: wouldn't localplayer be a better place for this?
int LuaCamera::l_get_look_vertical(lua_State *L)
{
LocalPlayer *player = getClient(L)->getEnv().getLocalPlayer();
sanity_check(player);
- lua_pushnumber(L, -1.0 * player->getPitch() * core::DEGTORAD);
+ lua_pushnumber(L, -1.0f * player->getPitch() * core::DEGTORAD);
return 1;
}
+// get_aspect_ratio(self)
int LuaCamera::l_get_aspect_ratio(lua_State *L)
{
Camera *camera = getobject(L, 1);
@@ -215,13 +222,19 @@ void LuaCamera::Register(lua_State *L)
lua_pop(L, 1);
}
+// clang-format off
const char LuaCamera::className[] = "Camera";
-const luaL_Reg LuaCamera::methods[] = {luamethod(LuaCamera, set_camera_mode),
- luamethod(LuaCamera, get_camera_mode), luamethod(LuaCamera, get_fov),
- luamethod(LuaCamera, get_pos), luamethod(LuaCamera, get_offset),
- luamethod(LuaCamera, get_look_dir),
- luamethod(LuaCamera, get_look_vertical),
- luamethod(LuaCamera, get_look_horizontal),
- luamethod(LuaCamera, get_aspect_ratio),
-
- {0, 0}};
+const luaL_Reg LuaCamera::methods[] = {
+ luamethod(LuaCamera, set_camera_mode),
+ luamethod(LuaCamera, get_camera_mode),
+ luamethod(LuaCamera, get_fov),
+ luamethod(LuaCamera, get_pos),
+ luamethod(LuaCamera, get_offset),
+ luamethod(LuaCamera, get_look_dir),
+ luamethod(LuaCamera, get_look_vertical),
+ luamethod(LuaCamera, get_look_horizontal),
+ luamethod(LuaCamera, get_aspect_ratio),
+
+ {0, 0}
+};
+// clang-format on
diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp
index fba182492..aaced7cd0 100644
--- a/src/script/lua_api/l_client.cpp
+++ b/src/script/lua_api/l_client.cpp
@@ -29,7 +29,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "cpp_api/s_base.h"
#include "gettext.h"
#include "l_internal.h"
-#include "lua_api/l_item.h"
#include "lua_api/l_nodemeta.h"
#include "gui/mainmenumanager.h"
#include "map.h"
@@ -209,7 +208,7 @@ int ModApiClient::l_gettext(lua_State *L)
return 1;
}
-// get_node(pos)
+// get_node_or_nil(pos)
// pos = {x=num, y=num, z=num}
int ModApiClient::l_get_node_or_nil(lua_State *L)
{
@@ -228,6 +227,7 @@ int ModApiClient::l_get_node_or_nil(lua_State *L)
return 1;
}
+// get_langauge()
int ModApiClient::l_get_language(lua_State *L)
{
#ifdef _WIN32
@@ -244,34 +244,30 @@ int ModApiClient::l_get_language(lua_State *L)
return 2;
}
-int ModApiClient::l_get_wielded_item(lua_State *L)
-{
- Client *client = getClient(L);
- LocalPlayer *player = client->getEnv().getLocalPlayer();
- if (!player)
- return 0;
-
- ItemStack selected_item;
- player->getWieldedItem(&selected_item, nullptr);
- LuaItemStack::create(L, selected_item);
- return 1;
-}
-
// get_meta(pos)
int ModApiClient::l_get_meta(lua_State *L)
{
v3s16 p = read_v3s16(L, 1);
- NodeMetadata *meta = getClient(L)->getEnv().getMap().getNodeMetadata(p);
+
+ // check restrictions first
+ bool pos_ok;
+ getClient(L)->CSMGetNode(p, &pos_ok);
+ if (!pos_ok)
+ return 0;
+
+ NodeMetadata *meta = getEnv(L)->getMap().getNodeMetadata(p);
NodeMetaRef::createClient(L, meta);
return 1;
}
+// sound_play(spec, parameters)
int ModApiClient::l_sound_play(lua_State *L)
{
ISoundManager *sound = getClient(L)->getSoundManager();
SimpleSoundSpec spec;
read_soundspec(L, 1, spec);
+
float gain = 1.0f;
float pitch = 1.0f;
bool looped = false;
@@ -293,21 +289,32 @@ int ModApiClient::l_sound_play(lua_State *L)
}
}
- handle = sound->playSound(spec.name, looped, gain * spec.gain, 0.0f, pitch);
+ handle = sound->playSound(spec.name, looped, gain * spec.gain, spec.fade, pitch);
lua_pushinteger(L, handle);
return 1;
}
+// sound_stop(handle)
int ModApiClient::l_sound_stop(lua_State *L)
{
- u32 handle = luaL_checkinteger(L, 1);
+ s32 handle = luaL_checkinteger(L, 1);
getClient(L)->getSoundManager()->stopSound(handle);
return 0;
}
+// sound_fade(handle, step, gain)
+int ModApiClient::l_sound_fade(lua_State *L)
+{
+ s32 handle = luaL_checkinteger(L, 1);
+ float step = readParam<float>(L, 2);
+ float gain = readParam<float>(L, 3);
+ getClient(L)->getSoundManager()->fadeSound(handle, step, gain);
+ return 0;
+}
+
// get_server_info()
int ModApiClient::l_get_server_info(lua_State *L)
{
@@ -375,6 +382,7 @@ int ModApiClient::l_get_node_def(lua_State *L)
return 1;
}
+// get_privilege_list()
int ModApiClient::l_get_privilege_list(lua_State *L)
{
const Client *client = getClient(L);
@@ -421,11 +429,11 @@ void ModApiClient::Initialize(lua_State *L, int top)
API_FCT(send_respawn);
API_FCT(gettext);
API_FCT(get_node_or_nil);
- API_FCT(get_wielded_item);
API_FCT(disconnect);
API_FCT(get_meta);
API_FCT(sound_play);
API_FCT(sound_stop);
+ API_FCT(sound_fade);
API_FCT(get_server_info);
API_FCT(get_item_def);
API_FCT(get_node_def);
diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h
index 6d1f70b1d..5dc3efdad 100644
--- a/src/script/lua_api/l_client.h
+++ b/src/script/lua_api/l_client.h
@@ -69,6 +69,7 @@ private:
// get_node(pos)
static int l_get_node_or_nil(lua_State *L);
+ // get_language()
static int l_get_language(lua_State *L);
// get_wielded_item()
@@ -77,10 +78,15 @@ private:
// get_meta(pos)
static int l_get_meta(lua_State *L);
+ // sound_play(spec, parameters)
static int l_sound_play(lua_State *L);
+ // sound_stop(handle)
static int l_sound_stop(lua_State *L);
+ // sound_fade(handle, step, gain)
+ static int l_sound_fade(lua_State *L);
+
// get_server_info()
static int l_get_server_info(lua_State *L);
diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp
index 3169fa4cf..89ec9dc7e 100644
--- a/src/script/lua_api/l_env.cpp
+++ b/src/script/lua_api/l_env.cpp
@@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <algorithm>
#include "lua_api/l_env.h"
#include "lua_api/l_internal.h"
#include "lua_api/l_nodemeta.h"
@@ -25,7 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_vmanip.h"
#include "common/c_converter.h"
#include "common/c_content.h"
-#include <algorithm>
#include "scripting_server.h"
#include "environment.h"
#include "mapblock.h"
@@ -33,12 +33,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "nodedef.h"
#include "daynightratio.h"
#include "util/pointedthing.h"
-#include "content_sao.h"
#include "mapgen/treegen.h"
#include "emerge.h"
#include "pathfinder.h"
#include "face_position_cache.h"
#include "remoteplayer.h"
+#include "server/luaentity_sao.h"
+#include "server/player_sao.h"
+#include "util/string.h"
+#include "translation.h"
#ifndef SERVER
#include "client/client.h"
#endif
@@ -72,7 +75,7 @@ void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n,
lua_remove(L, -2); // Remove core
// Get registered_abms[m_id]
- lua_pushnumber(L, m_id);
+ lua_pushinteger(L, m_id);
lua_gettable(L, -2);
if(lua_isnil(L, -1))
FATAL_ERROR("");
@@ -115,7 +118,7 @@ void LuaLBM::trigger(ServerEnvironment *env, v3s16 p, MapNode n)
lua_remove(L, -2); // Remove core
// Get registered_lbms[m_id]
- lua_pushnumber(L, m_id);
+ lua_pushinteger(L, m_id);
lua_gettable(L, -2);
FATAL_ERROR_IF(lua_isnil(L, -1), "Entry with given id not found in registered_lbms table");
lua_remove(L, -2); // Remove registered_lbms
@@ -139,8 +142,7 @@ void LuaLBM::trigger(ServerEnvironment *env, v3s16 p, MapNode n)
int LuaRaycast::l_next(lua_State *L)
{
- MAP_LOCK_REQUIRED;
- GET_ENV_PTR;
+ GET_PLAIN_ENV_PTR;
bool csm = false;
#ifndef SERVER
@@ -384,7 +386,7 @@ int ModApiEnvMod::l_get_node_or_nil(lua_State *L)
// timeofday: nil = current time, 0 = night, 0.5 = day
int ModApiEnvMod::l_get_node_light(lua_State *L)
{
- GET_ENV_PTR;
+ GET_PLAIN_ENV_PTR;
// Do it
v3s16 pos = read_v3s16(L, 1);
@@ -488,10 +490,7 @@ int ModApiEnvMod::l_punch_node(lua_State *L)
// pos = {x=num, y=num, z=num}
int ModApiEnvMod::l_get_node_max_level(lua_State *L)
{
- Environment *env = getEnv(L);
- if (!env) {
- return 0;
- }
+ GET_PLAIN_ENV_PTR;
v3s16 pos = read_v3s16(L, 1);
MapNode n = env->getMap().getNode(pos);
@@ -503,10 +502,7 @@ int ModApiEnvMod::l_get_node_max_level(lua_State *L)
// pos = {x=num, y=num, z=num}
int ModApiEnvMod::l_get_node_level(lua_State *L)
{
- Environment *env = getEnv(L);
- if (!env) {
- return 0;
- }
+ GET_PLAIN_ENV_PTR;
v3s16 pos = read_v3s16(L, 1);
MapNode n = env->getMap().getNode(pos);
@@ -533,13 +529,13 @@ int ModApiEnvMod::l_set_node_level(lua_State *L)
// add_node_level(pos, level)
// pos = {x=num, y=num, z=num}
-// level: 0..63
+// level: -127..127
int ModApiEnvMod::l_add_node_level(lua_State *L)
{
GET_ENV_PTR;
v3s16 pos = read_v3s16(L, 1);
- u8 level = 1;
+ s16 level = 1;
if(lua_isnumber(L, 2))
level = lua_tonumber(L, 2);
MapNode n = env->getMap().getNode(pos);
@@ -551,12 +547,12 @@ int ModApiEnvMod::l_add_node_level(lua_State *L)
// find_nodes_with_meta(pos1, pos2)
int ModApiEnvMod::l_find_nodes_with_meta(lua_State *L)
{
- GET_ENV_PTR;
+ GET_PLAIN_ENV_PTR;
std::vector<v3s16> positions = env->getMap().findNodesWithMetadata(
check_v3s16(L, 1), check_v3s16(L, 2));
- lua_newtable(L);
+ lua_createtable(L, positions.size(), 0);
for (size_t i = 0; i != positions.size(); i++) {
push_v3s16(L, positions[i]);
lua_rawseti(L, -2, i + 1);
@@ -583,7 +579,7 @@ int ModApiEnvMod::l_get_node_timer(lua_State *L)
// Do it
v3s16 p = read_v3s16(L, 1);
- NodeTimerRef::create(L, p, env);
+ NodeTimerRef::create(L, p, &env->getServerMap());
return 1;
}
@@ -593,19 +589,19 @@ int ModApiEnvMod::l_add_entity(lua_State *L)
{
GET_ENV_PTR;
- // pos
v3f pos = checkFloatPos(L, 1);
- // content
const char *name = luaL_checkstring(L, 2);
- // staticdata
const char *staticdata = luaL_optstring(L, 3, "");
- // Do it
+
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)
return 0;
- // Return ObjectRef
+
+ // If already deleted (can happen in on_activate), return nil
+ if (obj->isGone())
+ return 0;
getScriptApiBase(L)->objectrefGetOrCreate(L, obj);
return 1;
}
@@ -687,22 +683,22 @@ int ModApiEnvMod::l_get_player_by_name(lua_State *L)
int ModApiEnvMod::l_get_objects_inside_radius(lua_State *L)
{
GET_ENV_PTR;
+ ScriptApiBase *script = getScriptApiBase(L);
// Do it
v3f pos = checkFloatPos(L, 1);
float radius = readParam<float>(L, 2) * BS;
- std::vector<u16> ids;
- env->getObjectsInsideRadius(ids, pos, radius);
- ScriptApiBase *script = getScriptApiBase(L);
- lua_createtable(L, ids.size(), 0);
- std::vector<u16>::const_iterator iter = ids.begin();
- for(u32 i = 0; iter != ids.end(); ++iter) {
- ServerActiveObject *obj = env->getActiveObject(*iter);
- if (!obj->isGone()) {
- // Insert object reference into table
- script->objectrefGetOrCreate(L, obj);
- lua_rawseti(L, -2, ++i);
- }
+ std::vector<ServerActiveObject *> objs;
+
+ auto include_obj_cb = [](ServerActiveObject *obj){ return !obj->isGone(); };
+ env->getObjectsInsideRadius(objs, pos, radius, include_obj_cb);
+
+ int i = 0;
+ lua_createtable(L, objs.size(), 0);
+ for (const auto obj : objs) {
+ // Insert object reference into table
+ script->objectrefGetOrCreate(L, obj);
+ lua_rawseti(L, -2, ++i);
}
return 1;
}
@@ -728,10 +724,7 @@ int ModApiEnvMod::l_set_timeofday(lua_State *L)
// get_timeofday() -> 0...1
int ModApiEnvMod::l_get_timeofday(lua_State *L)
{
- Environment *env = getEnv(L);
- if (!env) {
- return 0;
- }
+ GET_PLAIN_ENV_PTR;
// Do it
int timeofday_mh = env->getTimeOfDay();
@@ -743,10 +736,7 @@ int ModApiEnvMod::l_get_timeofday(lua_State *L)
// get_day_count() -> int
int ModApiEnvMod::l_get_day_count(lua_State *L)
{
- Environment *env = getEnv(L);
- if (!env) {
- return 0;
- }
+ GET_PLAIN_ENV_PTR;
lua_pushnumber(L, env->getDayCount());
return 1;
@@ -767,12 +757,9 @@ int ModApiEnvMod::l_get_gametime(lua_State *L)
// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
int ModApiEnvMod::l_find_node_near(lua_State *L)
{
- Environment *env = getEnv(L);
- if (!env) {
- return 0;
- }
+ GET_PLAIN_ENV_PTR;
- const NodeDefManager *ndef = getGameDef(L)->ndef();
+ const NodeDefManager *ndef = env->getGameDef()->ndef();
v3s16 pos = read_v3s16(L, 1);
int radius = luaL_checkinteger(L, 2);
std::vector<content_t> filter;
@@ -793,8 +780,8 @@ int ModApiEnvMod::l_find_node_near(lua_State *L)
#ifndef SERVER
// Client API limitations
- if (getClient(L))
- radius = getClient(L)->CSMClampRadius(pos, radius);
+ if (Client *client = getClient(L))
+ radius = client->CSMClampRadius(pos, radius);
#endif
for (int d = start_radius; d <= radius; d++) {
@@ -815,20 +802,19 @@ int ModApiEnvMod::l_find_node_near(lua_State *L)
// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
int ModApiEnvMod::l_find_nodes_in_area(lua_State *L)
{
- GET_ENV_PTR;
+ GET_PLAIN_ENV_PTR;
v3s16 minp = read_v3s16(L, 1);
v3s16 maxp = read_v3s16(L, 2);
sortBoxVerticies(minp, maxp);
+ const NodeDefManager *ndef = env->getGameDef()->ndef();
+
#ifndef SERVER
- const NodeDefManager *ndef = getClient(L) ? getClient(L)->ndef() : getServer(L)->ndef();
- if (getClient(L)) {
- minp = getClient(L)->CSMClampPos(minp);
- maxp = getClient(L)->CSMClampPos(maxp);
+ if (Client *client = getClient(L)) {
+ minp = client->CSMClampPos(minp);
+ maxp = client->CSMClampPos(maxp);
}
-#else
- const NodeDefManager *ndef = getServer(L)->ndef();
#endif
v3s16 cube = maxp - minp + 1;
@@ -892,20 +878,19 @@ int ModApiEnvMod::l_find_nodes_in_area_under_air(lua_State *L)
* TODO
*/
- GET_ENV_PTR;
+ GET_PLAIN_ENV_PTR;
v3s16 minp = read_v3s16(L, 1);
v3s16 maxp = read_v3s16(L, 2);
sortBoxVerticies(minp, maxp);
+ const NodeDefManager *ndef = env->getGameDef()->ndef();
+
#ifndef SERVER
- const NodeDefManager *ndef = getClient(L) ? getClient(L)->ndef() : getServer(L)->ndef();
- if (getClient(L)) {
- minp = getClient(L)->CSMClampPos(minp);
- maxp = getClient(L)->CSMClampPos(maxp);
+ if (Client *client = getClient(L)) {
+ minp = client->CSMClampPos(minp);
+ maxp = client->CSMClampPos(maxp);
}
-#else
- const NodeDefManager *ndef = getServer(L)->ndef();
#endif
v3s16 cube = maxp - minp + 1;
@@ -1034,7 +1019,7 @@ int ModApiEnvMod::l_clear_objects(lua_State *L)
// line_of_sight(pos1, pos2) -> true/false, pos
int ModApiEnvMod::l_line_of_sight(lua_State *L)
{
- GET_ENV_PTR;
+ GET_PLAIN_ENV_PTR;
// read position 1 from lua
v3f pos1 = checkFloatPos(L, 1);
@@ -1210,11 +1195,11 @@ int ModApiEnvMod::l_find_path(lua_State *L)
algo = PA_DIJKSTRA;
}
- std::vector<v3s16> path = get_path(env, pos1, pos2,
+ std::vector<v3s16> path = get_path(&env->getServerMap(), env->getGameDef()->ndef(), pos1, pos2,
searchdistance, max_jump, max_drop, algo);
if (!path.empty()) {
- lua_newtable(L);
+ lua_createtable(L, path.size(), 0);
int top = lua_gettop(L);
unsigned int index = 1;
for (const v3s16 &i : path) {
@@ -1274,8 +1259,9 @@ int ModApiEnvMod::l_spawn_tree(lua_State *L)
else
return 0;
+ ServerMap *map = &env->getServerMap();
treegen::error e;
- if ((e = treegen::spawn_ltree (env, p0, ndef, tree_def)) != treegen::SUCCESS) {
+ if ((e = treegen::spawn_ltree (map, p0, ndef, tree_def)) != treegen::SUCCESS) {
if (e == treegen::UNBALANCED_BRACKETS) {
luaL_error(L, "spawn_tree(): closing ']' has no matching opening bracket");
} else {
@@ -1318,6 +1304,19 @@ int ModApiEnvMod::l_forceload_free_block(lua_State *L)
return 0;
}
+// get_translated_string(lang_code, string)
+int ModApiEnvMod::l_get_translated_string(lua_State * L)
+{
+ GET_ENV_PTR;
+ std::string lang_code = luaL_checkstring(L, 1);
+ std::string string = luaL_checkstring(L, 2);
+ getServer(L)->loadTranslationLanguage(lang_code);
+ string = wide_to_utf8(translate_string(utf8_to_wide(string),
+ &(*g_server_translations)[lang_code]));
+ lua_pushstring(L, string.c_str());
+ return 1;
+}
+
void ModApiEnvMod::Initialize(lua_State *L, int top)
{
API_FCT(set_node);
@@ -1365,6 +1364,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top)
API_FCT(transforming_liquid_add);
API_FCT(forceload_block);
API_FCT(forceload_free_block);
+ API_FCT(get_translated_string);
}
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 ac2f8b588..9050b4306 100644
--- a/src/script/lua_api/l_env.h
+++ b/src/script/lua_api/l_env.h
@@ -187,6 +187,9 @@ private:
// stops forceloading a position
static int l_forceload_free_block(lua_State *L);
+ // Get a string translated server side
+ static int l_get_translated_string(lua_State * L);
+
public:
static void Initialize(lua_State *L, int top);
static void InitializeClient(lua_State *L, int top);
diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp
index 2ff651cb5..ec43bf174 100644
--- a/src/script/lua_api/l_http.cpp
+++ b/src/script/lua_api/l_http.cpp
@@ -83,6 +83,24 @@ void ModApiHttp::push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool
setstringfield(L, -1, "data", res.data);
}
+// http_api.fetch_sync(HTTPRequest definition)
+int ModApiHttp::l_http_fetch_sync(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+
+ HTTPFetchRequest req;
+ read_http_fetch_request(L, req);
+
+ infostream << "Mod performs HTTP request with URL " << req.url << std::endl;
+
+ HTTPFetchResult res;
+ httpfetch_sync(req, res);
+
+ push_http_fetch_result(L, res, true);
+
+ return 1;
+}
+
// http_api.fetch_async(HTTPRequest definition)
int ModApiHttp::l_http_fetch_async(lua_State *L)
{
@@ -180,11 +198,42 @@ int ModApiHttp::l_request_http_api(lua_State *L)
return 1;
}
+
+int ModApiHttp::l_get_http_api(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+
+ lua_newtable(L);
+ HTTP_API(fetch_async);
+ HTTP_API(fetch_async_get);
+ HTTP_API(fetch_sync);
+
+ return 1;
+}
+
#endif
void ModApiHttp::Initialize(lua_State *L, int top)
{
#if USE_CURL
- API_FCT(request_http_api);
+
+ bool isMainmenu = false;
+#ifndef SERVER
+ isMainmenu = ModApiBase::getGuiEngine(L) != nullptr;
+#endif
+
+ if (isMainmenu) {
+ API_FCT(get_http_api);
+ } else {
+ API_FCT(request_http_api);
+ }
+
+#endif
+}
+
+void ModApiHttp::InitializeAsync(lua_State *L, int top)
+{
+#if USE_CURL
+ API_FCT(get_http_api);
#endif
}
diff --git a/src/script/lua_api/l_http.h b/src/script/lua_api/l_http.h
index 3d9cdad29..de6e51b37 100644
--- a/src/script/lua_api/l_http.h
+++ b/src/script/lua_api/l_http.h
@@ -32,6 +32,9 @@ private:
static void read_http_fetch_request(lua_State *L, HTTPFetchRequest &req);
static void push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool completed = true);
+ // http_fetch_sync({url=, timeout=, post_data=})
+ static int l_http_fetch_sync(lua_State *L);
+
// http_fetch_async({url=, timeout=, post_data=})
static int l_http_fetch_async(lua_State *L);
@@ -40,8 +43,12 @@ private:
// request_http_api()
static int l_request_http_api(lua_State *L);
+
+ // get_http_api()
+ static int l_get_http_api(lua_State *L);
#endif
public:
static void Initialize(lua_State *L, int top);
+ static void InitializeAsync(lua_State *L, int top);
};
diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h
index bbedfe46e..a86eeaf79 100644
--- a/src/script/lua_api/l_internal.h
+++ b/src/script/lua_api/l_internal.h
@@ -32,14 +32,39 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define luamethod_aliased(class, name, alias) {#name, class::l_##name}, {#alias, class::l_##name}
#define API_FCT(name) registerFunction(L, #name, l_##name, top)
-#define MAP_LOCK_REQUIRED
-#define NO_MAP_LOCK_REQUIRED
+// For future use
+#define MAP_LOCK_REQUIRED ((void)0)
+#define NO_MAP_LOCK_REQUIRED ((void)0)
+/* In debug mode ensure no code tries to retrieve the server env when it isn't
+ * actually available (in CSM) */
+#if !defined(SERVER) && !defined(NDEBUG)
+#define DEBUG_ASSERT_NO_CLIENTAPI \
+ FATAL_ERROR_IF(getClient(L) != nullptr, "Tried " \
+ "to retrieve ServerEnvironment on client")
+#else
+#define DEBUG_ASSERT_NO_CLIENTAPI ((void)0)
+#endif
+
+// Retrieve ServerEnvironment pointer as `env` (no map lock)
#define GET_ENV_PTR_NO_MAP_LOCK \
+ DEBUG_ASSERT_NO_CLIENTAPI; \
ServerEnvironment *env = (ServerEnvironment *)getEnv(L); \
if (env == NULL) \
return 0
+// Retrieve ServerEnvironment pointer as `env`
#define GET_ENV_PTR \
MAP_LOCK_REQUIRED; \
GET_ENV_PTR_NO_MAP_LOCK
+
+// Retrieve Environment pointer as `env` (no map lock)
+#define GET_PLAIN_ENV_PTR_NO_MAP_LOCK \
+ Environment *env = (Environment *)getEnv(L); \
+ if (env == NULL) \
+ return 0
+
+// Retrieve Environment pointer as `env`
+#define GET_PLAIN_ENV_PTR \
+ MAP_LOCK_REQUIRED; \
+ GET_PLAIN_ENV_PTR_NO_MAP_LOCK
diff --git a/src/script/lua_api/l_inventory.cpp b/src/script/lua_api/l_inventory.cpp
index 6e7afa4a4..e41b5cb41 100644
--- a/src/script/lua_api/l_inventory.cpp
+++ b/src/script/lua_api/l_inventory.cpp
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "common/c_converter.h"
#include "common/c_content.h"
#include "server.h"
+#include "server/serverinventorymgr.h"
#include "remoteplayer.h"
/*
@@ -38,7 +39,7 @@ InvRef* InvRef::checkobject(lua_State *L, int narg)
Inventory* InvRef::getinv(lua_State *L, InvRef *ref)
{
- return getServer(L)->getInventory(ref->m_loc);
+ return getServerInventoryMgr(L)->getInventory(ref->m_loc);
}
InventoryList* InvRef::getlist(lua_State *L, InvRef *ref,
@@ -54,7 +55,7 @@ InventoryList* InvRef::getlist(lua_State *L, InvRef *ref,
void InvRef::reportInventoryChange(lua_State *L, InvRef *ref)
{
// Inform other things that the inventory has changed
- getServer(L)->setInventoryModified(ref->m_loc);
+ getServerInventoryMgr(L)->setInventoryModified(ref->m_loc);
}
// Exported functions
@@ -487,7 +488,9 @@ int ModApiInventory::l_get_inventory(lua_State *L)
{
InventoryLocation loc;
- std::string type = checkstringfield(L, 1, "type");
+ lua_getfield(L, 1, "type");
+ std::string type = luaL_checkstring(L, -1);
+ lua_pop(L, 1);
if(type == "node"){
MAP_LOCK_REQUIRED;
@@ -495,7 +498,7 @@ int ModApiInventory::l_get_inventory(lua_State *L)
v3s16 pos = check_v3s16(L, -1);
loc.setNodeMeta(pos);
- if (getServer(L)->getInventory(loc) != NULL)
+ if (getServerInventoryMgr(L)->getInventory(loc) != NULL)
InvRef::create(L, loc);
else
lua_pushnil(L);
@@ -504,14 +507,16 @@ int ModApiInventory::l_get_inventory(lua_State *L)
NO_MAP_LOCK_REQUIRED;
if (type == "player") {
- std::string name = checkstringfield(L, 1, "name");
- loc.setPlayer(name);
+ lua_getfield(L, 1, "name");
+ loc.setPlayer(luaL_checkstring(L, -1));
+ lua_pop(L, 1);
} else if (type == "detached") {
- std::string name = checkstringfield(L, 1, "name");
- loc.setDetached(name);
+ lua_getfield(L, 1, "name");
+ loc.setDetached(luaL_checkstring(L, -1));
+ lua_pop(L, 1);
}
- if (getServer(L)->getInventory(loc) != NULL)
+ if (getServerInventoryMgr(L)->getInventory(loc) != NULL)
InvRef::create(L, loc);
else
lua_pushnil(L);
@@ -526,7 +531,7 @@ int ModApiInventory::l_create_detached_inventory_raw(lua_State *L)
NO_MAP_LOCK_REQUIRED;
const char *name = luaL_checkstring(L, 1);
std::string player = readParam<std::string>(L, 2, "");
- if (getServer(L)->createDetachedInventory(name, player) != NULL) {
+ if (getServerInventoryMgr(L)->createDetachedInventory(name, getServer(L)->idef(), player) != NULL) {
InventoryLocation loc;
loc.setDetached(name);
InvRef::create(L, loc);
@@ -541,7 +546,7 @@ int ModApiInventory::l_remove_detached_inventory_raw(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
const std::string &name = luaL_checkstring(L, 1);
- lua_pushboolean(L, getServer(L)->removeDetachedInventory(name));
+ lua_pushboolean(L, getServerInventoryMgr(L)->removeDetachedInventory(name));
return 1;
}
diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp
index 0c174feca..d67cab76f 100644
--- a/src/script/lua_api/l_item.cpp
+++ b/src/script/lua_api/l_item.cpp
@@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "itemdef.h"
#include "nodedef.h"
#include "server.h"
-#include "content_sao.h"
#include "inventory.h"
#include "log.h"
@@ -38,6 +37,15 @@ int LuaItemStack::gc_object(lua_State *L)
return 0;
}
+// __tostring metamethod
+int LuaItemStack::mt_tostring(lua_State *L)
+{
+ LuaItemStack *o = checkobject(L, 1);
+ std::string itemstring = o->m_stack.getItemString(false);
+ lua_pushfstring(L, "ItemStack(\"%s\")", itemstring.c_str());
+ return 1;
+}
+
// is_empty(self) -> true/false
int LuaItemStack::l_is_empty(lua_State *L)
{
@@ -434,12 +442,9 @@ int LuaItemStack::create(lua_State *L, const ItemStack &item)
return 1;
}
-LuaItemStack* LuaItemStack::checkobject(lua_State *L, int narg)
+LuaItemStack *LuaItemStack::checkobject(lua_State *L, int narg)
{
- luaL_checktype(L, narg, LUA_TUSERDATA);
- void *ud = luaL_checkudata(L, narg, className);
- if(!ud) luaL_typerror(L, narg, className);
- return *(LuaItemStack**)ud; // unbox pointer
+ return *(LuaItemStack **)luaL_checkudata(L, narg, className);
}
void LuaItemStack::Register(lua_State *L)
@@ -449,9 +454,10 @@ void LuaItemStack::Register(lua_State *L)
luaL_newmetatable(L, className);
int metatable = lua_gettop(L);
+ // hide metatable from Lua getmetatable()
lua_pushliteral(L, "__metatable");
lua_pushvalue(L, methodtable);
- lua_settable(L, metatable); // hide metatable from Lua getmetatable()
+ lua_settable(L, metatable);
lua_pushliteral(L, "__index");
lua_pushvalue(L, methodtable);
@@ -461,12 +467,16 @@ void LuaItemStack::Register(lua_State *L)
lua_pushcfunction(L, gc_object);
lua_settable(L, metatable);
+ lua_pushliteral(L, "__tostring");
+ lua_pushcfunction(L, mt_tostring);
+ lua_settable(L, metatable);
+
lua_pop(L, 1); // drop metatable
luaL_openlib(L, 0, methods, 0); // fill methodtable
lua_pop(L, 1); // drop methodtable
- // Can be created from Lua (LuaItemStack(itemstack or itemstring or table or nil))
+ // Can be created from Lua (ItemStack(itemstack or itemstring or table or nil))
lua_register(L, className, create_object);
}
@@ -522,7 +532,6 @@ int ModApiItemMod::l_register_item_raw(lua_State *L)
lua_getfield(L, table, "name");
if(lua_isstring(L, -1)){
name = readParam<std::string>(L, -1);
- verbosestream<<"register_item_raw: "<<name<<std::endl;
} else {
throw LuaError("register_item_raw: name is not defined or not a string");
}
@@ -611,10 +620,21 @@ int ModApiItemMod::l_get_content_id(lua_State *L)
NO_MAP_LOCK_REQUIRED;
std::string name = luaL_checkstring(L, 1);
+ const IItemDefManager *idef = getGameDef(L)->getItemDefManager();
const NodeDefManager *ndef = getGameDef(L)->getNodeDefManager();
+
+ // If this is called at mod load time, NodeDefManager isn't aware of
+ // aliases yet, so we need to handle them manually
+ std::string alias_name = idef->getAlias(name);
+
content_t content_id;
- if (!ndef->getId(name, content_id))
+ if (alias_name != name) {
+ if (!ndef->getId(alias_name, content_id))
+ throw LuaError("Unknown node: " + alias_name +
+ " (from alias " + name + ")");
+ } else if (!ndef->getId(name, content_id)) {
throw LuaError("Unknown node: " + name);
+ }
lua_pushinteger(L, content_id);
return 1; /* number of results */
diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h
index 6fab58045..98744c071 100644
--- a/src/script/lua_api/l_item.h
+++ b/src/script/lua_api/l_item.h
@@ -34,6 +34,9 @@ private:
// garbage collector
static int gc_object(lua_State *L);
+ // __tostring metamethod
+ static int mt_tostring(lua_State *L);
+
// is_empty(self) -> true/false
static int l_is_empty(lua_State *L);
diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp
index 821b1cb66..851ede535 100644
--- a/src/script/lua_api/l_localplayer.cpp
+++ b/src/script/lua_api/l_localplayer.cpp
@@ -19,10 +19,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "l_localplayer.h"
#include "l_internal.h"
+#include "lua_api/l_item.h"
#include "script/common/c_converter.h"
#include "client/localplayer.h"
#include "hud.h"
#include "common/c_content.h"
+#include "client/content_cao.h"
LuaLocalPlayer::LuaLocalPlayer(LocalPlayer *m) : m_localplayer(m)
{
@@ -74,6 +76,26 @@ int LuaLocalPlayer::l_get_name(lua_State *L)
return 1;
}
+// get_wield_index(self)
+int LuaLocalPlayer::l_get_wield_index(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
+
+ lua_pushinteger(L, player->getWieldIndex());
+ return 1;
+}
+
+// get_wielded_item(self)
+int LuaLocalPlayer::l_get_wielded_item(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
+
+ ItemStack selected_item;
+ player->getWieldedItem(&selected_item, nullptr);
+ LuaItemStack::create(L, selected_item);
+ return 1;
+}
+
int LuaLocalPlayer::l_is_attached(lua_State *L)
{
LocalPlayer *player = getobject(L, 1);
@@ -130,6 +152,7 @@ int LuaLocalPlayer::l_swimming_vertical(lua_State *L)
return 1;
}
+// get_physics_override(self)
int LuaLocalPlayer::l_get_physics_override(lua_State *L)
{
LocalPlayer *player = getobject(L, 1);
@@ -150,14 +173,9 @@ int LuaLocalPlayer::l_get_physics_override(lua_State *L)
lua_pushboolean(L, player->physics_override_sneak_glitch);
lua_setfield(L, -2, "sneak_glitch");
- return 1;
-}
-
-int LuaLocalPlayer::l_get_override_pos(lua_State *L)
-{
- LocalPlayer *player = getobject(L, 1);
+ lua_pushboolean(L, player->physics_override_new_move);
+ lua_setfield(L, -2, "new_move");
- push_v3f(L, player->getPosition());
return 1;
}
@@ -193,14 +211,33 @@ int LuaLocalPlayer::l_get_last_look_horizontal(lua_State *L)
return 1;
}
-int LuaLocalPlayer::l_get_key_pressed(lua_State *L)
+// get_control(self)
+int LuaLocalPlayer::l_get_control(lua_State *L)
{
LocalPlayer *player = getobject(L, 1);
+ const PlayerControl &c = player->getPlayerControl();
+
+ auto set = [L] (const char *name, bool value) {
+ lua_pushboolean(L, value);
+ lua_setfield(L, -2, name);
+ };
+
+ lua_createtable(L, 0, 12);
+ set("up", c.up);
+ set("down", c.down);
+ set("left", c.left);
+ set("right", c.right);
+ set("jump", c.jump);
+ set("aux1", c.aux1);
+ set("sneak", c.sneak);
+ set("zoom", c.zoom);
+ set("LMB", c.LMB);
+ set("RMB", c.RMB);
- lua_pushinteger(L, player->last_keyPressed);
return 1;
}
+// get_breath(self)
int LuaLocalPlayer::l_get_breath(lua_State *L)
{
LocalPlayer *player = getobject(L, 1);
@@ -209,6 +246,7 @@ int LuaLocalPlayer::l_get_breath(lua_State *L)
return 1;
}
+// get_pos(self)
int LuaLocalPlayer::l_get_pos(lua_State *L)
{
LocalPlayer *player = getobject(L, 1);
@@ -217,6 +255,7 @@ int LuaLocalPlayer::l_get_pos(lua_State *L)
return 1;
}
+// get_movement_acceleration(self)
int LuaLocalPlayer::l_get_movement_acceleration(lua_State *L)
{
LocalPlayer *player = getobject(L, 1);
@@ -234,6 +273,7 @@ int LuaLocalPlayer::l_get_movement_acceleration(lua_State *L)
return 1;
}
+// get_movement_speed(self)
int LuaLocalPlayer::l_get_movement_speed(lua_State *L)
{
LocalPlayer *player = getobject(L, 1);
@@ -257,6 +297,7 @@ int LuaLocalPlayer::l_get_movement_speed(lua_State *L)
return 1;
}
+// get_movement(self)
int LuaLocalPlayer::l_get_movement(lua_State *L)
{
LocalPlayer *player = getobject(L, 1);
@@ -278,6 +319,13 @@ int LuaLocalPlayer::l_get_movement(lua_State *L)
return 1;
}
+// get_armor_groups(self)
+int LuaLocalPlayer::l_get_armor_groups(lua_State *L)
+{
+ LocalPlayer *player = getobject(L, 1);
+ push_groups(L, player->getCAO()->getGroups());
+ return 1;
+}
// hud_add(self, form)
int LuaLocalPlayer::l_hud_add(lua_State *L)
@@ -407,6 +455,8 @@ const luaL_Reg LuaLocalPlayer::methods[] = {
luamethod(LuaLocalPlayer, get_velocity),
luamethod(LuaLocalPlayer, get_hp),
luamethod(LuaLocalPlayer, get_name),
+ luamethod(LuaLocalPlayer, get_wield_index),
+ luamethod(LuaLocalPlayer, get_wielded_item),
luamethod(LuaLocalPlayer, is_attached),
luamethod(LuaLocalPlayer, is_touching_ground),
luamethod(LuaLocalPlayer, is_in_liquid),
@@ -415,17 +465,19 @@ const luaL_Reg LuaLocalPlayer::methods[] = {
luamethod(LuaLocalPlayer, is_climbing),
luamethod(LuaLocalPlayer, swimming_vertical),
luamethod(LuaLocalPlayer, get_physics_override),
- luamethod(LuaLocalPlayer, get_override_pos),
+ // TODO: figure our if these are useful in any way
luamethod(LuaLocalPlayer, get_last_pos),
luamethod(LuaLocalPlayer, get_last_velocity),
luamethod(LuaLocalPlayer, get_last_look_horizontal),
luamethod(LuaLocalPlayer, get_last_look_vertical),
- luamethod(LuaLocalPlayer, get_key_pressed),
+ //
+ luamethod(LuaLocalPlayer, get_control),
luamethod(LuaLocalPlayer, get_breath),
luamethod(LuaLocalPlayer, get_pos),
luamethod(LuaLocalPlayer, get_movement_acceleration),
luamethod(LuaLocalPlayer, get_movement_speed),
luamethod(LuaLocalPlayer, get_movement),
+ luamethod(LuaLocalPlayer, get_armor_groups),
luamethod(LuaLocalPlayer, hud_add),
luamethod(LuaLocalPlayer, hud_remove),
luamethod(LuaLocalPlayer, hud_change),
diff --git a/src/script/lua_api/l_localplayer.h b/src/script/lua_api/l_localplayer.h
index 01de2ed4e..4413f2bdb 100644
--- a/src/script/lua_api/l_localplayer.h
+++ b/src/script/lua_api/l_localplayer.h
@@ -32,12 +32,21 @@ private:
// garbage collector
static int gc_object(lua_State *L);
+ // get_velocity(self)
static int l_get_velocity(lua_State *L);
+ // get_hp(self)
static int l_get_hp(lua_State *L);
+ // get_name(self)
static int l_get_name(lua_State *L);
+ // get_wield_index(self)
+ static int l_get_wield_index(lua_State *L);
+
+ // get_wielded_item(self)
+ static int l_get_wielded_item(lua_State *L);
+
static int l_is_attached(lua_State *L);
static int l_is_touching_ground(lua_State *L);
static int l_is_in_liquid(lua_State *L);
@@ -54,18 +63,28 @@ private:
static int l_get_last_velocity(lua_State *L);
static int l_get_last_look_vertical(lua_State *L);
static int l_get_last_look_horizontal(lua_State *L);
- static int l_get_key_pressed(lua_State *L);
+ // get_control(self)
+ static int l_get_control(lua_State *L);
+
+ // get_breath(self)
static int l_get_breath(lua_State *L);
+ // get_pos(self)
static int l_get_pos(lua_State *L);
+ // get_movement_acceleration(self)
static int l_get_movement_acceleration(lua_State *L);
+ // get_movement_speed(self)
static int l_get_movement_speed(lua_State *L);
+ // get_movement(self)
static int l_get_movement(lua_State *L);
+ // get_armor_groups(self)
+ static int l_get_armor_groups(lua_State *L);
+
// hud_add(self, id, form)
static int l_hud_add(lua_State *L);
diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp
index 76db7ed13..f32c477c2 100644
--- a/src/script/lua_api/l_mainmenu.cpp
+++ b/src/script/lua_api/l_mainmenu.cpp
@@ -280,8 +280,8 @@ int ModApiMainMenu::l_get_favorites(lua_State *L)
{
std::string listtype = "local";
- if (!lua_isnone(L,1)) {
- listtype = luaL_checkstring(L,1);
+ if (!lua_isnone(L, 1)) {
+ listtype = luaL_checkstring(L, 1);
}
std::vector<ServerListSpec> servers;
@@ -298,7 +298,7 @@ int ModApiMainMenu::l_get_favorites(lua_State *L)
for (const Json::Value &server : servers) {
- lua_pushnumber(L,index);
+ lua_pushnumber(L, index);
lua_newtable(L);
int top_lvl2 = lua_gettop(L);
@@ -306,11 +306,11 @@ int ModApiMainMenu::l_get_favorites(lua_State *L)
if (!server["clients"].asString().empty()) {
std::string clients_raw = server["clients"].asString();
char* endptr = 0;
- int numbervalue = strtol(clients_raw.c_str(),&endptr,10);
+ int numbervalue = strtol(clients_raw.c_str(), &endptr,10);
if ((!clients_raw.empty()) && (*endptr == 0)) {
- lua_pushstring(L,"clients");
- lua_pushnumber(L,numbervalue);
+ lua_pushstring(L, "clients");
+ lua_pushnumber(L, numbervalue);
lua_settable(L, top_lvl2);
}
}
@@ -319,83 +319,83 @@ int ModApiMainMenu::l_get_favorites(lua_State *L)
std::string clients_max_raw = server["clients_max"].asString();
char* endptr = 0;
- int numbervalue = strtol(clients_max_raw.c_str(),&endptr,10);
+ int numbervalue = strtol(clients_max_raw.c_str(), &endptr,10);
if ((!clients_max_raw.empty()) && (*endptr == 0)) {
- lua_pushstring(L,"clients_max");
- lua_pushnumber(L,numbervalue);
+ lua_pushstring(L, "clients_max");
+ lua_pushnumber(L, numbervalue);
lua_settable(L, top_lvl2);
}
}
if (!server["version"].asString().empty()) {
- lua_pushstring(L,"version");
+ lua_pushstring(L, "version");
std::string topush = server["version"].asString();
- lua_pushstring(L,topush.c_str());
+ lua_pushstring(L, topush.c_str());
lua_settable(L, top_lvl2);
}
if (!server["proto_min"].asString().empty()) {
- lua_pushstring(L,"proto_min");
+ lua_pushstring(L, "proto_min");
lua_pushinteger(L, server["proto_min"].asInt());
lua_settable(L, top_lvl2);
}
if (!server["proto_max"].asString().empty()) {
- lua_pushstring(L,"proto_max");
+ lua_pushstring(L, "proto_max");
lua_pushinteger(L, server["proto_max"].asInt());
lua_settable(L, top_lvl2);
}
if (!server["password"].asString().empty()) {
- lua_pushstring(L,"password");
+ lua_pushstring(L, "password");
lua_pushboolean(L, server["password"].asBool());
lua_settable(L, top_lvl2);
}
if (!server["creative"].asString().empty()) {
- lua_pushstring(L,"creative");
+ lua_pushstring(L, "creative");
lua_pushboolean(L, server["creative"].asBool());
lua_settable(L, top_lvl2);
}
if (!server["damage"].asString().empty()) {
- lua_pushstring(L,"damage");
+ lua_pushstring(L, "damage");
lua_pushboolean(L, server["damage"].asBool());
lua_settable(L, top_lvl2);
}
if (!server["pvp"].asString().empty()) {
- lua_pushstring(L,"pvp");
+ lua_pushstring(L, "pvp");
lua_pushboolean(L, server["pvp"].asBool());
lua_settable(L, top_lvl2);
}
if (!server["description"].asString().empty()) {
- lua_pushstring(L,"description");
+ lua_pushstring(L, "description");
std::string topush = server["description"].asString();
- lua_pushstring(L,topush.c_str());
+ lua_pushstring(L, topush.c_str());
lua_settable(L, top_lvl2);
}
if (!server["name"].asString().empty()) {
- lua_pushstring(L,"name");
+ lua_pushstring(L, "name");
std::string topush = server["name"].asString();
- lua_pushstring(L,topush.c_str());
+ lua_pushstring(L, topush.c_str());
lua_settable(L, top_lvl2);
}
if (!server["address"].asString().empty()) {
- lua_pushstring(L,"address");
+ lua_pushstring(L, "address");
std::string topush = server["address"].asString();
- lua_pushstring(L,topush.c_str());
+ lua_pushstring(L, topush.c_str());
lua_settable(L, top_lvl2);
}
if (!server["port"].asString().empty()) {
- lua_pushstring(L,"port");
+ lua_pushstring(L, "port");
std::string topush = server["port"].asString();
- lua_pushstring(L,topush.c_str());
+ lua_pushstring(L, topush.c_str());
lua_settable(L, top_lvl2);
}
@@ -406,6 +406,37 @@ int ModApiMainMenu::l_get_favorites(lua_State *L)
lua_settable(L, top_lvl2);
}
+ if (server["clients_list"].isArray()) {
+ unsigned int index_lvl2 = 1;
+ lua_pushstring(L, "clients_list");
+ lua_newtable(L);
+ int top_lvl3 = lua_gettop(L);
+ for (const Json::Value &client : server["clients_list"]) {
+ lua_pushnumber(L, index_lvl2);
+ std::string topush = client.asString();
+ lua_pushstring(L, topush.c_str());
+ lua_settable(L, top_lvl3);
+ index_lvl2++;
+ }
+ lua_settable(L, top_lvl2);
+ }
+
+ if (server["mods"].isArray()) {
+ unsigned int index_lvl2 = 1;
+ lua_pushstring(L, "mods");
+ lua_newtable(L);
+ int top_lvl3 = lua_gettop(L);
+ for (const Json::Value &mod : server["mods"]) {
+
+ lua_pushnumber(L, index_lvl2);
+ std::string topush = mod.asString();
+ lua_pushstring(L, topush.c_str());
+ lua_settable(L, top_lvl3);
+ index_lvl2++;
+ }
+ lua_settable(L, top_lvl2);
+ }
+
lua_settable(L, top);
index++;
}
@@ -571,9 +602,10 @@ int ModApiMainMenu::l_show_keys_menu(lua_State *L)
sanity_check(engine != NULL);
GUIKeyChangeMenu *kmenu = new GUIKeyChangeMenu(RenderingEngine::get_gui_env(),
- engine->m_parent,
- -1,
- engine->m_menumanager);
+ engine->m_parent,
+ -1,
+ engine->m_menumanager,
+ engine->m_texture_source);
kmenu->drop();
return 0;
}
@@ -904,12 +936,12 @@ int ModApiMainMenu::l_show_path_select_dialog(lua_State *L)
GUIFileSelectMenu* fileOpenMenu =
new GUIFileSelectMenu(RenderingEngine::get_gui_env(),
- engine->m_parent,
- -1,
- engine->m_menumanager,
- title,
- formname,
- is_file_select);
+ engine->m_parent,
+ -1,
+ engine->m_menumanager,
+ title,
+ formname,
+ is_file_select);
fileOpenMenu->setTextDest(engine->m_buttonhandler);
fileOpenMenu->drop();
return 0;
@@ -1032,6 +1064,14 @@ int ModApiMainMenu::l_get_max_supp_proto(lua_State *L)
}
/******************************************************************************/
+int ModApiMainMenu::l_open_url(lua_State *L)
+{
+ std::string url = luaL_checkstring(L, 1);
+ lua_pushboolean(L, porting::openURL(url));
+ return 1;
+}
+
+/******************************************************************************/
int ModApiMainMenu::l_do_async_callback(lua_State *L)
{
GUIEngine* engine = getGuiEngine(L);
@@ -1093,6 +1133,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(get_screen_info);
API_FCT(get_min_supp_proto);
API_FCT(get_max_supp_proto);
+ API_FCT(open_url);
API_FCT(do_async_callback);
}
diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h
index b2ca49320..5a16b3bfe 100644
--- a/src/script/lua_api/l_mainmenu.h
+++ b/src/script/lua_api/l_mainmenu.h
@@ -145,6 +145,9 @@ private:
static int l_get_max_supp_proto(lua_State *L);
+ // other
+ static int l_open_url(lua_State *L);
+
// async
static int l_do_async_callback(lua_State *L);
diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp
index cb0d6ac95..834938e56 100644
--- a/src/script/lua_api/l_mapgen.cpp
+++ b/src/script/lua_api/l_mapgen.cpp
@@ -91,13 +91,13 @@ struct EnumString ModApiMapgen::es_SchematicFormatType[] =
{0, NULL},
};
-ObjDef *get_objdef(lua_State *L, int index, ObjDefManager *objmgr);
+ObjDef *get_objdef(lua_State *L, int index, const ObjDefManager *objmgr);
Biome *get_or_load_biome(lua_State *L, int index,
BiomeManager *biomemgr);
Biome *read_biome_def(lua_State *L, int index, const NodeDefManager *ndef);
size_t get_biome_list(lua_State *L, int index,
- BiomeManager *biomemgr, std::unordered_set<u8> *biome_id_list);
+ BiomeManager *biomemgr, std::unordered_set<biome_t> *biome_id_list);
Schematic *get_or_load_schematic(lua_State *L, int index,
SchematicManager *schemmgr, StringMap *replace_names);
@@ -114,7 +114,7 @@ bool read_deco_schematic(lua_State *L, SchematicManager *schemmgr, DecoSchematic
///////////////////////////////////////////////////////////////////////////////
-ObjDef *get_objdef(lua_State *L, int index, ObjDefManager *objmgr)
+ObjDef *get_objdef(lua_State *L, int index, const ObjDefManager *objmgr)
{
if (index < 0)
index = lua_gettop(L) + 1 + index;
@@ -425,7 +425,7 @@ Biome *read_biome_def(lua_State *L, int index, const NodeDefManager *ndef)
size_t get_biome_list(lua_State *L, int index,
- BiomeManager *biomemgr, std::unordered_set<u8> *biome_id_list)
+ BiomeManager *biomemgr, std::unordered_set<biome_t> *biome_id_list)
{
if (index < 0)
index = lua_gettop(L) + 1 + index;
@@ -486,11 +486,11 @@ int ModApiMapgen::l_get_biome_id(lua_State *L)
if (!biome_str)
return 0;
- BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr;
+ const BiomeManager *bmgr = getServer(L)->getEmergeManager()->getBiomeManager();
if (!bmgr)
return 0;
- Biome *biome = (Biome *)bmgr->getByName(biome_str);
+ const Biome *biome = (Biome *)bmgr->getByName(biome_str);
if (!biome || biome->index == OBJDEF_INVALID_INDEX)
return 0;
@@ -508,11 +508,11 @@ int ModApiMapgen::l_get_biome_name(lua_State *L)
int biome_id = luaL_checkinteger(L, 1);
- BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr;
+ const BiomeManager *bmgr = getServer(L)->getEmergeManager()->getBiomeManager();
if (!bmgr)
return 0;
- Biome *b = (Biome *)bmgr->getRaw(biome_id);
+ const Biome *b = (Biome *)bmgr->getRaw(biome_id);
lua_pushstring(L, b->name.c_str());
return 1;
@@ -546,13 +546,11 @@ int ModApiMapgen::l_get_heat(lua_State *L)
u64 seed;
ss >> seed;
- BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr;
+ const BiomeManager *bmgr = getServer(L)->getEmergeManager()->getBiomeManager();
if (!bmgr)
return 0;
float heat = bmgr->getHeatAtPosOriginal(pos, np_heat, np_heat_blend, seed);
- if (!heat)
- return 0;
lua_pushnumber(L, heat);
@@ -587,14 +585,12 @@ int ModApiMapgen::l_get_humidity(lua_State *L)
u64 seed;
ss >> seed;
- BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr;
+ const BiomeManager *bmgr = getServer(L)->getEmergeManager()->getBiomeManager();
if (!bmgr)
return 0;
float humidity = bmgr->getHumidityAtPosOriginal(pos, np_humidity,
np_humidity_blend, seed);
- if (!humidity)
- return 0;
lua_pushnumber(L, humidity);
@@ -635,7 +631,7 @@ int ModApiMapgen::l_get_biome_data(lua_State *L)
u64 seed;
ss >> seed;
- BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr;
+ const BiomeManager *bmgr = getServer(L)->getEmergeManager()->getBiomeManager();
if (!bmgr)
return 0;
@@ -648,7 +644,7 @@ int ModApiMapgen::l_get_biome_data(lua_State *L)
if (!humidity)
return 0;
- Biome *biome = (Biome *)bmgr->getBiomeFromNoiseOriginal(heat, humidity, pos);
+ const Biome *biome = bmgr->getBiomeFromNoiseOriginal(heat, humidity, pos);
if (!biome || biome->index == OBJDEF_INVALID_INDEX)
return 0;
@@ -710,7 +706,7 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L)
if (!mg->heightmap)
return 0;
- lua_newtable(L);
+ lua_createtable(L, maplen, 0);
for (size_t i = 0; i != maplen; i++) {
lua_pushinteger(L, mg->heightmap[i]);
lua_rawseti(L, -2, i + 1);
@@ -722,7 +718,7 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L)
if (!mg->biomegen)
return 0;
- lua_newtable(L);
+ lua_createtable(L, maplen, 0);
for (size_t i = 0; i != maplen; i++) {
lua_pushinteger(L, mg->biomegen->biomemap[i]);
lua_rawseti(L, -2, i + 1);
@@ -736,7 +732,7 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L)
BiomeGenOriginal *bg = (BiomeGenOriginal *)mg->biomegen;
- lua_newtable(L);
+ lua_createtable(L, maplen, 0);
for (size_t i = 0; i != maplen; i++) {
lua_pushnumber(L, bg->heatmap[i]);
lua_rawseti(L, -2, i + 1);
@@ -751,7 +747,7 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L)
BiomeGenOriginal *bg = (BiomeGenOriginal *)mg->biomegen;
- lua_newtable(L);
+ lua_createtable(L, maplen, 0);
for (size_t i = 0; i != maplen; i++) {
lua_pushnumber(L, bg->humidmap[i]);
lua_rawseti(L, -2, i + 1);
@@ -761,13 +757,12 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L)
}
case MGOBJ_GENNOTIFY: {
std::map<std::string, std::vector<v3s16> >event_map;
- std::map<std::string, std::vector<v3s16> >::iterator it;
mg->gennotify.getEvents(event_map);
- lua_newtable(L);
- for (it = event_map.begin(); it != event_map.end(); ++it) {
- lua_newtable(L);
+ lua_createtable(L, 0, event_map.size());
+ for (auto it = event_map.begin(); it != event_map.end(); ++it) {
+ lua_createtable(L, it->second.size(), 0);
for (size_t j = 0; j != it->second.size(); j++) {
push_v3s16(L, it->second[j]);
@@ -1067,7 +1062,8 @@ int ModApiMapgen::l_get_decoration_id(lua_State *L)
if (!deco_str)
return 0;
- DecorationManager *dmgr = getServer(L)->getEmergeManager()->decomgr;
+ const DecorationManager *dmgr =
+ getServer(L)->getEmergeManager()->getDecorationManager();
if (!dmgr)
return 0;
@@ -1092,7 +1088,7 @@ int ModApiMapgen::l_register_biome(lua_State *L)
luaL_checktype(L, index, LUA_TTABLE);
const NodeDefManager *ndef = getServer(L)->getNodeDefManager();
- BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr;
+ BiomeManager *bmgr = getServer(L)->getEmergeManager()->getWritableBiomeManager();
Biome *biome = read_biome_def(L, index, ndef);
if (!biome)
@@ -1118,9 +1114,10 @@ int ModApiMapgen::l_register_decoration(lua_State *L)
luaL_checktype(L, index, LUA_TTABLE);
const NodeDefManager *ndef = getServer(L)->getNodeDefManager();
- DecorationManager *decomgr = getServer(L)->getEmergeManager()->decomgr;
- BiomeManager *biomemgr = getServer(L)->getEmergeManager()->biomemgr;
- SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr;
+ EmergeManager *emerge = getServer(L)->getEmergeManager();
+ DecorationManager *decomgr = emerge->getWritableDecorationManager();
+ BiomeManager *biomemgr = emerge->getWritableBiomeManager();
+ SchematicManager *schemmgr = emerge->getWritableSchematicManager();
enum DecorationType decotype = (DecorationType)getenumfield(L, index,
"deco_type", es_DecorationType, -1);
@@ -1275,8 +1272,9 @@ int ModApiMapgen::l_register_ore(lua_State *L)
luaL_checktype(L, index, LUA_TTABLE);
const NodeDefManager *ndef = getServer(L)->getNodeDefManager();
- BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr;
- OreManager *oremgr = getServer(L)->getEmergeManager()->oremgr;
+ EmergeManager *emerge = getServer(L)->getEmergeManager();
+ BiomeManager *bmgr = emerge->getWritableBiomeManager();
+ OreManager *oremgr = emerge->getWritableOreManager();
enum OreType oretype = (OreType)getenumfield(L, index,
"ore_type", es_OreType, ORE_SCATTER);
@@ -1423,7 +1421,8 @@ int ModApiMapgen::l_register_schematic(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
- SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr;
+ SchematicManager *schemmgr =
+ getServer(L)->getEmergeManager()->getWritableSchematicManager();
StringMap replace_names;
if (lua_istable(L, 2))
@@ -1450,7 +1449,8 @@ int ModApiMapgen::l_clear_registered_biomes(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
- BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr;
+ BiomeManager *bmgr =
+ getServer(L)->getEmergeManager()->getWritableBiomeManager();
bmgr->clear();
return 0;
}
@@ -1461,7 +1461,8 @@ int ModApiMapgen::l_clear_registered_decorations(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
- DecorationManager *dmgr = getServer(L)->getEmergeManager()->decomgr;
+ DecorationManager *dmgr =
+ getServer(L)->getEmergeManager()->getWritableDecorationManager();
dmgr->clear();
return 0;
}
@@ -1472,7 +1473,8 @@ int ModApiMapgen::l_clear_registered_ores(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
- OreManager *omgr = getServer(L)->getEmergeManager()->oremgr;
+ OreManager *omgr =
+ getServer(L)->getEmergeManager()->getWritableOreManager();
omgr->clear();
return 0;
}
@@ -1483,7 +1485,8 @@ int ModApiMapgen::l_clear_registered_schematics(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
- SchematicManager *smgr = getServer(L)->getEmergeManager()->schemmgr;
+ SchematicManager *smgr =
+ getServer(L)->getEmergeManager()->getWritableSchematicManager();
smgr->clear();
return 0;
}
@@ -1708,7 +1711,7 @@ int ModApiMapgen::l_serialize_schematic(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
- SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr;
+ const SchematicManager *schemmgr = getServer(L)->getEmergeManager()->getSchematicManager();
//// Read options
bool use_comments = getboolfield_default(L, 3, "lua_use_comments", false);
@@ -1716,7 +1719,7 @@ int ModApiMapgen::l_serialize_schematic(lua_State *L)
//// Get schematic
bool was_loaded = false;
- Schematic *schem = (Schematic *)get_objdef(L, 1, schemmgr);
+ const Schematic *schem = (Schematic *)get_objdef(L, 1, schemmgr);
if (!schem) {
schem = load_schematic(L, 1, NULL, NULL);
was_loaded = true;
@@ -1759,7 +1762,8 @@ int ModApiMapgen::l_read_schematic(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
- SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr;
+ const SchematicManager *schemmgr =
+ getServer(L)->getEmergeManager()->getSchematicManager();
//// Read options
std::string write_yslice = getstringfield_default(L, 2, "write_yslice_prob", "all");
diff --git a/src/script/lua_api/l_mapgen.h b/src/script/lua_api/l_mapgen.h
index 4a6a9ccf4..0bdc56fc5 100644
--- a/src/script/lua_api/l_mapgen.h
+++ b/src/script/lua_api/l_mapgen.h
@@ -21,6 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_base.h"
+typedef u16 biome_t; // copy from mg_biome.h to avoid an unnecessary include
+
class ModApiMapgen : public ModApiBase
{
private:
diff --git a/src/script/lua_api/l_metadata.cpp b/src/script/lua_api/l_metadata.cpp
index 21002e6a7..61a25a761 100644
--- a/src/script/lua_api/l_metadata.cpp
+++ b/src/script/lua_api/l_metadata.cpp
@@ -153,7 +153,9 @@ int MetaDataRef::l_set_int(lua_State *L)
MetaDataRef *ref = checkobject(L, 1);
std::string name = luaL_checkstring(L, 2);
int a = luaL_checkint(L, 3);
- std::string str = itos(a);
+ std::string str;
+ if (a != 0)
+ str = itos(a);
Metadata *meta = ref->getmeta(true);
if (meta == NULL || str == meta->getString(name))
@@ -191,7 +193,9 @@ int MetaDataRef::l_set_float(lua_State *L)
MetaDataRef *ref = checkobject(L, 1);
std::string name = luaL_checkstring(L, 2);
float a = readParam<float>(L, 3);
- std::string str = ftos(a);
+ std::string str;
+ if (a != 0)
+ str = ftos(a);
Metadata *meta = ref->getmeta(true);
if (meta == NULL || str == meta->getString(name))
diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp
index 229ce73db..57052cb42 100644
--- a/src/script/lua_api/l_nodemeta.cpp
+++ b/src/script/lua_api/l_nodemeta.cpp
@@ -55,11 +55,13 @@ Metadata* NodeMetaRef::getmeta(bool auto_create)
void NodeMetaRef::clearMeta()
{
+ SANITY_CHECK(!m_is_local);
m_env->getMap().removeNodeMetadata(m_p);
}
void NodeMetaRef::reportMetadataChange(const std::string *name)
{
+ SANITY_CHECK(!m_is_local);
// NOTE: This same code is in rollback_interface.cpp
// Inform other things that the metadata has changed
NodeMetadata *meta = dynamic_cast<NodeMetadata*>(m_meta);
diff --git a/src/script/lua_api/l_nodetimer.cpp b/src/script/lua_api/l_nodetimer.cpp
index 15a59744f..c2df52c05 100644
--- a/src/script/lua_api/l_nodetimer.cpp
+++ b/src/script/lua_api/l_nodetimer.cpp
@@ -41,11 +41,9 @@ int NodeTimerRef::l_set(lua_State *L)
{
MAP_LOCK_REQUIRED;
NodeTimerRef *o = checkobject(L, 1);
- ServerEnvironment *env = o->m_env;
- if(env == NULL) return 0;
f32 t = readParam<float>(L,2);
f32 e = readParam<float>(L,3);
- env->getMap().setNodeTimer(NodeTimer(t, e, o->m_p));
+ o->m_map->setNodeTimer(NodeTimer(t, e, o->m_p));
return 0;
}
@@ -53,10 +51,8 @@ int NodeTimerRef::l_start(lua_State *L)
{
MAP_LOCK_REQUIRED;
NodeTimerRef *o = checkobject(L, 1);
- ServerEnvironment *env = o->m_env;
- if(env == NULL) return 0;
f32 t = readParam<float>(L,2);
- env->getMap().setNodeTimer(NodeTimer(t, 0, o->m_p));
+ o->m_map->setNodeTimer(NodeTimer(t, 0, o->m_p));
return 0;
}
@@ -64,9 +60,7 @@ int NodeTimerRef::l_stop(lua_State *L)
{
MAP_LOCK_REQUIRED;
NodeTimerRef *o = checkobject(L, 1);
- ServerEnvironment *env = o->m_env;
- if(env == NULL) return 0;
- env->getMap().removeNodeTimer(o->m_p);
+ o->m_map->removeNodeTimer(o->m_p);
return 0;
}
@@ -74,10 +68,7 @@ int NodeTimerRef::l_is_started(lua_State *L)
{
MAP_LOCK_REQUIRED;
NodeTimerRef *o = checkobject(L, 1);
- ServerEnvironment *env = o->m_env;
- if(env == NULL) return 0;
-
- NodeTimer t = env->getMap().getNodeTimer(o->m_p);
+ NodeTimer t = o->m_map->getNodeTimer(o->m_p);
lua_pushboolean(L,(t.timeout != 0));
return 1;
}
@@ -86,10 +77,7 @@ int NodeTimerRef::l_get_timeout(lua_State *L)
{
MAP_LOCK_REQUIRED;
NodeTimerRef *o = checkobject(L, 1);
- ServerEnvironment *env = o->m_env;
- if(env == NULL) return 0;
-
- NodeTimer t = env->getMap().getNodeTimer(o->m_p);
+ NodeTimer t = o->m_map->getNodeTimer(o->m_p);
lua_pushnumber(L,t.timeout);
return 1;
}
@@ -98,37 +86,21 @@ int NodeTimerRef::l_get_elapsed(lua_State *L)
{
MAP_LOCK_REQUIRED;
NodeTimerRef *o = checkobject(L, 1);
- ServerEnvironment *env = o->m_env;
- if(env == NULL) return 0;
-
- NodeTimer t = env->getMap().getNodeTimer(o->m_p);
+ NodeTimer t = o->m_map->getNodeTimer(o->m_p);
lua_pushnumber(L,t.elapsed);
return 1;
}
-
-NodeTimerRef::NodeTimerRef(v3s16 p, ServerEnvironment *env):
- m_p(p),
- m_env(env)
-{
-}
-
// Creates an NodeTimerRef and leaves it on top of stack
// Not callable from Lua; all references are created on the C side.
-void NodeTimerRef::create(lua_State *L, v3s16 p, ServerEnvironment *env)
+void NodeTimerRef::create(lua_State *L, v3s16 p, ServerMap *map)
{
- NodeTimerRef *o = new NodeTimerRef(p, env);
+ NodeTimerRef *o = new NodeTimerRef(p, map);
*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
luaL_getmetatable(L, className);
lua_setmetatable(L, -2);
}
-void NodeTimerRef::set_null(lua_State *L)
-{
- NodeTimerRef *o = checkobject(L, -1);
- o->m_env = NULL;
-}
-
void NodeTimerRef::Register(lua_State *L)
{
lua_newtable(L);
diff --git a/src/script/lua_api/l_nodetimer.h b/src/script/lua_api/l_nodetimer.h
index b894c5c8c..bbc975fd2 100644
--- a/src/script/lua_api/l_nodetimer.h
+++ b/src/script/lua_api/l_nodetimer.h
@@ -22,13 +22,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irr_v3d.h"
#include "lua_api/l_base.h"
-class ServerEnvironment;
+class ServerMap;
class NodeTimerRef : public ModApiBase
{
private:
v3s16 m_p;
- ServerEnvironment *m_env = nullptr;
+ ServerMap *m_map;
static const char className[];
static const luaL_Reg methods[];
@@ -50,14 +50,12 @@ private:
static int l_get_elapsed(lua_State *L);
public:
- NodeTimerRef(v3s16 p, ServerEnvironment *env);
+ NodeTimerRef(v3s16 p, ServerMap *map) : m_p(p), m_map(map) {}
~NodeTimerRef() = default;
// Creates an NodeTimerRef and leaves it on top of stack
// Not callable from Lua; all references are created on the C side.
- static void create(lua_State *L, v3s16 p, ServerEnvironment *env);
-
- static void set_null(lua_State *L);
+ static void create(lua_State *L, v3s16 p, ServerMap *map);
static void Register(lua_State *L);
};
diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp
index e38d319f4..9aeb15709 100644
--- a/src/script/lua_api/l_noise.cpp
+++ b/src/script/lua_api/l_noise.cpp
@@ -171,9 +171,9 @@ int LuaPerlinNoiseMap::l_get_2d_map(lua_State *L)
Noise *n = o->noise;
n->perlinMap2D(p.X, p.Y);
- lua_newtable(L);
+ lua_createtable(L, n->sy, 0);
for (u32 y = 0; y != n->sy; y++) {
- lua_newtable(L);
+ lua_createtable(L, n->sx, 0);
for (u32 x = 0; x != n->sx; x++) {
lua_pushnumber(L, n->result[i++]);
lua_rawseti(L, -2, x + 1);
@@ -200,7 +200,7 @@ int LuaPerlinNoiseMap::l_get_2d_map_flat(lua_State *L)
if (use_buffer)
lua_pushvalue(L, 3);
else
- lua_newtable(L);
+ lua_createtable(L, maplen, 0);
for (size_t i = 0; i != maplen; i++) {
lua_pushnumber(L, n->result[i]);
@@ -224,11 +224,11 @@ int LuaPerlinNoiseMap::l_get_3d_map(lua_State *L)
Noise *n = o->noise;
n->perlinMap3D(p.X, p.Y, p.Z);
- lua_newtable(L);
+ lua_createtable(L, n->sz, 0);
for (u32 z = 0; z != n->sz; z++) {
- lua_newtable(L);
+ lua_createtable(L, n->sy, 0);
for (u32 y = 0; y != n->sy; y++) {
- lua_newtable(L);
+ lua_createtable(L, n->sx, 0);
for (u32 x = 0; x != n->sx; x++) {
lua_pushnumber(L, n->result[i++]);
lua_rawseti(L, -2, x + 1);
@@ -260,7 +260,7 @@ int LuaPerlinNoiseMap::l_get_3d_map_flat(lua_State *L)
if (use_buffer)
lua_pushvalue(L, 3);
else
- lua_newtable(L);
+ lua_createtable(L, maplen, 0);
for (size_t i = 0; i != maplen; i++) {
lua_pushnumber(L, n->result[i]);
diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp
index 23ed1ffe0..e7394133a 100644
--- a/src/script/lua_api/l_object.cpp
+++ b/src/script/lua_api/l_object.cpp
@@ -27,12 +27,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "common/c_content.h"
#include "log.h"
#include "tool.h"
-#include "serverobject.h"
-#include "content_sao.h"
#include "remoteplayer.h"
#include "server.h"
#include "hud.h"
#include "scripting_server.h"
+#include "server/luaentity_sao.h"
+#include "server/player_sao.h"
+#include "server/serverinventorymgr.h"
/*
ObjectRef
@@ -50,6 +51,8 @@ ObjectRef* ObjectRef::checkobject(lua_State *L, int narg)
ServerActiveObject* ObjectRef::getobject(ObjectRef *ref)
{
ServerActiveObject *co = ref->m_object;
+ if (co && co->isGone())
+ return NULL;
return co;
}
@@ -60,8 +63,6 @@ LuaEntitySAO* ObjectRef::getluaobject(ObjectRef *ref)
return NULL;
if (obj->getType() != ACTIVEOBJECT_TYPE_LUAENTITY)
return NULL;
- if (obj->isGone())
- return NULL;
return (LuaEntitySAO*)obj;
}
@@ -72,8 +73,6 @@ PlayerSAO* ObjectRef::getplayersao(ObjectRef *ref)
return NULL;
if (obj->getType() != ACTIVEOBJECT_TYPE_PLAYER)
return NULL;
- if (obj->isGone())
- return NULL;
return (PlayerSAO*)obj;
}
@@ -123,14 +122,7 @@ int ObjectRef::l_get_pos(lua_State *L)
ObjectRef *ref = checkobject(L, 1);
ServerActiveObject *co = getobject(ref);
if (co == NULL) return 0;
- v3f pos = co->getBasePosition() / BS;
- lua_newtable(L);
- lua_pushnumber(L, pos.X);
- lua_setfield(L, -2, "x");
- lua_pushnumber(L, pos.Y);
- lua_setfield(L, -2, "y");
- lua_pushnumber(L, pos.Z);
- lua_setfield(L, -2, "z");
+ push_v3f(L, co->getBasePosition() / BS);
return 1;
}
@@ -139,7 +131,6 @@ int ObjectRef::l_set_pos(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
ObjectRef *ref = checkobject(L, 1);
- //LuaEntitySAO *co = getluaobject(ref);
ServerActiveObject *co = getobject(ref);
if (co == NULL) return 0;
// pos
@@ -154,7 +145,6 @@ int ObjectRef::l_move_to(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
ObjectRef *ref = checkobject(L, 1);
- //LuaEntitySAO *co = getluaobject(ref);
ServerActiveObject *co = getobject(ref);
if (co == NULL) return 0;
// pos
@@ -300,7 +290,7 @@ int ObjectRef::l_get_inventory(lua_State *L)
if (co == NULL) return 0;
// Do it
InventoryLocation loc = co->getInventoryLocation();
- if (getServer(L)->getInventory(loc) != NULL)
+ if (getServerInventoryMgr(L)->getInventory(loc) != NULL)
InvRef::create(L, loc);
else
lua_pushnil(L); // An object may have no inventory (nil)
@@ -533,7 +523,7 @@ int ObjectRef::l_set_local_animation(lua_State *L)
// get_local_animation(self)
int ObjectRef::l_get_local_animation(lua_State *L)
{
- NO_MAP_LOCK_REQUIRED
+ NO_MAP_LOCK_REQUIRED;
ObjectRef *ref = checkobject(L, 1);
RemotePlayer *player = getplayer(ref);
if (player == NULL)
@@ -685,8 +675,13 @@ int ObjectRef::l_set_attach(lua_State *L)
ServerActiveObject *parent = getobject(parent_ref);
if (co == NULL)
return 0;
+
if (parent == NULL)
return 0;
+
+ if (co == parent)
+ throw LuaError("ObjectRef::set_attach: attaching object to itself is not allowed.");
+
// Do it
int parent_id = 0;
std::string bone;
@@ -1109,17 +1104,13 @@ int ObjectRef::l_add_player_velocity(lua_State *L)
ObjectRef *ref = checkobject(L, 1);
v3f vel = checkFloatPos(L, 2);
- RemotePlayer *player = getplayer(ref);
PlayerSAO *co = getplayersao(ref);
- if (!player || !co)
+ if (!co)
return 0;
- session_t peer_id = player->getPeerId();
- if (peer_id == PEER_ID_INEXISTENT)
- return 0;
// Do it
co->setMaxSpeedOverride(vel);
- getServer(L)->SendPlayerSpeed(peer_id, vel);
+ getServer(L)->SendPlayerSpeed(co->getPeerID(), vel);
return 0;
}
@@ -1259,7 +1250,7 @@ int ObjectRef::l_set_look_yaw(lua_State *L)
return 1;
}
-// set_fov(self, degrees[, is_multiplier])
+// set_fov(self, degrees[, is_multiplier, transition_time])
int ObjectRef::l_set_fov(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
@@ -1268,7 +1259,11 @@ int ObjectRef::l_set_fov(lua_State *L)
if (!player)
return 0;
- player->setFov({ static_cast<f32>(luaL_checknumber(L, 2)), readParam<bool>(L, 3) });
+ player->setFov({
+ static_cast<f32>(luaL_checknumber(L, 2)),
+ readParam<bool>(L, 3, false),
+ lua_isnumber(L, 4) ? static_cast<f32>(luaL_checknumber(L, 4)) : 0.0f
+ });
getServer(L)->SendPlayerFov(player->getPeerId());
return 0;
@@ -1286,8 +1281,9 @@ int ObjectRef::l_get_fov(lua_State *L)
PlayerFovSpec fov_spec = player->getFov();
lua_pushnumber(L, fov_spec.fov);
lua_pushboolean(L, fov_spec.is_multiplier);
+ lua_pushnumber(L, fov_spec.transition_time);
- return 2;
+ return 3;
}
// set_breath(self, breath)
@@ -1463,6 +1459,8 @@ int ObjectRef::l_get_player_control(lua_State *L)
lua_setfield(L, -2, "LMB");
lua_pushboolean(L, control.RMB);
lua_setfield(L, -2, "RMB");
+ lua_pushboolean(L, control.zoom);
+ lua_setfield(L, -2, "zoom");
return 1;
}
@@ -1787,19 +1785,19 @@ int ObjectRef::l_set_sky(lua_State *L)
lua_pop(L, 1);
// Prevent flickering clouds at dawn/dusk:
- skybox_params.sun_tint = video::SColor(255, 255, 255, 255);
+ skybox_params.fog_sun_tint = video::SColor(255, 255, 255, 255);
lua_getfield(L, -1, "fog_sun_tint");
- read_color(L, -1, &skybox_params.sun_tint);
+ read_color(L, -1, &skybox_params.fog_sun_tint);
lua_pop(L, 1);
- skybox_params.moon_tint = video::SColor(255, 255, 255, 255);
+ skybox_params.fog_moon_tint = video::SColor(255, 255, 255, 255);
lua_getfield(L, -1, "fog_moon_tint");
- read_color(L, -1, &skybox_params.moon_tint);
+ read_color(L, -1, &skybox_params.fog_moon_tint);
lua_pop(L, 1);
lua_getfield(L, -1, "fog_tint_type");
if (!lua_isnil(L, -1))
- skybox_params.tint_type = luaL_checkstring(L, -1);
+ skybox_params.fog_tint_type = luaL_checkstring(L, -1);
lua_pop(L, 1);
// Because we need to leave the "sky_color" table.
@@ -1917,12 +1915,12 @@ int ObjectRef::l_get_sky_color(lua_State *L)
push_ARGB8(L, skybox_params.sky_color.indoors);
lua_setfield(L, -2, "indoors");
}
- push_ARGB8(L, skybox_params.sun_tint);
- lua_setfield(L, -2, "sun_tint");
- push_ARGB8(L, skybox_params.moon_tint);
- lua_setfield(L, -2, "moon_tint");
- lua_pushstring(L, skybox_params.tint_type.c_str());
- lua_setfield(L, -2, "tint_type");
+ push_ARGB8(L, skybox_params.fog_sun_tint);
+ lua_setfield(L, -2, "fog_sun_tint");
+ push_ARGB8(L, skybox_params.fog_moon_tint);
+ lua_setfield(L, -2, "fog_moon_tint");
+ lua_pushstring(L, skybox_params.fog_tint_type.c_str());
+ lua_setfield(L, -2, "fog_tint_type");
return 1;
}
@@ -2177,9 +2175,7 @@ int ObjectRef::l_override_day_night_ratio(lua_State *L)
ratio = readParam<float>(L, 2);
}
- if (!getServer(L)->overrideDayNightRatio(player, do_override, ratio))
- return 0;
-
+ getServer(L)->overrideDayNightRatio(player, do_override, ratio);
lua_pushboolean(L, true);
return 1;
}
diff --git a/src/script/lua_api/l_particles.cpp b/src/script/lua_api/l_particles.cpp
index 340903ebf..a51c4fe20 100644
--- a/src/script/lua_api/l_particles.cpp
+++ b/src/script/lua_api/l_particles.cpp
@@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "common/c_converter.h"
#include "common/c_content.h"
#include "server.h"
-#include "client/particles.h"
+#include "particles.h"
// add_particle({pos=, velocity=, acceleration=, expirationtime=,
// size=, collisiondetection=, collision_removal=, object_collision=,
@@ -40,85 +40,88 @@ with this program; if not, write to the Free Software Foundation, Inc.,
// glow = num
int ModApiParticles::l_add_particle(lua_State *L)
{
- MAP_LOCK_REQUIRED;
+ NO_MAP_LOCK_REQUIRED;
// Get parameters
- v3f pos, vel, acc;
- float expirationtime, size;
- expirationtime = size = 1;
- bool collisiondetection, vertical, collision_removal, object_collision;
- collisiondetection = vertical = collision_removal = object_collision = false;
- struct TileAnimationParams animation;
- animation.type = TAT_NONE;
- std::string texture;
+ struct ParticleParameters p;
std::string playername;
- u8 glow = 0;
if (lua_gettop(L) > 1) // deprecated
{
- log_deprecated(L, "Deprecated add_particle call with individual parameters instead of definition");
- pos = check_v3f(L, 1);
- vel = check_v3f(L, 2);
- acc = check_v3f(L, 3);
- expirationtime = luaL_checknumber(L, 4);
- size = luaL_checknumber(L, 5);
- collisiondetection = readParam<bool>(L, 6);
- texture = luaL_checkstring(L, 7);
+ log_deprecated(L, "Deprecated add_particle call with "
+ "individual parameters instead of definition");
+ p.pos = check_v3f(L, 1);
+ p.vel = check_v3f(L, 2);
+ p.acc = check_v3f(L, 3);
+ p.expirationtime = luaL_checknumber(L, 4);
+ p.size = luaL_checknumber(L, 5);
+ p.collisiondetection = readParam<bool>(L, 6);
+ p.texture = luaL_checkstring(L, 7);
if (lua_gettop(L) == 8) // only spawn for a single player
playername = luaL_checkstring(L, 8);
}
else if (lua_istable(L, 1))
{
lua_getfield(L, 1, "pos");
- pos = lua_istable(L, -1) ? check_v3f(L, -1) : v3f();
+ if (lua_istable(L, -1))
+ p.pos = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "vel");
if (lua_istable(L, -1)) {
- vel = check_v3f(L, -1);
+ p.vel = check_v3f(L, -1);
log_deprecated(L, "The use of vel is deprecated. "
"Use velocity instead");
}
lua_pop(L, 1);
lua_getfield(L, 1, "velocity");
- vel = lua_istable(L, -1) ? check_v3f(L, -1) : vel;
+ if (lua_istable(L, -1))
+ p.vel = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "acc");
if (lua_istable(L, -1)) {
- acc = check_v3f(L, -1);
+ p.acc = check_v3f(L, -1);
log_deprecated(L, "The use of acc is deprecated. "
"Use acceleration instead");
}
lua_pop(L, 1);
lua_getfield(L, 1, "acceleration");
- acc = lua_istable(L, -1) ? check_v3f(L, -1) : acc;
+ if (lua_istable(L, -1))
+ p.acc = check_v3f(L, -1);
lua_pop(L, 1);
- expirationtime = getfloatfield_default(L, 1, "expirationtime", 1);
- size = getfloatfield_default(L, 1, "size", 1);
- collisiondetection = getboolfield_default(L, 1,
- "collisiondetection", collisiondetection);
- collision_removal = getboolfield_default(L, 1,
- "collision_removal", collision_removal);
- object_collision = getboolfield_default(L, 1,
- "object_collision", object_collision);
- vertical = getboolfield_default(L, 1, "vertical", vertical);
+ p.expirationtime = getfloatfield_default(L, 1, "expirationtime",
+ p.expirationtime);
+ p.size = getfloatfield_default(L, 1, "size", p.size);
+ p.collisiondetection = getboolfield_default(L, 1,
+ "collisiondetection", p.collisiondetection);
+ p.collision_removal = getboolfield_default(L, 1,
+ "collision_removal", p.collision_removal);
+ p.object_collision = getboolfield_default(L, 1,
+ "object_collision", p.object_collision);
+ p.vertical = getboolfield_default(L, 1, "vertical", p.vertical);
lua_getfield(L, 1, "animation");
- animation = read_animation_definition(L, -1);
+ p.animation = read_animation_definition(L, -1);
lua_pop(L, 1);
- texture = getstringfield_default(L, 1, "texture", "");
- playername = getstringfield_default(L, 1, "playername", "");
+ p.texture = getstringfield_default(L, 1, "texture", p.texture);
+ p.glow = getintfield_default(L, 1, "glow", p.glow);
+
+ lua_getfield(L, 1, "node");
+ if (lua_istable(L, -1))
+ p.node = readnode(L, -1, getGameDef(L)->ndef());
+ lua_pop(L, 1);
- glow = getintfield_default(L, 1, "glow", 0);
+ p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile);
+
+ playername = getstringfield_default(L, 1, "playername", "");
}
- getServer(L)->spawnParticle(playername, pos, vel, acc, expirationtime, size,
- collisiondetection, collision_removal, object_collision, vertical,
- texture, animation, glow);
+
+ getServer(L)->spawnParticle(playername, p);
return 1;
}
@@ -146,84 +149,82 @@ int ModApiParticles::l_add_particle(lua_State *L)
// glow = num
int ModApiParticles::l_add_particlespawner(lua_State *L)
{
- MAP_LOCK_REQUIRED;
+ NO_MAP_LOCK_REQUIRED;
// Get parameters
- u16 amount = 1;
- v3f minpos, maxpos, minvel, maxvel, minacc, maxacc;
- float time, minexptime, maxexptime, minsize, maxsize;
- time = minexptime = maxexptime = minsize = maxsize = 1;
- bool collisiondetection, vertical, collision_removal, object_collision;
- collisiondetection = vertical = collision_removal = object_collision = false;
- struct TileAnimationParams animation;
- animation.type = TAT_NONE;
+ ParticleSpawnerParameters p;
ServerActiveObject *attached = NULL;
- std::string texture;
std::string playername;
- u8 glow = 0;
if (lua_gettop(L) > 1) //deprecated
{
- log_deprecated(L,"Deprecated add_particlespawner call with individual parameters instead of definition");
- amount = luaL_checknumber(L, 1);
- time = luaL_checknumber(L, 2);
- minpos = check_v3f(L, 3);
- maxpos = check_v3f(L, 4);
- minvel = check_v3f(L, 5);
- maxvel = check_v3f(L, 6);
- minacc = check_v3f(L, 7);
- maxacc = check_v3f(L, 8);
- minexptime = luaL_checknumber(L, 9);
- maxexptime = luaL_checknumber(L, 10);
- minsize = luaL_checknumber(L, 11);
- maxsize = luaL_checknumber(L, 12);
- collisiondetection = readParam<bool>(L, 13);
- texture = luaL_checkstring(L, 14);
+ log_deprecated(L, "Deprecated add_particlespawner call with "
+ "individual parameters instead of definition");
+ p.amount = luaL_checknumber(L, 1);
+ p.time = luaL_checknumber(L, 2);
+ p.minpos = check_v3f(L, 3);
+ p.maxpos = check_v3f(L, 4);
+ p.minvel = check_v3f(L, 5);
+ p.maxvel = check_v3f(L, 6);
+ p.minacc = check_v3f(L, 7);
+ p.maxacc = check_v3f(L, 8);
+ p.minexptime = luaL_checknumber(L, 9);
+ p.maxexptime = luaL_checknumber(L, 10);
+ p.minsize = luaL_checknumber(L, 11);
+ p.maxsize = luaL_checknumber(L, 12);
+ p.collisiondetection = readParam<bool>(L, 13);
+ p.texture = luaL_checkstring(L, 14);
if (lua_gettop(L) == 15) // only spawn for a single player
playername = luaL_checkstring(L, 15);
}
else if (lua_istable(L, 1))
{
- amount = getintfield_default(L, 1, "amount", amount);
- time = getfloatfield_default(L, 1, "time", time);
+ p.amount = getintfield_default(L, 1, "amount", p.amount);
+ p.time = getfloatfield_default(L, 1, "time", p.time);
lua_getfield(L, 1, "minpos");
- minpos = lua_istable(L, -1) ? check_v3f(L, -1) : minpos;
+ if (lua_istable(L, -1))
+ p.minpos = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "maxpos");
- maxpos = lua_istable(L, -1) ? check_v3f(L, -1) : maxpos;
+ if (lua_istable(L, -1))
+ p.maxpos = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "minvel");
- minvel = lua_istable(L, -1) ? check_v3f(L, -1) : minvel;
+ if (lua_istable(L, -1))
+ p.minvel = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "maxvel");
- maxvel = lua_istable(L, -1) ? check_v3f(L, -1) : maxvel;
+ if (lua_istable(L, -1))
+ p.maxvel = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "minacc");
- minacc = lua_istable(L, -1) ? check_v3f(L, -1) : minacc;
+ if (lua_istable(L, -1))
+ p.minacc = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "maxacc");
- maxacc = lua_istable(L, -1) ? check_v3f(L, -1) : maxacc;
+ if (lua_istable(L, -1))
+ p.maxacc = check_v3f(L, -1);
lua_pop(L, 1);
- minexptime = getfloatfield_default(L, 1, "minexptime", minexptime);
- maxexptime = getfloatfield_default(L, 1, "maxexptime", maxexptime);
- minsize = getfloatfield_default(L, 1, "minsize", minsize);
- maxsize = getfloatfield_default(L, 1, "maxsize", maxsize);
- collisiondetection = getboolfield_default(L, 1,
- "collisiondetection", collisiondetection);
- collision_removal = getboolfield_default(L, 1,
- "collision_removal", collision_removal);
- object_collision = getboolfield_default(L, 1,
- "object_collision", object_collision);
+ p.minexptime = getfloatfield_default(L, 1, "minexptime", p.minexptime);
+ p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime);
+ p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize);
+ p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize);
+ p.collisiondetection = getboolfield_default(L, 1,
+ "collisiondetection", p.collisiondetection);
+ p.collision_removal = getboolfield_default(L, 1,
+ "collision_removal", p.collision_removal);
+ p.object_collision = getboolfield_default(L, 1,
+ "object_collision", p.object_collision);
lua_getfield(L, 1, "animation");
- animation = read_animation_definition(L, -1);
+ p.animation = read_animation_definition(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "attached");
@@ -233,25 +234,20 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
attached = ObjectRef::getobject(ref);
}
- vertical = getboolfield_default(L, 1, "vertical", vertical);
- texture = getstringfield_default(L, 1, "texture", "");
+ p.vertical = getboolfield_default(L, 1, "vertical", p.vertical);
+ p.texture = getstringfield_default(L, 1, "texture", p.texture);
playername = getstringfield_default(L, 1, "playername", "");
- glow = getintfield_default(L, 1, "glow", 0);
+ p.glow = getintfield_default(L, 1, "glow", p.glow);
+
+ lua_getfield(L, 1, "node");
+ if (lua_istable(L, -1))
+ p.node = readnode(L, -1, getGameDef(L)->ndef());
+ lua_pop(L, 1);
+
+ p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile);
}
- u32 id = getServer(L)->addParticleSpawner(amount, time,
- minpos, maxpos,
- minvel, maxvel,
- minacc, maxacc,
- minexptime, maxexptime,
- minsize, maxsize,
- collisiondetection,
- collision_removal,
- object_collision,
- attached,
- vertical,
- texture, playername,
- animation, glow);
+ u32 id = getServer(L)->addParticleSpawner(p, attached, playername);
lua_pushnumber(L, id);
return 1;
@@ -261,7 +257,7 @@ int ModApiParticles::l_add_particlespawner(lua_State *L)
// player (string) is optional
int ModApiParticles::l_delete_particlespawner(lua_State *L)
{
- MAP_LOCK_REQUIRED;
+ NO_MAP_LOCK_REQUIRED;
// Get parameters
u32 id = luaL_checknumber(L, 1);
diff --git a/src/script/lua_api/l_particles_local.cpp b/src/script/lua_api/l_particles_local.cpp
index a9bf55665..cc68b13a5 100644
--- a/src/script/lua_api/l_particles_local.cpp
+++ b/src/script/lua_api/l_particles_local.cpp
@@ -32,56 +32,51 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L)
luaL_checktype(L, 1, LUA_TTABLE);
// Get parameters
- v3f pos, vel, acc;
- float expirationtime, size;
- bool collisiondetection, vertical, collision_removal;
-
- struct TileAnimationParams animation;
- animation.type = TAT_NONE;
-
- std::string texture;
-
- u8 glow;
+ ParticleParameters p;
lua_getfield(L, 1, "pos");
- pos = lua_istable(L, -1) ? check_v3f(L, -1) : v3f(0, 0, 0);
+ if (lua_istable(L, -1))
+ p.pos = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "velocity");
- vel = lua_istable(L, -1) ? check_v3f(L, -1) : v3f(0, 0, 0);
+ if (lua_istable(L, -1))
+ p.vel = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "acceleration");
- acc = lua_istable(L, -1) ? check_v3f(L, -1) : v3f(0, 0, 0);
+ if (lua_istable(L, -1))
+ p.acc = check_v3f(L, -1);
lua_pop(L, 1);
- expirationtime = getfloatfield_default(L, 1, "expirationtime", 1);
- size = getfloatfield_default(L, 1, "size", 1);
- collisiondetection = getboolfield_default(L, 1, "collisiondetection", false);
- collision_removal = getboolfield_default(L, 1, "collision_removal", false);
- vertical = getboolfield_default(L, 1, "vertical", false);
+ p.expirationtime = getfloatfield_default(L, 1, "expirationtime",
+ p.expirationtime);
+ p.size = getfloatfield_default(L, 1, "size", p.size);
+ p.collisiondetection = getboolfield_default(L, 1,
+ "collisiondetection", p.collisiondetection);
+ p.collision_removal = getboolfield_default(L, 1,
+ "collision_removal", p.collision_removal);
+ p.object_collision = getboolfield_default(L, 1,
+ "object_collision", p.object_collision);
+ p.vertical = getboolfield_default(L, 1, "vertical", p.vertical);
lua_getfield(L, 1, "animation");
- animation = read_animation_definition(L, -1);
+ p.animation = read_animation_definition(L, -1);
lua_pop(L, 1);
- texture = getstringfield_default(L, 1, "texture", "");
+ p.texture = getstringfield_default(L, 1, "texture", p.texture);
+ p.glow = getintfield_default(L, 1, "glow", p.glow);
+
+ lua_getfield(L, 1, "node");
+ if (lua_istable(L, -1))
+ p.node = readnode(L, -1, getGameDef(L)->ndef());
+ lua_pop(L, 1);
- glow = getintfield_default(L, 1, "glow", 0);
+ p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile);
ClientEvent *event = new ClientEvent();
- event->type = CE_SPAWN_PARTICLE;
- event->spawn_particle.pos = new v3f (pos);
- event->spawn_particle.vel = new v3f (vel);
- event->spawn_particle.acc = new v3f (acc);
- event->spawn_particle.expirationtime = expirationtime;
- event->spawn_particle.size = size;
- event->spawn_particle.collisiondetection = collisiondetection;
- event->spawn_particle.collision_removal = collision_removal;
- event->spawn_particle.vertical = vertical;
- event->spawn_particle.texture = new std::string(texture);
- event->spawn_particle.animation = animation;
- event->spawn_particle.glow = glow;
+ event->type = CE_SPAWN_PARTICLE;
+ event->spawn_particle = new ParticleParameters(p);
getClient(L)->pushToEventQueue(event);
return 0;
@@ -90,94 +85,76 @@ int ModApiParticlesLocal::l_add_particle(lua_State *L)
int ModApiParticlesLocal::l_add_particlespawner(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
- // Get parameters
- u16 amount;
- v3f minpos, maxpos, minvel, maxvel, minacc, maxacc;
- float time, minexptime, maxexptime, minsize, maxsize;
- bool collisiondetection, vertical, collision_removal;
- struct TileAnimationParams animation;
- animation.type = TAT_NONE;
- // TODO: Implement this when there is a way to get an objectref.
- // ServerActiveObject *attached = NULL;
- std::string texture;
- u8 glow;
+ // Get parameters
+ ParticleSpawnerParameters p;
- amount = getintfield_default(L, 1, "amount", 1);
- time = getfloatfield_default(L, 1, "time", 1);
+ p.amount = getintfield_default(L, 1, "amount", p.amount);
+ p.time = getfloatfield_default(L, 1, "time", p.time);
lua_getfield(L, 1, "minpos");
- minpos = lua_istable(L, -1) ? check_v3f(L, -1) : v3f(0, 0, 0);
+ if (lua_istable(L, -1))
+ p.minpos = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "maxpos");
- maxpos = lua_istable(L, -1) ? check_v3f(L, -1) : v3f(0, 0, 0);
+ if (lua_istable(L, -1))
+ p.maxpos = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "minvel");
- minvel = lua_istable(L, -1) ? check_v3f(L, -1) : v3f(0, 0, 0);
+ if (lua_istable(L, -1))
+ p.minvel = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "maxvel");
- maxvel = lua_istable(L, -1) ? check_v3f(L, -1) : v3f(0, 0, 0);
+ if (lua_istable(L, -1))
+ p.maxvel = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "minacc");
- minacc = lua_istable(L, -1) ? check_v3f(L, -1) : v3f(0, 0, 0);
+ if (lua_istable(L, -1))
+ p.minacc = check_v3f(L, -1);
lua_pop(L, 1);
lua_getfield(L, 1, "maxacc");
- maxacc = lua_istable(L, -1) ? check_v3f(L, -1) : v3f(0, 0, 0);
+ if (lua_istable(L, -1))
+ p.maxacc = check_v3f(L, -1);
lua_pop(L, 1);
- minexptime = getfloatfield_default(L, 1, "minexptime", 1);
- maxexptime = getfloatfield_default(L, 1, "maxexptime", 1);
- minsize = getfloatfield_default(L, 1, "minsize", 1);
- maxsize = getfloatfield_default(L, 1, "maxsize", 1);
-
- collisiondetection = getboolfield_default(L, 1, "collisiondetection", false);
- collision_removal = getboolfield_default(L, 1, "collision_removal", false);
- vertical = getboolfield_default(L, 1, "vertical", false);
+ p.minexptime = getfloatfield_default(L, 1, "minexptime", p.minexptime);
+ p.maxexptime = getfloatfield_default(L, 1, "maxexptime", p.maxexptime);
+ p.minsize = getfloatfield_default(L, 1, "minsize", p.minsize);
+ p.maxsize = getfloatfield_default(L, 1, "maxsize", p.maxsize);
+ p.collisiondetection = getboolfield_default(L, 1,
+ "collisiondetection", p.collisiondetection);
+ p.collision_removal = getboolfield_default(L, 1,
+ "collision_removal", p.collision_removal);
+ p.object_collision = getboolfield_default(L, 1,
+ "object_collision", p.object_collision);
lua_getfield(L, 1, "animation");
- animation = read_animation_definition(L, -1);
+ p.animation = read_animation_definition(L, -1);
lua_pop(L, 1);
- // TODO: Implement this when a way to get an objectref on the client is added
-// lua_getfield(L, 1, "attached");
-// if (!lua_isnil(L, -1)) {
-// ObjectRef *ref = ObjectRef::checkobject(L, -1);
-// lua_pop(L, 1);
-// attached = ObjectRef::getobject(ref);
-// }
+ p.vertical = getboolfield_default(L, 1, "vertical", p.vertical);
+ p.texture = getstringfield_default(L, 1, "texture", p.texture);
+ p.glow = getintfield_default(L, 1, "glow", p.glow);
+
+ lua_getfield(L, 1, "node");
+ if (lua_istable(L, -1))
+ p.node = readnode(L, -1, getGameDef(L)->ndef());
+ lua_pop(L, 1);
- texture = getstringfield_default(L, 1, "texture", "");
- glow = getintfield_default(L, 1, "glow", 0);
+ p.node_tile = getintfield_default(L, 1, "node_tile", p.node_tile);
u64 id = getClient(L)->getParticleManager()->generateSpawnerId();
auto event = new ClientEvent();
- event->type = CE_ADD_PARTICLESPAWNER;
- event->add_particlespawner.amount = amount;
- event->add_particlespawner.spawntime = time;
- event->add_particlespawner.minpos = new v3f (minpos);
- event->add_particlespawner.maxpos = new v3f (maxpos);
- event->add_particlespawner.minvel = new v3f (minvel);
- event->add_particlespawner.maxvel = new v3f (maxvel);
- event->add_particlespawner.minacc = new v3f (minacc);
- event->add_particlespawner.maxacc = new v3f (maxacc);
- event->add_particlespawner.minexptime = minexptime;
- event->add_particlespawner.maxexptime = maxexptime;
- event->add_particlespawner.minsize = minsize;
- event->add_particlespawner.maxsize = maxsize;
- event->add_particlespawner.collisiondetection = collisiondetection;
- event->add_particlespawner.collision_removal = collision_removal;
- event->add_particlespawner.attached_id = 0;
- event->add_particlespawner.vertical = vertical;
- event->add_particlespawner.texture = new std::string(texture);
- event->add_particlespawner.id = id;
- event->add_particlespawner.animation = animation;
- event->add_particlespawner.glow = glow;
+ event->type = CE_ADD_PARTICLESPAWNER;
+ event->add_particlespawner.p = new ParticleSpawnerParameters(p);
+ event->add_particlespawner.attached_id = 0;
+ event->add_particlespawner.id = id;
getClient(L)->pushToEventQueue(event);
lua_pushnumber(L, id);
diff --git a/src/script/lua_api/l_particles_local.h b/src/script/lua_api/l_particles_local.h
index 5dff153b3..d8bb2b1c6 100644
--- a/src/script/lua_api/l_particles_local.h
+++ b/src/script/lua_api/l_particles_local.h
@@ -18,6 +18,8 @@ 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 "lua_api/l_base.h"
class ModApiParticlesLocal : public ModApiBase
diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp
index 00e849cdf..6f934bb9d 100644
--- a/src/script/lua_api/l_server.cpp
+++ b/src/script/lua_api/l_server.cpp
@@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "common/c_converter.h"
#include "common/c_content.h"
#include "cpp_api/s_base.h"
+#include "cpp_api/s_security.h"
#include "server.h"
#include "environment.h"
#include "remoteplayer.h"
@@ -138,52 +139,54 @@ int ModApiServer::l_get_player_ip(lua_State *L)
// get_player_information(name)
int ModApiServer::l_get_player_information(lua_State *L)
{
-
NO_MAP_LOCK_REQUIRED;
- const char * name = luaL_checkstring(L, 1);
- RemotePlayer *player = dynamic_cast<ServerEnvironment *>(getEnv(L))->getPlayer(name);
- if (player == NULL) {
+
+ Server *server = getServer(L);
+
+ const char *name = luaL_checkstring(L, 1);
+ RemotePlayer *player = server->getEnv().getPlayer(name);
+ if (!player) {
lua_pushnil(L); // no such player
return 1;
}
Address addr;
- try
- {
- addr = getServer(L)->getPeerAddress(player->getPeerId());
- } catch(const con::PeerNotFoundException &) {
+ try {
+ addr = server->getPeerAddress(player->getPeerId());
+ } catch (const con::PeerNotFoundException &) {
dstream << FUNCTION_NAME << ": peer was not found" << std::endl;
lua_pushnil(L); // error
return 1;
}
- float min_rtt,max_rtt,avg_rtt,min_jitter,max_jitter,avg_jitter;
+ float min_rtt, max_rtt, avg_rtt, min_jitter, max_jitter, avg_jitter;
ClientState state;
u32 uptime;
u16 prot_vers;
- u8 ser_vers,major,minor,patch;
- std::string vers_string;
-
-#define ERET(code) \
- if (!(code)) { \
- dstream << FUNCTION_NAME << ": peer was not found" << std::endl; \
- lua_pushnil(L); /* error */ \
- return 1; \
+ u8 ser_vers, major, minor, patch;
+ std::string vers_string, lang_code;
+
+ auto getConInfo = [&] (con::rtt_stat_type type, float *value) -> bool {
+ return server->getClientConInfo(player->getPeerId(), type, value);
+ };
+
+ bool have_con_info =
+ getConInfo(con::MIN_RTT, &min_rtt) &&
+ getConInfo(con::MAX_RTT, &max_rtt) &&
+ getConInfo(con::AVG_RTT, &avg_rtt) &&
+ getConInfo(con::MIN_JITTER, &min_jitter) &&
+ getConInfo(con::MAX_JITTER, &max_jitter) &&
+ getConInfo(con::AVG_JITTER, &avg_jitter);
+
+ bool r = server->getClientInfo(player->getPeerId(), &state, &uptime,
+ &ser_vers, &prot_vers, &major, &minor, &patch, &vers_string,
+ &lang_code);
+ if (!r) {
+ dstream << FUNCTION_NAME << ": peer was not found" << std::endl;
+ lua_pushnil(L); // error
+ return 1;
}
- ERET(getServer(L)->getClientConInfo(player->getPeerId(), con::MIN_RTT, &min_rtt))
- ERET(getServer(L)->getClientConInfo(player->getPeerId(), con::MAX_RTT, &max_rtt))
- ERET(getServer(L)->getClientConInfo(player->getPeerId(), con::AVG_RTT, &avg_rtt))
- ERET(getServer(L)->getClientConInfo(player->getPeerId(), con::MIN_JITTER,
- &min_jitter))
- ERET(getServer(L)->getClientConInfo(player->getPeerId(), con::MAX_JITTER,
- &max_jitter))
- ERET(getServer(L)->getClientConInfo(player->getPeerId(), con::AVG_JITTER,
- &avg_jitter))
-
- ERET(getServer(L)->getClientInfo(player->getPeerId(), &state, &uptime, &ser_vers,
- &prot_vers, &major, &minor, &patch, &vers_string))
-
lua_newtable(L);
int table = lua_gettop(L);
@@ -201,29 +204,31 @@ int ModApiServer::l_get_player_information(lua_State *L)
}
lua_settable(L, table);
- lua_pushstring(L,"min_rtt");
- lua_pushnumber(L, min_rtt);
- lua_settable(L, table);
+ if (have_con_info) { // may be missing
+ lua_pushstring(L, "min_rtt");
+ lua_pushnumber(L, min_rtt);
+ lua_settable(L, table);
- lua_pushstring(L,"max_rtt");
- lua_pushnumber(L, max_rtt);
- lua_settable(L, table);
+ lua_pushstring(L, "max_rtt");
+ lua_pushnumber(L, max_rtt);
+ lua_settable(L, table);
- lua_pushstring(L,"avg_rtt");
- lua_pushnumber(L, avg_rtt);
- lua_settable(L, table);
+ lua_pushstring(L, "avg_rtt");
+ lua_pushnumber(L, avg_rtt);
+ lua_settable(L, table);
- lua_pushstring(L,"min_jitter");
- lua_pushnumber(L, min_jitter);
- lua_settable(L, table);
+ lua_pushstring(L, "min_jitter");
+ lua_pushnumber(L, min_jitter);
+ lua_settable(L, table);
- lua_pushstring(L,"max_jitter");
- lua_pushnumber(L, max_jitter);
- lua_settable(L, table);
+ lua_pushstring(L, "max_jitter");
+ lua_pushnumber(L, max_jitter);
+ lua_settable(L, table);
- lua_pushstring(L,"avg_jitter");
- lua_pushnumber(L, avg_jitter);
- lua_settable(L, table);
+ lua_pushstring(L, "avg_jitter");
+ lua_pushnumber(L, avg_jitter);
+ lua_settable(L, table);
+ }
lua_pushstring(L,"connection_uptime");
lua_pushnumber(L, uptime);
@@ -237,6 +242,10 @@ int ModApiServer::l_get_player_information(lua_State *L)
lua_pushnumber(L, player->formspec_version);
lua_settable(L, table);
+ lua_pushstring(L, "lang_code");
+ lua_pushstring(L, lang_code.c_str());
+ lua_settable(L, table);
+
#ifndef NDEBUG
lua_pushstring(L,"serialization_version");
lua_pushnumber(L, ser_vers);
@@ -263,7 +272,6 @@ int ModApiServer::l_get_player_information(lua_State *L)
lua_settable(L, table);
#endif
-#undef ERET
return 1;
}
@@ -405,9 +413,6 @@ int ModApiServer::l_get_modnames(lua_State *L)
std::vector<std::string> modlist;
getServer(L)->getModNames(modlist);
- // Take unsorted items from mods_unsorted and sort them into
- // mods_sorted; not great performance but the number of mods on a
- // server will likely be small.
std::sort(modlist.begin(), modlist.end());
// Package them up for Lua
@@ -467,6 +472,23 @@ int ModApiServer::l_sound_fade(lua_State *L)
return 0;
}
+// dynamic_add_media(filepath)
+int ModApiServer::l_dynamic_add_media(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+
+ // Reject adding media before the server has started up
+ if (!getEnv(L))
+ throw LuaError("Dynamic media cannot be added before server has started up");
+
+ std::string filepath = readParam<std::string>(L, 1);
+ CHECK_SECURE_PATH(L, filepath.c_str(), false);
+
+ bool ok = getServer(L)->dynamicAddMedia(filepath);
+ lua_pushboolean(L, ok);
+ return 1;
+}
+
// is_singleplayer()
int ModApiServer::l_is_singleplayer(lua_State *L)
{
@@ -531,6 +553,7 @@ void ModApiServer::Initialize(lua_State *L, int top)
API_FCT(sound_play);
API_FCT(sound_stop);
API_FCT(sound_fade);
+ API_FCT(dynamic_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 3aa1785a2..938bfa8ef 100644
--- a/src/script/lua_api/l_server.h
+++ b/src/script/lua_api/l_server.h
@@ -70,6 +70,9 @@ private:
// sound_fade(handle, step, gain)
static int l_sound_fade(lua_State *L);
+ // dynamic_add_media(filepath)
+ static int l_dynamic_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_util.cpp b/src/script/lua_api/l_util.cpp
index ae3e5df3d..cd63e20c2 100644
--- a/src/script/lua_api/l_util.cpp
+++ b/src/script/lua_api/l_util.cpp
@@ -44,7 +44,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
// log([level,] text)
// Writes a line to the logger.
-// The one-argument version logs to infostream.
+// The one-argument version logs to LL_NONE.
// The two-argument version accepts a log level.
// Either the special case "deprecated" for deprecation notices, or any specified in
// Logger::stringToLevel(name).
@@ -318,9 +318,13 @@ int ModApiUtil::l_decode_base64(lua_State *L)
NO_MAP_LOCK_REQUIRED;
size_t size;
- const char *data = luaL_checklstring(L, 1, &size);
+ const char *d = luaL_checklstring(L, 1, &size);
+ const std::string data = std::string(d, size);
+
+ if (!base64_is_valid(data))
+ return 0;
- std::string out = base64_decode(std::string(data, size));
+ std::string out = base64_decode(data);
lua_pushlstring(L, out.data(), out.size());
return 1;
diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h
index 5697aab15..9ff91bb53 100644
--- a/src/script/lua_api/l_util.h
+++ b/src/script/lua_api/l_util.h
@@ -37,7 +37,7 @@ private:
// log([level,] text)
// Writes a line to the logger.
- // The one-argument version logs to infostream.
+ // The one-argument version logs to LL_NONE.
// The two-argument version accepts a log level.
static int l_log(lua_State *L);
diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp
index fd73d21d1..b99b1d98c 100644
--- a/src/script/lua_api/l_vmanip.cpp
+++ b/src/script/lua_api/l_vmanip.cpp
@@ -72,7 +72,7 @@ int LuaVoxelManip::l_get_data(lua_State *L)
if (use_buffer)
lua_pushvalue(L, 2);
else
- lua_newtable(L);
+ lua_createtable(L, volume, 0);
for (u32 i = 0; i != volume; i++) {
lua_Integer cid = vm->m_data[i].getContent();
@@ -261,7 +261,7 @@ int LuaVoxelManip::l_get_light_data(lua_State *L)
u32 volume = vm->m_area.getVolume();
- lua_newtable(L);
+ lua_createtable(L, volume, 0);
for (u32 i = 0; i != volume; i++) {
lua_Integer light = vm->m_data[i].param1;
lua_pushinteger(L, light);
@@ -309,7 +309,7 @@ int LuaVoxelManip::l_get_param2_data(lua_State *L)
if (use_buffer)
lua_pushvalue(L, 2);
else
- lua_newtable(L);
+ lua_createtable(L, volume, 0);
for (u32 i = 0; i != volume; i++) {
lua_Integer param2 = vm->m_data[i].param2;
diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp
index 1288b1df7..6643a9509 100644
--- a/src/script/scripting_client.cpp
+++ b/src/script/scripting_client.cpp
@@ -55,9 +55,6 @@ ClientScripting::ClientScripting(Client *client):
InitializeModApi(L, top);
lua_pop(L, 1);
- if (client->getMinimap())
- LuaMinimap::create(L, client->getMinimap());
-
// Push builtin initialization type
lua_pushstring(L, "client");
lua_setglobal(L, "INIT");
@@ -94,3 +91,8 @@ void ClientScripting::on_camera_ready(Camera *camera)
{
LuaCamera::create(getStack(), camera);
}
+
+void ClientScripting::on_minimap_ready(Minimap *minimap)
+{
+ LuaMinimap::create(getStack(), minimap);
+}
diff --git a/src/script/scripting_client.h b/src/script/scripting_client.h
index cfecfa165..3088029f0 100644
--- a/src/script/scripting_client.h
+++ b/src/script/scripting_client.h
@@ -28,6 +28,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class Client;
class LocalPlayer;
class Camera;
+class Minimap;
+
class ClientScripting:
virtual public ScriptApiBase,
public ScriptApiSecurity,
@@ -38,6 +40,7 @@ public:
ClientScripting(Client *client);
void on_client_ready(LocalPlayer *localplayer);
void on_camera_ready(Camera *camera);
+ void on_minimap_ready(Minimap *minimap);
private:
virtual void InitializeModApi(lua_State *L, int top);
diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp
index b6068439a..0f672f917 100644
--- a/src/script/scripting_mainmenu.cpp
+++ b/src/script/scripting_mainmenu.cpp
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "content/mods.h"
#include "cpp_api/s_internal.h"
#include "lua_api/l_base.h"
+#include "lua_api/l_http.h"
#include "lua_api/l_mainmenu.h"
#include "lua_api/l_sound.h"
#include "lua_api/l_util.h"
@@ -67,10 +68,12 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top)
ModApiMainMenu::Initialize(L, top);
ModApiUtil::Initialize(L, top);
ModApiSound::Initialize(L, top);
+ ModApiHttp::Initialize(L, top);
asyncEngine.registerStateInitializer(registerLuaClasses);
asyncEngine.registerStateInitializer(ModApiMainMenu::InitializeAsync);
asyncEngine.registerStateInitializer(ModApiUtil::InitializeAsync);
+ asyncEngine.registerStateInitializer(ModApiHttp::InitializeAsync);
// Initialize async environment
//TODO possibly make number of async threads configurable
diff --git a/src/script/scripting_server.cpp b/src/script/scripting_server.cpp
index cbf229640..85411ded4 100644
--- a/src/script/scripting_server.cpp
+++ b/src/script/scripting_server.cpp
@@ -62,6 +62,10 @@ ServerScripting::ServerScripting(Server* server):
if (g_settings->getBool("secure.enable_security")) {
initializeSecurity();
+ } else {
+ warningstream << "\\!/ Mod security should never be disabled, as it allows any mod to "
+ << "access the host machine."
+ << "Mods should use minetest.request_insecure_environment() instead \\!/" << std::endl;
}
lua_getglobal(L, "core");
diff --git a/src/server.cpp b/src/server.cpp
index 239ddd92e..fe2bb3840 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -34,8 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "version.h"
#include "filesys.h"
#include "mapblock.h"
-#include "serverobject.h"
-#include "genericobject.h"
+#include "server/serveractiveobject.h"
#include "settings.h"
#include "profiler.h"
#include "log.h"
@@ -48,7 +47,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapgen/mg_biome.h"
#include "content_mapnode.h"
#include "content_nodemeta.h"
-#include "content_sao.h"
#include "content/mods.h"
#include "modchannels.h"
#include "serverlist.h"
@@ -65,6 +63,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "chatmessage.h"
#include "chat_interface.h"
#include "remoteplayer.h"
+#include "server/player_sao.h"
+#include "server/serverinventorymgr.h"
+#include "translation.h"
class ClientNotFoundException : public BaseException
{
@@ -229,18 +230,46 @@ Server::Server(
m_nodedef(createNodeDefManager()),
m_craftdef(createCraftDefManager()),
m_thread(new ServerThread(this)),
- m_uptime(0),
m_clients(m_con),
m_admin_chat(iface),
m_modchannel_mgr(new ModChannelMgr())
{
- m_lag = g_settings->getFloat("dedicated_server_step");
-
if (m_path_world.empty())
throw ServerError("Supplied empty world path");
if (!gamespec.isValid())
throw ServerError("Supplied invalid gamespec");
+
+#if USE_PROMETHEUS
+ m_metrics_backend = std::unique_ptr<MetricsBackend>(createPrometheusMetricsBackend());
+#else
+ m_metrics_backend = std::unique_ptr<MetricsBackend>(new MetricsBackend());
+#endif
+
+ m_uptime_counter = m_metrics_backend->addCounter("minetest_core_server_uptime", "Server uptime (in seconds)");
+ m_player_gauge = m_metrics_backend->addGauge("minetest_core_player_number", "Number of connected players");
+
+ m_timeofday_gauge = m_metrics_backend->addGauge(
+ "minetest_core_timeofday",
+ "Time of day value");
+
+ m_lag_gauge = m_metrics_backend->addGauge(
+ "minetest_core_latency",
+ "Latency value (in seconds)");
+
+ m_aom_buffer_counter = m_metrics_backend->addCounter(
+ "minetest_core_aom_generated_count",
+ "Number of active object messages generated");
+
+ m_packet_recv_counter = m_metrics_backend->addCounter(
+ "minetest_core_server_packet_recv",
+ "Processable packets received");
+
+ m_packet_recv_processed_counter = m_metrics_backend->addCounter(
+ "minetest_core_server_packet_recv_processed",
+ "Valid received packets processed");
+
+ m_lag_gauge->set(g_settings->getFloat("dedicated_server_step"));
}
Server::~Server()
@@ -310,11 +339,6 @@ Server::~Server()
infostream << "Server: Deinitializing scripting" << std::endl;
delete m_script;
- // Delete detached inventories
- for (auto &detached_inventory : m_detached_inventories) {
- delete detached_inventory.second;
- }
-
while (!m_unsent_map_edit_queue.empty()) {
delete m_unsent_map_edit_queue.front();
m_unsent_map_edit_queue.pop();
@@ -353,13 +377,16 @@ void Server::init()
MutexAutoLock envlock(m_env_mutex);
// Create the Map (loads map_meta.txt, overriding configured mapgen params)
- ServerMap *servermap = new ServerMap(m_path_world, this, m_emerge);
+ ServerMap *servermap = new ServerMap(m_path_world, this, m_emerge, m_metrics_backend.get());
// Initialize scripting
infostream << "Server: Initializing Lua" << std::endl;
m_script = new ServerScripting(this);
+ // Must be created before mod loading because we have some inventory creation
+ m_inventory_mgr = std::unique_ptr<ServerInventoryManager>(new ServerInventoryManager());
+
m_script->loadMod(getBuiltinLuaPath() + DIR_DELIM "init.lua", BUILTIN_MOD_NAME);
m_modmgr->loadMods(m_script);
@@ -374,16 +401,19 @@ void Server::init()
std::vector<std::string> paths;
fs::GetRecursiveDirs(paths, g_settings->get("texture_path"));
fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures");
- for (const std::string &path : paths)
- m_nodedef->applyTextureOverrides(path + DIR_DELIM + "override.txt");
+ for (const std::string &path : paths) {
+ TextureOverrideSource override_source(path + DIR_DELIM + "override.txt");
+ m_nodedef->applyTextureOverrides(override_source.getNodeTileOverrides());
+ m_itemdef->applyTextureOverrides(override_source.getItemTextureOverrides());
+ }
m_nodedef->setNodeRegistrationStatus(true);
// Perform pending node name resolutions
m_nodedef->runNodeResolveCallbacks();
- // unmap node names for connected nodeboxes
- m_nodedef->mapNodeboxConnections();
+ // unmap node names in cross-references
+ m_nodedef->resolveCrossrefs();
// init the recipe hashes to speed up crafting
m_craftdef->initHashes(this);
@@ -391,6 +421,7 @@ void Server::init()
// Initialize Environment
m_env = new ServerEnvironment(servermap, m_script, this, m_path_world);
+ m_inventory_mgr->setEnv(m_env);
m_clients.setEnv(m_env);
if (!servermap->settings_mgr.makeMapgenParams())
@@ -412,6 +443,8 @@ void Server::init()
m_env->loadMeta();
+ // Those settings can be overwritten in world.mt, they are
+ // intended to be cached after environment loading.
m_liquid_transform_every = g_settings->getFloat("liquid_update");
m_max_chatmessage_length = g_settings->getU16("chat_message_max_size");
m_csm_restriction_flags = g_settings->getU64("csm_restriction_flags");
@@ -420,6 +453,8 @@ void Server::init()
void Server::start()
{
+ init();
+
infostream << "Starting server on " << m_bind_addr.serializeString()
<< "..." << std::endl;
@@ -508,9 +543,7 @@ void Server::AsyncRunStep(bool initial_step)
/*
Update uptime
*/
- {
- m_uptime.set(m_uptime.get() + dtime);
- }
+ m_uptime_counter->increment(dtime);
handlePeerChanges();
@@ -524,11 +557,13 @@ void Server::AsyncRunStep(bool initial_step)
*/
m_time_of_day_send_timer -= dtime;
- if(m_time_of_day_send_timer < 0.0) {
+ if (m_time_of_day_send_timer < 0.0) {
m_time_of_day_send_timer = g_settings->getFloat("time_send_interval");
u16 time = m_env->getTimeOfDay();
float time_speed = g_settings->getFloat("time_speed");
SendTimeOfDay(PEER_ID_INEXISTENT, time, time_speed);
+
+ m_timeofday_gauge->set(time);
}
{
@@ -600,7 +635,7 @@ void Server::AsyncRunStep(bool initial_step)
}
m_clients.step(dtime);
- m_lag += (m_lag > dtime ? -1 : 1) * dtime/100;
+ m_lag_gauge->increment((m_lag_gauge->get() > dtime ? -1 : 1) * dtime/100);
#if USE_CURL
// send masterserver announce
{
@@ -611,9 +646,9 @@ void Server::AsyncRunStep(bool initial_step)
ServerList::AA_START,
m_bind_addr.getPort(),
m_clients.getPlayerNames(),
- m_uptime.get(),
+ m_uptime_counter->get(),
m_env->getGameTime(),
- m_lag,
+ m_lag_gauge->get(),
m_gamespec.id,
Mapgen::getMapgenName(m_emerge->mgparams->mgtype),
m_modmgr->getMods(),
@@ -635,6 +670,7 @@ void Server::AsyncRunStep(bool initial_step)
const RemoteClientMap &clients = m_clients.getClientList();
ScopeProfiler sp(g_profiler, "Server: update objects within range");
+ m_player_gauge->set(clients.size());
for (const auto &client_it : clients) {
RemoteClient *client = client_it.second;
@@ -656,14 +692,17 @@ void Server::AsyncRunStep(bool initial_step)
// Save mod storages if modified
m_mod_storage_save_timer -= dtime;
if (m_mod_storage_save_timer <= 0.0f) {
- infostream << "Saving registered mod storages." << std::endl;
m_mod_storage_save_timer = g_settings->getFloat("server_map_save_interval");
+ int n = 0;
for (std::unordered_map<std::string, ModMetadata *>::const_iterator
it = m_mod_storages.begin(); it != m_mod_storages.end(); ++it) {
if (it->second->isModified()) {
it->second->save(getModStoragePath());
+ n++;
}
}
+ if (n > 0)
+ infostream << "Saved " << n << " modified mod storages." << std::endl;
}
}
@@ -679,32 +718,35 @@ void Server::AsyncRunStep(bool initial_step)
std::unordered_map<u16, std::vector<ActiveObjectMessage>*> buffered_messages;
// Get active object messages from environment
+ ActiveObjectMessage aom(0);
+ u32 aom_count = 0;
for(;;) {
- ActiveObjectMessage aom = m_env->getActiveObjectMessage();
- if (aom.id == 0)
+ if (!m_env->getActiveObjectMessage(&aom))
break;
std::vector<ActiveObjectMessage>* message_list = nullptr;
- std::unordered_map<u16, std::vector<ActiveObjectMessage>* >::iterator n;
- n = buffered_messages.find(aom.id);
+ auto n = buffered_messages.find(aom.id);
if (n == buffered_messages.end()) {
message_list = new std::vector<ActiveObjectMessage>;
buffered_messages[aom.id] = message_list;
- }
- else {
+ } else {
message_list = n->second;
}
- message_list->push_back(aom);
+ message_list->push_back(std::move(aom));
+ aom_count++;
}
+ m_aom_buffer_counter->increment(aom_count);
+
m_clients.lock();
const RemoteClientMap &clients = m_clients.getClientList();
// Route data to every client
+ std::string reliable_data, unreliable_data;
for (const auto &client_it : clients) {
+ reliable_data.clear();
+ unreliable_data.clear();
RemoteClient *client = client_it.second;
PlayerSAO *player = getPlayerSAO(client->peer_id);
- std::string reliable_data;
- std::string unreliable_data;
// Go through all objects in message buffer
for (const auto &buffered_message : buffered_messages) {
// If object does not exist or is not known by client, skip it
@@ -718,7 +760,7 @@ void Server::AsyncRunStep(bool initial_step)
// Go through every message
for (const ActiveObjectMessage &aom : *list) {
// Send position updates to players who do not see the attachment
- if (aom.datastring[0] == GENERIC_CMD_UPDATE_POSITION) {
+ if (aom.datastring[0] == AO_CMD_UPDATE_POSITION) {
if (sao->getId() == player->getId())
continue;
@@ -729,19 +771,15 @@ void Server::AsyncRunStep(bool initial_step)
client->m_known_objects.end())
continue;
}
- // Compose the full new data with header
- std::string new_data;
- // Add object id
- char buf[2];
- writeU16((u8*)&buf[0], aom.id);
- new_data.append(buf, 2);
- // Add data
- new_data += serializeString(aom.datastring);
- // Add data to buffer
- if (aom.reliable)
- reliable_data += new_data;
- else
- unreliable_data += new_data;
+
+ // Add full new data to appropriate buffer
+ std::string &buffer = aom.reliable ? reliable_data : unreliable_data;
+ char idbuf[2];
+ writeU16((u8*) idbuf, aom.id);
+ // u16 id
+ // std::string data
+ buffer.append(idbuf, sizeof(idbuf));
+ buffer.append(serializeString(aom.datastring));
}
}
/*
@@ -809,7 +847,6 @@ void Server::AsyncRunStep(bool initial_step)
disable_single_change_sending ? 5 : 30);
break;
case MEET_BLOCK_NODE_METADATA_CHANGED: {
- verbosestream << "Server: MEET_BLOCK_NODE_METADATA_CHANGED" << std::endl;
prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1);
if (!event->is_private_change) {
// Don't send the change yet. Collect them to eliminate dupes.
@@ -825,7 +862,6 @@ void Server::AsyncRunStep(bool initial_step)
break;
}
case MEET_OTHER:
- infostream << "Server: MEET_OTHER" << std::endl;
prof.add("MEET_OTHER", 1);
for (const v3s16 &modified_block : event->modified_blocks) {
m_clients.markBlockposAsNotSent(modified_block);
@@ -939,7 +975,9 @@ void Server::Receive()
}
peer_id = pkt.getPeerId();
+ m_packet_recv_counter->increment();
ProcessData(&pkt);
+ m_packet_recv_processed_counter->increment();
} catch (const con::InvalidIncomingDataException &e) {
infostream << "Server::Receive(): InvalidIncomingDataException: what()="
<< e.what() << std::endl;
@@ -1143,82 +1181,6 @@ void Server::onMapEditEvent(const MapEditEvent &event)
m_unsent_map_edit_queue.push(new MapEditEvent(event));
}
-Inventory* Server::getInventory(const InventoryLocation &loc)
-{
- switch (loc.type) {
- case InventoryLocation::UNDEFINED:
- case InventoryLocation::CURRENT_PLAYER:
- break;
- case InventoryLocation::PLAYER:
- {
- RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
- if(!player)
- return NULL;
- PlayerSAO *playersao = player->getPlayerSAO();
- if(!playersao)
- return NULL;
- return playersao->getInventory();
- }
- break;
- case InventoryLocation::NODEMETA:
- {
- NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p);
- if(!meta)
- return NULL;
- return meta->getInventory();
- }
- break;
- case InventoryLocation::DETACHED:
- {
- if(m_detached_inventories.count(loc.name) == 0)
- return NULL;
- return m_detached_inventories[loc.name];
- }
- break;
- default:
- sanity_check(false); // abort
- break;
- }
- return NULL;
-}
-
-void Server::setInventoryModified(const InventoryLocation &loc)
-{
- switch(loc.type){
- case InventoryLocation::UNDEFINED:
- break;
- case InventoryLocation::PLAYER:
- {
-
- RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
-
- if (!player)
- return;
-
- player->setModified(true);
- player->inventory.setModified(true);
- // Updates are sent in ServerEnvironment::step()
- }
- break;
- case InventoryLocation::NODEMETA:
- {
- MapEditEvent event;
- event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
- event.p = loc.p;
- m_env->getMap().dispatchEvent(event);
- }
- break;
- case InventoryLocation::DETACHED:
- {
- // Updates are sent in ServerEnvironment::step()
- }
- break;
- default:
- sanity_check(false); // abort
- break;
- }
-}
-
void Server::SetBlocksNotSent(std::map<v3s16, MapBlock *>& block)
{
std::vector<session_t> clients = m_clients.getClientIDs();
@@ -1263,7 +1225,8 @@ bool Server::getClientInfo(
u8* major,
u8* minor,
u8* patch,
- std::string* vers_string
+ std::string* vers_string,
+ std::string* lang_code
)
{
*state = m_clients.getClientState(peer_id);
@@ -1283,6 +1246,7 @@ bool Server::getClientInfo(
*minor = client->getMinor();
*patch = client->getPatch();
*vers_string = client->getFull();
+ *lang_code = client->getLangCode();
m_clients.unlock();
@@ -1537,17 +1501,15 @@ void Server::SendShowFormspecMessage(session_t peer_id, const std::string &forms
// Spawns a particle on peer with peer_id
void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version,
- v3f pos, v3f velocity, v3f acceleration,
- float expirationtime, float size, bool collisiondetection,
- bool collision_removal, bool object_collision,
- bool vertical, const std::string &texture,
- const struct TileAnimationParams &animation, u8 glow)
+ const ParticleParameters &p)
{
static thread_local const float radius =
g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * BS;
if (peer_id == PEER_ID_INEXISTENT) {
std::vector<session_t> clients = m_clients.getClientIDs();
+ const v3f pos = p.pos * BS;
+ const float radius_sq = radius * radius;
for (const session_t client_id : clients) {
RemotePlayer *player = m_env->getPlayer(client_id);
@@ -1559,76 +1521,78 @@ void Server::SendSpawnParticle(session_t peer_id, u16 protocol_version,
continue;
// Do not send to distant clients
- if (sao->getBasePosition().getDistanceFrom(pos * BS) > radius)
+ if (sao->getBasePosition().getDistanceFromSQ(pos) > radius_sq)
continue;
- SendSpawnParticle(client_id, player->protocol_version,
- pos, velocity, acceleration,
- expirationtime, size, collisiondetection, collision_removal,
- object_collision, vertical, texture, animation, glow);
+ SendSpawnParticle(client_id, player->protocol_version, p);
}
return;
}
+ assert(protocol_version != 0);
NetworkPacket pkt(TOCLIENT_SPAWN_PARTICLE, 0, peer_id);
- pkt << pos << velocity << acceleration << expirationtime
- << size << collisiondetection;
- pkt.putLongString(texture);
- pkt << vertical;
- pkt << collision_removal;
- // This is horrible but required (why are there two ways to serialize pkts?)
- std::ostringstream os(std::ios_base::binary);
- animation.serialize(os, protocol_version);
- pkt.putRawString(os.str());
- pkt << glow;
- pkt << object_collision;
+ {
+ // NetworkPacket and iostreams are incompatible...
+ std::ostringstream oss(std::ios_base::binary);
+ p.serialize(oss, protocol_version);
+ pkt.putRawString(oss.str());
+ }
Send(&pkt);
}
// Adds a ParticleSpawner on peer with peer_id
void Server::SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
- u16 amount, float spawntime, v3f minpos, v3f maxpos,
- v3f minvel, v3f maxvel, v3f minacc, v3f maxacc, float minexptime, float maxexptime,
- float minsize, float maxsize, bool collisiondetection, bool collision_removal,
- bool object_collision, u16 attached_id, bool vertical, const std::string &texture, u32 id,
- const struct TileAnimationParams &animation, u8 glow)
+ const ParticleSpawnerParameters &p, u16 attached_id, u32 id)
{
+ static thread_local const float radius =
+ g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE * BS;
+
if (peer_id == PEER_ID_INEXISTENT) {
- // This sucks and should be replaced:
std::vector<session_t> clients = m_clients.getClientIDs();
+ const v3f pos = (p.minpos + p.maxpos) / 2.0f * BS;
+ const float radius_sq = radius * radius;
+ /* Don't send short-lived spawners to distant players.
+ * This could be replaced with proper tracking at some point. */
+ const bool distance_check = !attached_id && p.time <= 1.0f;
+
for (const session_t client_id : clients) {
RemotePlayer *player = m_env->getPlayer(client_id);
if (!player)
continue;
+
+ if (distance_check) {
+ PlayerSAO *sao = player->getPlayerSAO();
+ if (!sao)
+ continue;
+ if (sao->getBasePosition().getDistanceFromSQ(pos) > radius_sq)
+ continue;
+ }
+
SendAddParticleSpawner(client_id, player->protocol_version,
- amount, spawntime, minpos, maxpos,
- minvel, maxvel, minacc, maxacc, minexptime, maxexptime,
- minsize, maxsize, collisiondetection, collision_removal,
- object_collision, attached_id, vertical, texture, id,
- animation, glow);
+ p, attached_id, id);
}
return;
}
+ assert(protocol_version != 0);
- NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 0, peer_id);
+ NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 100, peer_id);
- pkt << amount << spawntime << minpos << maxpos << minvel << maxvel
- << minacc << maxacc << minexptime << maxexptime << minsize
- << maxsize << collisiondetection;
+ pkt << p.amount << p.time << p.minpos << p.maxpos << p.minvel
+ << p.maxvel << p.minacc << p.maxacc << p.minexptime << p.maxexptime
+ << p.minsize << p.maxsize << p.collisiondetection;
- pkt.putLongString(texture);
+ pkt.putLongString(p.texture);
- pkt << id << vertical;
- pkt << collision_removal;
- pkt << attached_id;
- // This is horrible but required
- std::ostringstream os(std::ios_base::binary);
- animation.serialize(os, protocol_version);
- pkt.putRawString(os.str());
- pkt << glow;
- pkt << object_collision;
+ pkt << id << p.vertical << p.collision_removal << attached_id;
+ {
+ std::ostringstream os(std::ios_base::binary);
+ p.animation.serialize(os, protocol_version);
+ pkt.putRawString(os.str());
+ }
+ pkt << p.glow << p.object_collision;
+ pkt << p.node.param0 << p.node.param2 << p.node_tile;
Send(&pkt);
}
@@ -1637,7 +1601,6 @@ void Server::SendDeleteParticleSpawner(session_t peer_id, u32 id)
{
NetworkPacket pkt(TOCLIENT_DELETE_PARTICLESPAWNER, 4, peer_id);
- // Ugly error in this packet
pkt << id;
if (peer_id != PEER_ID_INEXISTENT)
@@ -1654,7 +1617,7 @@ void Server::SendHUDAdd(session_t peer_id, u32 id, HudElement *form)
pkt << id << (u8) form->type << form->pos << form->name << form->scale
<< form->text << form->number << form->item << form->dir
<< form->align << form->offset << form->world_pos << form->size
- << form->z_index;
+ << form->z_index << form->text2;
Send(&pkt);
}
@@ -1680,6 +1643,7 @@ void Server::SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void
break;
case HUD_STAT_NAME:
case HUD_STAT_TEXT:
+ case HUD_STAT_TEXT2:
pkt << *(std::string *) value;
break;
case HUD_STAT_WORLD_POS:
@@ -1731,8 +1695,8 @@ void Server::SendSetSky(session_t peer_id, const SkyboxParams &params)
pkt << params.clouds;
} else { // Handle current clients and future clients
pkt << params.bgcolor << params.type
- << params.clouds << params.sun_tint
- << params.moon_tint << params.tint_type;
+ << params.clouds << params.fog_sun_tint
+ << params.fog_moon_tint << params.fog_tint_type;
if (params.type == "skybox") {
pkt << (u16) params.textures.size();
@@ -1818,9 +1782,7 @@ void Server::SendPlayerHP(session_t peer_id)
m_script->player_event(playersao,"health_changed");
// Send to other clients
- std::string str = gob_cmd_punched(playersao->getHP());
- ActiveObjectMessage aom(playersao->getId(), true, str);
- playersao->m_messages_out.push(aom);
+ playersao->sendPunchCommand();
}
void Server::SendPlayerBreath(PlayerSAO *sao)
@@ -1855,10 +1817,10 @@ void Server::SendMovePlayer(session_t peer_id)
void Server::SendPlayerFov(session_t peer_id)
{
- NetworkPacket pkt(TOCLIENT_FOV, 4 + 1, peer_id);
+ NetworkPacket pkt(TOCLIENT_FOV, 4 + 1 + 4, peer_id);
PlayerFovSpec fov_spec = m_env->getPlayer(peer_id)->getFov();
- pkt << fov_spec.fov << fov_spec.is_multiplier;
+ pkt << fov_spec.fov << fov_spec.is_multiplier << fov_spec.transition_time;
Send(&pkt);
}
@@ -2443,9 +2405,87 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos)
return true;
}
+bool Server::addMediaFile(const std::string &filename,
+ const std::string &filepath, std::string *filedata_to,
+ std::string *digest_to)
+{
+ // If name contains illegal characters, ignore the file
+ if (!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) {
+ infostream << "Server: ignoring illegal file name: \""
+ << filename << "\"" << std::endl;
+ return false;
+ }
+ // If name is not in a supported format, ignore it
+ const char *supported_ext[] = {
+ ".png", ".jpg", ".bmp", ".tga",
+ ".pcx", ".ppm", ".psd", ".wal", ".rgb",
+ ".ogg",
+ ".x", ".b3d", ".md2", ".obj",
+ // Custom translation file format
+ ".tr",
+ NULL
+ };
+ if (removeStringEnd(filename, supported_ext).empty()) {
+ infostream << "Server: ignoring unsupported file extension: \""
+ << filename << "\"" << std::endl;
+ return false;
+ }
+ // Ok, attempt to load the file and add to cache
+
+ // Read data
+ std::ifstream fis(filepath.c_str(), std::ios_base::binary);
+ if (!fis.good()) {
+ errorstream << "Server::addMediaFile(): Could not open \""
+ << filename << "\" for reading" << std::endl;
+ return false;
+ }
+ std::string filedata;
+ bool bad = false;
+ for (;;) {
+ char buf[1024];
+ fis.read(buf, sizeof(buf));
+ std::streamsize len = fis.gcount();
+ filedata.append(buf, len);
+ if (fis.eof())
+ break;
+ if (!fis.good()) {
+ bad = true;
+ break;
+ }
+ }
+ if (bad) {
+ errorstream << "Server::addMediaFile(): Failed to read \""
+ << filename << "\"" << std::endl;
+ return false;
+ } else if (filedata.empty()) {
+ errorstream << "Server::addMediaFile(): Empty file \""
+ << filepath << "\"" << std::endl;
+ return false;
+ }
+
+ SHA1 sha1;
+ sha1.addBytes(filedata.c_str(), filedata.length());
+
+ 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;
+
+ if (filedata_to)
+ *filedata_to = std::move(filedata);
+ return true;
+}
+
void Server::fillMediaCache()
{
- infostream<<"Server: Calculating media file checksums"<<std::endl;
+ infostream << "Server: Calculating media file checksums" << std::endl;
// Collect all media file paths
std::vector<std::string> paths;
@@ -2457,87 +2497,19 @@ void Server::fillMediaCache()
for (const std::string &mediapath : paths) {
std::vector<fs::DirListNode> dirlist = fs::GetDirListing(mediapath);
for (const fs::DirListNode &dln : dirlist) {
- if (dln.dir) // Ignode dirs
- continue;
- std::string filename = dln.name;
- // If name contains illegal characters, ignore the file
- if (!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) {
- infostream<<"Server: ignoring illegal file name: \""
- << filename << "\"" << std::endl;
- continue;
- }
- // If name is not in a supported format, ignore it
- const char *supported_ext[] = {
- ".png", ".jpg", ".bmp", ".tga",
- ".pcx", ".ppm", ".psd", ".wal", ".rgb",
- ".ogg",
- ".x", ".b3d", ".md2", ".obj",
- // Custom translation file format
- ".tr",
- NULL
- };
- if (removeStringEnd(filename, supported_ext).empty()){
- infostream << "Server: ignoring unsupported file extension: \""
- << filename << "\"" << std::endl;
- continue;
- }
- // Ok, attempt to load the file and add to cache
- std::string filepath;
- filepath.append(mediapath).append(DIR_DELIM).append(filename);
-
- // Read data
- std::ifstream fis(filepath.c_str(), std::ios_base::binary);
- if (!fis.good()) {
- errorstream << "Server::fillMediaCache(): Could not open \""
- << filename << "\" for reading" << std::endl;
- continue;
- }
- std::ostringstream tmp_os(std::ios_base::binary);
- bool bad = false;
- for(;;) {
- char buf[1024];
- fis.read(buf, 1024);
- std::streamsize len = fis.gcount();
- tmp_os.write(buf, len);
- if (fis.eof())
- break;
- if (!fis.good()) {
- bad = true;
- break;
- }
- }
- if(bad) {
- errorstream<<"Server::fillMediaCache(): Failed to read \""
- << filename << "\"" << std::endl;
+ if (dln.dir) // Ignore dirs
continue;
- }
- if(tmp_os.str().length() == 0) {
- errorstream << "Server::fillMediaCache(): Empty file \""
- << filepath << "\"" << std::endl;
- continue;
- }
-
- SHA1 sha1;
- sha1.addBytes(tmp_os.str().c_str(), tmp_os.str().length());
-
- unsigned char *digest = sha1.getDigest();
- std::string sha1_base64 = base64_encode(digest, 20);
- std::string sha1_hex = hex_encode((char*)digest, 20);
- free(digest);
-
- // Put in list
- m_media[filename] = MediaInfo(filepath, sha1_base64);
- verbosestream << "Server: " << sha1_hex << " is " << filename
- << std::endl;
+ std::string filepath = mediapath;
+ filepath.append(DIR_DELIM).append(dln.name);
+ addMediaFile(dln.name, filepath);
}
}
+
+ infostream << "Server: " << m_media.size() << " media files collected" << std::endl;
}
void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code)
{
- verbosestream << "Server: Announcing files to id(" << peer_id << ")"
- << std::endl;
-
// Make packet
NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id);
@@ -2560,6 +2532,9 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co
pkt << g_settings->get("remote_media");
Send(&pkt);
+
+ verbosestream << "Server: Announcing files to id(" << peer_id
+ << "): count=" << media_sent << " size=" << pkt.getSize() << std::endl;
}
struct SendableMedia
@@ -2675,40 +2650,20 @@ void Server::sendRequestedMedia(session_t peer_id,
}
}
-void Server::sendDetachedInventory(const std::string &name, session_t peer_id)
+void Server::sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id)
{
- const auto &inv_it = m_detached_inventories.find(name);
- const auto &player_it = m_detached_inventories_player.find(name);
-
- if (player_it == m_detached_inventories_player.end() ||
- player_it->second.empty()) {
- // OK. Send to everyone
- } else {
- if (!m_env)
- return; // Mods are not done loading
-
- RemotePlayer *p = m_env->getPlayer(player_it->second.c_str());
- if (!p)
- return; // Player is offline
-
- if (peer_id != PEER_ID_INEXISTENT && peer_id != p->getPeerId())
- return; // Caller requested send to a different player, so don't send.
-
- peer_id = p->getPeerId();
- }
-
NetworkPacket pkt(TOCLIENT_DETACHED_INVENTORY, 0, peer_id);
pkt << name;
- if (inv_it == m_detached_inventories.end()) {
+ if (!inventory) {
pkt << false; // Remove inventory
} else {
pkt << true; // Update inventory
// Serialization & NetworkPacket isn't a love story
std::ostringstream os(std::ios_base::binary);
- inv_it->second->serialize(os);
- inv_it->second->setModified(false);
+ inventory->serialize(os);
+ inventory->setModified(false);
const std::string &os_str = os.str();
pkt << static_cast<u16>(os_str.size()); // HACK: to keep compatibility with 5.0.0 clients
@@ -2723,16 +2678,17 @@ void Server::sendDetachedInventory(const std::string &name, session_t peer_id)
void Server::sendDetachedInventories(session_t peer_id, bool incremental)
{
- for (const auto &detached_inventory : m_detached_inventories) {
- const std::string &name = detached_inventory.first;
- if (incremental) {
- Inventory *inv = detached_inventory.second;
- if (!inv || !inv->checkModified())
- continue;
- }
-
- sendDetachedInventory(name, peer_id);
+ // Lookup player name, to filter detached inventories just after
+ std::string peer_name;
+ if (peer_id != PEER_ID_INEXISTENT) {
+ peer_name = getClient(peer_id, CS_Created)->getName();
}
+
+ auto send_cb = [this, peer_id](const std::string &name, Inventory *inv) {
+ sendDetachedInventory(inv, name, peer_id);
+ };
+
+ m_inventory_mgr->sendDetachedInventories(peer_name, incremental, send_cb);
}
/*
@@ -2938,10 +2894,8 @@ void Server::UpdateCrafting(RemotePlayer *player)
if (!clist || clist->getSize() == 0)
return;
- if (!clist->checkModified()) {
- verbosestream << "Skip Server::UpdateCrafting(): list unmodified" << std::endl;
+ if (!clist->checkModified())
return;
- }
// Get a preview for crafting
ItemStack preview;
@@ -3032,8 +2986,16 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna
line += L"-!- You don't have permission to shout.";
broadcast_line = false;
} else {
+ /*
+ Workaround for fixing chat on Android. Lua doesn't handle
+ the Cyrillic alphabet and some characters on older Android devices
+ */
+#ifdef __ANDROID__
+ line += L"<" + wname + L"> " + wmessage;
+#else
line += narrow_to_wide(m_script->formatChatMessage(name,
wide_to_narrow(wmessage)));
+#endif
}
/*
@@ -3117,7 +3079,7 @@ std::wstring Server::getStatusString()
// Version
os << L"version=" << narrow_to_wide(g_version_string);
// Uptime
- os << L", uptime=" << m_uptime.get();
+ os << L", uptime=" << m_uptime_counter->get();
// Max lag estimate
os << L", max_lag=" << (m_env ? m_env->getMaxLagEstimate() : 0);
@@ -3399,15 +3361,12 @@ void Server::setClouds(RemotePlayer *player, const CloudParams &params)
SendCloudParams(player->getPeerId(), params);
}
-bool Server::overrideDayNightRatio(RemotePlayer *player, bool do_override,
+void Server::overrideDayNightRatio(RemotePlayer *player, bool do_override,
float ratio)
{
- if (!player)
- return false;
-
+ sanity_check(player);
player->overrideDayNightRatio(do_override, ratio);
SendOverrideDayNightRatio(player->getPeerId(), do_override, ratio);
- return true;
}
void Server::notifyPlayers(const std::wstring &msg)
@@ -3415,12 +3374,8 @@ void Server::notifyPlayers(const std::wstring &msg)
SendChatMessage(PEER_ID_INEXISTENT, ChatMessage(msg));
}
-void Server::spawnParticle(const std::string &playername, v3f pos,
- v3f velocity, v3f acceleration,
- float expirationtime, float size, bool
- collisiondetection, bool collision_removal, bool object_collision,
- bool vertical, const std::string &texture,
- const struct TileAnimationParams &animation, u8 glow)
+void Server::spawnParticle(const std::string &playername,
+ const ParticleParameters &p)
{
// m_env will be NULL if the server is initializing
if (!m_env)
@@ -3436,18 +3391,11 @@ void Server::spawnParticle(const std::string &playername, v3f pos,
proto_ver = player->protocol_version;
}
- SendSpawnParticle(peer_id, proto_ver, pos, velocity, acceleration,
- expirationtime, size, collisiondetection, collision_removal,
- object_collision, vertical, texture, animation, glow);
+ SendSpawnParticle(peer_id, proto_ver, p);
}
-u32 Server::addParticleSpawner(u16 amount, float spawntime,
- v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
- float minexptime, float maxexptime, float minsize, float maxsize,
- bool collisiondetection, bool collision_removal, bool object_collision,
- ServerActiveObject *attached, bool vertical, const std::string &texture,
- const std::string &playername, const struct TileAnimationParams &animation,
- u8 glow)
+u32 Server::addParticleSpawner(const ParticleSpawnerParameters &p,
+ ServerActiveObject *attached, const std::string &playername)
{
// m_env will be NULL if the server is initializing
if (!m_env)
@@ -3467,16 +3415,11 @@ u32 Server::addParticleSpawner(u16 amount, float spawntime,
u32 id;
if (attached_id == 0)
- id = m_env->addParticleSpawner(spawntime);
+ id = m_env->addParticleSpawner(p.time);
else
- id = m_env->addParticleSpawner(spawntime, attached_id);
-
- SendAddParticleSpawner(peer_id, proto_ver, amount, spawntime,
- minpos, maxpos, minvel, maxvel, minacc, maxacc,
- minexptime, maxexptime, minsize, maxsize, collisiondetection,
- collision_removal, object_collision, attached_id, vertical,
- texture, id, animation, glow);
+ id = m_env->addParticleSpawner(p.time, attached_id);
+ SendAddParticleSpawner(peer_id, proto_ver, p, attached_id, id);
return id;
}
@@ -3498,49 +3441,41 @@ void Server::deleteParticleSpawner(const std::string &playername, u32 id)
SendDeleteParticleSpawner(peer_id, id);
}
-Inventory* Server::createDetachedInventory(const std::string &name, const std::string &player)
+bool Server::dynamicAddMedia(const std::string &filepath)
{
- if(m_detached_inventories.count(name) > 0){
- infostream<<"Server clearing detached inventory \""<<name<<"\""<<std::endl;
- delete m_detached_inventories[name];
- } else {
- infostream<<"Server creating detached inventory \""<<name<<"\""<<std::endl;
+ std::string filename = fs::GetFilenameFromPath(filepath.c_str());
+ if (m_media.find(filename) != m_media.end()) {
+ errorstream << "Server::dynamicAddMedia(): file \"" << filename
+ << "\" already exists in media cache" << std::endl;
+ return false;
}
- Inventory *inv = new Inventory(m_itemdef);
- sanity_check(inv);
- m_detached_inventories[name] = inv;
- if (!player.empty())
- m_detached_inventories_player[name] = player;
- //TODO find a better way to do this
- sendDetachedInventory(name,PEER_ID_INEXISTENT);
- return inv;
-}
-
-bool Server::removeDetachedInventory(const std::string &name)
-{
- const auto &inv_it = m_detached_inventories.find(name);
- if (inv_it == m_detached_inventories.end())
+ // Load the file and add it to our media cache
+ std::string filedata, raw_hash;
+ bool ok = addMediaFile(filename, filepath, &filedata, &raw_hash);
+ if (!ok)
return false;
- delete inv_it->second;
- m_detached_inventories.erase(inv_it);
-
- if (!m_env) // Mods are not done loading
- return true;
-
- const auto &player_it = m_detached_inventories_player.find(name);
- if (player_it != m_detached_inventories_player.end()) {
- RemotePlayer *player = m_env->getPlayer(player_it->second.c_str());
+ // Push file to existing clients
+ NetworkPacket pkt(TOCLIENT_MEDIA_PUSH, 0);
+ pkt << raw_hash << filename << (bool) true;
+ pkt.putLongString(filedata);
- if (player && player->getPeerId() != PEER_ID_INEXISTENT)
- sendDetachedInventory(name, player->getPeerId());
-
- m_detached_inventories_player.erase(player_it);
- } else {
- // Notify all players about the change
- sendDetachedInventory(name, PEER_ID_INEXISTENT);
+ auto client_ids = m_clients.getClientIDs(CS_DefinitionsSent);
+ for (session_t client_id : client_ids) {
+ /*
+ The network layer only guarantees ordered delivery inside a channel.
+ Since the very next packet could be one that uses the media, we have
+ to push the media over ALL channels to ensure it is processed before
+ it is used.
+ In practice this means we have to send it twice:
+ - channel 1 (HUD)
+ - channel 0 (everything else: e.g. play_sound, object messages)
+ */
+ m_clients.send(client_id, 1, &pkt, true);
+ m_clients.send(client_id, 0, &pkt, true);
}
+
return true;
}
@@ -3564,7 +3499,7 @@ bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions,
for (const RollbackAction &action : actions) {
num_tried++;
- bool success = action.applyRevert(map, this, this);
+ bool success = action.applyRevert(map, m_inventory_mgr.get(), this);
if(!success){
num_failed++;
std::ostringstream os;
@@ -3930,3 +3865,20 @@ void Server::broadcastModChannelMessage(const std::string &channel,
m_script->on_modchannel_message(channel, sender, message);
}
}
+
+void Server::loadTranslationLanguage(const std::string &lang_code)
+{
+ if (g_server_translations->count(lang_code))
+ return; // Already loaded
+
+ std::string suffix = "." + lang_code + ".tr";
+ for (const auto &i : m_media) {
+ if (str_ends_with(i.first, suffix)) {
+ std::ifstream t(i.second.path);
+ std::string data((std::istreambuf_iterator<char>(t)),
+ std::istreambuf_iterator<char>());
+
+ (*g_server_translations)[lang_code].loadTranslation(data);
+ }
+ }
+}
diff --git a/src/server.h b/src/server.h
index 680de57b5..f44716531 100644
--- a/src/server.h
+++ b/src/server.h
@@ -27,12 +27,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "content/mods.h"
#include "inventorymanager.h"
#include "content/subgames.h"
-#include "tileanimation.h" // struct TileAnimationParams
+#include "tileanimation.h" // TileAnimationParams
+#include "particles.h" // ParticleParams
#include "network/peerhandler.h"
#include "network/address.h"
#include "util/numeric.h"
#include "util/thread.h"
#include "util/basic_macros.h"
+#include "util/metricsbackend.h"
#include "serverenvironment.h"
#include "clientiface.h"
#include "chatmessage.h"
@@ -67,6 +69,7 @@ struct MoonParams;
struct StarParams;
class ServerThread;
class ServerModManager;
+class ServerInventoryManager;
enum ClientDeletionReason {
CDR_LEAVE,
@@ -115,7 +118,7 @@ struct ServerPlayingSound
};
class Server : public con::PeerHandler, public MapEventReceiver,
- public InventoryManager, public IGameDef
+ public IGameDef
{
public:
/*
@@ -133,7 +136,6 @@ public:
~Server();
DISABLE_CLASS_COPY(Server);
- void init();
void start();
void stop();
// This is mainly a way to pass the time to the server.
@@ -165,7 +167,6 @@ public:
void handleCommand_InventoryAction(NetworkPacket* pkt);
void handleCommand_ChatMessage(NetworkPacket* pkt);
void handleCommand_Damage(NetworkPacket* pkt);
- void handleCommand_Password(NetworkPacket* pkt);
void handleCommand_PlayerItem(NetworkPacket* pkt);
void handleCommand_Respawn(NetworkPacket* pkt);
void handleCommand_Interact(NetworkPacket* pkt);
@@ -196,15 +197,9 @@ public:
*/
void onMapEditEvent(const MapEditEvent &event);
- /*
- Shall be called with the environment and the connection locked.
- */
- Inventory* getInventory(const InventoryLocation &loc);
- void setInventoryModified(const InventoryLocation &loc);
-
// Connection must be locked when called
std::wstring getStatusString();
- inline double getUptime() const { return m_uptime.m_value; }
+ inline double getUptime() const { return m_uptime_counter->get(); }
// read shutdown state
inline bool isShutdownRequested() const { return m_shutdown_state.is_requested; }
@@ -232,31 +227,19 @@ public:
void notifyPlayer(const char *name, const std::wstring &msg);
void notifyPlayers(const std::wstring &msg);
+
void spawnParticle(const std::string &playername,
- v3f pos, v3f velocity, v3f acceleration,
- float expirationtime, float size,
- bool collisiondetection, bool collision_removal, bool object_collision,
- bool vertical, const std::string &texture,
- const struct TileAnimationParams &animation, u8 glow);
-
- u32 addParticleSpawner(u16 amount, float spawntime,
- v3f minpos, v3f maxpos,
- v3f minvel, v3f maxvel,
- v3f minacc, v3f maxacc,
- float minexptime, float maxexptime,
- float minsize, float maxsize,
- bool collisiondetection, bool collision_removal, bool object_collision,
- ServerActiveObject *attached,
- bool vertical, const std::string &texture,
- const std::string &playername, const struct TileAnimationParams &animation,
- u8 glow);
+ const ParticleParameters &p);
+
+ u32 addParticleSpawner(const ParticleSpawnerParameters &p,
+ ServerActiveObject *attached, const std::string &playername);
void deleteParticleSpawner(const std::string &playername, u32 id);
- // Creates or resets inventory
- Inventory *createDetachedInventory(const std::string &name,
- const std::string &player = "");
- bool removeDetachedInventory(const std::string &name);
+ bool dynamicAddMedia(const std::string &filepath);
+
+ ServerInventoryManager *getInventoryMgr() const { return m_inventory_mgr.get(); }
+ void sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id);
// Envlock and conlock should be locked when using scriptapi
ServerScripting *getScriptIface(){ return m_script; }
@@ -318,7 +301,7 @@ public:
void setClouds(RemotePlayer *player, const CloudParams &params);
- bool overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness);
+ void overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness);
/* con::PeerHandler implementation. */
void peerAdded(con::Peer *peer);
@@ -335,7 +318,7 @@ public:
bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval);
bool getClientInfo(session_t peer_id, ClientState *state, u32 *uptime,
u8* ser_vers, u16* prot_vers, u8* major, u8* minor, u8* patch,
- std::string* vers_string);
+ std::string* vers_string, std::string* lang_code);
void printToConsoleOnly(const std::string &text);
@@ -359,6 +342,9 @@ public:
// Send block to specific player only
bool SendBlock(session_t peer_id, const v3s16 &blockpos);
+ // Load translations for a language
+ void loadTranslationLanguage(const std::string &lang_code);
+
// Bind address
Address m_bind_addr;
@@ -386,6 +372,8 @@ private:
float m_timer = 0.0f;
};
+ void init();
+
void SendMovement(session_t peer_id);
void SendHP(session_t peer_id, u16 hp);
void SendBreath(session_t peer_id, u16 breath);
@@ -449,35 +437,22 @@ 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,
const std::vector<std::string> &tosend);
- void sendDetachedInventory(const std::string &name, session_t peer_id);
-
// Adds a ParticleSpawner on peer with peer_id (PEER_ID_INEXISTENT == all)
void SendAddParticleSpawner(session_t peer_id, u16 protocol_version,
- u16 amount, float spawntime,
- v3f minpos, v3f maxpos,
- v3f minvel, v3f maxvel,
- v3f minacc, v3f maxacc,
- float minexptime, float maxexptime,
- float minsize, float maxsize,
- bool collisiondetection, bool collision_removal, bool object_collision,
- u16 attached_id,
- bool vertical, const std::string &texture, u32 id,
- const struct TileAnimationParams &animation, u8 glow);
+ const ParticleSpawnerParameters &p, u16 attached_id, u32 id);
void SendDeleteParticleSpawner(session_t peer_id, u32 id);
// Spawns particle on peer with peer_id (PEER_ID_INEXISTENT == all)
void SendSpawnParticle(session_t peer_id, u16 protocol_version,
- v3f pos, v3f velocity, v3f acceleration,
- float expirationtime, float size,
- bool collisiondetection, bool collision_removal, bool object_collision,
- bool vertical, const std::string &texture,
- const struct TileAnimationParams &animation, u8 glow);
+ const ParticleParameters &p);
void SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersao);
void SendActiveObjectMessages(session_t peer_id, const std::string &datas,
@@ -589,9 +564,6 @@ private:
float m_step_dtime = 0.0f;
std::mutex m_step_dtime_mutex;
- // current server step lag counter
- float m_lag;
-
// The server mainly operates in this thread
ServerThread *m_thread = nullptr;
@@ -600,8 +572,6 @@ private:
*/
// Timer for sending time of day over network
float m_time_of_day_send_timer = 0.0f;
- // Uptime of server in seconds
- MutexedVariable<double> m_uptime;
/*
Client interface
@@ -658,14 +628,6 @@ private:
s32 m_next_sound_id = 0; // positive values only
s32 nextSoundId();
- /*
- Detached inventories (behind m_env_mutex)
- */
- // key = name
- std::map<std::string, Inventory*> m_detached_inventories;
- // value = "" (visible to all players) or player name
- std::map<std::string, std::string> m_detached_inventories_player;
-
std::unordered_map<std::string, ModMetadata *> m_mod_storages;
float m_mod_storage_save_timer = 10.0f;
@@ -675,6 +637,22 @@ private:
// ModChannel manager
std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
+
+ // Inventory manager
+ std::unique_ptr<ServerInventoryManager> m_inventory_mgr;
+
+ // Global server metrics backend
+ std::unique_ptr<MetricsBackend> m_metrics_backend;
+
+ // Server metrics
+ MetricCounterPtr m_uptime_counter;
+ MetricGaugePtr m_player_gauge;
+ MetricGaugePtr m_timeofday_gauge;
+ // current server step lag
+ MetricGaugePtr m_lag_gauge;
+ MetricCounterPtr m_aom_buffer_counter;
+ MetricCounterPtr m_packet_recv_counter;
+ MetricCounterPtr m_packet_recv_processed_counter;
};
/*
diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt
index e964c69ff..0a5a8f3a7 100644
--- a/src/server/CMakeLists.txt
+++ b/src/server/CMakeLists.txt
@@ -1,4 +1,9 @@
set(server_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/activeobjectmgr.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/luaentity_sao.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/player_sao.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/serveractiveobject.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/serverinventorymgr.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/unit_sao.cpp
PARENT_SCOPE)
diff --git a/src/server/activeobjectmgr.cpp b/src/server/activeobjectmgr.cpp
index 984ae7794..1b8e31409 100644
--- a/src/server/activeobjectmgr.cpp
+++ b/src/server/activeobjectmgr.cpp
@@ -111,17 +111,19 @@ void ActiveObjectMgr::removeObject(u16 id)
}
// clang-format on
-void ActiveObjectMgr::getObjectsInsideRadius(
- const v3f &pos, float radius, std::vector<u16> &result)
+void ActiveObjectMgr::getObjectsInsideRadius(const v3f &pos, float radius,
+ std::vector<ServerActiveObject *> &result,
+ std::function<bool(ServerActiveObject *obj)> include_obj_cb)
{
float r2 = radius * radius;
for (auto &activeObject : m_active_objects) {
ServerActiveObject *obj = activeObject.second;
- u16 id = activeObject.first;
const v3f &objectpos = obj->getBasePosition();
if (objectpos.getDistanceFromSQ(pos) > r2)
continue;
- result.push_back(id);
+
+ if (!include_obj_cb || include_obj_cb(obj))
+ result.push_back(obj);
}
}
diff --git a/src/server/activeobjectmgr.h b/src/server/activeobjectmgr.h
index a502ac6ed..bc2085499 100644
--- a/src/server/activeobjectmgr.h
+++ b/src/server/activeobjectmgr.h
@@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <functional>
#include <vector>
#include "../activeobjectmgr.h"
-#include "serverobject.h"
+#include "serveractiveobject.h"
namespace server
{
@@ -35,8 +35,9 @@ public:
bool registerObject(ServerActiveObject *obj) override;
void removeObject(u16 id) override;
- void getObjectsInsideRadius(
- const v3f &pos, float radius, std::vector<u16> &result);
+ void getObjectsInsideRadius(const v3f &pos, float radius,
+ std::vector<ServerActiveObject *> &result,
+ std::function<bool(ServerActiveObject *obj)> include_obj_cb);
void getAddedActiveObjectsAroundPos(const v3f &player_pos, f32 radius,
f32 player_radius, std::set<u16> &current_objects,
diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp
new file mode 100644
index 000000000..d504c42ca
--- /dev/null
+++ b/src/server/luaentity_sao.cpp
@@ -0,0 +1,541 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2020 Minetest core developers & community
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "luaentity_sao.h"
+#include "collision.h"
+#include "constants.h"
+#include "player_sao.h"
+#include "scripting_server.h"
+#include "server.h"
+#include "serverenvironment.h"
+
+LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &data)
+ : UnitSAO(env, pos)
+{
+ std::string name;
+ std::string state;
+ u16 hp = 1;
+ v3f velocity;
+ v3f rotation;
+
+ while (!data.empty()) { // breakable, run for one iteration
+ std::istringstream is(data, std::ios::binary);
+ // 'version' does not allow to incrementally extend the parameter list thus
+ // we need another variable to build on top of 'version=1'. Ugly hack but works™
+ u8 version2 = 0;
+ u8 version = readU8(is);
+
+ name = deSerializeString(is);
+ state = deSerializeLongString(is);
+
+ if (version < 1)
+ break;
+
+ hp = readU16(is);
+ velocity = readV3F1000(is);
+ // yaw must be yaw to be backwards-compatible
+ rotation.Y = readF1000(is);
+
+ if (is.good()) // EOF for old formats
+ version2 = readU8(is);
+
+ if (version2 < 1) // PROTOCOL_VERSION < 37
+ break;
+
+ // version2 >= 1
+ rotation.X = readF1000(is);
+ rotation.Z = readF1000(is);
+
+ // if (version2 < 2)
+ // break;
+ // <read new values>
+ break;
+ }
+ // create object
+ infostream << "LuaEntitySAO::create(name=\"" << name << "\" state=\""
+ << state << "\")" << std::endl;
+
+ m_init_name = name;
+ m_init_state = state;
+ m_hp = hp;
+ m_velocity = velocity;
+ m_rotation = rotation;
+}
+
+LuaEntitySAO::~LuaEntitySAO()
+{
+ if(m_registered){
+ m_env->getScriptIface()->luaentity_Remove(m_id);
+ }
+
+ for (u32 attached_particle_spawner : m_attached_particle_spawners) {
+ m_env->deleteParticleSpawner(attached_particle_spawner, false);
+ }
+}
+
+void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
+{
+ ServerActiveObject::addedToEnvironment(dtime_s);
+
+ // Create entity from name
+ m_registered = m_env->getScriptIface()->
+ luaentity_Add(m_id, m_init_name.c_str());
+
+ if(m_registered){
+ // Get properties
+ m_env->getScriptIface()->
+ luaentity_GetProperties(m_id, this, &m_prop);
+ // Initialize HP from properties
+ m_hp = m_prop.hp_max;
+ // Activate entity, supplying serialized state
+ m_env->getScriptIface()->
+ luaentity_Activate(m_id, m_init_state, dtime_s);
+ } else {
+ m_prop.infotext = m_init_name;
+ }
+}
+
+void LuaEntitySAO::step(float dtime, bool send_recommended)
+{
+ if(!m_properties_sent)
+ {
+ m_properties_sent = true;
+ std::string str = getPropertyPacket();
+ // create message and add to list
+ m_messages_out.emplace(getId(), true, str);
+ }
+
+ // If attached, check that our parent is still there. If it isn't, detach.
+ if (m_attachment_parent_id && !isAttached()) {
+ // This is handled when objects are removed from the map
+ warningstream << "LuaEntitySAO::step() id=" << m_id <<
+ " is attached to nonexistent parent. This is a bug." << std::endl;
+ clearParentAttachment();
+ sendPosition(false, true);
+ }
+
+ m_last_sent_position_timer += dtime;
+
+ collisionMoveResult moveresult, *moveresult_p = nullptr;
+
+ // Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
+ // If the object gets detached this comes into effect automatically from the last known origin
+ if(isAttached())
+ {
+ v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
+ m_base_position = pos;
+ m_velocity = v3f(0,0,0);
+ m_acceleration = v3f(0,0,0);
+ }
+ else
+ {
+ if(m_prop.physical){
+ aabb3f box = m_prop.collisionbox;
+ box.MinEdge *= BS;
+ box.MaxEdge *= BS;
+ f32 pos_max_d = BS*0.25; // Distance per iteration
+ v3f p_pos = m_base_position;
+ v3f p_velocity = m_velocity;
+ v3f p_acceleration = m_acceleration;
+ moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
+ pos_max_d, box, m_prop.stepheight, dtime,
+ &p_pos, &p_velocity, p_acceleration,
+ this, m_prop.collideWithObjects);
+ moveresult_p = &moveresult;
+
+ // Apply results
+ m_base_position = p_pos;
+ m_velocity = p_velocity;
+ m_acceleration = p_acceleration;
+ } else {
+ m_base_position += dtime * m_velocity + 0.5 * dtime
+ * dtime * m_acceleration;
+ m_velocity += dtime * m_acceleration;
+ }
+
+ if (m_prop.automatic_face_movement_dir &&
+ (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
+ float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
+ + m_prop.automatic_face_movement_dir_offset;
+ float max_rotation_per_sec =
+ m_prop.automatic_face_movement_max_rotation_per_sec;
+
+ if (max_rotation_per_sec > 0) {
+ m_rotation.Y = wrapDegrees_0_360(m_rotation.Y);
+ wrappedApproachShortest(m_rotation.Y, target_yaw,
+ dtime * max_rotation_per_sec, 360.f);
+ } else {
+ // Negative values of max_rotation_per_sec mean disabled.
+ m_rotation.Y = target_yaw;
+ }
+ }
+ }
+
+ if(m_registered) {
+ m_env->getScriptIface()->luaentity_Step(m_id, dtime, moveresult_p);
+ }
+
+ if (!send_recommended)
+ return;
+
+ if(!isAttached())
+ {
+ // TODO: force send when acceleration changes enough?
+ float minchange = 0.2*BS;
+ if(m_last_sent_position_timer > 1.0){
+ minchange = 0.01*BS;
+ } else if(m_last_sent_position_timer > 0.2){
+ minchange = 0.05*BS;
+ }
+ float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
+ move_d += m_last_sent_move_precision;
+ float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
+ if (move_d > minchange || vel_d > minchange ||
+ std::fabs(m_rotation.X - m_last_sent_rotation.X) > 1.0f ||
+ std::fabs(m_rotation.Y - m_last_sent_rotation.Y) > 1.0f ||
+ std::fabs(m_rotation.Z - m_last_sent_rotation.Z) > 1.0f) {
+
+ sendPosition(true, false);
+ }
+ }
+
+ sendOutdatedData();
+}
+
+std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
+{
+ std::ostringstream os(std::ios::binary);
+
+ // PROTOCOL_VERSION >= 37
+ writeU8(os, 1); // version
+ os << serializeString(""); // name
+ writeU8(os, 0); // is_player
+ writeU16(os, getId()); //id
+ writeV3F32(os, m_base_position);
+ writeV3F32(os, m_rotation);
+ writeU16(os, m_hp);
+
+ std::ostringstream msg_os(std::ios::binary);
+ msg_os << serializeLongString(getPropertyPacket()); // message 1
+ msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2
+ msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3
+ for (const auto &bone_pos : m_bone_position) {
+ msg_os << serializeLongString(generateUpdateBonePositionCommand(
+ bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size
+ }
+ msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4
+
+ int message_count = 4 + m_bone_position.size();
+
+ for (const auto &id : getAttachmentChildIds()) {
+ if (ServerActiveObject *obj = m_env->getActiveObject(id)) {
+ message_count++;
+ msg_os << serializeLongString(obj->generateUpdateInfantCommand(
+ id, protocol_version));
+ }
+ }
+
+ msg_os << serializeLongString(generateSetTextureModCommand());
+ message_count++;
+
+ writeU8(os, message_count);
+ std::string serialized = msg_os.str();
+ os.write(serialized.c_str(), serialized.size());
+
+ // return result
+ return os.str();
+}
+
+void LuaEntitySAO::getStaticData(std::string *result) const
+{
+ verbosestream<<FUNCTION_NAME<<std::endl;
+ std::ostringstream os(std::ios::binary);
+ // version must be 1 to keep backwards-compatibility. See version2
+ writeU8(os, 1);
+ // name
+ os<<serializeString(m_init_name);
+ // state
+ if(m_registered){
+ std::string state = m_env->getScriptIface()->
+ luaentity_GetStaticdata(m_id);
+ os<<serializeLongString(state);
+ } else {
+ os<<serializeLongString(m_init_state);
+ }
+ writeU16(os, m_hp);
+ writeV3F1000(os, m_velocity);
+ // yaw
+ writeF1000(os, m_rotation.Y);
+
+ // version2. Increase this variable for new values
+ writeU8(os, 1); // PROTOCOL_VERSION >= 37
+
+ writeF1000(os, m_rotation.X);
+ writeF1000(os, m_rotation.Z);
+
+ // <write new values>
+
+ *result = os.str();
+}
+
+u16 LuaEntitySAO::punch(v3f dir,
+ const ToolCapabilities *toolcap,
+ ServerActiveObject *puncher,
+ float time_from_last_punch)
+{
+ if (!m_registered) {
+ // Delete unknown LuaEntities when punched
+ m_pending_removal = true;
+ return 0;
+ }
+
+ FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
+
+ s32 old_hp = getHP();
+ ItemStack selected_item, hand_item;
+ ItemStack tool_item = puncher->getWieldedItem(&selected_item, &hand_item);
+
+ PunchDamageResult result = getPunchDamage(
+ m_armor_groups,
+ toolcap,
+ &tool_item,
+ time_from_last_punch);
+
+ bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
+ time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
+
+ if (!damage_handled) {
+ if (result.did_punch) {
+ setHP((s32)getHP() - result.damage,
+ PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
+
+ // create message and add to list
+ sendPunchCommand();
+ }
+ }
+
+ if (getHP() == 0 && !isGone()) {
+ clearParentAttachment();
+ clearChildAttachments();
+ m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
+ m_pending_removal = true;
+ }
+
+ actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
+ ", hp=" << puncher->getHP() << ") punched " <<
+ getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
+ "), damage=" << (old_hp - (s32)getHP()) <<
+ (damage_handled ? " (handled by Lua)" : "") << std::endl;
+
+ // TODO: give Lua control over wear
+ return result.wear;
+}
+
+void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
+{
+ if (!m_registered)
+ return;
+
+ m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
+}
+
+void LuaEntitySAO::setPos(const v3f &pos)
+{
+ if(isAttached())
+ return;
+ m_base_position = pos;
+ sendPosition(false, true);
+}
+
+void LuaEntitySAO::moveTo(v3f pos, bool continuous)
+{
+ if(isAttached())
+ return;
+ m_base_position = pos;
+ if(!continuous)
+ sendPosition(true, true);
+}
+
+float LuaEntitySAO::getMinimumSavedMovement()
+{
+ return 0.1 * BS;
+}
+
+std::string LuaEntitySAO::getDescription()
+{
+ std::ostringstream oss;
+ oss << "LuaEntitySAO \"" << m_init_name << "\" ";
+ auto pos = floatToInt(m_base_position, BS);
+ oss << "at " << PP(pos);
+ return oss.str();
+}
+
+void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
+{
+ m_hp = rangelim(hp, 0, U16_MAX);
+}
+
+u16 LuaEntitySAO::getHP() const
+{
+ return m_hp;
+}
+
+void LuaEntitySAO::setVelocity(v3f velocity)
+{
+ m_velocity = velocity;
+}
+
+v3f LuaEntitySAO::getVelocity()
+{
+ return m_velocity;
+}
+
+void LuaEntitySAO::setAcceleration(v3f acceleration)
+{
+ m_acceleration = acceleration;
+}
+
+v3f LuaEntitySAO::getAcceleration()
+{
+ return m_acceleration;
+}
+
+void LuaEntitySAO::setTextureMod(const std::string &mod)
+{
+ m_current_texture_modifier = mod;
+ // create message and add to list
+ m_messages_out.emplace(getId(), true, generateSetTextureModCommand());
+}
+
+std::string LuaEntitySAO::getTextureMod() const
+{
+ return m_current_texture_modifier;
+}
+
+
+std::string LuaEntitySAO::generateSetTextureModCommand() const
+{
+ std::ostringstream os(std::ios::binary);
+ // command
+ writeU8(os, AO_CMD_SET_TEXTURE_MOD);
+ // parameters
+ os << serializeString(m_current_texture_modifier);
+ return os.str();
+}
+
+std::string LuaEntitySAO::generateSetSpriteCommand(v2s16 p, u16 num_frames,
+ f32 framelength, bool select_horiz_by_yawpitch)
+{
+ std::ostringstream os(std::ios::binary);
+ // command
+ writeU8(os, AO_CMD_SET_SPRITE);
+ // parameters
+ writeV2S16(os, p);
+ writeU16(os, num_frames);
+ writeF32(os, framelength);
+ writeU8(os, select_horiz_by_yawpitch);
+ return os.str();
+}
+
+void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
+ bool select_horiz_by_yawpitch)
+{
+ std::string str = generateSetSpriteCommand(
+ p,
+ num_frames,
+ framelength,
+ select_horiz_by_yawpitch
+ );
+ // create message and add to list
+ m_messages_out.emplace(getId(), true, str);
+}
+
+std::string LuaEntitySAO::getName()
+{
+ return m_init_name;
+}
+
+std::string LuaEntitySAO::getPropertyPacket()
+{
+ return generateSetPropertiesCommand(m_prop);
+}
+
+void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
+{
+ // If the object is attached client-side, don't waste bandwidth sending its position to clients
+ if(isAttached())
+ return;
+
+ m_last_sent_move_precision = m_base_position.getDistanceFrom(
+ m_last_sent_position);
+ m_last_sent_position_timer = 0;
+ m_last_sent_position = m_base_position;
+ m_last_sent_velocity = m_velocity;
+ //m_last_sent_acceleration = m_acceleration;
+ m_last_sent_rotation = m_rotation;
+
+ float update_interval = m_env->getSendRecommendedInterval();
+
+ std::string str = generateUpdatePositionCommand(
+ m_base_position,
+ m_velocity,
+ m_acceleration,
+ m_rotation,
+ do_interpolate,
+ is_movement_end,
+ update_interval
+ );
+ // create message and add to list
+ m_messages_out.emplace(getId(), false, str);
+}
+
+bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
+{
+ if (m_prop.physical)
+ {
+ //update collision box
+ toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
+ toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
+
+ toset->MinEdge += m_base_position;
+ toset->MaxEdge += m_base_position;
+
+ return true;
+ }
+
+ return false;
+}
+
+bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
+{
+ if (!m_prop.is_visible || !m_prop.pointable) {
+ return false;
+ }
+
+ toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
+ toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
+
+ return true;
+}
+
+bool LuaEntitySAO::collideWithObjects() const
+{
+ return m_prop.collideWithObjects;
+}
diff --git a/src/server/luaentity_sao.h b/src/server/luaentity_sao.h
new file mode 100644
index 000000000..2520c8f5d
--- /dev/null
+++ b/src/server/luaentity_sao.h
@@ -0,0 +1,93 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2020 Minetest core developers & community
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "unit_sao.h"
+
+class LuaEntitySAO : public UnitSAO
+{
+public:
+ LuaEntitySAO() = delete;
+ // Used by the environment to load SAO
+ LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &data);
+ // Used by the Lua API
+ LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &name,
+ const std::string &state) :
+ UnitSAO(env, pos),
+ m_init_name(name), m_init_state(state)
+ {
+ }
+ ~LuaEntitySAO();
+ ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_LUAENTITY; }
+ ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; }
+ virtual void addedToEnvironment(u32 dtime_s);
+ void step(float dtime, bool send_recommended);
+ std::string getClientInitializationData(u16 protocol_version);
+ bool isStaticAllowed() const { return m_prop.static_save; }
+ void getStaticData(std::string *result) const;
+ u16 punch(v3f dir, const ToolCapabilities *toolcap = nullptr,
+ ServerActiveObject *puncher = nullptr,
+ float time_from_last_punch = 1000000.0f);
+ void rightClick(ServerActiveObject *clicker);
+ void setPos(const v3f &pos);
+ void moveTo(v3f pos, bool continuous);
+ float getMinimumSavedMovement();
+ std::string getDescription();
+ void setHP(s32 hp, const PlayerHPChangeReason &reason);
+ u16 getHP() const;
+
+ /* LuaEntitySAO-specific */
+ void setVelocity(v3f velocity);
+ void addVelocity(v3f velocity) { m_velocity += velocity; }
+ v3f getVelocity();
+ void setAcceleration(v3f acceleration);
+ v3f getAcceleration();
+
+ void setTextureMod(const std::string &mod);
+ std::string getTextureMod() const;
+ void setSprite(v2s16 p, int num_frames, float framelength,
+ bool select_horiz_by_yawpitch);
+ std::string getName();
+ bool getCollisionBox(aabb3f *toset) const;
+ bool getSelectionBox(aabb3f *toset) const;
+ bool collideWithObjects() const;
+
+private:
+ std::string getPropertyPacket();
+ void sendPosition(bool do_interpolate, bool is_movement_end);
+ std::string generateSetTextureModCommand() const;
+ static std::string generateSetSpriteCommand(v2s16 p, u16 num_frames,
+ f32 framelength, bool select_horiz_by_yawpitch);
+
+ std::string m_init_name;
+ std::string m_init_state;
+ bool m_registered = false;
+
+ v3f m_velocity;
+ v3f m_acceleration;
+
+ v3f m_last_sent_position;
+ v3f m_last_sent_velocity;
+ v3f m_last_sent_rotation;
+ float m_last_sent_position_timer = 0.0f;
+ float m_last_sent_move_precision = 0.0f;
+ std::string m_current_texture_modifier = "";
+};
diff --git a/src/server/mods.cpp b/src/server/mods.cpp
index c5616dcd6..6ac530739 100644
--- a/src/server/mods.cpp
+++ b/src/server/mods.cpp
@@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "log.h"
#include "scripting_server.h"
#include "content/subgames.h"
+#include "porting.h"
+#include "util/metricsbackend.h"
/**
* Manage server mods
@@ -66,14 +68,10 @@ void ServerModManager::loadMods(ServerScripting *script)
"Only characters [a-z0-9_] are allowed.");
}
std::string script_path = mod.path + DIR_DELIM + "init.lua";
- infostream << " [" << padStringRight(mod.name, 12) << "] [\""
- << script_path << "\"]" << std::endl;
- auto t = std::chrono::steady_clock::now();
+ auto t = porting::getTimeMs();
script->loadMod(script_path, mod.name);
infostream << "Mod \"" << mod.name << "\" loaded after "
- << std::chrono::duration_cast<std::chrono::milliseconds>(
- std::chrono::steady_clock::now() - t).count() * 0.001f
- << " seconds" << std::endl;
+ << (porting::getTimeMs() - t) << " ms" << std::endl;
}
// Run a callback when mods are loaded
diff --git a/src/server/mods.h b/src/server/mods.h
index 2bc1aa22f..54774bd86 100644
--- a/src/server/mods.h
+++ b/src/server/mods.h
@@ -20,7 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include "content/mods.h"
+#include <memory>
+class MetricsBackend;
+class MetricCounter;
class ServerScripting;
/**
diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp
new file mode 100644
index 000000000..67efed210
--- /dev/null
+++ b/src/server/player_sao.cpp
@@ -0,0 +1,665 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2020 Minetest core developers & community
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "player_sao.h"
+#include "nodedef.h"
+#include "remoteplayer.h"
+#include "scripting_server.h"
+#include "server.h"
+#include "serverenvironment.h"
+
+PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
+ bool is_singleplayer):
+ UnitSAO(env_, v3f(0,0,0)),
+ m_player(player_),
+ m_peer_id(peer_id_),
+ m_is_singleplayer(is_singleplayer)
+{
+ SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT);
+
+ m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
+ m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
+ m_prop.physical = false;
+ m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
+ m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
+ m_prop.pointable = true;
+ // Start of default appearance, this should be overwritten by Lua
+ m_prop.visual = "upright_sprite";
+ m_prop.visual_size = v3f(1, 2, 1);
+ m_prop.textures.clear();
+ m_prop.textures.emplace_back("player.png");
+ m_prop.textures.emplace_back("player_back.png");
+ m_prop.colors.clear();
+ m_prop.colors.emplace_back(255, 255, 255, 255);
+ m_prop.spritediv = v2s16(1,1);
+ m_prop.eye_height = 1.625f;
+ // End of default appearance
+ m_prop.is_visible = true;
+ m_prop.backface_culling = false;
+ m_prop.makes_footstep_sound = true;
+ m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
+ m_hp = m_prop.hp_max;
+ m_breath = m_prop.breath_max;
+ // Disable zoom in survival mode using a value of 0
+ m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
+
+ if (!g_settings->getBool("enable_damage"))
+ m_armor_groups["immortal"] = 1;
+}
+
+void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
+{
+ assert(player);
+ m_player = player;
+ m_privs = privs;
+}
+
+v3f PlayerSAO::getEyeOffset() const
+{
+ return v3f(0, BS * m_prop.eye_height, 0);
+}
+
+std::string PlayerSAO::getDescription()
+{
+ return std::string("player ") + m_player->getName();
+}
+
+// Called after id has been set and has been inserted in environment
+void PlayerSAO::addedToEnvironment(u32 dtime_s)
+{
+ ServerActiveObject::addedToEnvironment(dtime_s);
+ ServerActiveObject::setBasePosition(m_base_position);
+ m_player->setPlayerSAO(this);
+ m_player->setPeerId(m_peer_id);
+ m_last_good_position = m_base_position;
+}
+
+// Called before removing from environment
+void PlayerSAO::removingFromEnvironment()
+{
+ ServerActiveObject::removingFromEnvironment();
+ if (m_player->getPlayerSAO() == this) {
+ unlinkPlayerSessionAndSave();
+ for (u32 attached_particle_spawner : m_attached_particle_spawners) {
+ m_env->deleteParticleSpawner(attached_particle_spawner, false);
+ }
+ }
+}
+
+std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
+{
+ std::ostringstream os(std::ios::binary);
+
+ // Protocol >= 15
+ writeU8(os, 1); // version
+ os << serializeString(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());
+
+ std::ostringstream msg_os(std::ios::binary);
+ msg_os << serializeLongString(getPropertyPacket()); // message 1
+ msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2
+ msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3
+ for (const auto &bone_pos : m_bone_position) {
+ msg_os << serializeLongString(generateUpdateBonePositionCommand(
+ bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size
+ }
+ msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4
+ msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5
+
+ int message_count = 5 + m_bone_position.size();
+
+ for (const auto &id : getAttachmentChildIds()) {
+ if (ServerActiveObject *obj = m_env->getActiveObject(id)) {
+ message_count++;
+ msg_os << serializeLongString(obj->generateUpdateInfantCommand(
+ id, protocol_version));
+ }
+ }
+
+ writeU8(os, message_count);
+ std::string serialized = msg_os.str();
+ os.write(serialized.c_str(), serialized.size());
+
+ // return result
+ return os.str();
+}
+
+void PlayerSAO::getStaticData(std::string * result) const
+{
+ FATAL_ERROR("Obsolete function");
+}
+
+void PlayerSAO::step(float dtime, bool send_recommended)
+{
+ if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) {
+ // Get nose/mouth position, approximate with eye position
+ v3s16 p = floatToInt(getEyePosition(), BS);
+ 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) {
+ 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);
+ m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
+ }
+ }
+ }
+
+ if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) {
+ // Get nose/mouth position, approximate with eye position
+ v3s16 p = floatToInt(getEyePosition(), BS);
+ 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 &&
+ n.getContent() != CONTENT_IGNORE && m_hp > 0)
+ setBreath(m_breath + 1);
+ }
+
+ if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) {
+ u32 damage_per_second = 0;
+ std::string nodename;
+ // Lowest and highest damage points are 0.1 within collisionbox
+ float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
+
+ // Sequence of damage points, starting 0.1 above feet and progressing
+ // upwards in 1 node intervals, stopping below top damage point.
+ for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
+ v3s16 p = floatToInt(m_base_position +
+ v3f(0.0f, dam_height * BS, 0.0f), BS);
+ MapNode n = m_env->getMap().getNode(p);
+ const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
+ if (c.damage_per_second > damage_per_second) {
+ damage_per_second = c.damage_per_second;
+ nodename = c.name;
+ }
+ }
+
+ // Top damage point
+ v3s16 ptop = floatToInt(m_base_position +
+ v3f(0.0f, dam_top * BS, 0.0f), BS);
+ MapNode ntop = m_env->getMap().getNode(ptop);
+ const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
+ if (c.damage_per_second > damage_per_second) {
+ damage_per_second = c.damage_per_second;
+ nodename = c.name;
+ }
+
+ if (damage_per_second != 0 && m_hp > 0) {
+ s32 newhp = (s32)m_hp - (s32)damage_per_second;
+ PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
+ setHP(newhp, reason);
+ m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
+ }
+ }
+
+ if (!m_properties_sent) {
+ m_properties_sent = true;
+ std::string str = getPropertyPacket();
+ // create message and add to list
+ m_messages_out.emplace(getId(), true, str);
+ m_env->getScriptIface()->player_event(this, "properties_changed");
+ }
+
+ // If attached, check that our parent is still there. If it isn't, detach.
+ if (m_attachment_parent_id && !isAttached()) {
+ // This is handled when objects are removed from the map
+ warningstream << "PlayerSAO::step() id=" << m_id <<
+ " is attached to nonexistent parent. This is a bug." << std::endl;
+ clearParentAttachment();
+ setBasePosition(m_last_good_position);
+ m_env->getGameDef()->SendMovePlayer(m_peer_id);
+ }
+
+ //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
+
+ // Set lag pool maximums based on estimated lag
+ const float LAG_POOL_MIN = 5.0f;
+ float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
+ if(lag_pool_max < LAG_POOL_MIN)
+ lag_pool_max = LAG_POOL_MIN;
+ m_dig_pool.setMax(lag_pool_max);
+ m_move_pool.setMax(lag_pool_max);
+
+ // Increment cheat prevention timers
+ m_dig_pool.add(dtime);
+ m_move_pool.add(dtime);
+ m_time_from_last_teleport += dtime;
+ m_time_from_last_punch += dtime;
+ m_nocheat_dig_time += dtime;
+ m_max_speed_override_time = MYMAX(m_max_speed_override_time - dtime, 0.0f);
+
+ // Each frame, parent position is copied if the object is attached,
+ // otherwise it's calculated normally.
+ // If the object gets detached this comes into effect automatically from
+ // the last known origin.
+ if (isAttached()) {
+ v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
+ m_last_good_position = pos;
+ setBasePosition(pos);
+ }
+
+ if (!send_recommended)
+ return;
+
+ if (m_position_not_sent) {
+ m_position_not_sent = false;
+ float update_interval = m_env->getSendRecommendedInterval();
+ v3f pos;
+ // When attached, the position is only sent to clients where the
+ // parent isn't known
+ if (isAttached())
+ pos = m_last_good_position;
+ else
+ pos = m_base_position;
+
+ std::string str = generateUpdatePositionCommand(
+ pos,
+ v3f(0.0f, 0.0f, 0.0f),
+ v3f(0.0f, 0.0f, 0.0f),
+ m_rotation,
+ true,
+ false,
+ update_interval
+ );
+ // create message and add to list
+ m_messages_out.emplace(getId(), false, str);
+ }
+
+ if (!m_physics_override_sent) {
+ m_physics_override_sent = true;
+ // create message and add to list
+ m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand());
+ }
+
+ sendOutdatedData();
+}
+
+std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() 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);
+ // 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);
+ writeU8(os, !m_physics_override_new_move);
+ return os.str();
+}
+
+void PlayerSAO::setBasePosition(const v3f &position)
+{
+ if (m_player && position != m_base_position)
+ m_player->setDirty(true);
+
+ // This needs to be ran for attachments too
+ ServerActiveObject::setBasePosition(position);
+
+ // Updating is not wanted/required for player migration
+ if (m_env) {
+ m_position_not_sent = true;
+ }
+}
+
+void PlayerSAO::setPos(const v3f &pos)
+{
+ if(isAttached())
+ return;
+
+ // Send mapblock of target location
+ v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
+ m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
+
+ setBasePosition(pos);
+ // Movement caused by this command is always valid
+ m_last_good_position = pos;
+ m_move_pool.empty();
+ m_time_from_last_teleport = 0.0;
+ m_env->getGameDef()->SendMovePlayer(m_peer_id);
+}
+
+void PlayerSAO::moveTo(v3f pos, bool continuous)
+{
+ if(isAttached())
+ return;
+
+ setBasePosition(pos);
+ // Movement caused by this command is always valid
+ m_last_good_position = pos;
+ m_move_pool.empty();
+ m_time_from_last_teleport = 0.0;
+ m_env->getGameDef()->SendMovePlayer(m_peer_id);
+}
+
+void PlayerSAO::setPlayerYaw(const float yaw)
+{
+ v3f rotation(0, yaw, 0);
+ if (m_player && yaw != m_rotation.Y)
+ m_player->setDirty(true);
+
+ // Set player model yaw, not look view
+ UnitSAO::setRotation(rotation);
+}
+
+void PlayerSAO::setFov(const float fov)
+{
+ if (m_player && fov != m_fov)
+ m_player->setDirty(true);
+
+ m_fov = fov;
+}
+
+void PlayerSAO::setWantedRange(const s16 range)
+{
+ if (m_player && range != m_wanted_range)
+ m_player->setDirty(true);
+
+ m_wanted_range = range;
+}
+
+void PlayerSAO::setPlayerYawAndSend(const float yaw)
+{
+ setPlayerYaw(yaw);
+ m_env->getGameDef()->SendMovePlayer(m_peer_id);
+}
+
+void PlayerSAO::setLookPitch(const float pitch)
+{
+ if (m_player && pitch != m_pitch)
+ m_player->setDirty(true);
+
+ m_pitch = pitch;
+}
+
+void PlayerSAO::setLookPitchAndSend(const float pitch)
+{
+ setLookPitch(pitch);
+ m_env->getGameDef()->SendMovePlayer(m_peer_id);
+}
+
+u16 PlayerSAO::punch(v3f dir,
+ const ToolCapabilities *toolcap,
+ ServerActiveObject *puncher,
+ float time_from_last_punch)
+{
+ if (!toolcap)
+ return 0;
+
+ FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
+
+ // No effect if PvP disabled or if immortal
+ if (isImmortal() || !g_settings->getBool("enable_pvp")) {
+ if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
+ // create message and add to list
+ sendPunchCommand();
+ return 0;
+ }
+ }
+
+ s32 old_hp = getHP();
+ HitParams hitparams = getHitParams(m_armor_groups, toolcap,
+ time_from_last_punch);
+
+ PlayerSAO *playersao = m_player->getPlayerSAO();
+
+ bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
+ puncher, time_from_last_punch, toolcap, dir,
+ hitparams.hp);
+
+ if (!damage_handled) {
+ setHP((s32)getHP() - (s32)hitparams.hp,
+ PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
+ } else { // override client prediction
+ if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
+ // create message and add to list
+ sendPunchCommand();
+ }
+ }
+
+ actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
+ ", hp=" << puncher->getHP() << ") punched " <<
+ getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
+ "), damage=" << (old_hp - (s32)getHP()) <<
+ (damage_handled ? " (handled by Lua)" : "") << std::endl;
+
+ return hitparams.wear;
+}
+
+void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
+{
+ s32 oldhp = m_hp;
+
+ hp = rangelim(hp, 0, m_prop.hp_max);
+
+ if (oldhp != hp) {
+ s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
+ if (hp_change == 0)
+ return;
+
+ hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max);
+ }
+
+ if (hp < oldhp && isImmortal())
+ return;
+
+ m_hp = hp;
+
+ // Update properties on death
+ if ((hp == 0) != (oldhp == 0))
+ m_properties_sent = false;
+}
+
+void PlayerSAO::setBreath(const u16 breath, bool send)
+{
+ if (m_player && breath != m_breath)
+ m_player->setDirty(true);
+
+ m_breath = rangelim(breath, 0, m_prop.breath_max);
+
+ if (send)
+ m_env->getGameDef()->SendPlayerBreath(this);
+}
+
+Inventory *PlayerSAO::getInventory() const
+{
+ return m_player ? &m_player->inventory : nullptr;
+}
+
+InventoryLocation PlayerSAO::getInventoryLocation() const
+{
+ InventoryLocation loc;
+ loc.setPlayer(m_player->getName());
+ return loc;
+}
+
+u16 PlayerSAO::getWieldIndex() const
+{
+ return m_player->getWieldIndex();
+}
+
+ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
+{
+ return m_player->getWieldedItem(selected, hand);
+}
+
+bool PlayerSAO::setWieldedItem(const ItemStack &item)
+{
+ InventoryList *mlist = m_player->inventory.getList(getWieldList());
+ if (mlist) {
+ mlist->changeItem(m_player->getWieldIndex(), item);
+ return true;
+ }
+ return false;
+}
+
+void PlayerSAO::disconnected()
+{
+ m_peer_id = PEER_ID_INEXISTENT;
+ m_pending_removal = true;
+}
+
+void PlayerSAO::unlinkPlayerSessionAndSave()
+{
+ assert(m_player->getPlayerSAO() == this);
+ m_player->setPeerId(PEER_ID_INEXISTENT);
+ m_env->savePlayer(m_player);
+ m_player->setPlayerSAO(NULL);
+ m_env->removePlayer(m_player);
+}
+
+std::string PlayerSAO::getPropertyPacket()
+{
+ m_prop.is_visible = (true);
+ return generateSetPropertiesCommand(m_prop);
+}
+
+void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
+{
+ if (m_max_speed_override_time == 0.0f)
+ m_max_speed_override = vel;
+ else
+ m_max_speed_override += vel;
+ if (m_player) {
+ float accel = MYMIN(m_player->movement_acceleration_default,
+ m_player->movement_acceleration_air);
+ m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
+ }
+}
+
+bool PlayerSAO::checkMovementCheat()
+{
+ if (isAttached() || m_is_singleplayer ||
+ g_settings->getBool("disable_anticheat")) {
+ m_last_good_position = m_base_position;
+ return false;
+ }
+
+ bool cheated = false;
+ /*
+ Check player movements
+
+ NOTE: Actually the server should handle player physics like the
+ client does and compare player's position to what is calculated
+ on our side. This is required when eg. players fly due to an
+ explosion. Altough a node-based alternative might be possible
+ too, and much more lightweight.
+ */
+
+ float override_max_H, override_max_V;
+ if (m_max_speed_override_time > 0.0f) {
+ override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
+ override_max_V = fabs(m_max_speed_override.Y);
+ } else {
+ override_max_H = override_max_V = 0.0f;
+ }
+
+ float player_max_walk = 0; // horizontal movement
+ float player_max_jump = 0; // vertical upwards movement
+
+ if (m_privs.count("fast") != 0)
+ player_max_walk = m_player->movement_speed_fast; // Fast speed
+ else
+ player_max_walk = m_player->movement_speed_walk; // Normal speed
+ player_max_walk *= m_physics_override_speed;
+ player_max_walk = MYMAX(player_max_walk, override_max_H);
+
+ player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
+ // FIXME: Bouncy nodes cause practically unbound increase in Y speed,
+ // until this can be verified correctly, tolerate higher jumping speeds
+ player_max_jump *= 2.0;
+ player_max_jump = MYMAX(player_max_jump, override_max_V);
+
+ // Don't divide by zero!
+ if (player_max_walk < 0.0001f)
+ player_max_walk = 0.0001f;
+ if (player_max_jump < 0.0001f)
+ player_max_jump = 0.0001f;
+
+ v3f diff = (m_base_position - m_last_good_position);
+ float d_vert = diff.Y;
+ diff.Y = 0;
+ float d_horiz = diff.getLength();
+ float required_time = d_horiz / player_max_walk;
+
+ // FIXME: Checking downwards movement is not easily possible currently,
+ // the server could calculate speed differences to examine the gravity
+ if (d_vert > 0) {
+ // In certain cases (water, ladders) walking speed is applied vertically
+ float s = MYMAX(player_max_jump, player_max_walk);
+ required_time = MYMAX(required_time, d_vert / s);
+ }
+
+ if (m_move_pool.grab(required_time)) {
+ m_last_good_position = m_base_position;
+ } else {
+ const float LAG_POOL_MIN = 5.0;
+ float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
+ lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
+ if (m_time_from_last_teleport > lag_pool_max) {
+ actionstream << "Server: " << m_player->getName()
+ << " moved too fast: V=" << d_vert << ", H=" << d_horiz
+ << "; resetting position." << std::endl;
+ cheated = true;
+ }
+ setBasePosition(m_last_good_position);
+ }
+ return cheated;
+}
+
+bool PlayerSAO::getCollisionBox(aabb3f *toset) const
+{
+ //update collision box
+ toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
+ toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
+
+ toset->MinEdge += m_base_position;
+ toset->MaxEdge += m_base_position;
+ return true;
+}
+
+bool PlayerSAO::getSelectionBox(aabb3f *toset) const
+{
+ if (!m_prop.is_visible || !m_prop.pointable) {
+ return false;
+ }
+
+ toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
+ toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
+
+ return true;
+}
+
+float PlayerSAO::getZoomFOV() const
+{
+ return m_prop.zoom_fov;
+}
diff --git a/src/content_sao.h b/src/server/player_sao.h
index e9047daf0..8571bd4f9 100644
--- a/src/content_sao.h
+++ b/src/server/player_sao.h
@@ -1,6 +1,7 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2020 Minetest core developers & community
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
@@ -19,157 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
+#include "constants.h"
#include "network/networkprotocol.h"
+#include "unit_sao.h"
#include "util/numeric.h"
-#include "serverobject.h"
-#include "itemgroup.h"
-#include "object_properties.h"
-#include "constants.h"
-
-class UnitSAO: public ServerActiveObject
-{
-public:
- UnitSAO(ServerEnvironment *env, v3f pos);
- virtual ~UnitSAO() = default;
-
- void setRotation(v3f rotation) { m_rotation = rotation; }
- const v3f &getRotation() const { return m_rotation; }
- v3f getRadRotation() { return m_rotation * core::DEGTORAD; }
-
- // Deprecated
- f32 getRadYawDep() const { return (m_rotation.Y + 90.) * core::DEGTORAD; }
-
- u16 getHP() const { return m_hp; }
- // Use a function, if isDead can be defined by other conditions
- bool isDead() const { return m_hp == 0; }
-
- inline bool isAttached() const
- { return getParent(); }
-
- inline bool isImmortal() const
- { return itemgroup_get(getArmorGroups(), "immortal"); }
-
- void setArmorGroups(const ItemGroupList &armor_groups);
- const ItemGroupList &getArmorGroups() const;
- void setAnimation(v2f frame_range, float frame_speed, float frame_blend, bool frame_loop);
- void getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, bool *frame_loop);
- void setAnimationSpeed(float frame_speed);
- void setBonePosition(const std::string &bone, v3f position, v3f rotation);
- void getBonePosition(const std::string &bone, v3f *position, v3f *rotation);
- void setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation);
- void getAttachment(int *parent_id, std::string *bone, v3f *position,
- v3f *rotation) const;
- void clearChildAttachments();
- void clearParentAttachment();
- void addAttachmentChild(int child_id);
- void removeAttachmentChild(int child_id);
- const std::unordered_set<int> &getAttachmentChildIds() const;
- ServerActiveObject *getParent() const;
- ObjectProperties* accessObjectProperties();
- void notifyObjectPropertiesModified();
-protected:
- u16 m_hp = 1;
-
- v3f m_rotation;
-
- bool m_properties_sent = true;
- ObjectProperties m_prop;
-
- ItemGroupList m_armor_groups;
- bool m_armor_groups_sent = false;
-
- v2f m_animation_range;
- float m_animation_speed = 0.0f;
- float m_animation_blend = 0.0f;
- bool m_animation_loop = true;
- bool m_animation_sent = false;
- bool m_animation_speed_sent = false;
-
- // Stores position and rotation for each bone name
- std::unordered_map<std::string, core::vector2d<v3f>> m_bone_position;
- bool m_bone_position_sent = false;
-
- int m_attachment_parent_id = 0;
- std::unordered_set<int> m_attachment_child_ids;
- std::string m_attachment_bone = "";
- v3f m_attachment_position;
- v3f m_attachment_rotation;
- bool m_attachment_sent = false;
-private:
- void onAttach(int parent_id);
- void onDetach(int parent_id);
-};
-
-/*
- LuaEntitySAO needs some internals exposed.
-*/
-
-class LuaEntitySAO : public UnitSAO
-{
-public:
- LuaEntitySAO(ServerEnvironment *env, v3f pos,
- const std::string &name, const std::string &state);
- ~LuaEntitySAO();
- ActiveObjectType getType() const
- { return ACTIVEOBJECT_TYPE_LUAENTITY; }
- ActiveObjectType getSendType() const
- { return ACTIVEOBJECT_TYPE_GENERIC; }
- virtual void addedToEnvironment(u32 dtime_s);
- static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
- const std::string &data);
- void step(float dtime, bool send_recommended);
- std::string getClientInitializationData(u16 protocol_version);
- bool isStaticAllowed() const
- { return m_prop.static_save; }
- void getStaticData(std::string *result) const;
- u16 punch(v3f dir,
- const ToolCapabilities *toolcap = nullptr,
- ServerActiveObject *puncher = nullptr,
- float time_from_last_punch = 1000000.0f);
- void rightClick(ServerActiveObject *clicker);
- void setPos(const v3f &pos);
- void moveTo(v3f pos, bool continuous);
- float getMinimumSavedMovement();
- std::string getDescription();
- void setHP(s32 hp, const PlayerHPChangeReason &reason);
- u16 getHP() const;
-
- /* LuaEntitySAO-specific */
- void setVelocity(v3f velocity);
- void addVelocity(v3f velocity)
- {
- m_velocity += velocity;
- }
- v3f getVelocity();
- void setAcceleration(v3f acceleration);
- v3f getAcceleration();
-
- void setTextureMod(const std::string &mod);
- std::string getTextureMod() const;
- void setSprite(v2s16 p, int num_frames, float framelength,
- bool select_horiz_by_yawpitch);
- std::string getName();
- bool getCollisionBox(aabb3f *toset) const;
- bool getSelectionBox(aabb3f *toset) const;
- bool collideWithObjects() const;
-private:
- std::string getPropertyPacket();
- void sendPosition(bool do_interpolate, bool is_movement_end);
-
- std::string m_init_name;
- std::string m_init_state;
- bool m_registered = false;
-
- v3f m_velocity;
- v3f m_acceleration;
-
- v3f m_last_sent_position;
- v3f m_last_sent_velocity;
- v3f m_last_sent_rotation;
- float m_last_sent_position_timer = 0.0f;
- float m_last_sent_move_precision = 0.0f;
- std::string m_current_texture_modifier = "";
-};
/*
PlayerSAO needs some internals exposed.
@@ -179,33 +33,31 @@ class LagPool
{
float m_pool = 15.0f;
float m_max = 15.0f;
+
public:
LagPool() = default;
void setMax(float new_max)
{
m_max = new_max;
- if(m_pool > new_max)
+ if (m_pool > new_max)
m_pool = new_max;
}
void add(float dtime)
{
m_pool -= dtime;
- if(m_pool < 0)
+ if (m_pool < 0)
m_pool = 0;
}
- void empty()
- {
- m_pool = m_max;
- }
+ void empty() { m_pool = m_max; }
bool grab(float dtime)
{
- if(dtime <= 0)
+ if (dtime <= 0)
return true;
- if(m_pool + dtime > m_max)
+ if (m_pool + dtime > m_max)
return false;
m_pool += dtime;
return true;
@@ -220,10 +72,8 @@ public:
PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
bool is_singleplayer);
- ActiveObjectType getType() const
- { return ACTIVEOBJECT_TYPE_PLAYER; }
- ActiveObjectType getSendType() const
- { return ACTIVEOBJECT_TYPE_GENERIC; }
+ ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_PLAYER; }
+ ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; }
std::string getDescription();
/*
@@ -258,10 +108,8 @@ public:
Interaction interface
*/
- u16 punch(v3f dir,
- const ToolCapabilities *toolcap,
- ServerActiveObject *puncher,
- float time_from_last_punch);
+ u16 punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher,
+ float time_from_last_punch);
void rightClick(ServerActiveObject *clicker) {}
void setHP(s32 hp, const PlayerHPChangeReason &reason);
void setHPRaw(u16 hp) { m_hp = hp; }
@@ -291,10 +139,7 @@ public:
// Cheat prevention
- v3f getLastGoodPosition() const
- {
- return m_last_good_position;
- }
+ v3f getLastGoodPosition() const { return m_last_good_position; }
float resetTimeFromLastPunch()
{
float r = m_time_from_last_punch;
@@ -306,30 +151,17 @@ public:
m_nocheat_dig_pos = p;
m_nocheat_dig_time = 0;
}
- v3s16 getNoCheatDigPos()
- {
- return m_nocheat_dig_pos;
- }
- float getNoCheatDigTime()
- {
- return m_nocheat_dig_time;
- }
- void noCheatDigEnd()
- {
- m_nocheat_dig_pos = v3s16(32767, 32767, 32767);
- }
- LagPool& getDigPool()
- {
- return m_dig_pool;
- }
+ v3s16 getNoCheatDigPos() { return m_nocheat_dig_pos; }
+ float getNoCheatDigTime() { return m_nocheat_dig_time; }
+ void noCheatDigEnd() { m_nocheat_dig_pos = v3s16(32767, 32767, 32767); }
+ LagPool &getDigPool() { return m_dig_pool; }
void setMaxSpeedOverride(const v3f &vel);
// Returns true if cheated
bool checkMovementCheat();
// Other
- void updatePrivileges(const std::set<std::string> &privs,
- bool is_singleplayer)
+ void updatePrivileges(const std::set<std::string> &privs, bool is_singleplayer)
{
m_privs = privs;
m_is_singleplayer = is_singleplayer;
@@ -350,6 +182,7 @@ public:
private:
std::string getPropertyPacket();
void unlinkPlayerSessionAndSave();
+ std::string generateUpdatePhysicsOverrideCommand() const;
RemotePlayer *m_player = nullptr;
session_t m_peer_id = 0;
@@ -382,6 +215,7 @@ private:
s16 m_wanted_range = 0.0f;
Metadata m_meta;
+
public:
float m_physics_override_speed = 1.0f;
float m_physics_override_jump = 1.0f;
@@ -392,9 +226,10 @@ public:
bool m_physics_override_sent = false;
};
-
-struct PlayerHPChangeReason {
- enum Type : u8 {
+struct PlayerHPChangeReason
+{
+ enum Type : u8
+ {
SET_HP,
PLAYER_PUNCH,
FALL,
@@ -412,10 +247,7 @@ struct PlayerHPChangeReason {
// For NODE_DAMAGE
std::string node;
- inline bool hasLuaReference() const
- {
- return lua_reference >= 0;
- }
+ inline bool hasLuaReference() const { return lua_reference >= 0; }
bool setTypeFromString(const std::string &typestr)
{
@@ -457,15 +289,12 @@ struct PlayerHPChangeReason {
}
}
- PlayerHPChangeReason(Type type):
- type(type)
- {}
+ PlayerHPChangeReason(Type type) : type(type) {}
- PlayerHPChangeReason(Type type, ServerActiveObject *object):
+ PlayerHPChangeReason(Type type, ServerActiveObject *object) :
type(type), object(object)
- {}
+ {
+ }
- PlayerHPChangeReason(Type type, std::string node):
- type(type), node(node)
- {}
+ PlayerHPChangeReason(Type type, std::string node) : type(type), node(node) {}
};
diff --git a/src/serverobject.cpp b/src/server/serveractiveobject.cpp
index 1ed33f66b..3341dc008 100644
--- a/src/serverobject.cpp
+++ b/src/server/serveractiveobject.cpp
@@ -17,7 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "serverobject.h"
+#include "serveractiveobject.h"
#include <fstream>
#include "inventory.h"
#include "constants.h" // BS
@@ -30,39 +30,6 @@ ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos):
{
}
-ServerActiveObject* ServerActiveObject::create(ActiveObjectType type,
- ServerEnvironment *env, u16 id, v3f pos,
- const std::string &data)
-{
- // Find factory function
- std::map<u16, Factory>::iterator n;
- n = m_types.find(type);
- if(n == m_types.end()) {
- // These are 0.3 entity types, return without error.
- if (ACTIVEOBJECT_TYPE_ITEM <= type && type <= ACTIVEOBJECT_TYPE_MOBV2) {
- return NULL;
- }
-
- // If factory is not found, just return.
- warningstream<<"ServerActiveObject: No factory for type="
- <<type<<std::endl;
- return NULL;
- }
-
- Factory f = n->second;
- ServerActiveObject *object = (*f)(env, pos, data);
- return object;
-}
-
-void ServerActiveObject::registerType(u16 type, Factory f)
-{
- std::map<u16, Factory>::iterator n;
- n = m_types.find(type);
- if(n != m_types.end())
- return;
- m_types[type] = f;
-}
-
float ServerActiveObject::getMinimumSavedMovement()
{
return 2.0*BS;
@@ -81,3 +48,28 @@ bool ServerActiveObject::setWieldedItem(const ItemStack &item)
{
return false;
}
+
+std::string ServerActiveObject::generateUpdateInfantCommand(u16 infant_id, u16 protocol_version)
+{
+ std::ostringstream os(std::ios::binary);
+ // command
+ writeU8(os, AO_CMD_SPAWN_INFANT);
+ // parameters
+ writeU16(os, infant_id);
+ writeU8(os, getSendType());
+ if (protocol_version < 38) {
+ // Clients since 4aa9a66 so no longer need this data
+ // Version 38 is the first bump after that commit.
+ // See also: ClientEnvironment::addActiveObject
+ os << serializeLongString(getClientInitializationData(protocol_version));
+ }
+ return os.str();
+}
+
+void ServerActiveObject::dumpAOMessagesToQueue(std::queue<ActiveObjectMessage> &queue)
+{
+ while (!m_messages_out.empty()) {
+ queue.push(std::move(m_messages_out.front()));
+ m_messages_out.pop();
+ }
+}
diff --git a/src/serverobject.h b/src/server/serveractiveobject.h
index 48689fcb4..927009aef 100644
--- a/src/serverobject.h
+++ b/src/server/serveractiveobject.h
@@ -113,7 +113,7 @@ public:
The return value of this is passed to the client-side object
when it is created
*/
- virtual std::string getClientInitializationData(u16 protocol_version){return "";}
+ virtual std::string getClientInitializationData(u16 protocol_version) {return "";}
/*
The return value of this is passed to the server-side object
@@ -192,6 +192,10 @@ public:
m_attached_particle_spawners.erase(id);
}
+ std::string generateUpdateInfantCommand(u16 infant_id, u16 protocol_version);
+ std::string generateUpdateNametagAttributesCommand(const video::SColor &color) const;
+
+ void dumpAOMessagesToQueue(std::queue<ActiveObjectMessage> &queue);
/*
Number of players which know about this object. Object won't be
@@ -236,26 +240,16 @@ public:
*/
v3s16 m_static_block = v3s16(1337,1337,1337);
- /*
- Queue of messages to be sent to the client
- */
- std::queue<ActiveObjectMessage> m_messages_out;
-
protected:
virtual void onAttach(int parent_id) {}
virtual void onDetach(int parent_id) {}
- // Used for creating objects based on type
- typedef ServerActiveObject* (*Factory)
- (ServerEnvironment *env, v3f pos,
- const std::string &data);
- static void registerType(u16 type, Factory f);
-
ServerEnvironment *m_env;
v3f m_base_position;
std::unordered_set<u32> m_attached_particle_spawners;
-private:
- // Used for creating objects based on type
- static std::map<u16, Factory> m_types;
+ /*
+ Queue of messages to be sent to the client
+ */
+ std::queue<ActiveObjectMessage> m_messages_out;
};
diff --git a/src/server/serverinventorymgr.cpp b/src/server/serverinventorymgr.cpp
new file mode 100644
index 000000000..555e01ec6
--- /dev/null
+++ b/src/server/serverinventorymgr.cpp
@@ -0,0 +1,192 @@
+/*
+Minetest
+Copyright (C) 2010-2020 Minetest core development team
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "serverinventorymgr.h"
+#include "map.h"
+#include "nodemetadata.h"
+#include "player_sao.h"
+#include "remoteplayer.h"
+#include "server.h"
+#include "serverenvironment.h"
+
+ServerInventoryManager::ServerInventoryManager() : InventoryManager()
+{
+}
+
+ServerInventoryManager::~ServerInventoryManager()
+{
+ // Delete detached inventories
+ for (auto &detached_inventory : m_detached_inventories) {
+ delete detached_inventory.second.inventory;
+ }
+}
+
+Inventory *ServerInventoryManager::getInventory(const InventoryLocation &loc)
+{
+ switch (loc.type) {
+ case InventoryLocation::UNDEFINED:
+ case InventoryLocation::CURRENT_PLAYER:
+ break;
+ case InventoryLocation::PLAYER: {
+ RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
+ if (!player)
+ return NULL;
+ PlayerSAO *playersao = player->getPlayerSAO();
+ if (!playersao)
+ return NULL;
+ return playersao->getInventory();
+ } break;
+ case InventoryLocation::NODEMETA: {
+ NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p);
+ if (!meta)
+ return NULL;
+ return meta->getInventory();
+ } break;
+ case InventoryLocation::DETACHED: {
+ auto it = m_detached_inventories.find(loc.name);
+ if (it == m_detached_inventories.end())
+ return nullptr;
+ return it->second.inventory;
+ } break;
+ default:
+ sanity_check(false); // abort
+ break;
+ }
+ return NULL;
+}
+
+void ServerInventoryManager::setInventoryModified(const InventoryLocation &loc)
+{
+ switch (loc.type) {
+ case InventoryLocation::UNDEFINED:
+ break;
+ case InventoryLocation::PLAYER: {
+
+ RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
+
+ if (!player)
+ return;
+
+ player->setModified(true);
+ player->inventory.setModified(true);
+ // Updates are sent in ServerEnvironment::step()
+ } break;
+ case InventoryLocation::NODEMETA: {
+ MapEditEvent event;
+ event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
+ event.p = loc.p;
+ m_env->getMap().dispatchEvent(event);
+ } break;
+ case InventoryLocation::DETACHED: {
+ // Updates are sent in ServerEnvironment::step()
+ } break;
+ default:
+ sanity_check(false); // abort
+ break;
+ }
+}
+
+Inventory *ServerInventoryManager::createDetachedInventory(
+ const std::string &name, IItemDefManager *idef, const std::string &player)
+{
+ if (m_detached_inventories.count(name) > 0) {
+ infostream << "Server clearing detached inventory \"" << name << "\""
+ << std::endl;
+ delete m_detached_inventories[name].inventory;
+ } else {
+ infostream << "Server creating detached inventory \"" << name << "\""
+ << std::endl;
+ }
+
+ Inventory *inv = new Inventory(idef);
+ sanity_check(inv);
+ m_detached_inventories[name].inventory = inv;
+ if (!player.empty()) {
+ m_detached_inventories[name].owner = player;
+
+ if (!m_env)
+ return inv; // Mods are not loaded yet, ignore
+
+ RemotePlayer *p = m_env->getPlayer(name.c_str());
+
+ // if player is connected, send him the inventory
+ if (p && p->getPeerId() != PEER_ID_INEXISTENT) {
+ m_env->getGameDef()->sendDetachedInventory(
+ inv, name, p->getPeerId());
+ }
+ } else {
+ if (!m_env)
+ return inv; // Mods are not loaded yet, don't send
+
+ // Inventory is for everybody, broadcast
+ m_env->getGameDef()->sendDetachedInventory(inv, name, PEER_ID_INEXISTENT);
+ }
+
+ return inv;
+}
+
+bool ServerInventoryManager::removeDetachedInventory(const std::string &name)
+{
+ const auto &inv_it = m_detached_inventories.find(name);
+ if (inv_it == m_detached_inventories.end())
+ return false;
+
+ delete inv_it->second.inventory;
+ const std::string &owner = inv_it->second.owner;
+
+ if (!owner.empty()) {
+ RemotePlayer *player = m_env->getPlayer(owner.c_str());
+
+ if (player && player->getPeerId() != PEER_ID_INEXISTENT)
+ m_env->getGameDef()->sendDetachedInventory(
+ nullptr, name, player->getPeerId());
+
+ } else {
+ // Notify all players about the change
+ m_env->getGameDef()->sendDetachedInventory(
+ nullptr, name, PEER_ID_INEXISTENT);
+ }
+
+ m_detached_inventories.erase(inv_it);
+
+ return true;
+}
+
+void ServerInventoryManager::sendDetachedInventories(const std::string &peer_name,
+ bool incremental,
+ std::function<void(const std::string &, Inventory *)> apply_cb)
+{
+ for (const auto &detached_inventory : m_detached_inventories) {
+ const DetachedInventory &dinv = detached_inventory.second;
+ if (incremental) {
+ if (!dinv.inventory || !dinv.inventory->checkModified())
+ continue;
+ }
+
+ // if we are pushing inventories to a specific player
+ // we should filter to send only the right inventories
+ if (!peer_name.empty()) {
+ const std::string &attached_player = dinv.owner;
+ if (!attached_player.empty() && peer_name != attached_player)
+ continue;
+ }
+
+ apply_cb(detached_inventory.first, detached_inventory.second.inventory);
+ }
+}
diff --git a/src/server/serverinventorymgr.h b/src/server/serverinventorymgr.h
new file mode 100644
index 000000000..ccf6d3b2e
--- /dev/null
+++ b/src/server/serverinventorymgr.h
@@ -0,0 +1,60 @@
+/*
+Minetest
+Copyright (C) 2010-2020 Minetest core development team
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "inventorymanager.h"
+#include <functional>
+
+class ServerEnvironment;
+
+class ServerInventoryManager : public InventoryManager
+{
+public:
+ ServerInventoryManager();
+ virtual ~ServerInventoryManager();
+
+ void setEnv(ServerEnvironment *env)
+ {
+ assert(!m_env);
+ m_env = env;
+ }
+
+ Inventory *getInventory(const InventoryLocation &loc);
+ void setInventoryModified(const InventoryLocation &loc);
+
+ // Creates or resets inventory
+ Inventory *createDetachedInventory(const std::string &name, IItemDefManager *idef,
+ const std::string &player = "");
+ bool removeDetachedInventory(const std::string &name);
+
+ void sendDetachedInventories(const std::string &peer_name, bool incremental,
+ std::function<void(const std::string &, Inventory *)> apply_cb);
+
+private:
+ struct DetachedInventory
+ {
+ Inventory *inventory;
+ std::string owner;
+ };
+
+ ServerEnvironment *m_env = nullptr;
+
+ std::unordered_map<std::string, DetachedInventory> m_detached_inventories;
+};
diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp
new file mode 100644
index 000000000..ef0e87f2c
--- /dev/null
+++ b/src/server/unit_sao.cpp
@@ -0,0 +1,345 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2020 Minetest core developers & community
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "unit_sao.h"
+#include "scripting_server.h"
+#include "serverenvironment.h"
+
+UnitSAO::UnitSAO(ServerEnvironment *env, v3f pos) : ServerActiveObject(env, pos)
+{
+ // Initialize something to armor groups
+ m_armor_groups["fleshy"] = 100;
+}
+
+ServerActiveObject *UnitSAO::getParent() const
+{
+ if (!m_attachment_parent_id)
+ return nullptr;
+ // Check if the parent still exists
+ ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id);
+
+ return obj;
+}
+
+void UnitSAO::setArmorGroups(const ItemGroupList &armor_groups)
+{
+ m_armor_groups = armor_groups;
+ m_armor_groups_sent = false;
+}
+
+const ItemGroupList &UnitSAO::getArmorGroups() const
+{
+ return m_armor_groups;
+}
+
+void UnitSAO::setAnimation(
+ v2f frame_range, float frame_speed, float frame_blend, bool frame_loop)
+{
+ // store these so they can be updated to clients
+ m_animation_range = frame_range;
+ m_animation_speed = frame_speed;
+ m_animation_blend = frame_blend;
+ m_animation_loop = frame_loop;
+ m_animation_sent = false;
+}
+
+void UnitSAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend,
+ bool *frame_loop)
+{
+ *frame_range = m_animation_range;
+ *frame_speed = m_animation_speed;
+ *frame_blend = m_animation_blend;
+ *frame_loop = m_animation_loop;
+}
+
+void UnitSAO::setAnimationSpeed(float frame_speed)
+{
+ m_animation_speed = frame_speed;
+ m_animation_speed_sent = false;
+}
+
+void UnitSAO::setBonePosition(const std::string &bone, v3f position, v3f rotation)
+{
+ // store these so they can be updated to clients
+ m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
+ m_bone_position_sent = false;
+}
+
+void UnitSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation)
+{
+ *position = m_bone_position[bone].X;
+ *rotation = m_bone_position[bone].Y;
+}
+
+// clang-format off
+void UnitSAO::sendOutdatedData()
+{
+ if (!m_armor_groups_sent) {
+ m_armor_groups_sent = true;
+ m_messages_out.emplace(getId(), true, generateUpdateArmorGroupsCommand());
+ }
+
+ if (!m_animation_sent) {
+ m_animation_sent = true;
+ m_animation_speed_sent = true;
+ m_messages_out.emplace(getId(), true, generateUpdateAnimationCommand());
+ } 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());
+ }
+
+ 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));
+ }
+ }
+
+ if (!m_attachment_sent) {
+ m_attachment_sent = true;
+ m_messages_out.emplace(getId(), true, generateUpdateAttachmentCommand());
+ }
+}
+// clang-format on
+
+void UnitSAO::setAttachment(
+ int parent_id, const std::string &bone, v3f position, v3f rotation)
+{
+ // Attachments need to be handled on both the server and client.
+ // If we just attach on the server, we can only copy the position of the parent.
+ // Attachments are still sent to clients at an interval so players might see them
+ // lagging, plus we can't read and attach to skeletal bones. If we just attach on
+ // the client, the server still sees the child at its original location. This
+ // breaks some things so we also give the server the most accurate representation
+ // even if players only see the client changes.
+
+ int old_parent = m_attachment_parent_id;
+ m_attachment_parent_id = parent_id;
+ m_attachment_bone = bone;
+ m_attachment_position = position;
+ m_attachment_rotation = rotation;
+ m_attachment_sent = false;
+
+ if (parent_id != old_parent) {
+ onDetach(old_parent);
+ onAttach(parent_id);
+ }
+}
+
+void UnitSAO::getAttachment(
+ int *parent_id, std::string *bone, v3f *position, v3f *rotation) const
+{
+ *parent_id = m_attachment_parent_id;
+ *bone = m_attachment_bone;
+ *position = m_attachment_position;
+ *rotation = m_attachment_rotation;
+}
+
+void UnitSAO::clearChildAttachments()
+{
+ for (int child_id : m_attachment_child_ids) {
+ // Child can be NULL if it was deleted earlier
+ if (ServerActiveObject *child = m_env->getActiveObject(child_id))
+ child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
+ }
+ m_attachment_child_ids.clear();
+}
+
+void UnitSAO::clearParentAttachment()
+{
+ ServerActiveObject *parent = nullptr;
+ if (m_attachment_parent_id) {
+ parent = m_env->getActiveObject(m_attachment_parent_id);
+ setAttachment(0, "", m_attachment_position, m_attachment_rotation);
+ } else {
+ setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0));
+ }
+ // Do it
+ if (parent)
+ parent->removeAttachmentChild(m_id);
+}
+
+void UnitSAO::addAttachmentChild(int child_id)
+{
+ m_attachment_child_ids.insert(child_id);
+}
+
+void UnitSAO::removeAttachmentChild(int child_id)
+{
+ m_attachment_child_ids.erase(child_id);
+}
+
+const std::unordered_set<int> &UnitSAO::getAttachmentChildIds() const
+{
+ return m_attachment_child_ids;
+}
+
+void UnitSAO::onAttach(int parent_id)
+{
+ if (!parent_id)
+ return;
+
+ ServerActiveObject *parent = m_env->getActiveObject(parent_id);
+
+ if (!parent || parent->isGone())
+ return; // Do not try to notify soon gone parent
+
+ if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) {
+ // Call parent's on_attach field
+ m_env->getScriptIface()->luaentity_on_attach_child(parent_id, this);
+ }
+}
+
+void UnitSAO::onDetach(int parent_id)
+{
+ if (!parent_id)
+ return;
+
+ ServerActiveObject *parent = m_env->getActiveObject(parent_id);
+ if (getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
+ m_env->getScriptIface()->luaentity_on_detach(m_id, parent);
+
+ if (!parent || parent->isGone())
+ return; // Do not try to notify soon gone parent
+
+ if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
+ m_env->getScriptIface()->luaentity_on_detach_child(parent_id, this);
+}
+
+ObjectProperties *UnitSAO::accessObjectProperties()
+{
+ return &m_prop;
+}
+
+void UnitSAO::notifyObjectPropertiesModified()
+{
+ m_properties_sent = false;
+}
+
+std::string UnitSAO::generateUpdateAttachmentCommand() const
+{
+ std::ostringstream os(std::ios::binary);
+ // command
+ writeU8(os, AO_CMD_ATTACH_TO);
+ // parameters
+ writeS16(os, m_attachment_parent_id);
+ os << serializeString(m_attachment_bone);
+ writeV3F32(os, m_attachment_position);
+ writeV3F32(os, m_attachment_rotation);
+ return os.str();
+}
+
+std::string UnitSAO::generateUpdateBonePositionCommand(
+ const std::string &bone, const v3f &position, const v3f &rotation)
+{
+ std::ostringstream os(std::ios::binary);
+ // command
+ writeU8(os, AO_CMD_SET_BONE_POSITION);
+ // parameters
+ os << serializeString(bone);
+ writeV3F32(os, position);
+ writeV3F32(os, rotation);
+ return os.str();
+}
+
+std::string UnitSAO::generateUpdateAnimationSpeedCommand() const
+{
+ std::ostringstream os(std::ios::binary);
+ // command
+ writeU8(os, AO_CMD_SET_ANIMATION_SPEED);
+ // parameters
+ writeF32(os, m_animation_speed);
+ return os.str();
+}
+
+std::string UnitSAO::generateUpdateAnimationCommand() 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);
+ // these are sent inverted so we get true when the server sends nothing
+ writeU8(os, !m_animation_loop);
+ return os.str();
+}
+
+std::string UnitSAO::generateUpdateArmorGroupsCommand() const
+{
+ std::ostringstream os(std::ios::binary);
+ writeU8(os, AO_CMD_UPDATE_ARMOR_GROUPS);
+ writeU16(os, m_armor_groups.size());
+ for (const auto &armor_group : m_armor_groups) {
+ os << serializeString(armor_group.first);
+ writeS16(os, armor_group.second);
+ }
+ return os.str();
+}
+
+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)
+{
+ std::ostringstream os(std::ios::binary);
+ // command
+ writeU8(os, AO_CMD_UPDATE_POSITION);
+ // pos
+ writeV3F32(os, position);
+ // velocity
+ writeV3F32(os, velocity);
+ // acceleration
+ writeV3F32(os, acceleration);
+ // rotation
+ writeV3F32(os, rotation);
+ // do_interpolate
+ writeU8(os, do_interpolate);
+ // is_end_position (for interpolation)
+ writeU8(os, is_movement_end);
+ // update_interval (for interpolation)
+ writeF32(os, update_interval);
+ return os.str();
+}
+
+std::string UnitSAO::generateSetPropertiesCommand(const ObjectProperties &prop) const
+{
+ std::ostringstream os(std::ios::binary);
+ writeU8(os, AO_CMD_SET_PROPERTIES);
+ prop.serialize(os);
+ return os.str();
+}
+
+std::string UnitSAO::generatePunchCommand(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()));
+}
diff --git a/src/server/unit_sao.h b/src/server/unit_sao.h
new file mode 100644
index 000000000..3cb7f0ad5
--- /dev/null
+++ b/src/server/unit_sao.h
@@ -0,0 +1,136 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013-2020 Minetest core developers & community
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "object_properties.h"
+#include "serveractiveobject.h"
+
+class UnitSAO : public ServerActiveObject
+{
+public:
+ UnitSAO(ServerEnvironment *env, v3f pos);
+ virtual ~UnitSAO() = default;
+
+ u16 getHP() const { return m_hp; }
+ // Use a function, if isDead can be defined by other conditions
+ bool isDead() const { return m_hp == 0; }
+
+ // Rotation
+ void setRotation(v3f rotation) { m_rotation = rotation; }
+ const v3f &getRotation() const { return m_rotation; }
+ v3f getRadRotation() { return m_rotation * core::DEGTORAD; }
+
+ // Deprecated
+ f32 getRadYawDep() const { return (m_rotation.Y + 90.) * core::DEGTORAD; }
+
+ // Armor groups
+ inline bool isImmortal() const
+ {
+ return itemgroup_get(getArmorGroups(), "immortal");
+ }
+ void setArmorGroups(const ItemGroupList &armor_groups);
+ const ItemGroupList &getArmorGroups() const;
+
+ // Animation
+ void setAnimation(v2f frame_range, float frame_speed, float frame_blend,
+ bool frame_loop);
+ void getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend,
+ bool *frame_loop);
+ void setAnimationSpeed(float frame_speed);
+
+ // Bone position
+ void setBonePosition(const std::string &bone, v3f position, v3f rotation);
+ void getBonePosition(const std::string &bone, v3f *position, v3f *rotation);
+
+ // Attachments
+ ServerActiveObject *getParent() const;
+ inline bool isAttached() const { return getParent(); }
+ void setAttachment(int parent_id, const std::string &bone, v3f position,
+ v3f rotation);
+ void getAttachment(int *parent_id, std::string *bone, v3f *position,
+ v3f *rotation) const;
+ void clearChildAttachments();
+ void clearParentAttachment();
+ void addAttachmentChild(int child_id);
+ void removeAttachmentChild(int child_id);
+ const std::unordered_set<int> &getAttachmentChildIds() const;
+
+ // Object properties
+ ObjectProperties *accessObjectProperties();
+ void notifyObjectPropertiesModified();
+ void sendOutdatedData();
+
+ // Update packets
+ std::string generateUpdateAttachmentCommand() const;
+ std::string generateUpdateAnimationSpeedCommand() const;
+ std::string generateUpdateAnimationCommand() 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;
+ static std::string generateUpdateBonePositionCommand(const std::string &bone,
+ const v3f &position, const v3f &rotation);
+ void sendPunchCommand();
+
+protected:
+ u16 m_hp = 1;
+
+ v3f m_rotation;
+
+ ItemGroupList m_armor_groups;
+
+ // Object properties
+ bool m_properties_sent = true;
+ ObjectProperties m_prop;
+
+ // Stores position and rotation for each bone name
+ std::unordered_map<std::string, core::vector2d<v3f>> m_bone_position;
+
+ int m_attachment_parent_id = 0;
+
+private:
+ void onAttach(int parent_id);
+ void onDetach(int parent_id);
+
+ std::string generatePunchCommand(u16 result_hp) const;
+
+ // Armor groups
+ bool m_armor_groups_sent = false;
+
+ // Animation
+ v2f m_animation_range;
+ float m_animation_speed = 0.0f;
+ float m_animation_blend = 0.0f;
+ bool m_animation_loop = true;
+ bool m_animation_sent = false;
+ bool m_animation_speed_sent = false;
+
+ // Bone positions
+ bool m_bone_position_sent = false;
+
+ // Attachments
+ std::unordered_set<int> m_attachment_child_ids;
+ std::string m_attachment_bone = "";
+ v3f m_attachment_position;
+ v3f m_attachment_rotation;
+ bool m_attachment_sent = false;
+};
diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp
index 333d32ff5..ad2ffc9a4 100644
--- a/src/serverenvironment.cpp
+++ b/src/serverenvironment.cpp
@@ -17,8 +17,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <algorithm>
#include "serverenvironment.h"
-#include "content_sao.h"
#include "settings.h"
#include "log.h"
#include "mapblock.h"
@@ -44,7 +44,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#if USE_POSTGRESQL
#include "database/database-postgresql.h"
#endif
-#include <algorithm>
+#if USE_LEVELDB
+#include "database/database-leveldb.h"
+#endif
+#include "server/luaentity_sao.h"
+#include "server/player_sao.h"
#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
@@ -545,24 +549,6 @@ bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
return m_player_database->removePlayer(name);
}
-bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p)
-{
- // Iterate trough nodes on the line
- voxalgo::VoxelLineIterator iterator(pos1 / BS, (pos2 - pos1) / BS);
- do {
- MapNode n = getMap().getNode(iterator.m_current_node_pos);
-
- // Return non-air
- if (n.param0 != CONTENT_AIR) {
- if (p)
- *p = iterator.m_current_node_pos;
- return false;
- }
- iterator.next();
- } while (iterator.m_current_index <= iterator.m_last_index);
- return true;
-}
-
void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
const std::string &str_reason, bool reconnect)
{
@@ -638,6 +624,9 @@ PlayerSAO *ServerEnvironment::loadPlayer(RemotePlayer *player, bool *new_player,
void ServerEnvironment::saveMeta()
{
+ if (!m_meta_loaded)
+ return;
+
std::string path = m_path_world + DIR_DELIM "env_meta.txt";
// Open file and serialize
@@ -664,6 +653,9 @@ void ServerEnvironment::saveMeta()
void ServerEnvironment::loadMeta()
{
+ SANITY_CHECK(!m_meta_loaded);
+ m_meta_loaded = true;
+
// If file doesn't exist, load default environment metadata
if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
infostream << "ServerEnvironment: Loading default environment metadata"
@@ -1420,10 +1412,7 @@ void ServerEnvironment::step(float dtime)
// Step object
obj->step(dtime, send_recommended);
// Read messages from object
- while (!obj->m_messages_out.empty()) {
- this->m_active_object_messages.push(obj->m_messages_out.front());
- obj->m_messages_out.pop();
- }
+ obj->dumpAOMessagesToQueue(m_active_object_messages);
};
m_ao_manager.step(dtime, cb_state);
}
@@ -1609,28 +1598,28 @@ void ServerEnvironment::setStaticForActiveObjectsInBlock(
}
}
-ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
+bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest)
{
if(m_active_object_messages.empty())
- return ActiveObjectMessage(0);
+ return false;
- ActiveObjectMessage message = m_active_object_messages.front();
+ *dest = std::move(m_active_object_messages.front());
m_active_object_messages.pop();
- return message;
+ return true;
}
void ServerEnvironment::getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects)
{
- std::vector<u16> objectIds;
- getObjectsInsideRadius(objectIds, shootline_on_map.start,
- shootline_on_map.getLength() + 10.0f);
+ std::vector<ServerActiveObject *> objs;
+ getObjectsInsideRadius(objs, shootline_on_map.start,
+ shootline_on_map.getLength() + 10.0f, nullptr);
const v3f line_vector = shootline_on_map.getVector();
- for (u16 objectId : objectIds) {
- ServerActiveObject* obj = getActiveObject(objectId);
-
+ for (auto obj : objs) {
+ if (obj->isGone())
+ continue;
aabb3f selection_box;
if (!obj->getSelectionBox(&selection_box))
continue;
@@ -1645,7 +1634,7 @@ void ServerEnvironment::getSelectedActiveObjects(
if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
&current_intersection, &current_normal)) {
objects.emplace_back(
- (s16) objectId, current_intersection, current_normal,
+ (s16) obj->getId(), current_intersection, current_normal,
(current_intersection - shootline_on_map.start).getLengthSQ());
}
}
@@ -1799,6 +1788,18 @@ static void print_hexdump(std::ostream &o, const std::string &data)
}
}
+ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
+ const std::string &data)
+{
+ switch (type) {
+ case ACTIVEOBJECT_TYPE_LUAENTITY:
+ return new LuaEntitySAO(this, pos, data);
+ default:
+ warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
+ }
+ return nullptr;
+}
+
/*
Convert stored objects from blocks near the players to active.
*/
@@ -1832,10 +1833,10 @@ void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s)
std::vector<StaticObject> new_stored;
for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
// Create an active object from the data
- ServerActiveObject *obj = ServerActiveObject::create
- ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
+ ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos,
+ s_obj.data);
// If couldn't create object, store static data back.
- if(obj == NULL) {
+ if (!obj) {
errorstream<<"ServerEnvironment::activateObjects(): "
<<"failed to create active object from static object "
<<"in block "<<PP(s_obj.pos/BS)
@@ -2083,6 +2084,7 @@ PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
if (name == "dummy")
return new Database_Dummy();
+
#if USE_POSTGRESQL
if (name == "postgresql") {
std::string connect_string;
@@ -2090,6 +2092,12 @@ PlayerDatabase *ServerEnvironment::openPlayerDatabase(const std::string &name,
return new PlayerDatabasePostgreSQL(connect_string);
}
#endif
+
+#if USE_LEVELDB
+ if (name == "leveldb")
+ return new PlayerDatabaseLevelDB(savedir);
+#endif
+
if (name == "files")
return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
@@ -2110,7 +2118,7 @@ bool ServerEnvironment::migratePlayersDatabase(const GameParams &game_params,
if (!world_mt.exists("player_backend")) {
errorstream << "Please specify your current backend in world.mt:"
<< std::endl
- << " player_backend = {files|sqlite3|postgresql}"
+ << " player_backend = {files|sqlite3|leveldb|postgresql}"
<< std::endl;
return false;
}
@@ -2189,9 +2197,22 @@ AuthDatabase *ServerEnvironment::openAuthDatabase(
if (name == "sqlite3")
return new AuthDatabaseSQLite3(savedir);
+#if USE_POSTGRESQL
+ if (name == "postgresql") {
+ std::string connect_string;
+ conf.getNoEx("pgsql_auth_connection", connect_string);
+ return new AuthDatabasePostgreSQL(connect_string);
+ }
+#endif
+
if (name == "files")
return new AuthDatabaseFiles(savedir);
+#if USE_LEVELDB
+ if (name == "leveldb")
+ return new AuthDatabaseLevelDB(savedir);
+#endif
+
throw BaseException(std::string("Database backend ") + name + " not supported.");
}
diff --git a/src/serverenvironment.h b/src/serverenvironment.h
index 3c7b7d059..af742e290 100644
--- a/src/serverenvironment.h
+++ b/src/serverenvironment.h
@@ -289,9 +289,9 @@ public:
/*
Get the next message emitted by some active object.
- Returns a message with id=0 if no messages are available.
+ Returns false if no messages are available, true otherwise.
*/
- ActiveObjectMessage getActiveObjectMessage();
+ bool getActiveObjectMessage(ActiveObjectMessage *dest);
virtual void getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
@@ -323,9 +323,10 @@ public:
bool swapNode(v3s16 p, const MapNode &n);
// Find all active objects inside a radius around a point
- void getObjectsInsideRadius(std::vector<u16> &objects, const v3f &pos, float radius)
+ void getObjectsInsideRadius(std::vector<ServerActiveObject *> &objects, const v3f &pos, float radius,
+ std::function<bool(ServerActiveObject *obj)> include_obj_cb)
{
- return m_ao_manager.getObjectsInsideRadius(pos, radius, objects);
+ return m_ao_manager.getObjectsInsideRadius(pos, radius, objects, include_obj_cb);
}
// Clear objects, loading and going through every MapBlock
@@ -334,16 +335,6 @@ public:
// This makes stuff happen
void step(f32 dtime);
- /*!
- * Returns false if the given line intersects with a
- * non-air node, true otherwise.
- * \param pos1 start of the line
- * \param pos2 end of the line
- * \param p output, position of the first non-air node
- * the line intersects
- */
- bool line_of_sight(v3f pos1, v3f pos2, v3s16 *p = NULL);
-
u32 getGameTime() const { return m_game_time; }
void reportMaxLagEstimate(float f) { m_max_lag_estimate = f; }
@@ -448,6 +439,8 @@ private:
IntervalLimiter m_active_blocks_management_interval;
IntervalLimiter m_active_block_modifier_interval;
IntervalLimiter m_active_blocks_nodemetadata_interval;
+ // Whether the variables below have been read from file yet
+ bool m_meta_loaded = false;
// Time from the beginning of the game in seconds.
// Incremented in step().
u32 m_game_time = 0;
@@ -478,4 +471,6 @@ private:
IntervalLimiter m_particle_management_interval;
std::unordered_map<u32, float> m_particle_spawners;
std::unordered_map<u32, u16> m_particle_spawner_attachments;
+
+ ServerActiveObject* createSAO(ActiveObjectType type, v3f pos, const std::string &data);
};
diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp
index 9156580c0..febfbb9d3 100644
--- a/src/settings_translation_file.cpp
+++ b/src/settings_translation_file.cpp
@@ -254,8 +254,6 @@ fake_function() {
gettext("Enables parallax occlusion mapping.\nRequires shaders to be enabled.");
gettext("Parallax occlusion mode");
gettext("0 = parallax occlusion with slope information (faster).\n1 = relief mapping (slower, more accurate).");
- gettext("Parallax occlusion strength");
- gettext("Strength of parallax.");
gettext("Parallax occlusion iterations");
gettext("Number of parallax occlusion iterations.");
gettext("Parallax occlusion scale");
@@ -326,6 +324,8 @@ fake_function() {
gettext("Multiplier for fall bobbing.\nFor example: 0 for no view bobbing; 1.0 for normal; 2.0 for double.");
gettext("3D mode");
gettext("3D support.\nCurrently supported:\n- none: no 3d output.\n- anaglyph: cyan/magenta color 3d.\n- interlaced: odd/even line based polarisation screen support.\n- topbottom: split screen top/bottom.\n- sidebyside: split screen side by side.\n- crossview: Cross-eyed 3d\n- pageflip: quadbuffer based 3d.\nNote that the interlaced mode requires shaders to be enabled.");
+ gettext("3D mode parallax strength");
+ gettext("Strength of 3D mode parallax.");
gettext("Console height");
gettext("In-game chat console height, between 0.1 (10%) and 1.0 (100%).");
gettext("Console color");
@@ -427,8 +427,10 @@ fake_function() {
gettext("Opaqueness (alpha) of the shadow behind the fallback font, between 0 and 255.");
gettext("Fallback font path");
gettext("Path of the fallback font.\nIf “freetype” setting is enabled: Must be a TrueType font.\nIf “freetype” setting is disabled: Must be a bitmap or XML vectors font.\nThis font will be used for certain languages or if the default font is unavailable.");
+ gettext("Chat font size");
+ gettext("Font size of the recent chat text and chat prompt in point (pt).\nValue 0 will use the default font size.");
gettext("Screenshot folder");
- gettext("Path to save screenshots at.");
+ gettext("Path to save screenshots at. Can be an absolute or relative path.\nThe folder will be created if it doesn't already exist.");
gettext("Screenshot format");
gettext("Format of screenshots.");
gettext("Screenshot quality");
@@ -451,6 +453,8 @@ fake_function() {
gettext("Address to connect to.\nLeave this blank to start a local server.\nNote that the address field in the main menu overrides this setting.");
gettext("Remote port");
gettext("Port to connect to (UDP).\nNote that the port field in the main menu overrides this setting.");
+ gettext("Prometheus listener address");
+ gettext("Prometheus listener address.\nIf minetest is compiled with ENABLE_PROMETHEUS option enabled,\nenable metrics listener for Prometheus on that address.\nMetrics can be fetch on http://127.0.0.1:30000/metrics");
gettext("Saving map received from server");
gettext("Save the map received by the client on disk.");
gettext("Connect to external media server");
@@ -516,6 +520,8 @@ fake_function() {
gettext("World directory (everything in the world is stored here).\nNot needed if starting from the main menu.");
gettext("Item entity TTL");
gettext("Time in seconds for item entity (dropped items) to live.\nSetting it to -1 disables the feature.");
+ gettext("Default stack size");
+ gettext("Specifies the default stack size of nodes, items and tools.\nNote that mods or games may explicitly set a stack for certain (or all) items.");
gettext("Damage");
gettext("Enable players getting damage and dying.");
gettext("Creative");
@@ -555,7 +561,7 @@ fake_function() {
gettext("Active object send range");
gettext("From how far clients know about objects, stated in mapblocks (16 nodes).\n\nSetting this larger than active_block_range will also cause the server\nto maintain active objects up to this distance in the direction the\nplayer is looking. (This can avoid mobs suddenly disappearing from view)");
gettext("Active block range");
- gettext("The radius of the volume of blocks around every player that is subject to the\nactive block stuff, stated in mapblocks (16 nodes).\nIn active blocks objects are loaded and ABMs run.\nThis is also the minimum range in which active objects (mobs) are maintained.\nThis should be configured together with active_object_range.");
+ gettext("The radius of the volume of blocks around every player that is subject to the\nactive block stuff, stated in mapblocks (16 nodes).\nIn active blocks objects are loaded and ABMs run.\nThis is also the minimum range in which active objects (mobs) are maintained.\nThis should be configured together with active_object_send_range_blocks.");
gettext("Max block send distance");
gettext("From how far blocks are sent to clients, stated in mapblocks (16 nodes).");
gettext("Maximum forceloaded blocks");
@@ -674,6 +680,8 @@ fake_function() {
gettext("Level of logging to be written to debug.txt:\n- <nothing> (no logging)\n- none (messages with no level)\n- error\n- warning\n- action\n- info\n- verbose");
gettext("Debug log file size threshold");
gettext("If the file size of debug.txt exceeds the number of megabytes specified in\nthis setting when it is opened, the file is moved to debug.txt.1,\ndeleting an older debug.txt.1 if it exists.\ndebug.txt is only moved if this setting is positive.");
+ gettext("Chat log level");
+ gettext("Minimal level of logging to be written to chat.");
gettext("IPv6");
gettext("Enable IPv6 support (for both client and server).\nRequired for IPv6 connections to work at all.");
gettext("Advanced");
@@ -791,9 +799,21 @@ fake_function() {
gettext("Defines areas where trees have apples.");
gettext("Mapgen V7");
gettext("Mapgen V7 specific flags");
- gettext("Map generation attributes specific to Mapgen v7.\n'ridges' enables the rivers.");
+ gettext("Map generation attributes specific to Mapgen v7.\n'ridges': Rivers.\n'floatlands': Floating land masses in the atmosphere.\n'caverns': Giant caves deep underground.");
gettext("Mountain zero level");
gettext("Y of mountain density gradient zero level. Used to shift mountains vertically.");
+ gettext("Floatland minimum Y");
+ gettext("Lower Y limit of floatlands.");
+ gettext("Floatland maximum Y");
+ gettext("Upper Y limit of floatlands.");
+ gettext("Floatland tapering distance");
+ gettext("Y-distance over which floatlands taper from full density to nothing.\nTapering starts at this distance from the Y limit.\nFor a solid floatland layer, this controls the height of hills/mountains.\nMust be less than or equal to half the distance between the Y limits.");
+ gettext("Floatland taper exponent");
+ gettext("Exponent of the floatland tapering. Alters the tapering behaviour.\nValue = 1.0 creates a uniform, linear tapering.\nValues > 1.0 create a smooth tapering suitable for the default separated\nfloatlands.\nValues < 1.0 (for example 0.25) create a more defined surface level with\nflatter lowlands, suitable for a solid floatland layer.");
+ gettext("Floatland density");
+ gettext("Adjusts the density of the floatland layer.\nIncrease value to increase density. Can be positive or negative.\nValue = 0.0: 50% of volume is floatland.\nValue = 2.0 (can be higher depending on 'mgv7_np_floatland', always test\nto be sure) creates a solid floatland layer.");
+ gettext("Floatland water level");
+ gettext("Surface level of optional water placed on a solid floatland layer.\nWater is disabled by default and will only be placed if this value is set\nto above 'mgv7_floatland_ymax' - 'mgv7_floatland_taper' (the start of the\nupper tapering).\n***WARNING, POTENTIAL DANGER TO WORLDS AND SERVER PERFORMANCE***:\nWhen enabling water placement the floatlands must be configured and tested\nto be a solid layer by setting 'mgv7_floatland_density' to 2.0 (or other\nrequired value depending on 'mgv7_np_floatland'), to avoid\nserver-intensive extreme water flow and to avoid vast flooding of the\nworld surface below.");
gettext("Cave width");
gettext("Controls width of tunnels, a smaller value creates wider tunnels.\nValue >= 10.0 completely disables generation of tunnels and avoids the\nintensive noise calculations.");
gettext("Large cave depth");
@@ -837,6 +857,8 @@ fake_function() {
gettext("3D noise defining mountain structure and height.\nAlso defines structure of floatland mountain terrain.");
gettext("Ridge noise");
gettext("3D noise defining structure of river canyon walls.");
+ gettext("Floatland noise");
+ gettext("3D noise defining structure of floatlands.\nIf altered from the default, the noise 'scale' (0.7 by default) may need\nto be adjusted, as floatland tapering functions best when this noise has\na value range of approximately -2.0 to 2.0.");
gettext("Cavern noise");
gettext("3D noise defining giant caverns.");
gettext("Cave1 noise");
@@ -1068,14 +1090,14 @@ fake_function() {
gettext("Size of mapchunks generated by mapgen, stated in mapblocks (16 nodes).\nWARNING!: There is no benefit, and there are several dangers, in\nincreasing this value above 5.\nReducing this value increases cave and dungeon density.\nAltering this value is for special usage, leaving it unchanged is\nrecommended.");
gettext("Mapgen debug");
gettext("Dump the mapgen debug information.");
- gettext("Absolute limit of emerge queues");
+ gettext("Absolute limit of queued blocks to emerge");
gettext("Maximum number of blocks that can be queued for loading.");
- gettext("Limit of emerge queues on disk");
- gettext("Maximum number of blocks to be queued that are to be loaded from file.\nSet to blank for an appropriate amount to be chosen automatically.");
- gettext("Limit of emerge queues to generate");
- gettext("Maximum number of blocks to be queued that are to be generated.\nSet to blank for an appropriate amount to be chosen automatically.");
+ gettext("Per-player limit of queued blocks load from disk");
+ gettext("Maximum number of blocks to be queued that are to be loaded from file.\nThis limit is enforced per player.");
+ gettext("Per-player limit of queued blocks to generate");
+ gettext("Maximum number of blocks to be queued that are to be generated.\nThis limit is enforced per player.");
gettext("Number of emerge threads");
- gettext("Number of emerge threads to use.\nWARNING: Currently there are multiple bugs that may cause crashes when\n'num_emerge_threads' is larger than 1. Until this warning is removed it is\nstrongly recommended this value is set to the default '1'.\nValue 0:\n- Automatic selection. The number of emerge threads will be\n- 'number of processors - 2', with a lower limit of 1.\nAny other value:\n- Specifies the number of emerge threads, with a lower limit of 1.\nWARNING: Increasing the number of emerge threads increases engine mapgen\nspeed, but this may harm game performance by interfering with other\nprocesses, especially in singleplayer and/or when running Lua code in\n'on_generated'. For many users the optimum setting may be '1'.");
+ gettext("Number of emerge threads to use.\nValue 0:\n- Automatic selection. The number of emerge threads will be\n- 'number of processors - 2', with a lower limit of 1.\nAny other value:\n- Specifies the number of emerge threads, with a lower limit of 1.\nWARNING: Increasing the number of emerge threads increases engine mapgen\nspeed, but this may harm game performance by interfering with other\nprocesses, especially in singleplayer and/or when running Lua code in\n'on_generated'. For many users the optimum setting may be '1'.");
gettext("Online Content Repository");
gettext("ContentDB URL");
gettext("The URL for the content repository");
diff --git a/src/skyparams.h b/src/skyparams.h
index 9fdfd89da..1de494d69 100644
--- a/src/skyparams.h
+++ b/src/skyparams.h
@@ -37,9 +37,9 @@ struct SkyboxParams
std::vector<std::string> textures;
bool clouds;
SkyColor sky_color;
- video::SColor sun_tint;
- video::SColor moon_tint;
- std::string tint_type;
+ video::SColor fog_sun_tint;
+ video::SColor fog_moon_tint;
+ std::string fog_tint_type;
};
struct SunParams
@@ -76,12 +76,12 @@ public:
{
SkyColor sky;
// Horizon colors
- sky.day_horizon = video::SColor(255, 155, 193, 240);
+ sky.day_horizon = video::SColor(255, 144, 211, 246);
sky.indoors = video::SColor(255, 100, 100, 100);
sky.dawn_horizon = video::SColor(255, 186, 193, 240);
sky.night_horizon = video::SColor(255, 64, 144, 255);
// Sky colors
- sky.day_sky = video::SColor(255, 140, 186, 250);
+ sky.day_sky = video::SColor(255, 97, 181, 245);
sky.dawn_sky = video::SColor(255, 180, 186, 250);
sky.night_sky = video::SColor(255, 0, 107, 255);
return sky;
diff --git a/src/staticobject.cpp b/src/staticobject.cpp
index bebca12ec..5ccb7baf5 100644
--- a/src/staticobject.cpp
+++ b/src/staticobject.cpp
@@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "staticobject.h"
#include "util/serialize.h"
-#include "content_sao.h"
+#include "server/serveractiveobject.h"
StaticObject::StaticObject(const ServerActiveObject *s_obj, const v3f &pos_):
type(s_obj->getType()),
diff --git a/src/texture_override.cpp b/src/texture_override.cpp
new file mode 100644
index 000000000..10d129b87
--- /dev/null
+++ b/src/texture_override.cpp
@@ -0,0 +1,120 @@
+/*
+Minetest
+Copyright (C) 2020 Hugues Ross <hugues.ross@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "texture_override.h"
+
+#include "log.h"
+#include "util/string.h"
+#include <algorithm>
+#include <fstream>
+
+TextureOverrideSource::TextureOverrideSource(std::string filepath)
+{
+ std::ifstream infile(filepath.c_str());
+ std::string line;
+ int line_index = 0;
+ while (std::getline(infile, line)) {
+ line_index++;
+
+ // Also trim '\r' on DOS-style files
+ line = trim(line);
+
+ // Ignore empty lines and comments
+ if (line.empty() || line[0] == '#')
+ continue;
+
+ std::vector<std::string> splitted = str_split(line, ' ');
+ if (splitted.size() != 3) {
+ warningstream << filepath << ":" << line_index
+ << " Syntax error in texture override \"" << line
+ << "\": Expected 3 arguments, got " << splitted.size()
+ << std::endl;
+ continue;
+ }
+
+ TextureOverride texture_override = {};
+ texture_override.id = splitted[0];
+ texture_override.texture = splitted[2];
+
+ // Parse the target mask
+ std::vector<std::string> targets = str_split(splitted[1], ',');
+ for (const std::string &target : targets) {
+ if (target == "top")
+ texture_override.target |= static_cast<u8>(OverrideTarget::TOP);
+ else if (target == "bottom")
+ texture_override.target |= static_cast<u8>(OverrideTarget::BOTTOM);
+ else if (target == "left")
+ texture_override.target |= static_cast<u8>(OverrideTarget::LEFT);
+ else if (target == "right")
+ texture_override.target |= static_cast<u8>(OverrideTarget::RIGHT);
+ else if (target == "front")
+ texture_override.target |= static_cast<u8>(OverrideTarget::FRONT);
+ else if (target == "back")
+ texture_override.target |= static_cast<u8>(OverrideTarget::BACK);
+ else if (target == "inventory")
+ texture_override.target |= static_cast<u8>(OverrideTarget::INVENTORY);
+ else if (target == "wield")
+ texture_override.target |= static_cast<u8>(OverrideTarget::WIELD);
+ else if (target == "sides")
+ texture_override.target |= static_cast<u8>(OverrideTarget::SIDES);
+ else if (target == "all" || target == "*")
+ texture_override.target |= static_cast<u8>(OverrideTarget::ALL_FACES);
+ else {
+ // Report invalid target
+ warningstream << filepath << ":" << line_index
+ << " Syntax error in texture override \"" << line
+ << "\": Unknown target \"" << target << "\""
+ << std::endl;
+ }
+ }
+
+ // If there are no valid targets, skip adding this override
+ if (texture_override.target == static_cast<u8>(OverrideTarget::INVALID)) {
+ continue;
+ }
+
+ m_overrides.push_back(texture_override);
+ }
+}
+
+//! Get all overrides that apply to item definitions
+std::vector<TextureOverride> TextureOverrideSource::getItemTextureOverrides()
+{
+ std::vector<TextureOverride> found_overrides;
+
+ for (const TextureOverride &texture_override : m_overrides) {
+ if (texture_override.hasTarget(OverrideTarget::ITEM_TARGETS))
+ found_overrides.push_back(texture_override);
+ }
+
+ return found_overrides;
+}
+
+//! Get all overrides that apply to node definitions
+std::vector<TextureOverride> TextureOverrideSource::getNodeTileOverrides()
+{
+ std::vector<TextureOverride> found_overrides;
+
+ for (const TextureOverride &texture_override : m_overrides) {
+ if (texture_override.hasTarget(OverrideTarget::ALL_FACES))
+ found_overrides.push_back(texture_override);
+ }
+
+ return found_overrides;
+}
diff --git a/src/texture_override.h b/src/texture_override.h
new file mode 100644
index 000000000..db03bd014
--- /dev/null
+++ b/src/texture_override.h
@@ -0,0 +1,72 @@
+/*
+Minetest
+Copyright (C) 2020 Hugues Ross <hugues.ross@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+
+#include "irrlichttypes.h"
+#include <string>
+#include <vector>
+
+//! Bitmask enum specifying what a texture override should apply to
+enum class OverrideTarget : u8
+{
+ INVALID = 0,
+ TOP = 1 << 0,
+ BOTTOM = 1 << 1,
+ LEFT = 1 << 2,
+ RIGHT = 1 << 3,
+ FRONT = 1 << 4,
+ BACK = 1 << 5,
+ INVENTORY = 1 << 6,
+ WIELD = 1 << 7,
+
+ SIDES = LEFT | RIGHT | FRONT | BACK,
+ ALL_FACES = TOP | BOTTOM | SIDES,
+ ITEM_TARGETS = INVENTORY | WIELD,
+};
+
+struct TextureOverride
+{
+ std::string id;
+ std::string texture;
+ u8 target;
+
+ // Helper function for checking if an OverrideTarget is found in
+ // a TextureOverride without casting
+ inline bool hasTarget(OverrideTarget overrideTarget) const
+ {
+ return (target & static_cast<u8>(overrideTarget)) != 0;
+ }
+};
+
+//! Class that provides texture override information from a texture pack
+class TextureOverrideSource
+{
+public:
+ TextureOverrideSource(std::string filepath);
+
+ //! Get all overrides that apply to item definitions
+ std::vector<TextureOverride> getItemTextureOverrides();
+
+ //! Get all overrides that apply to node definitions
+ std::vector<TextureOverride> getNodeTileOverrides();
+
+private:
+ std::vector<TextureOverride> m_overrides;
+};
diff --git a/src/tool.cpp b/src/tool.cpp
index d911c518f..22e41d28e 100644
--- a/src/tool.cpp
+++ b/src/tool.cpp
@@ -130,7 +130,7 @@ void ToolCapabilities::serializeJson(std::ostream &os) const
root["punch_attack_uses"] = punch_attack_uses;
Json::Value groupcaps_object;
- for (auto groupcap : groupcaps) {
+ for (const auto &groupcap : groupcaps) {
groupcap.second.toJson(groupcaps_object[groupcap.first]);
}
root["groupcaps"] = groupcaps_object;
diff --git a/src/translation.cpp b/src/translation.cpp
index d17467ce7..8bbaee0a3 100644
--- a/src/translation.cpp
+++ b/src/translation.cpp
@@ -20,9 +20,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "translation.h"
#include "log.h"
#include "util/string.h"
+#include <unordered_map>
-static Translations main_translations;
-Translations *g_translations = &main_translations;
+
+#ifndef SERVER
+// Client translations
+Translations client_translations;
+Translations *g_client_translations = &client_translations;
+#endif
+
+// Per language server translations
+std::unordered_map<std::string,Translations> server_translations;
+std::unordered_map<std::string,Translations> *g_server_translations = &server_translations;
Translations::~Translations()
{
diff --git a/src/translation.h b/src/translation.h
index 18fc6c38f..71423b15e 100644
--- a/src/translation.h
+++ b/src/translation.h
@@ -23,7 +23,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string>
class Translations;
-extern Translations *g_translations;
+extern std::unordered_map<std::string, Translations> *g_server_translations;
+#ifndef SERVER
+extern Translations *g_client_translations;
+#endif
class Translations
{
diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt
index 82f9a4a13..5703b8906 100644
--- a/src/unittest/CMakeLists.txt
+++ b/src/unittest/CMakeLists.txt
@@ -43,7 +43,7 @@ set (UNITTEST_CLIENT_SRCS
PARENT_SCOPE)
set (TEST_WORLDDIR ${CMAKE_CURRENT_SOURCE_DIR}/test_world)
-set (TEST_SUBGAME_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../games/minimal)
+set (TEST_SUBGAME_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../games/devtest)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/test_config.h.in"
diff --git a/src/unittest/test_ban.cpp b/src/unittest/test_ban.cpp
index 2a95b945f..bab2b9ee8 100644
--- a/src/unittest/test_ban.cpp
+++ b/src/unittest/test_ban.cpp
@@ -61,6 +61,9 @@ void TestBan::runTests(IGameDef *gamedef)
reinitTestEnv();
TEST(testGetBanDescription);
+
+ // Delete leftover files
+ reinitTestEnv();
}
// This module is stateful due to disk writes, add helper to remove files
diff --git a/src/unittest/test_collision.cpp b/src/unittest/test_collision.cpp
index 332d3fa13..2f39c2489 100644
--- a/src/unittest/test_collision.cpp
+++ b/src/unittest/test_collision.cpp
@@ -50,38 +50,38 @@ void TestCollision::testAxisAlignedCollision()
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx-2, by, bz, bx-1, by+1, bz+1);
v3f v(1, 0, 0);
- f32 dtime = 0;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 0);
+ f32 dtime = 1.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 1.000) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx-2, by, bz, bx-1, by+1, bz+1);
v3f v(-1, 0, 0);
- f32 dtime = 0;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == -1);
+ f32 dtime = 1.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == -1);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx-2, by+1.5, bz, bx-1, by+2.5, bz-1);
v3f v(1, 0, 0);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == -1);
+ f32 dtime = 1.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == -1);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx-2, by-1.5, bz, bx-1.5, by+0.5, bz+1);
v3f v(0.5, 0.1, 0);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 0);
+ f32 dtime = 3.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 3.000) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx-2, by-1.5, bz, bx-1.5, by+0.5, bz+1);
v3f v(0.5, 0.1, 0);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 0);
+ f32 dtime = 3.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 3.000) < 0.001);
}
@@ -90,38 +90,38 @@ void TestCollision::testAxisAlignedCollision()
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx+2, by, bz, bx+3, by+1, bz+1);
v3f v(-1, 0, 0);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 0);
+ f32 dtime = 1.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 1.000) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx+2, by, bz, bx+3, by+1, bz+1);
v3f v(1, 0, 0);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == -1);
+ f32 dtime = 1.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == -1);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx+2, by, bz+1.5, bx+3, by+1, bz+3.5);
v3f v(-1, 0, 0);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == -1);
+ f32 dtime = 1.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == -1);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx+2, by-1.5, bz, bx+2.5, by-0.5, bz+1);
v3f v(-0.5, 0.2, 0);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 1); // Y, not X!
+ f32 dtime = 2.5f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 1); // Y, not X!
UASSERT(fabs(dtime - 2.500) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx+2, by-1.5, bz, bx+2.5, by-0.5, bz+1);
v3f v(-0.5, 0.3, 0);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 0);
+ f32 dtime = 2.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 2.000) < 0.001);
}
@@ -132,48 +132,48 @@ void TestCollision::testAxisAlignedCollision()
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx+2.3, by+2.29, bz+2.29, bx+4.2, by+4.2, bz+4.2);
v3f v(-1./3, -1./3, -1./3);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 0);
+ f32 dtime = 1.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 0.9) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx+2.29, by+2.3, bz+2.29, bx+4.2, by+4.2, bz+4.2);
v3f v(-1./3, -1./3, -1./3);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 1);
+ f32 dtime = 1.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 1);
UASSERT(fabs(dtime - 0.9) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx+2.29, by+2.29, bz+2.3, bx+4.2, by+4.2, bz+4.2);
v3f v(-1./3, -1./3, -1./3);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 2);
+ f32 dtime = 1.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 2);
UASSERT(fabs(dtime - 0.9) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.3, by-2.29, bz-2.29);
v3f v(1./7, 1./7, 1./7);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 0);
+ f32 dtime = 17.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 16.1) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.29, by-2.3, bz-2.29);
v3f v(1./7, 1./7, 1./7);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 1);
+ f32 dtime = 17.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 1);
UASSERT(fabs(dtime - 16.1) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.29, by-2.29, bz-2.3);
v3f v(1./7, 1./7, 1./7);
- f32 dtime;
- UASSERT(axisAlignedCollision(s, m, v, 0, &dtime) == 2);
+ f32 dtime = 17.0f;
+ UASSERT(axisAlignedCollision(s, m, v, &dtime) == 2);
UASSERT(fabs(dtime - 16.1) < 0.001);
}
}
diff --git a/src/unittest/test_objdef.cpp b/src/unittest/test_objdef.cpp
index c2acdcfe7..40f7faa9d 100644
--- a/src/unittest/test_objdef.cpp
+++ b/src/unittest/test_objdef.cpp
@@ -32,6 +32,7 @@ public:
void testHandles();
void testAddGetSetClear();
+ void testClone();
};
static TestObjDef g_test_instance;
@@ -40,10 +41,42 @@ void TestObjDef::runTests(IGameDef *gamedef)
{
TEST(testHandles);
TEST(testAddGetSetClear);
+ TEST(testClone);
}
////////////////////////////////////////////////////////////////////////////////
+/* Minimal implementation of ObjDef and ObjDefManager subclass */
+
+class MyObjDef : public ObjDef
+{
+public:
+ ObjDef *clone() const
+ {
+ auto def = new MyObjDef();
+ ObjDef::cloneTo(def);
+ def->testvalue = testvalue;
+ return def;
+ };
+
+ u32 testvalue;
+};
+
+class MyObjDefManager : public ObjDefManager
+{
+public:
+ MyObjDefManager(ObjDefType type) : ObjDefManager(NULL, type){};
+ MyObjDefManager *clone() const
+ {
+ auto mgr = new MyObjDefManager();
+ ObjDefManager::cloneTo(mgr);
+ return mgr;
+ };
+
+protected:
+ MyObjDefManager(){};
+};
+
void TestObjDef::testHandles()
{
u32 uid = 0;
@@ -69,25 +102,25 @@ void TestObjDef::testAddGetSetClear()
UASSERTEQ(ObjDefType, testmgr.getType(), OBJDEF_GENERIC);
- obj0 = new ObjDef;
+ obj0 = new MyObjDef;
obj0->name = "foobar";
hObj0 = testmgr.add(obj0);
UASSERT(hObj0 != OBJDEF_INVALID_HANDLE);
UASSERTEQ(u32, obj0->index, 0);
- obj1 = new ObjDef;
+ obj1 = new MyObjDef;
obj1->name = "FooBaz";
hObj1 = testmgr.add(obj1);
UASSERT(hObj1 != OBJDEF_INVALID_HANDLE);
UASSERTEQ(u32, obj1->index, 1);
- obj2 = new ObjDef;
+ obj2 = new MyObjDef;
obj2->name = "asdf";
hObj2 = testmgr.add(obj2);
UASSERT(hObj2 != OBJDEF_INVALID_HANDLE);
UASSERTEQ(u32, obj2->index, 2);
- obj3 = new ObjDef;
+ obj3 = new MyObjDef;
obj3->name = "foobaz";
hObj3 = testmgr.add(obj3);
UASSERT(hObj3 == OBJDEF_INVALID_HANDLE);
@@ -104,3 +137,38 @@ void TestObjDef::testAddGetSetClear()
testmgr.clear();
UASSERTEQ(size_t, testmgr.getNumObjects(), 0);
}
+
+void TestObjDef::testClone()
+{
+ MyObjDefManager testmgr(OBJDEF_GENERIC);
+ ObjDefManager *mgrcopy;
+ MyObjDef *obj, *temp2;
+ ObjDef *temp1;
+ ObjDefHandle hObj;
+
+ obj = new MyObjDef;
+ obj->testvalue = 0xee00ff11;
+ hObj = testmgr.add(obj);
+ UASSERT(hObj != OBJDEF_INVALID_HANDLE);
+
+ mgrcopy = testmgr.clone();
+ UASSERT(mgrcopy);
+ UASSERTEQ(ObjDefType, mgrcopy->getType(), testmgr.getType());
+ UASSERTEQ(size_t, mgrcopy->getNumObjects(), testmgr.getNumObjects());
+
+ // 1) check that the same handle is still valid on the copy
+ temp1 = mgrcopy->get(hObj);
+ UASSERT(temp1);
+ UASSERT(temp1 == mgrcopy->getRaw(0));
+ // 2) check that the copy has the correct C++ class
+ temp2 = dynamic_cast<MyObjDef *>(temp1);
+ UASSERT(temp2);
+ // 3) check that it was correctly copied
+ UASSERTEQ(u32, obj->testvalue, temp2->testvalue);
+ // 4) check that it was copied AT ALL (not the same)
+ UASSERT(obj != temp2);
+
+ testmgr.clear();
+ mgrcopy->clear();
+ delete mgrcopy;
+}
diff --git a/src/unittest/test_player.cpp b/src/unittest/test_player.cpp
index e2b1cd855..6990b4016 100644
--- a/src/unittest/test_player.cpp
+++ b/src/unittest/test_player.cpp
@@ -21,7 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "exceptions.h"
#include "remoteplayer.h"
-#include "content_sao.h"
#include "server.h"
class TestPlayer : public TestBase
diff --git a/src/unittest/test_serveractiveobjectmgr.cpp b/src/unittest/test_serveractiveobjectmgr.cpp
index 0806972ab..aa0047400 100644
--- a/src/unittest/test_serveractiveobjectmgr.cpp
+++ b/src/unittest/test_serveractiveobjectmgr.cpp
@@ -148,14 +148,26 @@ void TestServerActiveObjectMgr::testGetObjectsInsideRadius()
saomgr.registerObject(new TestServerActiveObject(p));
}
- std::vector<u16> result;
- saomgr.getObjectsInsideRadius(v3f(), 50, result);
+ std::vector<ServerActiveObject *> result;
+ saomgr.getObjectsInsideRadius(v3f(), 50, result, nullptr);
UASSERTCMP(int, ==, result.size(), 1);
result.clear();
- saomgr.getObjectsInsideRadius(v3f(), 750, result);
+ saomgr.getObjectsInsideRadius(v3f(), 750, result, nullptr);
UASSERTCMP(int, ==, result.size(), 2);
+ result.clear();
+ saomgr.getObjectsInsideRadius(v3f(), 750000, result, nullptr);
+ UASSERTCMP(int, ==, result.size(), 5);
+
+ result.clear();
+ auto include_obj_cb = [](ServerActiveObject *obj) {
+ return (obj->getBasePosition().X != 10);
+ };
+
+ saomgr.getObjectsInsideRadius(v3f(), 750000, result, include_obj_cb);
+ UASSERTCMP(int, ==, result.size(), 4);
+
clearSAOMgr(&saomgr);
}
diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp
index 0757323f4..799936757 100644
--- a/src/unittest/test_servermodmanager.cpp
+++ b/src/unittest/test_servermodmanager.cpp
@@ -88,7 +88,7 @@ void TestServerModManager::testCreation()
{
std::string path = std::string(TEST_WORLDDIR) + DIR_DELIM + "world.mt";
Settings world_config;
- world_config.set("gameid", "minimal");
+ world_config.set("gameid", "devtest");
UASSERTEQ(bool, world_config.updateConfigFile(path.c_str()), true);
ServerModManager sm(TEST_WORLDDIR);
}
@@ -118,10 +118,10 @@ void TestServerModManager::testGetMods()
const auto &mods = sm.getMods();
UASSERTEQ(bool, mods.empty(), false);
- // Ensure we found default mod inside the test folder
+ // Ensure we found basenodes mod (part of devtest)
bool default_found = false;
for (const auto &m : mods) {
- if (m.name == "default")
+ if (m.name == "basenodes")
default_found = true;
// Verify if paths are not empty
@@ -135,7 +135,7 @@ void TestServerModManager::testGetModspec()
{
ServerModManager sm(std::string(TEST_WORLDDIR));
UASSERTEQ(const ModSpec *, sm.getModSpec("wrongmod"), NULL);
- UASSERT(sm.getModSpec("default") != NULL);
+ UASSERT(sm.getModSpec("basenodes") != NULL);
}
void TestServerModManager::testGetModNamesWrongDir()
@@ -152,7 +152,7 @@ void TestServerModManager::testGetModNames()
std::vector<std::string> result;
sm.getModNames(result);
UASSERTEQ(bool, result.empty(), false);
- UASSERT(std::find(result.begin(), result.end(), "default") != result.end());
+ UASSERT(std::find(result.begin(), result.end(), "basenodes") != result.end());
}
void TestServerModManager::testGetModMediaPathsWrongDir()
diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt
index 199d3aeaa..cd2e468d1 100644
--- a/src/util/CMakeLists.txt
+++ b/src/util/CMakeLists.txt
@@ -5,6 +5,7 @@ set(UTIL_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp
${CMAKE_CURRENT_SOURCE_DIR}/enriched_string.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ieee_float.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/metricsbackend.cpp
${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp
${CMAKE_CURRENT_SOURCE_DIR}/quicktune.cpp
diff --git a/src/util/md32_common.h b/src/util/md32_common.h
index 085d1d7a5..a4c2099c9 100644
--- a/src/util/md32_common.h
+++ b/src/util/md32_common.h
@@ -109,6 +109,8 @@
* <appro@fy.chalmers.se>
*/
+#pragma once
+
#if !defined(DATA_ORDER_IS_BIG_ENDIAN) && !defined(DATA_ORDER_IS_LITTLE_ENDIAN)
# error "DATA_ORDER must be defined!"
#endif
diff --git a/src/util/metricsbackend.cpp b/src/util/metricsbackend.cpp
new file mode 100644
index 000000000..4454557a3
--- /dev/null
+++ b/src/util/metricsbackend.cpp
@@ -0,0 +1,140 @@
+/*
+Minetest
+Copyright (C) 2013-2020 Minetest core developers team
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "metricsbackend.h"
+#if USE_PROMETHEUS
+#include <prometheus/exposer.h>
+#include <prometheus/registry.h>
+#include <prometheus/counter.h>
+#include <prometheus/gauge.h>
+#include "log.h"
+#include "settings.h"
+#endif
+
+MetricCounterPtr MetricsBackend::addCounter(
+ const std::string &name, const std::string &help_str)
+{
+ return std::make_shared<SimpleMetricCounter>(name, help_str);
+}
+
+MetricGaugePtr MetricsBackend::addGauge(
+ const std::string &name, const std::string &help_str)
+{
+ return std::make_shared<SimpleMetricGauge>(name, help_str);
+}
+
+#if USE_PROMETHEUS
+
+class PrometheusMetricCounter : public MetricCounter
+{
+public:
+ PrometheusMetricCounter() = delete;
+
+ PrometheusMetricCounter(const std::string &name, const std::string &help_str,
+ std::shared_ptr<prometheus::Registry> registry) :
+ MetricCounter(),
+ m_family(prometheus::BuildCounter()
+ .Name(name)
+ .Help(help_str)
+ .Register(*registry)),
+ m_counter(m_family.Add({}))
+ {
+ }
+
+ virtual ~PrometheusMetricCounter() {}
+
+ virtual void increment(double number) { m_counter.Increment(number); }
+ virtual double get() const { return m_counter.Value(); }
+
+private:
+ prometheus::Family<prometheus::Counter> &m_family;
+ prometheus::Counter &m_counter;
+};
+
+class PrometheusMetricGauge : public MetricGauge
+{
+public:
+ PrometheusMetricGauge() = delete;
+
+ PrometheusMetricGauge(const std::string &name, const std::string &help_str,
+ std::shared_ptr<prometheus::Registry> registry) :
+ MetricGauge(),
+ m_family(prometheus::BuildGauge()
+ .Name(name)
+ .Help(help_str)
+ .Register(*registry)),
+ m_gauge(m_family.Add({}))
+ {
+ }
+
+ virtual ~PrometheusMetricGauge() {}
+
+ virtual void increment(double number) { m_gauge.Increment(number); }
+ virtual void decrement(double number) { m_gauge.Decrement(number); }
+ virtual void set(double number) { m_gauge.Set(number); }
+ virtual double get() const { return m_gauge.Value(); }
+
+private:
+ prometheus::Family<prometheus::Gauge> &m_family;
+ prometheus::Gauge &m_gauge;
+};
+
+class PrometheusMetricsBackend : public MetricsBackend
+{
+public:
+ PrometheusMetricsBackend(const std::string &addr) :
+ MetricsBackend(), m_exposer(std::unique_ptr<prometheus::Exposer>(
+ new prometheus::Exposer(addr))),
+ m_registry(std::make_shared<prometheus::Registry>())
+ {
+ m_exposer->RegisterCollectable(m_registry);
+ }
+
+ virtual ~PrometheusMetricsBackend() {}
+
+ virtual MetricCounterPtr addCounter(
+ const std::string &name, const std::string &help_str);
+ virtual MetricGaugePtr addGauge(
+ const std::string &name, const std::string &help_str);
+
+private:
+ std::unique_ptr<prometheus::Exposer> m_exposer;
+ std::shared_ptr<prometheus::Registry> m_registry;
+};
+
+MetricCounterPtr PrometheusMetricsBackend::addCounter(
+ const std::string &name, const std::string &help_str)
+{
+ return std::make_shared<PrometheusMetricCounter>(name, help_str, m_registry);
+}
+
+MetricGaugePtr PrometheusMetricsBackend::addGauge(
+ const std::string &name, const std::string &help_str)
+{
+ return std::make_shared<PrometheusMetricGauge>(name, help_str, m_registry);
+}
+
+MetricsBackend *createPrometheusMetricsBackend()
+{
+ std::string addr;
+ g_settings->getNoEx("prometheus_listener_address", addr);
+ return new PrometheusMetricsBackend(addr);
+}
+
+#endif
diff --git a/src/util/metricsbackend.h b/src/util/metricsbackend.h
new file mode 100644
index 000000000..c37306392
--- /dev/null
+++ b/src/util/metricsbackend.h
@@ -0,0 +1,140 @@
+/*
+Minetest
+Copyright (C) 2013-2020 Minetest core developers team
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#pragma once
+#include <memory>
+#include "config.h"
+#include "util/thread.h"
+
+class MetricCounter
+{
+public:
+ MetricCounter() = default;
+
+ virtual ~MetricCounter() {}
+
+ virtual void increment(double number = 1.0) = 0;
+ virtual double get() const = 0;
+};
+
+typedef std::shared_ptr<MetricCounter> MetricCounterPtr;
+
+class SimpleMetricCounter : public MetricCounter
+{
+public:
+ SimpleMetricCounter() = delete;
+
+ virtual ~SimpleMetricCounter() {}
+
+ SimpleMetricCounter(const std::string &name, const std::string &help_str) :
+ MetricCounter(), m_name(name), m_help_str(help_str),
+ m_counter(0.0)
+ {
+ }
+
+ virtual void increment(double number)
+ {
+ MutexAutoLock lock(m_mutex);
+ m_counter += number;
+ }
+ virtual double get() const
+ {
+ MutexAutoLock lock(m_mutex);
+ return m_counter;
+ }
+
+private:
+ std::string m_name;
+ std::string m_help_str;
+
+ mutable std::mutex m_mutex;
+ double m_counter;
+};
+
+class MetricGauge
+{
+public:
+ MetricGauge() = default;
+ virtual ~MetricGauge() {}
+
+ virtual void increment(double number = 1.0) = 0;
+ virtual void decrement(double number = 1.0) = 0;
+ virtual void set(double number) = 0;
+ virtual double get() const = 0;
+};
+
+typedef std::shared_ptr<MetricGauge> MetricGaugePtr;
+
+class SimpleMetricGauge : public MetricGauge
+{
+public:
+ SimpleMetricGauge() = delete;
+
+ SimpleMetricGauge(const std::string &name, const std::string &help_str) :
+ MetricGauge(), m_name(name), m_help_str(help_str), m_gauge(0.0)
+ {
+ }
+
+ virtual ~SimpleMetricGauge() {}
+
+ virtual void increment(double number)
+ {
+ MutexAutoLock lock(m_mutex);
+ m_gauge += number;
+ }
+ virtual void decrement(double number)
+ {
+ MutexAutoLock lock(m_mutex);
+ m_gauge -= number;
+ }
+ virtual void set(double number)
+ {
+ MutexAutoLock lock(m_mutex);
+ m_gauge = number;
+ }
+ virtual double get() const
+ {
+ MutexAutoLock lock(m_mutex);
+ return m_gauge;
+ }
+
+private:
+ std::string m_name;
+ std::string m_help_str;
+
+ mutable std::mutex m_mutex;
+ double m_gauge;
+};
+
+class MetricsBackend
+{
+public:
+ MetricsBackend() = default;
+
+ virtual ~MetricsBackend() {}
+
+ virtual MetricCounterPtr addCounter(
+ const std::string &name, const std::string &help_str);
+ virtual MetricGaugePtr addGauge(
+ const std::string &name, const std::string &help_str);
+};
+
+#if USE_PROMETHEUS
+MetricsBackend *createPrometheusMetricsBackend();
+#endif
diff --git a/src/util/numeric.h b/src/util/numeric.h
index 6f82a18c1..864ab7543 100644
--- a/src/util/numeric.h
+++ b/src/util/numeric.h
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irr_v2d.h"
#include "irr_v3d.h"
#include "irr_aabb3d.h"
+#include "SColor.h"
#include <matrix4.h>
#define rangelim(d, min, max) ((d) < (min) ? (min) : ((d) > (max) ? (max) : (d)))
@@ -432,3 +433,12 @@ inline v3f getPitchYawRoll(const core::matrix4 &m)
{
return getPitchYawRollRad(m) * core::RADTODEG64;
}
+
+// Muliply the RGB value of a color linearly, and clamp to black/white
+inline irr::video::SColor multiplyColorValue(const irr::video::SColor &color, float mod)
+{
+ return irr::video::SColor(color.getAlpha(),
+ core::clamp<u32>(color.getRed() * mod, 0, 255),
+ core::clamp<u32>(color.getGreen() * mod, 0, 255),
+ core::clamp<u32>(color.getBlue() * mod, 0, 255));
+}
diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp
index f0e177d57..5b276668d 100644
--- a/src/util/serialize.cpp
+++ b/src/util/serialize.cpp
@@ -110,6 +110,7 @@ std::string serializeString(const std::string &plain)
if (plain.size() > STRING_MAX_LEN)
throw SerializationError("String too long for serializeString");
+ s.reserve(2 + plain.size());
writeU16((u8 *)&buf[0], plain.size());
s.append(buf, 2);
@@ -131,13 +132,11 @@ std::string deSerializeString(std::istream &is)
if (s_size == 0)
return s;
- Buffer<char> buf2(s_size);
- is.read(&buf2[0], s_size);
+ s.resize(s_size);
+ is.read(&s[0], s_size);
if (is.gcount() != s_size)
throw SerializationError("deSerializeString: couldn't read all chars");
- s.reserve(s_size);
- s.append(&buf2[0], s_size);
return s;
}
@@ -152,6 +151,7 @@ std::string serializeWideString(const std::wstring &plain)
if (plain.size() > WIDE_STRING_MAX_LEN)
throw SerializationError("String too long for serializeWideString");
+ s.reserve(2 + 2 * plain.size());
writeU16((u8 *)buf, plain.size());
s.append(buf, 2);
@@ -196,13 +196,14 @@ std::wstring deSerializeWideString(std::istream &is)
std::string serializeLongString(const std::string &plain)
{
+ std::string s;
char buf[4];
if (plain.size() > LONG_STRING_MAX_LEN)
throw SerializationError("String too long for serializeLongString");
+ s.reserve(4 + plain.size());
writeU32((u8*)&buf[0], plain.size());
- std::string s;
s.append(buf, 4);
s.append(plain);
return s;
@@ -227,13 +228,11 @@ std::string deSerializeLongString(std::istream &is)
"string too long: " + itos(s_size) + " bytes");
}
- Buffer<char> buf2(s_size);
- is.read(&buf2[0], s_size);
+ s.resize(s_size);
+ is.read(&s[0], s_size);
if ((u32)is.gcount() != s_size)
throw SerializationError("deSerializeLongString: couldn't read all chars");
- s.reserve(s_size);
- s.append(&buf2[0], s_size);
return s;
}
diff --git a/src/util/serialize.h b/src/util/serialize.h
index 8ef0ad1c2..a4b5a234a 100644
--- a/src/util/serialize.h
+++ b/src/util/serialize.h
@@ -52,8 +52,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
// not represent the full range, but rather the largest safe range, of values on
// all supported architectures. Note: This definition makes assumptions on
// platform float-to-int conversion behavior.
-#define F1000_MIN ((float)(s32)((-0x7FFFFFFF - 1) / FIXEDPOINT_FACTOR))
-#define F1000_MAX ((float)(s32)((0x7FFFFFFF) / FIXEDPOINT_FACTOR))
+#define F1000_MIN ((float)(s32)((float)(-0x7FFFFFFF - 1) / FIXEDPOINT_FACTOR))
+#define F1000_MAX ((float)(s32)((float)(0x7FFFFFFF) / FIXEDPOINT_FACTOR))
#define STRING_MAX_LEN 0xFFFF
#define WIDE_STRING_MAX_LEN 0xFFFF
diff --git a/src/util/string.cpp b/src/util/string.cpp
index e6c52585d..6e1db798c 100644
--- a/src/util/string.cpp
+++ b/src/util/string.cpp
@@ -209,6 +209,9 @@ wchar_t *narrow_to_wide_c(const char *str)
}
std::wstring narrow_to_wide(const std::string &mbs) {
+#ifdef __ANDROID__
+ return utf8_to_wide(mbs);
+#else
size_t wcl = mbs.size();
Buffer<wchar_t> wcs(wcl + 1);
size_t len = mbstowcs(*wcs, mbs.c_str(), wcl);
@@ -216,11 +219,15 @@ std::wstring narrow_to_wide(const std::string &mbs) {
return L"<invalid multibyte string>";
wcs[len] = 0;
return *wcs;
+#endif
}
std::string wide_to_narrow(const std::wstring &wcs)
{
+#ifdef __ANDROID__
+ return wide_to_utf8(wcs);
+#else
size_t mbl = wcs.size() * 4;
SharedBuffer<char> mbs(mbl+1);
size_t len = wcstombs(*mbs, wcs.c_str(), mbl);
@@ -229,6 +236,7 @@ std::string wide_to_narrow(const std::wstring &wcs)
mbs[len] = 0;
return *mbs;
+#endif
}
@@ -685,10 +693,12 @@ void str_replace(std::string &str, char from, char to)
* before filling it again.
*/
-void translate_all(const std::wstring &s, size_t &i, std::wstring &res);
+void translate_all(const std::wstring &s, size_t &i,
+ Translations *translations, std::wstring &res);
-void translate_string(const std::wstring &s, const std::wstring &textdomain,
- size_t &i, std::wstring &res) {
+void translate_string(const std::wstring &s, Translations *translations,
+ const std::wstring &textdomain, size_t &i, std::wstring &res)
+{
std::wostringstream output;
std::vector<std::wstring> args;
int arg_number = 1;
@@ -742,7 +752,7 @@ void translate_string(const std::wstring &s, const std::wstring &textdomain,
if (arg_number >= 10) {
errorstream << "Ignoring too many arguments to translation" << std::endl;
std::wstring arg;
- translate_all(s, i, arg);
+ translate_all(s, i, translations, arg);
args.push_back(arg);
continue;
}
@@ -750,7 +760,7 @@ void translate_string(const std::wstring &s, const std::wstring &textdomain,
output << arg_number;
++arg_number;
std::wstring arg;
- translate_all(s, i, arg);
+ translate_all(s, i, translations, arg);
args.push_back(arg);
} else {
// This is an escape sequence *inside* the template string to translate itself.
@@ -759,8 +769,13 @@ void translate_string(const std::wstring &s, const std::wstring &textdomain,
}
}
+ std::wstring toutput;
// Translate the template.
- std::wstring toutput = g_translations->getTranslation(textdomain, output.str());
+ if (translations != nullptr)
+ toutput = translations->getTranslation(
+ textdomain, output.str());
+ else
+ toutput = output.str();
// Put back the arguments in the translated template.
std::wostringstream result;
@@ -794,7 +809,9 @@ void translate_string(const std::wstring &s, const std::wstring &textdomain,
res = result.str();
}
-void translate_all(const std::wstring &s, size_t &i, std::wstring &res) {
+void translate_all(const std::wstring &s, size_t &i,
+ Translations *translations, std::wstring &res)
+{
std::wostringstream output;
while (i < s.length()) {
// Not an escape sequence: just add the character.
@@ -843,7 +860,7 @@ void translate_all(const std::wstring &s, size_t &i, std::wstring &res) {
if (parts.size() > 1)
textdomain = parts[1];
std::wstring translated;
- translate_string(s, textdomain, i, translated);
+ translate_string(s, translations, textdomain, i, translated);
output << translated;
} else {
// Another escape sequence, such as colors. Preserve it.
@@ -854,9 +871,21 @@ void translate_all(const std::wstring &s, size_t &i, std::wstring &res) {
res = output.str();
}
-std::wstring translate_string(const std::wstring &s) {
+// Translate string server side
+std::wstring translate_string(const std::wstring &s, Translations *translations)
+{
size_t i = 0;
std::wstring res;
- translate_all(s, i, res);
+ translate_all(s, i, translations, res);
return res;
}
+
+// Translate string client side
+std::wstring translate_string(const std::wstring &s)
+{
+#ifdef SERVER
+ return translate_string(s, nullptr);
+#else
+ return translate_string(s, g_client_translations);
+#endif
+}
diff --git a/src/util/string.h b/src/util/string.h
index 0d2a6bdb2..185fb55e2 100644
--- a/src/util/string.h
+++ b/src/util/string.h
@@ -31,6 +31,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cctype>
#include <unordered_map>
+class Translations;
+
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
@@ -650,6 +652,8 @@ std::vector<std::basic_string<T> > split(const std::basic_string<T> &s, T delim)
return tokens;
}
+std::wstring translate_string(const std::wstring &s, Translations *translations);
+
std::wstring translate_string(const std::wstring &s);
inline std::wstring unescape_translate(const std::wstring &s) {
diff --git a/src/version.cpp b/src/version.cpp
index c0c59ee99..241228a6a 100644
--- a/src/version.cpp
+++ b/src/version.cpp
@@ -20,10 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "version.h"
#include "config.h"
-#if defined(__ANDROID__)
- #include "android_version.h"
- #include "android_version_githash.h"
-#elif defined(USE_CMAKE_CONFIG_H)
+#if USE_CMAKE_CONFIG_H
#include "cmake_config_githash.h"
#endif
@@ -33,4 +30,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
const char *g_version_string = VERSION_STRING;
const char *g_version_hash = VERSION_GITHASH;
-const char *g_build_info = BUILD_INFO;
+const char *g_build_info =
+ "BUILD_TYPE=" BUILD_TYPE "\n"
+ "RUN_IN_PLACE=" STR(RUN_IN_PLACE) "\n"
+ "USE_GETTEXT=" STR(USE_GETTEXT) "\n"
+ "USE_SOUND=" STR(USE_SOUND) "\n"
+ "USE_CURL=" STR(USE_CURL) "\n"
+ "USE_FREETYPE=" STR(USE_FREETYPE) "\n"
+ "USE_LUAJIT=" STR(USE_LUAJIT) "\n"
+ "STATIC_SHAREDIR=" STR(STATIC_SHAREDIR)
+#if USE_GETTEXT && defined(STATIC_LOCALEDIR)
+ "\n" "STATIC_LOCALEDIR=" STR(STATIC_LOCALEDIR)
+#endif
+;