summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt204
-rw-r--r--src/activeobject.h12
-rw-r--r--src/client/activeobjectmgr.cpp13
-rw-r--r--src/client/camera.cpp26
-rw-r--r--src/client/client.cpp191
-rw-r--r--src/client/client.h32
-rw-r--r--src/client/clientenvironment.cpp46
-rw-r--r--src/client/clientenvironment.h7
-rw-r--r--src/client/clientmap.cpp155
-rw-r--r--src/client/clientmap.h2
-rw-r--r--src/client/clientmedia.cpp10
-rw-r--r--src/client/clientobject.h51
-rw-r--r--src/client/clouds.cpp2
-rw-r--r--src/client/content_cao.cpp242
-rw-r--r--src/client/content_cao.h39
-rw-r--r--src/client/content_cso.cpp2
-rw-r--r--src/client/fontengine.cpp323
-rw-r--r--src/client/fontengine.h9
-rw-r--r--src/client/game.cpp706
-rw-r--r--src/client/gameui.cpp74
-rw-r--r--src/client/gameui.h24
-rw-r--r--src/client/guiscalingfilter.cpp85
-rw-r--r--src/client/guiscalingfilter.h8
-rw-r--r--src/client/hud.cpp39
-rw-r--r--src/client/hud.h2
-rw-r--r--src/client/localplayer.cpp475
-rw-r--r--src/client/localplayer.h23
-rw-r--r--src/client/mapblock_mesh.cpp5
-rw-r--r--src/client/mesh.cpp193
-rw-r--r--src/client/mesh_generator_thread.cpp39
-rw-r--r--src/client/minimap.cpp2
-rw-r--r--src/client/particles.cpp95
-rw-r--r--src/client/particles.h3
-rw-r--r--src/client/render/core.cpp2
-rw-r--r--src/client/render/plain.cpp8
-rw-r--r--src/client/renderingengine.cpp73
-rw-r--r--src/client/shader.cpp10
-rw-r--r--src/client/sky.cpp286
-rw-r--r--src/client/sky.h10
-rw-r--r--src/client/tile.cpp93
-rw-r--r--src/client/tile.h16
-rw-r--r--src/clientiface.cpp28
-rw-r--r--src/clientiface.h2
-rw-r--r--src/cmake_config.h.in1
-rw-r--r--src/collision.cpp65
-rw-r--r--src/collision.h11
-rw-r--r--src/content/subgames.cpp20
-rw-r--r--src/content_sao.cpp244
-rw-r--r--src/content_sao.h61
-rw-r--r--src/craftdef.cpp265
-rw-r--r--src/craftdef.h96
-rw-r--r--src/daynightratio.h64
-rw-r--r--src/defaultsettings.cpp28
-rw-r--r--src/emerge.cpp23
-rw-r--r--src/emerge.h2
-rw-r--r--src/environment.cpp2
-rw-r--r--src/gettime.h1
-rw-r--r--src/gui/CMakeLists.txt3
-rw-r--r--src/gui/StyleSpec.h135
-rw-r--r--src/gui/guiButton.cpp649
-rw-r--r--src/gui/guiButton.h310
-rw-r--r--src/gui/guiChatConsole.cpp18
-rw-r--r--src/gui/guiChatConsole.h2
-rw-r--r--src/gui/guiConfirmRegistration.cpp9
-rw-r--r--src/gui/guiConfirmRegistration.h3
-rw-r--r--src/gui/guiEditBoxWithScrollbar.cpp12
-rw-r--r--src/gui/guiEditBoxWithScrollbar.h6
-rw-r--r--src/gui/guiEngine.cpp19
-rw-r--r--src/gui/guiEngine.h6
-rw-r--r--src/gui/guiFormSpecMenu.cpp864
-rw-r--r--src/gui/guiFormSpecMenu.h46
-rw-r--r--src/gui/guiKeyChangeMenu.cpp13
-rw-r--r--src/gui/guiScrollBar.cpp425
-rw-r--r--src/gui/guiScrollBar.h69
-rw-r--r--src/gui/guiSkin.cpp1084
-rw-r--r--src/gui/guiSkin.h376
-rw-r--r--src/gui/guiTable.cpp9
-rw-r--r--src/gui/guiTable.h3
-rw-r--r--src/gui/guiVolumeChange.cpp4
-rw-r--r--src/gui/intlGUIEditBox.cpp10
-rw-r--r--src/gui/intlGUIEditBox.h4
-rw-r--r--src/gui/mainmenumanager.h16
-rw-r--r--src/gui/modalMenu.cpp2
-rw-r--r--src/gui/touchscreengui.h9
-rw-r--r--src/inventory.cpp201
-rw-r--r--src/inventory.h51
-rw-r--r--src/inventorymanager.cpp27
-rw-r--r--src/inventorymanager.h4
-rw-r--r--src/irr_ptr.h137
-rw-r--r--src/irrlicht_changes/static_text.cpp43
-rw-r--r--src/itemdef.cpp4
-rw-r--r--src/light.cpp2
-rw-r--r--src/log.cpp26
-rw-r--r--src/log.h12
-rw-r--r--src/main.cpp55
-rw-r--r--src/map.cpp259
-rw-r--r--src/map.h32
-rw-r--r--src/map_settings_manager.cpp4
-rw-r--r--src/mapblock.cpp2
-rw-r--r--src/mapgen/cavegen.cpp56
-rw-r--r--src/mapgen/cavegen.h4
-rw-r--r--src/mapgen/dungeongen.cpp89
-rw-r--r--src/mapgen/dungeongen.h43
-rw-r--r--src/mapgen/mapgen.cpp232
-rw-r--r--src/mapgen/mapgen.h31
-rw-r--r--src/mapgen/mapgen_carpathian.cpp228
-rw-r--r--src/mapgen/mapgen_carpathian.h28
-rw-r--r--src/mapgen/mapgen_flat.cpp51
-rw-r--r--src/mapgen/mapgen_flat.h3
-rw-r--r--src/mapgen/mapgen_fractal.cpp115
-rw-r--r--src/mapgen/mapgen_fractal.h16
-rw-r--r--src/mapgen/mapgen_singlenode.cpp5
-rw-r--r--src/mapgen/mapgen_singlenode.h2
-rw-r--r--src/mapgen/mapgen_v5.cpp28
-rw-r--r--src/mapgen/mapgen_v5.h3
-rw-r--r--src/mapgen/mapgen_v6.cpp278
-rw-r--r--src/mapgen/mapgen_v6.h4
-rw-r--r--src/mapgen/mapgen_v7.cpp16
-rw-r--r--src/mapgen/mapgen_v7.h3
-rw-r--r--src/mapgen/mapgen_valleys.cpp385
-rw-r--r--src/mapgen/mapgen_valleys.h30
-rw-r--r--src/mapgen/mg_biome.cpp3
-rw-r--r--src/mapgen/mg_biome.h2
-rw-r--r--src/mapgen/mg_schematic.cpp2
-rw-r--r--src/mapgen/treegen.cpp2
-rw-r--r--src/mapnode.cpp14
-rw-r--r--src/mapnode.h5
-rw-r--r--src/network/clientopcodes.cpp10
-rw-r--r--src/network/clientpackethandler.cpp37
-rw-r--r--src/network/connection.cpp157
-rw-r--r--src/network/connection.h24
-rw-r--r--src/network/connectionthreads.cpp18
-rw-r--r--src/network/connectionthreads.h25
-rw-r--r--src/network/networkprotocol.h53
-rw-r--r--src/network/peerhandler.h3
-rw-r--r--src/network/serveropcodes.cpp90
-rw-r--r--src/network/serverpackethandler.cpp202
-rw-r--r--src/nodedef.cpp59
-rw-r--r--src/nodedef.h8
-rw-r--r--src/pathfinder.cpp16
-rw-r--r--src/player.cpp23
-rw-r--r--src/player.h24
-rw-r--r--src/profiler.cpp130
-rw-r--r--src/profiler.h112
-rw-r--r--src/remoteplayer.h10
-rw-r--r--src/rollback_interface.cpp15
-rw-r--r--src/script/common/c_content.cpp47
-rw-r--r--src/script/common/c_content.h2
-rw-r--r--src/script/common/c_converter.cpp4
-rw-r--r--src/script/common/c_converter.h2
-rw-r--r--src/script/common/helper.cpp5
-rw-r--r--src/script/cpp_api/s_base.cpp11
-rw-r--r--src/script/cpp_api/s_entity.cpp6
-rw-r--r--src/script/cpp_api/s_entity.h2
-rw-r--r--src/script/cpp_api/s_env.cpp8
-rw-r--r--src/script/cpp_api/s_node.cpp2
-rw-r--r--src/script/cpp_api/s_nodemeta.cpp12
-rw-r--r--src/script/cpp_api/s_security.cpp3
-rw-r--r--src/script/cpp_api/s_server.cpp22
-rw-r--r--src/script/cpp_api/s_server.h12
-rw-r--r--src/script/lua_api/l_areastore.cpp1
-rw-r--r--src/script/lua_api/l_camera.cpp21
-rw-r--r--src/script/lua_api/l_client.cpp16
-rw-r--r--src/script/lua_api/l_craft.cpp10
-rw-r--r--src/script/lua_api/l_env.cpp32
-rw-r--r--src/script/lua_api/l_http.cpp15
-rw-r--r--src/script/lua_api/l_item.cpp11
-rw-r--r--src/script/lua_api/l_item.h3
-rw-r--r--src/script/lua_api/l_localplayer.cpp22
-rw-r--r--src/script/lua_api/l_mainmenu.cpp28
-rw-r--r--src/script/lua_api/l_mainmenu.h4
-rw-r--r--src/script/lua_api/l_mapgen.cpp89
-rw-r--r--src/script/lua_api/l_mapgen.h3
-rw-r--r--src/script/lua_api/l_nodemeta.cpp2
-rw-r--r--src/script/lua_api/l_object.cpp125
-rw-r--r--src/script/lua_api/l_object.h11
-rw-r--r--src/script/lua_api/l_server.cpp4
-rw-r--r--src/script/lua_api/l_vmanip.cpp2
-rw-r--r--src/script/scripting_client.cpp5
-rw-r--r--src/serialization.cpp4
-rw-r--r--src/server.cpp483
-rw-r--r--src/server.h30
-rw-r--r--src/server/activeobjectmgr.cpp6
-rw-r--r--src/serverenvironment.cpp120
-rw-r--r--src/serverenvironment.h5
-rw-r--r--src/serverobject.cpp19
-rw-r--r--src/serverobject.h31
-rw-r--r--src/settings.cpp9
-rw-r--r--src/settings_translation_file.cpp74
-rw-r--r--src/staticobject.cpp9
-rw-r--r--src/tool.cpp59
-rw-r--r--src/tool.h19
-rw-r--r--src/translation.cpp4
-rw-r--r--src/unittest/CMakeLists.txt1
-rw-r--r--src/unittest/test.cpp6
-rw-r--r--src/unittest/test_areastore.cpp31
-rw-r--r--src/unittest/test_inventory.cpp89
-rw-r--r--src/unittest/test_irrptr.cpp131
-rw-r--r--src/unittest/test_servermodmanager.cpp5
-rw-r--r--src/unittest/test_world/do_not_remove.txt0
-rw-r--r--src/unittest/test_world/world.mt1
-rw-r--r--src/util/CMakeLists.txt2
-rw-r--r--src/util/areastore.cpp38
-rw-r--r--src/util/areastore.h12
-rw-r--r--src/util/hex.h2
-rw-r--r--src/util/quicktune.cpp (renamed from src/quicktune.cpp)0
-rw-r--r--src/util/quicktune.h (renamed from src/quicktune.h)0
-rw-r--r--src/util/quicktune_shortcutter.h (renamed from src/quicktune_shortcutter.h)0
-rw-r--r--src/voxelalgorithms.cpp10
209 files changed, 9341 insertions, 4610 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 764706776..70a5ab3c8 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -102,10 +102,20 @@ if(BUILD_CLIENT AND ENABLE_SOUND)
endif()
-option(ENABLE_GLES "Enable OpenGL ES support" FALSE)
+option(ENABLE_GLES "Use OpenGL ES instead of OpenGL" FALSE)
mark_as_advanced(ENABLE_GLES)
-if(ENABLE_GLES)
- find_package(OpenGLES2)
+if(BUILD_CLIENT)
+ if(ENABLE_GLES)
+ find_package(OpenGLES2 REQUIRED)
+ else()
+ if(NOT WIN32) # Unix probably
+ set(OPENGL_GL_PREFERENCE "LEGACY" CACHE STRING
+ "See CMake Policy CMP0072 for reference. GLVND is broken on some nvidia setups")
+ set(OpenGL_GL_PREFERENCE ${OPENGL_GL_PREFERENCE})
+
+ find_package(OpenGL REQUIRED)
+ endif()
+ endif()
endif()
@@ -159,27 +169,14 @@ option(ENABLE_POSTGRESQL "Enable PostgreSQL backend" TRUE)
set(USE_POSTGRESQL FALSE)
if(ENABLE_POSTGRESQL)
- find_program(POSTGRESQL_CONFIG_EXECUTABLE pg_config DOC "pg_config")
- find_library(POSTGRESQL_LIBRARY pq)
- if(POSTGRESQL_CONFIG_EXECUTABLE)
- execute_process(COMMAND ${POSTGRESQL_CONFIG_EXECUTABLE} --includedir-server
- OUTPUT_VARIABLE POSTGRESQL_SERVER_INCLUDE_DIRS
- OUTPUT_STRIP_TRAILING_WHITESPACE)
- execute_process(COMMAND ${POSTGRESQL_CONFIG_EXECUTABLE}
- OUTPUT_VARIABLE POSTGRESQL_CLIENT_INCLUDE_DIRS
- OUTPUT_STRIP_TRAILING_WHITESPACE)
- # This variable is case sensitive for the cmake PostgreSQL module
- set(PostgreSQL_ADDITIONAL_SEARCH_PATHS ${POSTGRESQL_SERVER_INCLUDE_DIRS} ${POSTGRESQL_CLIENT_INCLUDE_DIRS})
- endif()
-
find_package("PostgreSQL")
- if(POSTGRESQL_FOUND)
+ if(PostgreSQL_FOUND)
set(USE_POSTGRESQL TRUE)
message(STATUS "PostgreSQL backend enabled")
# This variable is case sensitive, don't try to change it to POSTGRESQL_INCLUDE_DIR
- message(STATUS "PostgreSQL includes: ${PostgreSQL_INCLUDE_DIR}")
- include_directories(${PostgreSQL_INCLUDE_DIR})
+ message(STATUS "PostgreSQL includes: ${PostgreSQL_INCLUDE_DIRS}")
+ include_directories(${PostgreSQL_INCLUDE_DIRS})
else()
message(STATUS "PostgreSQL not found!")
endif()
@@ -189,7 +186,7 @@ option(ENABLE_LEVELDB "Enable LevelDB backend" TRUE)
set(USE_LEVELDB FALSE)
if(ENABLE_LEVELDB)
- find_library(LEVELDB_LIBRARY leveldb)
+ find_library(LEVELDB_LIBRARY NAMES leveldb libleveldb)
find_path(LEVELDB_INCLUDE_DIR db.h PATH_SUFFIXES leveldb)
if(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR)
set(USE_LEVELDB TRUE)
@@ -264,32 +261,23 @@ if(WIN32)
set(PLATFORM_LIBS ws2_32.lib version.lib shlwapi.lib ${PLATFORM_LIBS})
# Zlib stuff
- set(ZLIB_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/../../zlib/zlib-1.2.5"
- CACHE PATH "Zlib include directory")
- set(ZLIB_LIBRARIES "${PROJECT_SOURCE_DIR}/../../zlib125dll/dll32/zlibwapi.lib"
- CACHE FILEPATH "Path to zlib library (usually zlibwapi.lib)")
- set(ZLIB_DLL "${PROJECT_SOURCE_DIR}/../../zlib125dll/dll32/zlibwapi.dll"
- CACHE FILEPATH "Path to zlib DLL (for installation)")
- set(ZLIBWAPI_DLL "" CACHE FILEPATH "Path to zlibwapi DLL")
- set(IRRLICHT_SOURCE_DIR "${PROJECT_SOURCE_DIR}/../../irrlicht-1.7.2"
- CACHE PATH "irrlicht dir")
- if(USE_FREETYPE)
- set(FREETYPE_INCLUDE_DIR_ft2build "${PROJECT_SOURCE_DIR}/../../freetype2/include/"
- CACHE PATH "freetype include dir")
- set(FREETYPE_INCLUDE_DIR_freetype2 "${PROJECT_SOURCE_DIR}/../../freetype2/include/freetype"
- CACHE PATH "freetype include dir")
- set(FREETYPE_LIBRARY "${PROJECT_SOURCE_DIR}/../../freetype2/objs/win32/vc2005/freetype247.lib"
- CACHE FILEPATH "Path to freetype247.lib")
- endif()
- if(ENABLE_SOUND)
- set(OPENAL_DLL "" CACHE FILEPATH "Path to OpenAL32.dll for installation (optional)")
- set(OGG_DLL "" CACHE FILEPATH "Path to libogg.dll for installation (optional)")
- set(VORBIS_DLL "" CACHE FILEPATH "Path to libvorbis.dll for installation (optional)")
- set(VORBISFILE_DLL "" CACHE FILEPATH "Path to libvorbisfile.dll for installation (optional)")
- endif()
- if(USE_LUAJIT)
- set(LUA_DLL "" CACHE FILEPATH "Path to lua51.dll for installation (optional)")
+ find_path(ZLIB_INCLUDE_DIR "zlib.h" DOC "Zlib include directory")
+ find_library(ZLIB_LIBRARIES "zlib" DOC "Path to zlib library")
+
+ # Dll's are automatically copied to the output directory by vcpkg when VCPKG_APPLOCAL_DEPS=ON
+ if(NOT VCPKG_APPLOCAL_DEPS)
+ find_file(ZLIB_DLL NAMES "zlib.dll" "zlib1.dll" DOC "Path to zlib.dll for installation (optional)")
+ if(ENABLE_SOUND)
+ set(OPENAL_DLL "" CACHE FILEPATH "Path to OpenAL32.dll for installation (optional)")
+ set(OGG_DLL "" CACHE FILEPATH "Path to libogg.dll for installation (optional)")
+ set(VORBIS_DLL "" CACHE FILEPATH "Path to libvorbis.dll for installation (optional)")
+ set(VORBISFILE_DLL "" CACHE FILEPATH "Path to libvorbisfile.dll for installation (optional)")
+ endif()
+ if(USE_LUAJIT)
+ set(LUA_DLL "" CACHE FILEPATH "Path to lua51.dll for installation (optional)")
+ endif()
endif()
+
else()
# Unix probably
if(BUILD_CLIENT)
@@ -297,11 +285,6 @@ else()
find_package(X11 REQUIRED)
endif(NOT HAIKU)
- set(OPENGL_GL_PREFERENCE "LEGACY" CACHE STRING
- "See CMake Policy CMP0072 for reference. GLVND is broken on some nvidia setups")
- set(OpenGL_GL_PREFERENCE ${OPENGL_GL_PREFERENCE})
-
- find_package(OpenGL REQUIRED)
find_package(JPEG REQUIRED)
find_package(BZip2 REQUIRED)
find_package(PNG REQUIRED)
@@ -423,7 +406,6 @@ set(common_SRCS
player.cpp
porting.cpp
profiler.cpp
- quicktune.cpp
raycast.cpp
reflowscan.cpp
remoteplayer.cpp
@@ -541,7 +523,6 @@ if(BUILD_CLIENT)
${PROJECT_NAME}
${ZLIB_LIBRARIES}
${IRRLICHT_LIBRARY}
- ${OPENGL_LIBRARIES}
${JPEG_LIBRARIES}
${BZIP2_LIBRARIES}
${PNG_LIBRARIES}
@@ -551,7 +532,6 @@ if(BUILD_CLIENT)
${LUA_LIBRARY}
${GMP_LIBRARY}
${JSON_LIBRARY}
- ${OPENGLES2_LIBRARIES}
${PLATFORM_LIBS}
${CLIENT_PLATFORM_LIBS}
)
@@ -565,6 +545,18 @@ if(BUILD_CLIENT)
${client_LIBS}
)
endif()
+ if(ENABLE_GLES)
+ target_link_libraries(
+ ${PROJECT_NAME}
+ ${OPENGLES2_LIBRARIES}
+ ${EGL_LIBRARIES}
+ )
+ else()
+ target_link_libraries(
+ ${PROJECT_NAME}
+ ${OPENGL_LIBRARIES}
+ )
+ endif()
if(USE_GETTEXT)
target_link_libraries(
${PROJECT_NAME}
@@ -593,7 +585,7 @@ if(BUILD_CLIENT)
target_link_libraries(${PROJECT_NAME} ${CURSES_LIBRARIES})
endif()
if (USE_POSTGRESQL)
- target_link_libraries(${PROJECT_NAME} ${POSTGRESQL_LIBRARY})
+ target_link_libraries(${PROJECT_NAME} ${PostgreSQL_LIBRARIES})
endif()
if (USE_LEVELDB)
target_link_libraries(${PROJECT_NAME} ${LEVELDB_LIBRARY})
@@ -628,7 +620,7 @@ if(BUILD_SERVER)
target_link_libraries(${PROJECT_NAME}server ${CURSES_LIBRARIES})
endif()
if (USE_POSTGRESQL)
- target_link_libraries(${PROJECT_NAME}server ${POSTGRESQL_LIBRARY})
+ target_link_libraries(${PROJECT_NAME}server ${PostgreSQL_LIBRARIES})
endif()
if (USE_LEVELDB)
target_link_libraries(${PROJECT_NAME}server ${LEVELDB_LIBRARY})
@@ -716,12 +708,6 @@ else()
set(OTHER_FLAGS "${OTHER_FLAGS} -Wsign-compare")
endif()
- if(WIN32 AND NOT ZLIBWAPI_DLL AND CMAKE_SIZEOF_VOID_P EQUAL 4)
- set(OTHER_FLAGS "${OTHER_FLAGS} -DWIN32_NO_ZLIB_WINAPI")
- message(WARNING "Defaulting to cdecl for zlib on win32 because ZLIBWAPI_DLL"
- " isn't set, ensure that ZLIBWAPI_DLL is set if you want stdcall.")
- endif()
-
if(MINGW)
set(OTHER_FLAGS "${OTHER_FLAGS} -mthreads -fexceptions")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DWIN32_LEAN_AND_MEAN")
@@ -749,40 +735,58 @@ endif()
# Installation
if(WIN32)
- if(USE_SOUND)
- if(OPENAL_DLL)
- install(FILES ${OPENAL_DLL} DESTINATION ${BINDIR})
+ if(VCPKG_APPLOCAL_DEPS)
+ # Collect the dll's from the output path
+ install(DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/Release/
+ DESTINATION ${BINDIR}
+ CONFIGURATIONS Release
+ FILES_MATCHING PATTERN "*.dll")
+ install(DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/Debug/
+ DESTINATION ${BINDIR}
+ CONFIGURATIONS Debug
+ FILES_MATCHING PATTERN "*.dll")
+ install(DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/RelWithDebInfo/
+ DESTINATION ${BINDIR}
+ CONFIGURATIONS RelWithDebInfo
+ FILES_MATCHING PATTERN "*.dll")
+ install(DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/MinSizeRel/
+ DESTINATION ${BINDIR}
+ CONFIGURATIONS RelWithDebInfo
+ FILES_MATCHING PATTERN "*.dll")
+ else()
+ # Use the old-style way to install dll's
+ if(USE_SOUND)
+ if(OPENAL_DLL)
+ install(FILES ${OPENAL_DLL} DESTINATION ${BINDIR})
+ endif()
+ if(OGG_DLL)
+ install(FILES ${OGG_DLL} DESTINATION ${BINDIR})
+ endif()
+ if(VORBIS_DLL)
+ install(FILES ${VORBIS_DLL} DESTINATION ${BINDIR})
+ endif()
+ if(VORBISFILE_DLL)
+ install(FILES ${VORBISFILE_DLL} DESTINATION ${BINDIR})
+ endif()
endif()
- if(OGG_DLL)
- install(FILES ${OGG_DLL} DESTINATION ${BINDIR})
+ if(CURL_DLL)
+ install(FILES ${CURL_DLL} DESTINATION ${BINDIR})
endif()
- if(VORBIS_DLL)
- install(FILES ${VORBIS_DLL} DESTINATION ${BINDIR})
+ if(ZLIB_DLL)
+ install(FILES ${ZLIB_DLL} DESTINATION ${BINDIR})
endif()
- if(VORBISFILE_DLL)
- install(FILES ${VORBISFILE_DLL} DESTINATION ${BINDIR})
+ if(FREETYPE_DLL)
+ install(FILES ${FREETYPE_DLL} DESTINATION ${BINDIR})
+ endif()
+ if(SQLITE3_DLL)
+ install(FILES ${SQLITE3_DLL} DESTINATION ${BINDIR})
+ endif()
+ if(LEVELDB_DLL)
+ install(FILES ${LEVELDB_DLL} DESTINATION ${BINDIR})
+ endif()
+ if(LUA_DLL)
+ install(FILES ${LUA_DLL} DESTINATION ${BINDIR})
endif()
- endif()
- if(CURL_DLL)
- install(FILES ${CURL_DLL} DESTINATION ${BINDIR})
- endif()
- if(ZLIB_DLL)
- install(FILES ${ZLIB_DLL} DESTINATION ${BINDIR})
- endif()
- if(ZLIBWAPI_DLL)
- install(FILES ${ZLIBWAPI_DLL} DESTINATION ${BINDIR})
- endif()
- if(FREETYPE_DLL)
- install(FILES ${FREETYPE_DLL} DESTINATION ${BINDIR})
- endif()
- if(SQLITE3_DLL)
- install(FILES ${SQLITE3_DLL} DESTINATION ${BINDIR})
- endif()
- if(LEVELDB_DLL)
- install(FILES ${LEVELDB_DLL} DESTINATION ${BINDIR})
- endif()
- if(LUA_DLL)
- install(FILES ${LUA_DLL} DESTINATION ${BINDIR})
endif()
endif()
@@ -819,15 +823,17 @@ if(BUILD_CLIENT)
endif()
if(WIN32)
- if(DEFINED IRRLICHT_DLL)
- install(FILES ${IRRLICHT_DLL} DESTINATION ${BINDIR})
- endif()
- if(USE_GETTEXT)
- if(DEFINED GETTEXT_DLL)
- install(FILES ${GETTEXT_DLL} DESTINATION ${BINDIR})
+ if(NOT VCPKG_APPLOCAL_DEPS)
+ if(DEFINED IRRLICHT_DLL)
+ install(FILES ${IRRLICHT_DLL} DESTINATION ${BINDIR})
endif()
- if(DEFINED GETTEXT_ICONV_DLL)
- install(FILES ${GETTEXT_ICONV_DLL} DESTINATION ${BINDIR})
+ if(USE_GETTEXT)
+ if(DEFINED GETTEXT_DLL)
+ install(FILES ${GETTEXT_DLL} DESTINATION ${BINDIR})
+ endif()
+ if(DEFINED GETTEXT_ICONV_DLL)
+ install(FILES ${GETTEXT_ICONV_DLL} DESTINATION ${BINDIR})
+ endif()
endif()
endif()
endif()
diff --git a/src/activeobject.h b/src/activeobject.h
index b6a0e67af..a319ef904 100644
--- a/src/activeobject.h
+++ b/src/activeobject.h
@@ -20,8 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include "irr_aabb3d.h"
+#include "irr_v3d.h"
#include <string>
+
enum ActiveObjectType {
ACTIVEOBJECT_TYPE_INVALID = 0,
ACTIVEOBJECT_TYPE_TEST = 1,
@@ -98,6 +100,16 @@ public:
virtual bool collideWithObjects() const = 0;
+
+
+ virtual void setAttachment(int parent_id, const std::string &bone, v3f position,
+ v3f rotation) {}
+ virtual void getAttachment(int *parent_id, std::string *bone, v3f *position,
+ v3f *rotation) const {}
+ virtual void clearChildAttachments() {}
+ virtual void clearParentAttachment() {}
+ virtual void addAttachmentChild(int child_id) {}
+ virtual void removeAttachmentChild(int child_id) {}
protected:
u16 m_id; // 0 is invalid, "no id"
};
diff --git a/src/client/activeobjectmgr.cpp b/src/client/activeobjectmgr.cpp
index 4ed98d79b..82f3cb944 100644
--- a/src/client/activeobjectmgr.cpp
+++ b/src/client/activeobjectmgr.cpp
@@ -29,14 +29,16 @@ void ActiveObjectMgr::clear()
// delete active objects
for (auto &active_object : m_active_objects) {
delete active_object.second;
+ // Object must be marked as gone when children try to detach
+ active_object.second = nullptr;
}
+ m_active_objects.clear();
}
void ActiveObjectMgr::step(
float dtime, const std::function<void(ClientActiveObject *)> &f)
{
- g_profiler->avg("Client::ActiveObjectMgr: num of objects",
- m_active_objects.size());
+ g_profiler->avg("ActiveObjectMgr: CAO count [#]", m_active_objects.size());
for (auto &ao_it : m_active_objects) {
f(ao_it.second);
}
@@ -91,15 +93,16 @@ void ActiveObjectMgr::removeObject(u16 id)
void ActiveObjectMgr::getActiveObjects(const v3f &origin, f32 max_d,
std::vector<DistanceSortedActiveObject> &dest)
{
+ f32 max_d2 = max_d * max_d;
for (auto &ao_it : m_active_objects) {
ClientActiveObject *obj = ao_it.second;
- f32 d = (obj->getPosition() - origin).getLength();
+ f32 d2 = (obj->getPosition() - origin).getLengthSQ();
- if (d > max_d)
+ if (d2 > max_d2)
continue;
- dest.emplace_back(obj, d);
+ dest.emplace_back(obj, d2);
}
}
diff --git a/src/client/camera.cpp b/src/client/camera.cpp
index 1bbdb56ea..d1e76026d 100644
--- a/src/client/camera.cpp
+++ b/src/client/camera.cpp
@@ -99,9 +99,9 @@ bool Camera::successfullyCreated(std::string &error_message)
error_message.clear();
}
- if (g_settings->getBool("enable_client_modding")) {
+ if (m_client->modsLoaded())
m_client->getScript()->on_camera_ready(this);
- }
+
return error_message.empty();
}
@@ -412,7 +412,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
// Prevent camera positioned inside nodes
const NodeDefManager *nodemgr = m_client->ndef();
MapNode n = m_client->getEnv().getClientMap()
- .getNodeNoEx(floatToInt(my_cp, BS));
+ .getNode(floatToInt(my_cp, BS));
const ContentFeatures& features = nodemgr->get(n);
if (features.walkable) {
@@ -448,12 +448,26 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
if (m_camera_mode != CAMERA_MODE_FIRST)
m_camera_position = my_cp;
- // Get FOV
+ /*
+ * Apply server-sent FOV. If server doesn't enforce FOV,
+ * check for zoom and set to zoom FOV.
+ * Otherwise, default to m_cache_fov
+ */
+
f32 fov_degrees;
- // Disable zoom with zoom FOV = 0
- if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) {
+ 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;
+ } else if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) {
+ // Player requests zoom, apply zoom FOV
fov_degrees = player->getZoomFOV();
} else {
+ // Set to client's selected FOV
fov_degrees = m_cache_fov;
}
fov_degrees = rangelim(fov_degrees, 1.0f, 160.0f);
diff --git a/src/client/client.cpp b/src/client/client.cpp
index a4a379a73..caa3cc78c 100644
--- a/src/client/client.cpp
+++ b/src/client/client.cpp
@@ -108,30 +108,15 @@ Client::Client(
m_minimap = new Minimap(this);
}
m_cache_save_interval = g_settings->getU16("server_map_save_interval");
-
- m_modding_enabled = g_settings->getBool("enable_client_modding");
- // Only create the client script environment if client scripting is enabled by the
- // client.
- if (m_modding_enabled) {
- m_script = new ClientScripting(this);
- m_env.setScript(m_script);
- m_script->setEnv(&m_env);
- }
}
void Client::loadMods()
{
- // Don't load mods twice
- if (m_mods_loaded) {
- return;
- }
-
+ // Don't load mods twice.
// If client scripting is disabled by the client, don't load builtin or
// client-provided mods.
- if (!m_modding_enabled) {
- warningstream << "Client side scripting is disabled by client." << std::endl;
+ if (m_mods_loaded || !g_settings->getBool("enable_client_modding"))
return;
- }
// If client scripting is disabled by the server, don't load builtin or
// client-provided mods.
@@ -140,11 +125,13 @@ void Client::loadMods()
if (checkCSMRestrictionFlag(CSMRestrictionFlags::CSM_RF_LOAD_CLIENT_MODS)) {
warningstream << "Client-provided mod loading is disabled by server." <<
std::endl;
- // This line is needed because builtin is not loaded
- m_modding_enabled = false;
return;
}
+ m_script = new ClientScripting(this);
+ m_env.setScript(m_script);
+ m_script->setEnv(&m_env);
+
// Load builtin
scanModIntoMemory(BUILTIN_MOD_NAME, getBuiltinLuaPath());
m_script->loadModFromMemory(BUILTIN_MOD_NAME);
@@ -190,9 +177,15 @@ void Client::loadMods()
for (const ModSpec &mod : m_mods)
m_script->loadModFromMemory(mod.name);
+ // Mods are done loading. Unlock callbacks
+ m_mods_loaded = true;
+
// Run a callback when mods are loaded
m_script->on_mods_loaded();
- m_mods_loaded = true;
+ if (m_state == LC_Ready)
+ m_script->on_client_ready(m_env.getLocalPlayer());
+ if (m_camera)
+ m_script->on_camera_ready(m_camera);
}
bool Client::checkBuiltinIntegrity()
@@ -244,7 +237,7 @@ const ModSpec* Client::getModSpec(const std::string &modname) const
void Client::Stop()
{
m_shutdown = true;
- if (m_modding_enabled)
+ if (m_mods_loaded)
m_script->on_shutdown();
//request all client managed threads to stop
m_mesh_update_thread.stop();
@@ -254,7 +247,7 @@ void Client::Stop()
m_localdb->endSave();
}
- if (m_modding_enabled)
+ if (m_mods_loaded)
delete m_script;
}
@@ -371,7 +364,6 @@ void Client::step(float dtime)
*/
const float map_timer_and_unload_dtime = 5.25;
if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) {
- ScopeProfiler sp(g_profiler, "Client: map timer and unload");
std::vector<v3s16> deleted_blocks;
m_env.getMap().timerUpdate(map_timer_and_unload_dtime,
g_settings->getFloat("client_unload_unused_data_timeout"),
@@ -480,6 +472,7 @@ void Client::step(float dtime)
*/
{
int num_processed_meshes = 0;
+ std::vector<v3s16> blocks_to_ack;
while (!m_mesh_update_thread.m_queue_out.empty())
{
num_processed_meshes++;
@@ -518,14 +511,18 @@ void Client::step(float dtime)
m_minimap->addBlock(r.p, minimap_mapblock);
if (r.ack_block_to_server) {
- /*
- Acknowledge block
- [0] u8 count
- [1] v3s16 pos_0
- */
- sendGotBlocks(r.p);
+ if (blocks_to_ack.size() == 255) {
+ sendGotBlocks(blocks_to_ack);
+ blocks_to_ack.clear();
+ }
+
+ blocks_to_ack.emplace_back(r.p);
}
}
+ if (blocks_to_ack.size() > 0) {
+ // Acknowledge block(s)
+ sendGotBlocks(blocks_to_ack);
+ }
if (num_processed_meshes > 0)
g_profiler->graphAdd("num_processed_meshes", num_processed_meshes);
@@ -558,8 +555,8 @@ void Client::step(float dtime)
if (count_after != count_before) {
// Do this every <interval> seconds after TOCLIENT_INVENTORY
// Reset the locally changed inventory to the authoritative inventory
- m_env.getLocalPlayer()->inventory = *m_inventory_from_server;
- m_inventory_updated = true;
+ player->inventory = *m_inventory_from_server;
+ m_update_wielded_item = true;
}
}
@@ -908,7 +905,7 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *
*pkt << fov << wanted_range;
}
-void Client::interact(u8 action, const PointedThing& pointed)
+void Client::interact(InteractAction action, const PointedThing& pointed)
{
if(m_state != LC_Ready) {
errorstream << "Client::interact() "
@@ -928,19 +925,12 @@ void Client::interact(u8 action, const PointedThing& pointed)
[5] u32 length of the next item (plen)
[9] serialized PointedThing
[9 + plen] player position information
- actions:
- 0: start digging (from undersurface) or use
- 1: stop digging (all parameters ignored)
- 2: digging completed
- 3: place block or item (to abovesurface)
- 4: use item
- 5: perform secondary action of item
*/
NetworkPacket pkt(TOSERVER_INTERACT, 1 + 2 + 0);
- pkt << action;
- pkt << (u16)getPlayerItem();
+ pkt << (u8)action;
+ pkt << myplayer->getWieldIndex();
std::ostringstream tmp_os(std::ios::binary);
pointed.serialize(tmp_os);
@@ -1074,10 +1064,13 @@ void Client::sendDeletedBlocks(std::vector<v3s16> &blocks)
Send(&pkt);
}
-void Client::sendGotBlocks(v3s16 block)
+void Client::sendGotBlocks(const std::vector<v3s16> &blocks)
{
- NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6);
- pkt << (u8) 1 << block;
+ NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6 * blocks.size());
+ pkt << (u8) blocks.size();
+ for (const v3s16 &block : blocks)
+ pkt << block;
+
Send(&pkt);
}
@@ -1200,7 +1193,7 @@ void Client::clearOutChatQueue()
}
void Client::sendChangePassword(const std::string &oldpassword,
- const std::string &newpassword)
+ const std::string &newpassword)
{
LocalPlayer *player = m_env.getLocalPlayer();
if (player == NULL)
@@ -1229,60 +1222,54 @@ void Client::sendRespawn()
void Client::sendReady()
{
NetworkPacket pkt(TOSERVER_CLIENT_READY,
- 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash));
+ 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash) + 2);
pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH
<< (u8) 0 << (u16) strlen(g_version_hash);
pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash));
+ pkt << (u16)FORMSPEC_API_VERSION;
Send(&pkt);
}
void Client::sendPlayerPos()
{
- LocalPlayer *myplayer = m_env.getLocalPlayer();
- if (!myplayer)
+ LocalPlayer *player = m_env.getLocalPlayer();
+ if (!player)
return;
ClientMap &map = m_env.getClientMap();
-
- u8 camera_fov = map.getCameraFov();
- u8 wanted_range = map.getControl().wanted_range;
-
- // Save bandwidth by only updating position when something changed
- if(myplayer->last_position == myplayer->getPosition() &&
- myplayer->last_speed == myplayer->getSpeed() &&
- myplayer->last_pitch == myplayer->getPitch() &&
- myplayer->last_yaw == myplayer->getYaw() &&
- myplayer->last_keyPressed == myplayer->keyPressed &&
- myplayer->last_camera_fov == camera_fov &&
- myplayer->last_wanted_range == wanted_range)
+ u8 camera_fov = map.getCameraFov();
+ u8 wanted_range = map.getControl().wanted_range;
+
+ // Save bandwidth by only updating position when
+ // player is not dead and something changed
+
+ // FIXME: This part causes breakages in mods like 3d_armor, and has been commented for now
+ // if (m_activeobjects_received && player->isDead())
+ // return;
+
+ if (
+ player->last_position == player->getPosition() &&
+ player->last_speed == player->getSpeed() &&
+ player->last_pitch == player->getPitch() &&
+ player->last_yaw == player->getYaw() &&
+ player->last_keyPressed == player->keyPressed &&
+ player->last_camera_fov == camera_fov &&
+ player->last_wanted_range == wanted_range)
return;
- myplayer->last_position = myplayer->getPosition();
- myplayer->last_speed = myplayer->getSpeed();
- myplayer->last_pitch = myplayer->getPitch();
- myplayer->last_yaw = myplayer->getYaw();
- myplayer->last_keyPressed = myplayer->keyPressed;
- myplayer->last_camera_fov = camera_fov;
- myplayer->last_wanted_range = wanted_range;
+ player->last_position = player->getPosition();
+ player->last_speed = player->getSpeed();
+ player->last_pitch = player->getPitch();
+ player->last_yaw = player->getYaw();
+ player->last_keyPressed = player->keyPressed;
+ player->last_camera_fov = camera_fov;
+ player->last_wanted_range = wanted_range;
NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1);
- writePlayerPos(myplayer, &map, &pkt);
-
- Send(&pkt);
-}
-
-void Client::sendPlayerItem(u16 item)
-{
- LocalPlayer *myplayer = m_env.getLocalPlayer();
- if (!myplayer)
- return;
-
- NetworkPacket pkt(TOSERVER_PLAYERITEM, 2);
-
- pkt << item;
+ writePlayerPos(player, &map, &pkt);
Send(&pkt);
}
@@ -1318,7 +1305,7 @@ MapNode Client::getNode(v3s16 p, bool *is_valid_position)
return {};
}
}
- return m_env.getMap().getNodeNoEx(p, is_valid_position);
+ return m_env.getMap().getNode(p, is_valid_position);
}
void Client::addNode(v3s16 p, MapNode n, bool remove_metadata)
@@ -1346,28 +1333,33 @@ void Client::setPlayerControl(PlayerControl &control)
player->control = control;
}
-void Client::selectPlayerItem(u16 item)
+void Client::setPlayerItem(u16 item)
{
- m_playeritem = item;
- m_inventory_updated = true;
- sendPlayerItem(item);
-}
+ m_env.getLocalPlayer()->setWieldIndex(item);
+ m_update_wielded_item = true;
-// Returns true if the inventory of the local player has been
-// updated from the server. If it is true, it is set to false.
-bool Client::getLocalInventoryUpdated()
-{
- bool updated = m_inventory_updated;
- m_inventory_updated = false;
- return updated;
+ NetworkPacket pkt(TOSERVER_PLAYERITEM, 2);
+ pkt << item;
+ Send(&pkt);
}
-// Copies the inventory of the local player to parameter
-void Client::getLocalInventory(Inventory &dst)
+// Returns true once after the inventory of the local player
+// has been updated from the server.
+bool Client::updateWieldedItem()
{
+ if (!m_update_wielded_item)
+ return false;
+
+ m_update_wielded_item = false;
+
LocalPlayer *player = m_env.getLocalPlayer();
assert(player);
- dst = player->inventory;
+ if (auto *list = player->inventory.getList("main"))
+ list->setModified(false);
+ if (auto *list = player->inventory.getList("hand"))
+ list->setModified(false);
+
+ return true;
}
Inventory* Client::getInventory(const InventoryLocation &loc)
@@ -1510,7 +1502,7 @@ void Client::typeChatMessage(const std::wstring &message)
return;
// If message was consumed by script API, don't send it to server
- if (m_modding_enabled && m_script->on_sending_message(wide_to_utf8(message)))
+ if (m_mods_loaded && m_script->on_sending_message(wide_to_utf8(message)))
return;
// Send to others
@@ -1706,9 +1698,8 @@ void Client::afterContentReceived()
m_state = LC_Ready;
sendReady();
- if (g_settings->getBool("enable_client_modding")) {
+ if (m_mods_loaded)
m_script->on_client_ready(m_env.getLocalPlayer());
- }
text = wgettext("Done!");
RenderingEngine::draw_load_screen(text, guienv, m_tsrc, 0, 100);
diff --git a/src/client/client.h b/src/client/client.h
index 312b8c87f..e3c931837 100644
--- a/src/client/client.h
+++ b/src/client/client.h
@@ -193,6 +193,7 @@ public:
void handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt);
void handleCommand_ActiveObjectMessages(NetworkPacket* pkt);
void handleCommand_Movement(NetworkPacket* pkt);
+ void handleCommand_Fov(NetworkPacket *pkt);
void handleCommand_HP(NetworkPacket* pkt);
void handleCommand_Breath(NetworkPacket* pkt);
void handleCommand_MovePlayer(NetworkPacket* pkt);
@@ -227,12 +228,13 @@ public:
void handleCommand_SrpBytesSandB(NetworkPacket *pkt);
void handleCommand_FormspecPrepend(NetworkPacket *pkt);
void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt);
+ void handleCommand_PlayerSpeed(NetworkPacket *pkt);
void ProcessData(NetworkPacket *pkt);
void Send(NetworkPacket* pkt);
- void interact(u8 action, const PointedThing& pointed);
+ void interact(InteractAction action, const PointedThing &pointed);
void sendNodemetaFields(v3s16 p, const std::string &formname,
const StringMap &fields);
@@ -271,20 +273,17 @@ public:
void setPlayerControl(PlayerControl &control);
- void selectPlayerItem(u16 item);
- u16 getPlayerItem() const
- { return m_playeritem; }
-
// Returns true if the inventory of the local player has been
// updated from the server. If it is true, it is set to false.
- bool getLocalInventoryUpdated();
- // Copies the inventory of the local player to parameter
- void getLocalInventory(Inventory &dst);
+ bool updateWieldedItem();
/* InventoryManager interface */
Inventory* getInventory(const InventoryLocation &loc) override;
void inventoryAction(InventoryAction *a) override;
+ // Send the item number 'item' as player item to the server
+ void setPlayerItem(u16 item);
+
const std::list<std::string> &getConnectedPlayerNames()
{
return m_env.getPlayerNames();
@@ -335,12 +334,14 @@ public:
// disconnect client when CSM failed.
const std::string &accessDeniedReason() const { return m_access_denied_reason; }
- bool itemdefReceived()
+ const bool itemdefReceived() const
{ return m_itemdef_received; }
- bool nodedefReceived()
+ const bool nodedefReceived() const
{ return m_nodedef_received; }
- bool mediaReceived()
+ const bool mediaReceived() const
{ return !m_media_downloader; }
+ const bool activeObjectsReceived() const
+ { return m_activeobjects_received; }
u16 getProtoVersion()
{ return m_proto_ver; }
@@ -399,7 +400,6 @@ public:
}
ClientScripting *getScript() { return m_script; }
- const bool moddingEnabled() const { return m_modding_enabled; }
const bool modsLoaded() const { return m_mods_loaded; }
void pushToEventQueue(ClientEvent *event);
@@ -454,8 +454,6 @@ private:
void Receive();
void sendPlayerPos();
- // Send the item number 'item' as player item to the server
- void sendPlayerItem(u16 item);
void deleteAuthData();
// helper method shared with clientpackethandler
@@ -465,7 +463,7 @@ private:
void promptConfirmRegistration(AuthMechanism chosen_auth_mechanism);
void startAuth(AuthMechanism chosen_auth_mechanism);
void sendDeletedBlocks(std::vector<v3s16> &blocks);
- void sendGotBlocks(v3s16 block);
+ void sendGotBlocks(const std::vector<v3s16> &blocks);
void sendRemovedSounds(std::vector<s32> &soundList);
// Helper function
@@ -506,8 +504,7 @@ private:
// If 0, server init hasn't been received yet.
u16 m_proto_ver = 0;
- u16 m_playeritem = 0;
- bool m_inventory_updated = false;
+ bool m_update_wielded_item = false;
Inventory *m_inventory_from_server = nullptr;
float m_inventory_from_server_age = 0.0f;
PacketCounter m_packetcounter;
@@ -545,6 +542,7 @@ private:
std::queue<ClientEvent *> m_client_event_queue;
bool m_itemdef_received = false;
bool m_nodedef_received = false;
+ bool m_activeobjects_received = false;
bool m_mods_loaded = false;
ClientMediaDownloader *m_media_downloader;
diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp
index a788c93c2..5eb033302 100644
--- a/src/client/clientenvironment.cpp
+++ b/src/client/clientenvironment.cpp
@@ -47,8 +47,6 @@ ClientEnvironment::ClientEnvironment(ClientMap *map,
m_texturesource(texturesource),
m_client(client)
{
- char zero = 0;
- memset(attachement_parent_ids, zero, sizeof(attachement_parent_ids));
}
ClientEnvironment::~ClientEnvironment()
@@ -220,7 +218,7 @@ void ClientEnvironment::step(float dtime)
f32 post_factor = 1; // 1 hp per node/s
if (info.type == COLLISION_NODE) {
const ContentFeatures &f = m_client->ndef()->
- get(m_map->getNodeNoEx(info.node_p));
+ get(m_map->getNode(info.node_p));
// Determine fall damage multiplier
int addp = itemgroup_get(f.groups, "fall_damage_add_percent");
pre_factor = 1.0f + (float)addp / 100.0f;
@@ -250,7 +248,7 @@ void ClientEnvironment::step(float dtime)
MapNode node_at_lplayer(CONTENT_AIR, 0x0f, 0);
v3s16 p = lplayer->getLightPosition();
- node_at_lplayer = m_map->getNodeNoEx(p);
+ node_at_lplayer = m_map->getNode(p);
u16 light = getInteriorLight(node_at_lplayer, 0, m_client->ndef());
final_color_blend(&lplayer->light_color, light, day_night_ratio);
@@ -272,7 +270,7 @@ void ClientEnvironment::step(float dtime)
// Get node at head
v3s16 p = cao->getLightPosition();
- MapNode n = this->m_map->getNodeNoEx(p, &pos_ok);
+ MapNode n = this->m_map->getNode(p, &pos_ok);
if (pos_ok)
light = n.getLightBlend(day_night_ratio, m_client->ndef());
else
@@ -287,15 +285,14 @@ void ClientEnvironment::step(float dtime)
/*
Step and handle simple objects
*/
- g_profiler->avg("CEnv: num of simple objects", m_simple_objects.size());
+ g_profiler->avg("ClientEnv: CSO count [#]", m_simple_objects.size());
for (auto i = m_simple_objects.begin(); i != m_simple_objects.end();) {
- auto cur = i;
- ClientSimpleObject *simple = *cur;
+ ClientSimpleObject *simple = *i;
simple->step(dtime);
if(simple->m_to_be_removed) {
delete simple;
- i = m_simple_objects.erase(cur);
+ i = m_simple_objects.erase(i);
}
else {
++i;
@@ -353,7 +350,7 @@ u16 ClientEnvironment::addActiveObject(ClientActiveObject *object)
// Get node at head
v3s16 p = object->getLightPosition();
- MapNode n = m_map->getNodeNoEx(p, &pos_ok);
+ MapNode n = m_map->getNode(p, &pos_ok);
if (pos_ok)
light = n.getLightBlend(getDayNightRatio(), m_client->ndef());
else
@@ -392,7 +389,34 @@ void ClientEnvironment::addActiveObject(u16 id, u8 type,
<<std::endl;
}
- addActiveObject(obj);
+ u16 new_id = addActiveObject(obj);
+ // 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
+ const auto &children = obj->getAttachmentChildIds();
+ for (auto c_id : children) {
+ if (auto *o = getActiveObject(c_id))
+ o->updateAttachments();
+ }
+ }
+}
+
+
+void ClientEnvironment::removeActiveObject(u16 id)
+{
+ // Get current attachment childs to detach them visually
+ std::unordered_set<int> attachment_childs;
+ if (auto *obj = getActiveObject(id))
+ attachment_childs = obj->getAttachmentChildIds();
+
+ m_ao_manager.removeObject(id);
+
+ // Perform a proper detach in Irrlicht
+ for (auto c_id : attachment_childs) {
+ if (ClientActiveObject *child = getActiveObject(c_id))
+ child->updateAttachments();
+ }
}
void ClientEnvironment::processActiveObjectMessage(u16 id, const std::string &data)
diff --git a/src/client/clientenvironment.h b/src/client/clientenvironment.h
index 4fa3f4848..864496a41 100644
--- a/src/client/clientenvironment.h
+++ b/src/client/clientenvironment.h
@@ -104,10 +104,7 @@ public:
u16 addActiveObject(ClientActiveObject *object);
void addActiveObject(u16 id, u8 type, const std::string &init_data);
- void removeActiveObject(u16 id)
- {
- m_ao_manager.removeObject(id);
- }
+ void removeActiveObject(u16 id);
void processActiveObjectMessage(u16 id, const std::string &data);
@@ -138,8 +135,6 @@ public:
std::vector<PointedThing> &objects
);
- u16 attachement_parent_ids[USHRT_MAX + 1];
-
const std::list<std::string> &getPlayerNames() { return m_player_names; }
void addPlayerName(const std::string &name) { m_player_names.push_back(name); }
void removePlayerName(const std::string &name) { m_player_names.remove(name); }
diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp
index 969c55539..3e4ab2e94 100644
--- a/src/client/clientmap.cpp
+++ b/src/client/clientmap.cpp
@@ -63,14 +63,13 @@ ClientMap::ClientMap(
MapSector * ClientMap::emergeSector(v2s16 p2d)
{
// Check that it doesn't exist already
- try {
- return getSectorNoGenerate(p2d);
- } catch(InvalidPositionException &e) {
- }
+ MapSector *sector = getSectorNoGenerate(p2d);
- // Create a sector
- MapSector *sector = new MapSector(this, p2d, m_gamedef);
- m_sectors[p2d] = sector;
+ // Create it if it does not exist yet
+ if (!sector) {
+ sector = new MapSector(this, p2d, m_gamedef);
+ m_sectors[p2d] = sector;
+ }
return sector;
}
@@ -116,7 +115,6 @@ void ClientMap::getBlocksInViewRange(v3s16 cam_pos_nodes,
void ClientMap::updateDrawList()
{
ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
- g_profiler->add("CM::updateDrawList() count", 1);
for (auto &i : m_drawlist) {
MapBlock *block = i.second;
@@ -138,34 +136,26 @@ void ClientMap::updateDrawList()
v3s16 p_blocks_max;
getBlocksInViewRange(cam_pos_nodes, &p_blocks_min, &p_blocks_max);
- // Number of blocks in rendering range
- u32 blocks_in_range = 0;
+ // Number of blocks with mesh in rendering range
+ u32 blocks_in_range_with_mesh = 0;
// Number of blocks occlusion culled
u32 blocks_occlusion_culled = 0;
- // Number of blocks in rendering range but don't have a mesh
- u32 blocks_in_range_without_mesh = 0;
- // Blocks that had mesh that would have been drawn according to
- // rendering range (if max blocks limit didn't kick in)
- u32 blocks_would_have_drawn = 0;
- // Blocks that were drawn and had a mesh
- u32 blocks_drawn = 0;
- // Blocks which had a corresponding meshbuffer for this pass
- //u32 blocks_had_pass_meshbuf = 0;
- // Blocks from which stuff was actually drawn
- //u32 blocks_without_stuff = 0;
- // Distance to farthest drawn block
- float farthest_drawn = 0;
// No occlusion culling when free_move is on and camera is
// inside ground
bool occlusion_culling_enabled = true;
- if (g_settings->getBool("free_move")) {
- MapNode n = getNodeNoEx(cam_pos_nodes);
+ if (g_settings->getBool("free_move") && g_settings->getBool("noclip")) {
+ MapNode n = getNode(cam_pos_nodes);
if (n.getContent() == CONTENT_IGNORE ||
m_nodedef->get(n).solidness == 2)
occlusion_culling_enabled = false;
}
+ // Uncomment to debug occluded blocks in the wireframe mode
+ // TODO: Include this as a flag for an extended debugging setting
+ //if (occlusion_culling_enabled && m_control.show_wireframe)
+ // occlusion_culling_enabled = porting::getTimeS() & 1;
+
for (const auto &sector_it : m_sectors) {
MapSector *sector = sector_it.second;
v2s16 sp = sector->getPos();
@@ -185,11 +175,11 @@ void ClientMap::updateDrawList()
u32 sector_blocks_drawn = 0;
- for (auto block : sectorblocks) {
+ for (MapBlock *block : sectorblocks) {
/*
- Compare block position to camera position, skip
- if not seen on display
- */
+ Compare block position to camera position, skip
+ if not seen on display
+ */
if (block->mesh)
block->mesh->updateCameraOffset(m_camera_offset);
@@ -203,20 +193,20 @@ void ClientMap::updateDrawList()
camera_direction, camera_fov, range, &d))
continue;
- blocks_in_range++;
/*
Ignore if mesh doesn't exist
*/
- if (!block->mesh) {
- blocks_in_range_without_mesh++;
+ if (!block->mesh)
continue;
- }
+
+ blocks_in_range_with_mesh++;
/*
Occlusion culling
*/
- if (occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes)) {
+ if ((!m_control.range_all && d > m_control.wanted_range * BS) ||
+ (occlusion_culling_enabled && isBlockOccluded(block, cam_pos_nodes))) {
blocks_occlusion_culled++;
continue;
}
@@ -224,36 +214,20 @@ void ClientMap::updateDrawList()
// This block is in range. Reset usage timer.
block->resetUsageTimer();
- // Limit block count in case of a sudden increase
- blocks_would_have_drawn++;
- if (blocks_drawn >= m_control.wanted_max_blocks &&
- !m_control.range_all &&
- d > m_control.wanted_range * BS)
- continue;
-
// Add to set
block->refGrab();
m_drawlist[block->getPos()] = block;
sector_blocks_drawn++;
- blocks_drawn++;
- if (d / BS > farthest_drawn)
- farthest_drawn = d / BS;
-
} // foreach sectorblocks
if (sector_blocks_drawn != 0)
m_last_drawn_sectors.insert(sp);
}
- g_profiler->avg("CM: blocks in range", blocks_in_range);
- g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled);
- if (blocks_in_range != 0)
- g_profiler->avg("CM: blocks in range without mesh (frac)",
- (float)blocks_in_range_without_mesh / blocks_in_range);
- g_profiler->avg("CM: blocks drawn", blocks_drawn);
- g_profiler->avg("CM: farthest drawn", farthest_drawn);
- g_profiler->avg("CM: wanted max blocks", m_control.wanted_max_blocks);
+ g_profiler->avg("MapBlock meshes in range [#]", blocks_in_range_with_mesh);
+ g_profiler->avg("MapBlocks occlusion culled [#]", blocks_occlusion_culled);
+ g_profiler->avg("MapBlocks drawn [#]", m_drawlist.size());
}
struct MeshBufList
@@ -306,9 +280,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
std::string prefix;
if (pass == scene::ESNRP_SOLID)
- prefix = "CM: solid: ";
+ prefix = "renderMap(SOLID): ";
else
- prefix = "CM: transparent: ";
+ prefix = "renderMap(TRANSPARENT): ";
/*
This is called two times per frame, reset on the non-transparent one
@@ -317,14 +291,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
m_last_drawn_sectors.clear();
/*
- Get time for measuring timeout.
-
- Measuring time is very useful for long delays when the
- machine is swapping a lot.
- */
- std::time_t time1 = time(0);
-
- /*
Get animation parameters
*/
float animation_time = m_client->getAnimationTime();
@@ -340,26 +306,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
*/
u32 vertex_count = 0;
- u32 meshbuffer_count = 0;
// For limiting number of mesh animations per frame
u32 mesh_animate_count = 0;
- u32 mesh_animate_count_far = 0;
-
- // Blocks that were drawn and had a mesh
- u32 blocks_drawn = 0;
- // Blocks which had a corresponding meshbuffer for this pass
- u32 blocks_had_pass_meshbuf = 0;
- // Blocks from which stuff was actually drawn
- u32 blocks_without_stuff = 0;
+ //u32 mesh_animate_count_far = 0;
/*
Draw the selected MapBlocks
*/
- {
- ScopeProfiler sp(g_profiler, prefix + "drawing blocks", SPT_AVG);
-
MeshBufListList drawbufs;
for (auto &i : m_drawlist) {
@@ -381,15 +336,13 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
assert(mapBlockMesh);
// Pretty random but this should work somewhat nicely
bool faraway = d >= BS * 50;
- //bool faraway = d >= m_control.wanted_range * BS;
if (mapBlockMesh->isAnimationForced() || !faraway ||
- mesh_animate_count_far < (m_control.range_all ? 200 : 50)) {
+ mesh_animate_count < (m_control.range_all ? 200 : 50)) {
+
bool animated = mapBlockMesh->animate(faraway, animation_time,
crack, daynight_ratio);
if (animated)
mesh_animate_count++;
- if (animated && faraway)
- mesh_animate_count_far++;
} else {
mapBlockMesh->decreaseAnimationForceTimer();
}
@@ -437,46 +390,33 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
}
}
+ TimeTaker draw("Drawing mesh buffers");
+
// Render all layers in order
for (auto &lists : drawbufs.lists) {
- int timecheck_counter = 0;
for (MeshBufList &list : lists) {
- timecheck_counter++;
- if (timecheck_counter > 50) {
- timecheck_counter = 0;
- std::time_t time2 = time(0);
- if (time2 > time1 + 4) {
- infostream << "ClientMap::renderMap(): "
- "Rendering takes ages, returning."
- << std::endl;
- return;
- }
+ // Check and abort if the machine is swapping a lot
+ if (draw.getTimerTime() > 2000) {
+ infostream << "ClientMap::renderMap(): Rendering took >2s, " <<
+ "returning." << std::endl;
+ return;
}
-
driver->setMaterial(list.m);
for (scene::IMeshBuffer *buf : list.bufs) {
driver->drawMeshBuffer(buf);
vertex_count += buf->getVertexCount();
- meshbuffer_count++;
}
}
}
- } // ScopeProfiler
+ g_profiler->avg(prefix + "draw meshes [ms]", draw.stop(true));
// Log only on solid pass because values are the same
if (pass == scene::ESNRP_SOLID) {
- g_profiler->avg("CM: animated meshes", mesh_animate_count);
- g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far);
+ g_profiler->avg("renderMap(): animated meshes [#]", mesh_animate_count);
}
- g_profiler->avg(prefix + "vertices drawn", vertex_count);
- if (blocks_had_pass_meshbuf != 0)
- g_profiler->avg(prefix + "meshbuffers per block",
- (float)meshbuffer_count / (float)blocks_had_pass_meshbuf);
- if (blocks_drawn != 0)
- g_profiler->avg(prefix + "empty blocks (frac)",
- (float)blocks_without_stuff / blocks_drawn);
+ g_profiler->avg(prefix + "vertices drawn [#]", vertex_count);
}
static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
@@ -497,7 +437,7 @@ static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
// Check content nearly at camera position
{
v3s16 p = floatToInt(p0 /*+ dir * 3*BS*/, BS);
- MapNode n = map->getNodeNoEx(p);
+ MapNode n = map->getNode(p);
if(ndef->get(n).param_type == CPT_LIGHT &&
!ndef->get(n).sunlight_propagates)
allow_allowing_non_sunlight_propagates = true;
@@ -505,7 +445,7 @@ static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
// If would start at CONTENT_IGNORE, start closer
{
v3s16 p = floatToInt(pf, BS);
- MapNode n = map->getNodeNoEx(p);
+ MapNode n = map->getNode(p);
if(n.getContent() == CONTENT_IGNORE){
float newd = 2*BS;
pf = p0 + dir * 2*newd;
@@ -519,7 +459,7 @@ static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
step *= step_multiplier;
v3s16 p = floatToInt(pf, BS);
- MapNode n = map->getNodeNoEx(p);
+ MapNode n = map->getNode(p);
if (allow_allowing_non_sunlight_propagates && i == 0 &&
ndef->get(n).param_type == CPT_LIGHT &&
!ndef->get(n).sunlight_propagates) {
@@ -555,6 +495,7 @@ static bool getVisibleBrightness(Map *map, const v3f &p0, v3f dir, float step,
int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor,
int oldvalue, bool *sunlight_seen_result)
{
+ ScopeProfiler sp(g_profiler, "CM::getBackgroundBrightness", SPT_AVG);
static v3f z_directions[50] = {
v3f(-100, 0, 0)
};
@@ -621,7 +562,7 @@ int ClientMap::getBackgroundBrightness(float max_d, u32 daylight_factor,
int ret = 0;
if(brightness_count == 0){
- MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
+ MapNode n = getNode(floatToInt(m_camera_position, BS));
if(m_nodedef->get(n).param_type == CPT_LIGHT){
ret = decode_light(n.getLightBlend(daylight_factor, m_nodedef));
} else {
@@ -640,7 +581,7 @@ void ClientMap::renderPostFx(CameraMode cam_mode)
// Sadly ISceneManager has no "post effects" render pass, in that case we
// could just register for that and handle it in renderMap().
- MapNode n = getNodeNoEx(floatToInt(m_camera_position, BS));
+ MapNode n = getNode(floatToInt(m_camera_position, BS));
// - If the player is in a solid node, make everything black.
// - If the player is in liquid, draw a semi-transparent overlay.
diff --git a/src/client/clientmap.h b/src/client/clientmap.h
index 8402bb00d..172e3a1d6 100644
--- a/src/client/clientmap.h
+++ b/src/client/clientmap.h
@@ -31,8 +31,6 @@ struct MapDrawControl
bool range_all = false;
// Wanted drawing range
float wanted_range = 0.0f;
- // Maximum number of blocks to draw
- u32 wanted_max_blocks = 0;
// show a wire frame for debugging
bool show_wireframe = false;
};
diff --git a/src/client/clientmedia.cpp b/src/client/clientmedia.cpp
index e3ad92dbc..6da99bbbf 100644
--- a/src/client/clientmedia.cpp
+++ b/src/client/clientmedia.cpp
@@ -254,6 +254,16 @@ void ClientMediaDownloader::initialStep(Client *client)
fetch_request.post_data = required_hash_set;
fetch_request.extra_headers.emplace_back(
"Content-Type: application/octet-stream");
+
+ // Encapsulate possible IPv6 plain address in []
+ std::string addr = client->getAddressName();
+ if (addr.find(':', 0) != std::string::npos)
+ addr = '[' + addr + ']';
+ fetch_request.extra_headers.emplace_back(
+ std::string("Referer: minetest://") +
+ addr + ":" +
+ std::to_string(client->getServerAddress().getPort()));
+
httpfetch_async(fetch_request);
m_httpfetch_active++;
diff --git a/src/client/clientobject.h b/src/client/clientobject.h
index 9377d1e67..c673fff9a 100644
--- a/src/client/clientobject.h
+++ b/src/client/clientobject.h
@@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irrlichttypes_extrabloated.h"
#include "activeobject.h"
#include <unordered_map>
+#include <unordered_set>
+
class ClientEnvironment;
class ITextureSource;
@@ -37,51 +39,53 @@ public:
ClientActiveObject(u16 id, Client *client, ClientEnvironment *env);
virtual ~ClientActiveObject();
- virtual void addToScene(ITextureSource *tsrc) {};
+ 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(u8 light_at_pos) {}
+ virtual void updateLightNoCheck(u8 light_at_pos) {}
+ virtual v3s16 getLightPosition() { return v3s16(0, 0, 0); }
virtual bool getCollisionBox(aabb3f *toset) const { return false; }
virtual bool getSelectionBox(aabb3f *toset) const { return false; }
virtual bool collideWithObjects() const { return false; }
- virtual v3f getPosition(){ return v3f(0,0,0); }
- virtual float getYaw() const { return 0; }
+ virtual const v3f getPosition() const { return v3f(0.0f); }
virtual scene::ISceneNode *getSceneNode() { return NULL; }
virtual scene::IAnimatedMeshSceneNode *getAnimatedMeshSceneNode() { return NULL; }
- virtual bool isLocalPlayer() const {return false;}
+ virtual bool isLocalPlayer() const { return false; }
+
virtual ClientActiveObject *getParent() const { return nullptr; };
- virtual void setAttachments() {}
- virtual bool doShowSelectionBox(){return true;}
+ virtual const std::unordered_set<int> &getAttachmentChildIds() const
+ { static std::unordered_set<int> rv; return rv; }
+ virtual void updateAttachments() {};
+
+ virtual bool doShowSelectionBox() { return true; }
// Step object in time
- virtual void step(float dtime, ClientEnvironment *env){}
+ virtual void step(float dtime, ClientEnvironment *env) {}
// Process a message sent by the server side object
- virtual void processMessage(const std::string &data){}
+ virtual void processMessage(const std::string &data) {}
- virtual std::string infoText() {return "";}
- virtual std::string debugInfoText() {return "";}
+ virtual std::string infoText() { return ""; }
+ virtual std::string debugInfoText() { return ""; }
/*
This takes the return value of
ServerActiveObject::getClientInitializationData
*/
- virtual void initialize(const std::string &data){}
+ virtual void initialize(const std::string &data) {}
// Create a certain type of ClientActiveObject
- static ClientActiveObject* create(ActiveObjectType type, Client *client,
- ClientEnvironment *env);
+ static ClientActiveObject *create(ActiveObjectType type, Client *client,
+ ClientEnvironment *env);
// If returns true, punch will not be sent to the server
- virtual bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL,
- float time_from_last_punch=1000000)
- { return false; }
+ virtual bool directReportPunch(v3f dir, const ItemStack *punchitem = nullptr,
+ float time_from_last_punch = 1000000) { return false; }
protected:
// Used for creating objects based on type
- typedef ClientActiveObject* (*Factory)(Client *client, ClientEnvironment *env);
+ typedef ClientActiveObject *(*Factory)(Client *client, ClientEnvironment *env);
static void registerType(u16 type, Factory f);
Client *m_client;
ClientEnvironment *m_env;
@@ -90,10 +94,10 @@ private:
static std::unordered_map<u16, Factory> m_types;
};
-struct DistanceSortedActiveObject
+class DistanceSortedActiveObject
{
+public:
ClientActiveObject *obj;
- f32 d;
DistanceSortedActiveObject(ClientActiveObject *a_obj, f32 a_d)
{
@@ -105,4 +109,7 @@ struct DistanceSortedActiveObject
{
return d < other.d;
}
+
+private:
+ f32 d;
};
diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp
index 13051f32c..887a62f25 100644
--- a/src/client/clouds.cpp
+++ b/src/client/clouds.cpp
@@ -99,7 +99,7 @@ void Clouds::render()
//if(SceneManager->getSceneNodeRenderPass() != scene::ESNRP_SOLID)
return;
- ScopeProfiler sp(g_profiler, "Rendering of clouds, avg", SPT_AVG);
+ ScopeProfiler sp(g_profiler, "Clouds::render()", SPT_AVG);
int num_faces_to_draw = m_enable_3d ? 6 : 1;
diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp
index 8643b5824..a15c1cc0b 100644
--- a/src/client/content_cao.cpp
+++ b/src/client/content_cao.cpp
@@ -17,36 +17,35 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "content_cao.h"
+#include <IBillboardSceneNode.h>
#include <ICameraSceneNode.h>
#include <ITextSceneNode.h>
-#include <IBillboardSceneNode.h>
#include <IMeshManipulator.h>
#include <IAnimatedMeshSceneNode.h>
-#include "content_cao.h"
-#include "util/numeric.h" // For IntervalLimiter & setPitchYawRoll
-#include "util/serialize.h"
-#include "util/basic_macros.h"
+#include "client/client.h"
+#include "client/renderingengine.h"
#include "client/sound.h"
#include "client/tile.h"
-#include "environment.h"
+#include "util/basic_macros.h"
+#include "util/numeric.h" // For IntervalLimiter & setPitchYawRoll
+#include "util/serialize.h"
+#include "camera.h" // CameraModes
#include "collision.h"
-#include "settings.h"
-#include "serialization.h" // For decompressZlib
-#include "clientobject.h"
-#include "mesh.h"
-#include "itemdef.h"
-#include "tool.h"
#include "content_cso.h"
-#include "sound.h"
-#include "nodedef.h"
+#include "environment.h"
+#include "itemdef.h"
#include "localplayer.h"
#include "map.h"
-#include "camera.h" // CameraModes
-#include "client.h"
+#include "mesh.h"
+#include "nodedef.h"
+#include "serialization.h" // For decompressZlib
+#include "settings.h"
+#include "sound.h"
+#include "tool.h"
#include "wieldmesh.h"
#include <algorithm>
#include <cmath>
-#include "client/renderingengine.h"
class Settings;
struct ToolCapabilities;
@@ -305,6 +304,7 @@ void TestCAO::processMessage(const std::string &data)
*/
#include "genericobject.h"
+#include "clientobject.h"
GenericCAO::GenericCAO(Client *client, ClientEnvironment *env):
ClientActiveObject(0, client, env)
@@ -372,6 +372,7 @@ void GenericCAO::processInitData(const std::string &data)
m_position = readV3F32(is);
m_rotation = readV3F32(is);
m_hp = readU16(is);
+
const u8 num_messages = readU8(is);
for (int i = 0; i < num_messages; i++) {
@@ -400,7 +401,7 @@ bool GenericCAO::getSelectionBox(aabb3f *toset) const
return true;
}
-v3f GenericCAO::getPosition()
+const v3f GenericCAO::getPosition() const
{
if (getParent() != nullptr) {
if (m_matrixnode)
@@ -443,7 +444,7 @@ scene::IAnimatedMeshSceneNode* GenericCAO::getAnimatedMeshSceneNode()
void GenericCAO::setChildrenVisible(bool toset)
{
- for (u16 cao_id : m_children) {
+ for (u16 cao_id : m_attachment_child_ids) {
GenericCAO *obj = m_env->getGenericCAO(cao_id);
if (obj) {
obj->setVisible(toset);
@@ -451,43 +452,81 @@ void GenericCAO::setChildrenVisible(bool toset)
}
}
-void GenericCAO::setAttachments()
+void GenericCAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation)
{
+ 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;
+
+ ClientActiveObject *parent = m_env->getActiveObject(parent_id);
+
+ if (parent_id != old_parent) {
+ if (auto *o = m_env->getActiveObject(old_parent))
+ o->removeAttachmentChild(m_id);
+ if (parent)
+ parent->addAttachmentChild(m_id);
+ }
+
updateAttachments();
}
-ClientActiveObject* GenericCAO::getParent() const
+void GenericCAO::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 GenericCAO::clearChildAttachments()
{
- ClientActiveObject *obj = NULL;
+ // Cannot use for-loop here: setAttachment() modifies 'm_attachment_child_ids'!
+ while (!m_attachment_child_ids.empty()) {
+ int child_id = *m_attachment_child_ids.begin();
- u16 attached_id = m_env->attachement_parent_ids[getId()];
+ if (ClientActiveObject *child = m_env->getActiveObject(child_id))
+ child->setAttachment(0, "", v3f(), v3f());
- if ((attached_id != 0) &&
- (attached_id != getId())) {
- obj = m_env->getActiveObject(attached_id);
+ removeAttachmentChild(child_id);
}
- return obj;
}
-void GenericCAO::removeFromScene(bool permanent)
+void GenericCAO::clearParentAttachment()
{
- // Should be true when removing the object permanently and false when refreshing (eg: updating visuals)
- if((m_env != NULL) && (permanent))
- {
- for (u16 ci : m_children) {
- if (m_env->attachement_parent_ids[ci] == getId()) {
- m_env->attachement_parent_ids[ci] = 0;
- }
- }
- m_children.clear();
+ if (m_attachment_parent_id)
+ setAttachment(0, "", m_attachment_position, m_attachment_rotation);
+ else
+ setAttachment(0, "", v3f(), v3f());
+}
- m_env->attachement_parent_ids[getId()] = 0;
+void GenericCAO::addAttachmentChild(int child_id)
+{
+ m_attachment_child_ids.insert(child_id);
+}
- LocalPlayer* player = m_env->getLocalPlayer();
- if (this == player->parent) {
- player->parent = nullptr;
- player->isAttached = false;
- }
+void GenericCAO::removeAttachmentChild(int child_id)
+{
+ m_attachment_child_ids.erase(child_id);
+}
+
+ClientActiveObject* GenericCAO::getParent() const
+{
+ return m_attachment_parent_id ? m_env->getActiveObject(m_attachment_parent_id) :
+ nullptr;
+}
+
+void GenericCAO::removeFromScene(bool permanent)
+{
+ // Should be true when removing the object permanently
+ // and false when refreshing (eg: updating visuals)
+ if (m_env && permanent) {
+ // The client does not know whether this object does re-appear to
+ // a later time, thus do not clear child attachments.
+
+ clearParentAttachment();
}
if (m_meshnode) {
@@ -711,6 +750,7 @@ void GenericCAO::addToScene(ITextureSource *tsrc)
updateTextures(m_current_texture_modifier);
scene::ISceneNode *node = getSceneNode();
+
if (node && !m_prop.nametag.empty() && !m_is_local_player) {
// Add nametag
v3f pos;
@@ -736,7 +776,7 @@ void GenericCAO::updateLight(u8 light_at_pos)
updateLightNoCheck(light_at_pos);
// Update light of all children
- for (u16 i : m_children) {
+ for (u16 i : m_attachment_child_ids) {
ClientActiveObject *obj = m_env->getActiveObject(i);
if (obj) {
obj->updateLightNoCheck(light_at_pos);
@@ -871,12 +911,8 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
// Attachments, part 1: All attached objects must be unparented first,
// or Irrlicht causes a segmentation fault
- for (auto ci = m_children.begin(); ci != m_children.end();) {
- if (m_env->attachement_parent_ids[*ci] != getId()) {
- ci = m_children.erase(ci);
- continue;
- }
- ClientActiveObject *obj = m_env->getActiveObject(*ci);
+ for (u16 cao_id : m_attachment_child_ids) {
+ ClientActiveObject *obj = m_env->getActiveObject(cao_id);
if (obj) {
scene::ISceneNode *child_node = obj->getSceneNode();
// The node's parent is always an IDummyTraformationSceneNode,
@@ -884,18 +920,16 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
if (child_node)
child_node->getParent()->setParent(m_smgr->getRootSceneNode());
}
- ++ci;
}
removeFromScene(false);
addToScene(m_client->tsrc());
// Attachments, part 2: Now that the parent has been refreshed, put its attachments back
- for (u16 cao_id : m_children) {
- // Get the object of the child
+ for (u16 cao_id : m_attachment_child_ids) {
ClientActiveObject *obj = m_env->getActiveObject(cao_id);
if (obj)
- obj->setAttachments();
+ obj->updateAttachments();
}
}
@@ -916,7 +950,6 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
{
LocalPlayer *player = m_env->getLocalPlayer();
player->overridePosition = getParent()->getPosition();
- m_env->getLocalPlayer()->parent = getParent();
}
} else {
rot_translator.translate(dtime);
@@ -960,7 +993,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
const NodeDefManager *ndef = m_client->ndef();
v3s16 p = floatToInt(getPosition() +
v3f(0.0f, (m_prop.collisionbox.MinEdge.Y - 0.5f) * BS, 0.0f), BS);
- MapNode n = m_env->getMap().getNodeNoEx(p);
+ MapNode n = m_env->getMap().getNode(p);
SimpleSoundSpec spec = ndef->get(n).sound_footstep;
// Reduce footstep gain, as non-local-player footsteps are
// somehow louder.
@@ -997,15 +1030,20 @@ void GenericCAO::step(float dtime, ClientEnvironment *env)
if (!getParent() && 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_delta =
- dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
+ float max_rotation_per_sec =
+ m_prop.automatic_face_movement_max_rotation_per_sec;
- wrappedApproachShortest(m_rotation.Y, target_yaw, max_rotation_delta, 360.f);
- rot_translator.val_current = m_rotation;
+ if (max_rotation_per_sec > 0) {
+ 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;
+ }
+ rot_translator.val_current = m_rotation;
updateNodePos();
}
}
@@ -1057,6 +1095,7 @@ void GenericCAO::updateTexturePos()
}
}
+// Do not pass by reference, see header.
void GenericCAO::updateTextures(std::string mod)
{
ITextureSource *tsrc = m_client->tsrc();
@@ -1225,18 +1264,19 @@ void GenericCAO::updateTextures(std::string mod)
buf->getMaterial().AmbientColor = m_prop.colors[1];
buf->getMaterial().DiffuseColor = m_prop.colors[1];
buf->getMaterial().SpecularColor = m_prop.colors[1];
- setMeshColor(mesh, m_prop.colors[1]);
} else if (!m_prop.colors.empty()) {
buf->getMaterial().AmbientColor = m_prop.colors[0];
buf->getMaterial().DiffuseColor = m_prop.colors[0];
buf->getMaterial().SpecularColor = m_prop.colors[0];
- setMeshColor(mesh, m_prop.colors[0]);
}
buf->getMaterial().setFlag(video::EMF_TRILINEAR_FILTER, use_trilinear_filter);
buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, use_bilinear_filter);
buf->getMaterial().setFlag(video::EMF_ANISOTROPIC_FILTER, use_anisotropic_filter);
}
+ // Set mesh color (only if lighting is disabled)
+ if (!m_prop.colors.empty() && m_glow < 0)
+ setMeshColor(mesh, m_prop.colors[0]);
}
}
}
@@ -1290,6 +1330,21 @@ void GenericCAO::updateBonePosition()
void GenericCAO::updateAttachments()
{
ClientActiveObject *parent = getParent();
+
+ m_attached_to_local = parent && parent->isLocalPlayer();
+
+ /*
+ Following cases exist:
+ m_attachment_parent_id == 0 && !parent
+ This object is not attached
+ m_attachment_parent_id != 0 && parent
+ This object is attached
+ m_attachment_parent_id != 0 && !parent
+ This object will be attached as soon the parent is known
+ m_attachment_parent_id == 0 && parent
+ Impossible case
+ */
+
if (!parent) { // Detach or don't attach
if (m_matrixnode) {
v3f old_pos = m_matrixnode->getAbsolutePosition();
@@ -1297,10 +1352,6 @@ void GenericCAO::updateAttachments()
getPosRotMatrix().setTranslation(old_pos);
m_matrixnode->updateAbsolutePosition();
}
- if (m_is_local_player) {
- LocalPlayer *player = m_env->getLocalPlayer();
- player->isAttached = false;
- }
}
else // Attach
{
@@ -1319,10 +1370,11 @@ void GenericCAO::updateAttachments()
getPosRotMatrix().setRotationDegrees(m_attachment_rotation);
m_matrixnode->updateAbsolutePosition();
}
- if (m_is_local_player) {
- LocalPlayer *player = m_env->getLocalPlayer();
- player->isAttached = true;
- }
+ }
+ if (m_is_local_player) {
+ LocalPlayer *player = m_env->getLocalPlayer();
+ player->isAttached = parent;
+ player->parent = parent;
}
}
@@ -1482,31 +1534,15 @@ void GenericCAO::processMessage(const std::string &data)
updateBonePosition();
} else if (cmd == GENERIC_CMD_ATTACH_TO) {
u16 parent_id = readS16(is);
- u16 &old_parent_id = m_env->attachement_parent_ids[getId()];
- if (parent_id != old_parent_id) {
- if (GenericCAO *old_parent = m_env->getGenericCAO(old_parent_id)) {
- old_parent->m_children.erase(std::remove(
- m_children.begin(), m_children.end(),
- getId()), m_children.end());
- }
- if (GenericCAO *new_parent = m_env->getGenericCAO(parent_id))
- new_parent->m_children.push_back(getId());
-
- old_parent_id = parent_id;
- }
+ std::string bone = deSerializeString(is);
+ v3f position = readV3F32(is);
+ v3f rotation = readV3F32(is);
- m_attachment_bone = deSerializeString(is);
- m_attachment_position = readV3F32(is);
- m_attachment_rotation = readV3F32(is);
+ setAttachment(parent_id, bone, position, rotation);
// localplayer itself can't be attached to localplayer
- if (!m_is_local_player) {
- m_attached_to_local = getParent() != NULL && getParent()->isLocalPlayer();
- // Objects attached to the local player should be hidden by default
+ if (!m_is_local_player)
m_is_visible = !m_attached_to_local;
- }
-
- updateAttachments();
} else if (cmd == GENERIC_CMD_PUNCHED) {
u16 result_hp = readU16(is);
@@ -1515,9 +1551,12 @@ void GenericCAO::processMessage(const std::string &data)
m_hp = result_hp;
+ if (m_is_local_player)
+ m_env->getLocalPlayer()->hp = m_hp;
+
if (damage > 0)
{
- if (m_hp <= 0)
+ if (m_hp == 0)
{
// TODO: Execute defined fast response
// As there is no definition, make a smoke puff
@@ -1534,6 +1573,14 @@ void GenericCAO::processMessage(const std::string &data)
updateTextures(m_current_texture_modifier + "^[brighten");
}
}
+
+ if (m_hp == 0) {
+ // Same as 'Server::DiePlayer'
+ clearParentAttachment();
+ // Same as 'ObjectRef::l_remove'
+ if (!m_is_player)
+ clearChildAttachments();
+ }
} else if (cmd == GENERIC_CMD_UPDATE_ARMOR_GROUPS) {
m_armor_groups.clear();
int armor_groups_size = readU16(is);
@@ -1555,13 +1602,10 @@ void GenericCAO::processMessage(const std::string &data)
}
} else if (cmd == GENERIC_CMD_SPAWN_INFANT) {
u16 child_id = readU16(is);
- u8 type = readU8(is);
+ u8 type = readU8(is); // maybe this will be useful later
+ (void)type;
- if (GenericCAO *childobj = m_env->getGenericCAO(child_id)) {
- childobj->processInitData(deSerializeLongString(is));
- } else {
- m_env->addActiveObject(child_id, type, deSerializeLongString(is));
- }
+ addAttachmentChild(child_id);
} else {
warningstream << FUNCTION_NAME
<< ": unknown command or outdated client \""
diff --git a/src/client/content_cao.h b/src/client/content_cao.h
index 3ce628d30..2c2d11077 100644
--- a/src/client/content_cao.h
+++ b/src/client/content_cao.h
@@ -102,10 +102,14 @@ private:
bool m_animation_loop = true;
// 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;
+ std::unordered_set<int> m_attachment_child_ids;
std::string m_attachment_bone = "";
v3f m_attachment_position;
v3f m_attachment_rotation;
bool m_attached_to_local = false;
+
int m_anim_frame = 0;
int m_anim_num_frames = 1;
float m_anim_framelength = 0.2f;
@@ -122,8 +126,6 @@ private:
bool m_is_visible = false;
s8 m_glow = 0;
- std::vector<u16> m_children;
-
public:
GenericCAO(Client *client, ClientEnvironment *env);
@@ -152,13 +154,15 @@ public:
virtual bool getSelectionBox(aabb3f *toset) const;
- v3f getPosition();
+ const v3f getPosition() const;
- inline const v3f &getRotation()
+ void setPosition(const v3f &pos)
{
- return m_rotation;
+ pos_translator.val_current = pos;
}
+ inline const v3f &getRotation() const { return m_rotation; }
+
const bool isImmortal();
scene::ISceneNode *getSceneNode();
@@ -178,6 +182,12 @@ public:
return m_matrixnode->getRelativeTransformationMatrix();
}
+ inline const core::matrix4 &getAbsolutePosRotMatrix() const
+ {
+ assert(m_matrixnode);
+ return m_matrixnode->getAbsoluteTransformation();
+ }
+
inline f32 getStepHeight() const
{
return m_prop.stepheight;
@@ -199,10 +209,17 @@ public:
}
void setChildrenVisible(bool toset);
-
+ 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);
ClientActiveObject *getParent() const;
-
- void setAttachments();
+ const std::unordered_set<int> &getAttachmentChildIds() const
+ { return m_attachment_child_ids; }
+ void updateAttachments();
void removeFromScene(bool permanent);
@@ -225,8 +242,8 @@ public:
void updateTexturePos();
- // std::string copy is mandatory as mod can be a class member and there is a swap
- // on those class members... do NOT pass by reference
+ // ffs this HAS TO BE a string copy! See #5739 if you think otherwise
+ // Reason: updateTextures(m_previous_texture_modifier);
void updateTextures(std::string mod);
void updateAnimation();
@@ -235,8 +252,6 @@ public:
void updateBonePosition();
- void updateAttachments();
-
void processMessage(const std::string &data);
bool directReportPunch(v3f dir, const ItemStack *punchitem=NULL,
diff --git a/src/client/content_cso.cpp b/src/client/content_cso.cpp
index 04c503f44..f9641afbe 100644
--- a/src/client/content_cso.cpp
+++ b/src/client/content_cso.cpp
@@ -48,7 +48,7 @@ public:
/* Update brightness */
u8 light;
bool pos_ok;
- MapNode n = env->getMap().getNodeNoEx(floatToInt(pos, BS), &pos_ok);
+ MapNode n = env->getMap().getNode(floatToInt(pos, BS), &pos_ok);
light = pos_ok ? decode_light(n.getLightBlend(env->getDayNightRatio(),
env->getGameDef()->ndef()))
: 64;
diff --git a/src/client/fontengine.cpp b/src/client/fontengine.cpp
index 59e5bedee..858d6780e 100644
--- a/src/client/fontengine.cpp
+++ b/src/client/fontengine.cpp
@@ -23,9 +23,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "config.h"
#include "porting.h"
#include "filesys.h"
+#include "gettext.h"
#if USE_FREETYPE
-#include "gettext.h"
#include "irrlicht_changes/CGUITTFont.h"
#endif
@@ -55,36 +55,7 @@ FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) :
assert(m_env != NULL); // pre-condition
assert(m_env->getSkin() != NULL); // pre-condition
- m_currentMode = FM_Simple;
-
-#if USE_FREETYPE
- if (g_settings->getBool("freetype")) {
- m_default_size[FM_Standard] = m_settings->getU16("font_size");
- m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
- m_default_size[FM_Mono] = m_settings->getU16("mono_font_size");
-
- if (is_yes(gettext("needs_fallback_font"))) {
- m_currentMode = FM_Fallback;
- }
- else {
- m_currentMode = FM_Standard;
- }
- }
-
- // having freetype but not using it is quite a strange case so we need to do
- // special handling for it
- if (m_currentMode == FM_Simple) {
- std::stringstream fontsize;
- fontsize << DEFAULT_FONT_SIZE;
- m_settings->setDefault("font_size", fontsize.str());
- m_settings->setDefault("mono_font_size", fontsize.str());
- }
-#endif
-
- m_default_size[FM_Simple] = m_settings->getU16("font_size");
- m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size");
-
- updateSkin();
+ readSettings();
if (m_currentMode == FM_Standard) {
m_settings->registerChangedCallback("font_size", font_setting_changed, NULL);
@@ -129,32 +100,26 @@ irr::gui::IGUIFont* FontEngine::getFont(unsigned int font_size, FontMode mode)
{
if (mode == FM_Unspecified) {
mode = m_currentMode;
- }
- else if ((mode == FM_Mono) && (m_currentMode == FM_Simple)) {
- mode = FM_SimpleMono;
+ } else if (m_currentMode == FM_Simple) {
+ // Freetype disabled -> Force simple mode
+ mode = (mode == FM_Mono || mode == FM_SimpleMono) ?
+ FM_SimpleMono : FM_Simple;
}
- if (font_size == FONT_SIZE_UNSPECIFIED) {
+ // Fallback to default size
+ if (font_size == FONT_SIZE_UNSPECIFIED)
font_size = m_default_size[mode];
- }
-
- if ((font_size == m_lastSize) && (mode == m_lastMode)) {
- return m_lastFont;
- }
-
- if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
- initFont(font_size, mode);
- }
- if (m_font_cache[mode].find(font_size) == m_font_cache[mode].end()) {
- return NULL;
+ const auto &cache = m_font_cache[mode];
+ if (cache.find(font_size) == cache.end()) {
+ if (mode == FM_Simple || mode == FM_SimpleMono)
+ initSimpleFont(font_size, mode);
+ else
+ initFont(font_size, mode);
}
- m_lastSize = font_size;
- m_lastMode = mode;
- m_lastFont = m_font_cache[mode][font_size];
-
- return m_font_cache[mode][font_size];
+ const auto &font = cache.find(font_size);
+ return font != cache.end() ? font->second : nullptr;
}
/******************************************************************************/
@@ -211,20 +176,17 @@ unsigned int FontEngine::getDefaultFontSize()
/******************************************************************************/
void FontEngine::readSettings()
{
-#if USE_FREETYPE
- if (g_settings->getBool("freetype")) {
+ if (USE_FREETYPE && g_settings->getBool("freetype")) {
m_default_size[FM_Standard] = m_settings->getU16("font_size");
m_default_size[FM_Fallback] = m_settings->getU16("fallback_font_size");
m_default_size[FM_Mono] = m_settings->getU16("mono_font_size");
- if (is_yes(gettext("needs_fallback_font"))) {
- m_currentMode = FM_Fallback;
- }
- else {
- m_currentMode = FM_Standard;
- }
+ m_currentMode = is_yes(gettext("needs_fallback_font")) ?
+ FM_Fallback : FM_Standard;
+ } else {
+ m_currentMode = FM_Simple;
}
-#endif
+
m_default_size[FM_Simple] = m_settings->getU16("font_size");
m_default_size[FM_SimpleMono] = m_settings->getU16("mono_font_size");
@@ -260,81 +222,55 @@ void FontEngine::updateFontCache()
{
/* the only font to be initialized is default one,
* all others are re-initialized on demand */
- initFont(m_default_size[m_currentMode], m_currentMode);
-
- /* reset font quick access */
- m_lastMode = FM_Unspecified;
- m_lastSize = 0;
- m_lastFont = NULL;
+ getFont(FONT_SIZE_UNSPECIFIED, FM_Unspecified);
}
/******************************************************************************/
void FontEngine::initFont(unsigned int basesize, FontMode mode)
{
+ assert(mode != FM_Unspecified);
+ assert(basesize != FONT_SIZE_UNSPECIFIED);
- std::string font_config_prefix;
-
- if (mode == FM_Unspecified) {
- mode = m_currentMode;
- }
+ if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end())
+ return;
- switch (mode) {
- case FM_Standard:
- font_config_prefix = "";
- break;
+ std::string setting_prefix = "";
+ switch (mode) {
case FM_Fallback:
- font_config_prefix = "fallback_";
+ setting_prefix = "fallback_";
break;
-
case FM_Mono:
- font_config_prefix = "mono_";
- if (m_currentMode == FM_Simple)
- mode = FM_SimpleMono;
+ case FM_SimpleMono:
+ setting_prefix = "mono_";
break;
-
- case FM_Simple: /* Fallthrough */
- case FM_SimpleMono: /* Fallthrough */
default:
- font_config_prefix = "";
-
+ break;
}
- if (m_font_cache[mode].find(basesize) != m_font_cache[mode].end())
- return;
-
- if ((mode == FM_Simple) || (mode == FM_SimpleMono)) {
- initSimpleFont(basesize, mode);
- return;
+ u32 size = std::floor(RenderingEngine::getDisplayDensity() *
+ m_settings->getFloat("gui_scaling") * basesize);
+ if (size == 0) {
+ errorstream << "FontEngine: attempt to use font size 0" << std::endl;
+ errorstream << " display density: " << RenderingEngine::getDisplayDensity() << std::endl;
+ abort();
}
-#if USE_FREETYPE
- else {
- if (!is_yes(m_settings->get("freetype"))) {
- return;
- }
- u32 size = std::floor(RenderingEngine::getDisplayDensity() *
- m_settings->getFloat("gui_scaling") * basesize);
- if (size == 0) {
- errorstream << "FontEngine: attempt to use font size 0" << std::endl;
- errorstream << " display density: " << RenderingEngine::getDisplayDensity() << std::endl;
- abort();
- }
- u32 font_shadow = 0;
- u32 font_shadow_alpha = 0;
- try {
- font_shadow =
- g_settings->getU16(font_config_prefix + "font_shadow");
- } catch (SettingNotFoundException&) {}
- try {
- font_shadow_alpha =
- g_settings->getU16(font_config_prefix + "font_shadow_alpha");
- } catch (SettingNotFoundException&) {}
+ u16 font_shadow = 0;
+ u16 font_shadow_alpha = 0;
+ g_settings->getU16NoEx(setting_prefix + "font_shadow", font_shadow);
+ g_settings->getU16NoEx(setting_prefix + "font_shadow_alpha", font_shadow_alpha);
- std::string font_path = g_settings->get(font_config_prefix + "font_path");
+ std::string fallback_settings[] = {
+ m_settings->get(setting_prefix + "font_path"),
+ m_settings->get("fallback_font_path"),
+ m_settings->getDefault(setting_prefix + "font_path")
+ };
- irr::gui::IGUIFont* font = gui::CGUITTFont::createTTFont(m_env,
+#if USE_FREETYPE
+ for (const std::string &font_path : fallback_settings) {
+ irr::gui::IGUIFont *font = gui::CGUITTFont::createTTFont(m_env,
font_path.c_str(), size, true, true, font_shadow,
font_shadow_alpha);
@@ -343,93 +279,42 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode)
return;
}
- if (font_config_prefix == "mono_") {
- const std::string &mono_font_path = m_settings->getDefault("mono_font_path");
-
- if (font_path != mono_font_path) {
- // try original mono font
- errorstream << "FontEngine: failed to load custom mono "
- "font: " << font_path << ", trying to fall back to "
- "original mono font" << std::endl;
-
- font = gui::CGUITTFont::createTTFont(m_env,
- mono_font_path.c_str(), size, true, true,
- font_shadow, font_shadow_alpha);
-
- if (font) {
- m_font_cache[mode][basesize] = font;
- return;
- }
- }
- } else {
- // try fallback font
- errorstream << "FontEngine: failed to load: " << font_path <<
- ", trying to fall back to fallback font" << std::endl;
-
- font_path = g_settings->get(font_config_prefix + "fallback_font_path");
-
- font = gui::CGUITTFont::createTTFont(m_env,
- font_path.c_str(), size, true, true, font_shadow,
- font_shadow_alpha);
-
- if (font) {
- m_font_cache[mode][basesize] = font;
- return;
- }
-
- const std::string &fallback_font_path = m_settings->getDefault("fallback_font_path");
-
- if (font_path != fallback_font_path) {
- // try original fallback font
- errorstream << "FontEngine: failed to load custom fallback "
- "font: " << font_path << ", trying to fall back to "
- "original fallback font" << std::endl;
-
- font = gui::CGUITTFont::createTTFont(m_env,
- fallback_font_path.c_str(), size, true, true,
- font_shadow, font_shadow_alpha);
+ errorstream << "FontEngine: Cannot load '" << font_path <<
+ "'. Trying to fall back to another path." << std::endl;
+ }
- if (font) {
- m_font_cache[mode][basesize] = font;
- return;
- }
- }
- }
- // give up
- errorstream << "FontEngine: failed to load freetype font: "
- << font_path << std::endl;
- errorstream << "minetest can not continue without a valid font. "
- "Please correct the 'font_path' setting or install the font "
- "file in the proper location" << std::endl;
- abort();
- }
+ // give up
+ errorstream << "minetest can not continue without a valid font. "
+ "Please correct the 'font_path' setting or install the font "
+ "file in the proper location" << std::endl;
+#else
+ errorstream << "FontEngine: Tried to load freetype fonts but Minetest was"
+ " not compiled with that library." << std::endl;
#endif
+ abort();
}
/** initialize a font without freetype */
void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
{
- assert(mode == FM_Simple || mode == FM_SimpleMono); // pre-condition
+ assert(mode == FM_Simple || mode == FM_SimpleMono);
- std::string font_path;
- if (mode == FM_Simple) {
- font_path = m_settings->get("font_path");
- } else {
- font_path = m_settings->get("mono_font_path");
- }
+ const std::string &font_path = m_settings->get(
+ (mode == FM_SimpleMono) ? "mono_font_path" : "font_path");
+
+ size_t pos_dot = font_path.find_last_of('.');
std::string basename = font_path;
- std::string ending = font_path.substr(font_path.length() -4);
+ std::string ending = lowercase(font_path.substr(pos_dot));
if (ending == ".ttf") {
- errorstream << "FontEngine: Not trying to open \"" << font_path
- << "\" which seems to be a truetype font." << std::endl;
+ errorstream << "FontEngine: Found font \"" << font_path
+ << "\" but freetype is not available." << std::endl;
return;
}
- if ((ending == ".xml") || (ending == ".png")) {
- basename = font_path.substr(0,font_path.length()-4);
- }
+ if (ending == ".xml" || ending == ".png")
+ basename = font_path.substr(0, pos_dot);
if (basesize == FONT_SIZE_UNSPECIFIED)
basesize = DEFAULT_FONT_SIZE;
@@ -439,59 +324,35 @@ void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
m_settings->getFloat("gui_scaling") *
basesize);
- irr::gui::IGUIFont* font = NULL;
+ irr::gui::IGUIFont *font = nullptr;
+ std::string font_extensions[] = { ".png", ".xml" };
- for(unsigned int offset = 0; offset < MAX_FONT_SIZE_OFFSET; offset++) {
+ // Find nearest matching font scale
+ // Does a "zig-zag motion" (positibe/negative), from 0 to MAX_FONT_SIZE_OFFSET
+ for (s32 zoffset = 0; zoffset < MAX_FONT_SIZE_OFFSET * 2; zoffset++) {
+ std::stringstream path;
- // try opening positive offset
- std::stringstream fontsize_plus_png;
- fontsize_plus_png << basename << "_" << (size + offset) << ".png";
+ // LSB to sign
+ s32 sign = (zoffset & 1) ? -1 : 1;
+ s32 offset = zoffset >> 1;
- if (fs::PathExists(fontsize_plus_png.str())) {
- font = m_env->getFont(fontsize_plus_png.str().c_str());
+ for (const std::string &ext : font_extensions) {
+ path.str(""); // Clear
+ path << basename << "_" << (size + offset * sign) << ext;
- if (font) {
- verbosestream << "FontEngine: found font: " << fontsize_plus_png.str() << std::endl;
- break;
- }
- }
-
- std::stringstream fontsize_plus_xml;
- fontsize_plus_xml << basename << "_" << (size + offset) << ".xml";
+ if (!fs::PathExists(path.str()))
+ continue;
- if (fs::PathExists(fontsize_plus_xml.str())) {
- font = m_env->getFont(fontsize_plus_xml.str().c_str());
+ font = m_env->getFont(path.str().c_str());
if (font) {
- verbosestream << "FontEngine: found font: " << fontsize_plus_xml.str() << std::endl;
+ verbosestream << "FontEngine: found font: " << path.str() << std::endl;
break;
}
}
- // try negative offset
- std::stringstream fontsize_minus_png;
- fontsize_minus_png << basename << "_" << (size - offset) << ".png";
-
- if (fs::PathExists(fontsize_minus_png.str())) {
- font = m_env->getFont(fontsize_minus_png.str().c_str());
-
- if (font) {
- verbosestream << "FontEngine: found font: " << fontsize_minus_png.str() << std::endl;
- break;
- }
- }
-
- std::stringstream fontsize_minus_xml;
- fontsize_minus_xml << basename << "_" << (size - offset) << ".xml";
-
- if (fs::PathExists(fontsize_minus_xml.str())) {
- font = m_env->getFont(fontsize_minus_xml.str().c_str());
-
- if (font) {
- verbosestream << "FontEngine: found font: " << fontsize_minus_xml.str() << std::endl;
- break;
- }
- }
+ if (font)
+ break;
}
// try name direct
@@ -503,8 +364,6 @@ void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode)
}
}
- if (font) {
- font->grab();
+ if (font)
m_font_cache[mode][basesize] = font;
- }
}
diff --git a/src/client/fontengine.h b/src/client/fontengine.h
index a75618f86..62aa71897 100644
--- a/src/client/fontengine.h
+++ b/src/client/fontengine.h
@@ -112,15 +112,6 @@ private:
/** current font engine mode */
FontMode m_currentMode = FM_Standard;
- /** font mode of last request */
- FontMode m_lastMode;
-
- /** size of last request */
- unsigned int m_lastSize = 0;
-
- /** last font returned */
- irr::gui::IGUIFont* m_lastFont = nullptr;
-
DISABLE_CLASS_COPY(FontEngine);
};
diff --git a/src/client/game.cpp b/src/client/game.cpp
index 37680dda3..450eb4e32 100644
--- a/src/client/game.cpp
+++ b/src/client/game.cpp
@@ -55,7 +55,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "particles.h"
#include "porting.h"
#include "profiler.h"
-#include "quicktune_shortcutter.h"
#include "raycast.h"
#include "server.h"
#include "settings.h"
@@ -65,6 +64,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/basic_macros.h"
#include "util/directiontables.h"
#include "util/pointedthing.h"
+#include "util/quicktune_shortcutter.h"
#include "irrlicht_changes/static_text.h"
#include "version.h"
#include "script/scripting_client.h"
@@ -184,7 +184,7 @@ struct LocalFormspecHandler : public TextDest
return;
}
- if (m_client && m_client->moddingEnabled())
+ if (m_client && m_client->modsLoaded())
m_client->getScript()->on_formspec_input(m_formname, fields);
}
@@ -413,6 +413,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
CachedPixelShaderSetting<float, 3> m_eye_position_pixel;
CachedVertexShaderSetting<float, 3> m_eye_position_vertex;
CachedPixelShaderSetting<float, 3> m_minimap_yaw;
+ CachedPixelShaderSetting<float, 3> m_camera_offset_pixel;
+ CachedPixelShaderSetting<float, 3> m_camera_offset_vertex;
CachedPixelShaderSetting<SamplerLayer_t> m_base_texture;
CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture;
CachedPixelShaderSetting<SamplerLayer_t> m_texture_flags;
@@ -445,6 +447,8 @@ public:
m_eye_position_pixel("eyePosition"),
m_eye_position_vertex("eyePosition"),
m_minimap_yaw("yawVec"),
+ m_camera_offset_pixel("cameraOffset"),
+ m_camera_offset_vertex("cameraOffset"),
m_base_texture("baseTexture"),
m_normal_texture("normalTexture"),
m_texture_flags("textureFlags"),
@@ -493,7 +497,7 @@ public:
sunlight.b };
m_day_light.set(dnc, services);
- u32 animation_timer = porting::getTimeMs() % 100000;
+ u32 animation_timer = porting::getTimeMs() % 1000000;
float animation_timer_f = (float)animation_timer / 100000.f;
m_animation_timer_vertex.set(&animation_timer_f, services);
m_animation_timer_pixel.set(&animation_timer_f, services);
@@ -523,6 +527,18 @@ public:
m_minimap_yaw.set(minimap_yaw_array, services);
}
+ float camera_offset_array[3];
+ v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
+ camera_offset_array[0] = offset.X;
+ camera_offset_array[1] = offset.Y;
+ camera_offset_array[2] = offset.Z;
+#else
+ offset.getAs3Values(camera_offset_array);
+#endif
+ m_camera_offset_pixel.set(camera_offset_array, services);
+ m_camera_offset_vertex.set(camera_offset_array, services);
+
SamplerLayer_t base_tex = 0,
normal_tex = 1,
flags_tex = 2;
@@ -599,7 +615,6 @@ struct GameRunData {
bool dig_instantly;
bool digging_blocked;
bool left_punch;
- bool update_wielded_item_trigger;
bool reset_jump_timer;
float nodig_delay_timer;
float dig_time;
@@ -689,8 +704,8 @@ protected:
bool handleCallbacks();
void processQueues();
void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
- void addProfilerGraphs(const RunStats &stats, const FpsControl &draw_times, f32 dtime);
void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
+ void updateProfilerGraphs(ProfilerGraph *graph);
// Input related
void processUserInput(f32 dtime);
@@ -744,15 +759,13 @@ protected:
bool look_for_object, const v3s16 &camera_offset);
void handlePointingAtNothing(const ItemStack &playerItem);
void handlePointingAtNode(const PointedThing &pointed,
- const ItemDefinition &playeritem_def, const ItemStack &playeritem,
- const ToolCapabilities &playeritem_toolcap, f32 dtime);
+ const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
const v3f &player_position, bool show_debug);
void handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
- const ToolCapabilities &playeritem_toolcap, f32 dtime);
+ const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime);
void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
const CameraOrientation &cam);
- void updateProfilerGraphs(ProfilerGraph *graph);
// Misc
void limitFps(FpsControl *fps_timings, f32 *dtime);
@@ -804,8 +817,9 @@ private:
void updateChat(f32 dtime, const v2u32 &screensize);
- bool nodePlacementPrediction(const ItemDefinition &playeritem_def,
- const ItemStack &playeritem, const v3s16 &nodepos, const v3s16 &neighbourpos);
+ bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item,
+ const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed,
+ const NodeMetadata *meta);
static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
InputHandler *input = nullptr;
@@ -827,10 +841,6 @@ private:
ChatBackend *chat_backend = nullptr;
- GUIFormSpecMenu *current_formspec = nullptr;
- //default: "". If other than "", empty show_formspec packets will only close the formspec when the formname matches
- std::string cur_formname;
-
EventManager *eventmgr = nullptr;
QuicktuneShortcutter *quicktune = nullptr;
bool registration_confirmation_shown = false;
@@ -841,7 +851,6 @@ private:
Camera *camera = nullptr;
Clouds *clouds = nullptr; // Free using ->Drop()
Sky *sky = nullptr; // Free using ->Drop()
- Inventory *local_inventory = nullptr;
Hud *hud = nullptr;
Minimap *mapper = nullptr;
@@ -955,7 +964,6 @@ Game::~Game()
delete server; // deleted first to stop all server threads
delete hud;
- delete local_inventory;
delete camera;
delete quicktune;
delete eventmgr;
@@ -1026,7 +1034,6 @@ bool Game::startup(bool *kill,
// Reinit runData
runData = GameRunData();
runData.time_from_last_punch = 10.0;
- runData.update_wielded_item_trigger = true;
m_game_ui->initFlags();
@@ -1089,11 +1096,13 @@ void Game::run()
previous_screen_size = current_screen_size;
}
- /* Must be called immediately after a device->run() call because it
- * uses device->getTimer()->getTime()
- */
+ // Calculate dtime =
+ // RenderingEngine::run() from this iteration
+ // + Sleep time until the wanted FPS are reached
limitFps(&draw_times, &dtime);
+ // Prepare render data for next iteration
+
updateStats(&stats, draw_times, dtime);
updateInteractTimers(dtime);
@@ -1143,8 +1152,9 @@ void Game::shutdown()
driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS);
}
#endif
- if (current_formspec)
- current_formspec->quitMenu();
+ auto formspec = m_game_ui->getFormspecGUI();
+ if (formspec)
+ formspec->quitMenu();
showOverlayMessage(N_("Shutting down..."), 0, 0, false);
@@ -1163,10 +1173,7 @@ void Game::shutdown()
g_menumgr.deletingMenu(g_menumgr.m_stack.front());
}
- if (current_formspec) {
- current_formspec->drop();
- current_formspec = NULL;
- }
+ m_game_ui->deleteFormspec();
chat_backend->addMessage(L"", L"# Disconnected.");
chat_backend->addMessage(L"", L"");
@@ -1355,10 +1362,8 @@ bool Game::createClient(const std::string &playername,
scsf->setSky(sky);
skybox = NULL; // This is used/set later on in the main run loop
- local_inventory = new Inventory(itemdef_manager);
-
- if (!(sky && local_inventory)) {
- *error_message = "Memory allocation error (sky or local inventory)";
+ if (!sky) {
+ *error_message = "Memory allocation error sky";
errorstream << *error_message << std::endl;
return false;
}
@@ -1390,7 +1395,7 @@ bool Game::createClient(const std::string &playername,
player->hurt_tilt_timer = 0;
player->hurt_tilt_strength = 0;
- hud = new Hud(guienv, client, player, local_inventory);
+ hud = new Hud(guienv, client, player, &player->inventory);
if (!hud) {
*error_message = "Memory error: could not create HUD";
@@ -1545,7 +1550,7 @@ bool Game::connectToServer(const std::string &playername,
} else {
registration_confirmation_shown = true;
(new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
- &g_menumgr, client, playername, password, *address, connection_aborted))->drop();
+ &g_menumgr, client, playername, password, connection_aborted))->drop();
}
} else {
wait_time += dtime;
@@ -1733,7 +1738,8 @@ void Game::processQueues()
}
-void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime)
+void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
+ f32 dtime)
{
float profiler_print_interval =
g_settings->getFloat("profiler_print_interval");
@@ -1741,7 +1747,7 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
if (profiler_print_interval == 0) {
print_to_log = false;
- profiler_print_interval = 5;
+ profiler_print_interval = 3;
}
if (profiler_interval.step(dtime, profiler_print_interval)) {
@@ -1754,25 +1760,14 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
g_profiler->clear();
}
- addProfilerGraphs(stats, draw_times, dtime);
-}
-
-
-void Game::addProfilerGraphs(const RunStats &stats,
- const FpsControl &draw_times, f32 dtime)
-{
- g_profiler->graphAdd("mainloop_other",
- draw_times.busy_time / 1000.0f - stats.drawtime / 1000.0f);
-
- if (draw_times.sleep_time != 0)
- g_profiler->graphAdd("mainloop_sleep", draw_times.sleep_time / 1000.0f);
- g_profiler->graphAdd("mainloop_dtime", dtime);
+ // Update update graphs
+ g_profiler->graphAdd("Time non-rendering [ms]",
+ draw_times.busy_time - stats.drawtime);
- g_profiler->add("Elapsed time", dtime);
- g_profiler->avg("FPS", 1. / dtime);
+ g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time);
+ g_profiler->graphAdd("FPS", 1.0f / dtime);
}
-
void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
f32 dtime)
{
@@ -1853,8 +1848,9 @@ void Game::processUserInput(f32 dtime)
input->step(dtime);
#ifdef __ANDROID__
- if (current_formspec != NULL)
- current_formspec->getAndroidUIInput();
+ auto formspec = m_game_ui->getFormspecGUI();
+ if (formspec)
+ formspec->getAndroidUIInput();
else
handleAndroidChatInput();
#endif
@@ -1880,6 +1876,9 @@ void Game::processKeyInput()
} else if (wasKeyDown(KeyType::INVENTORY)) {
openInventory();
} else if (input->cancelPressed()) {
+#ifdef __ANDROID__
+ m_android_chat_open = false;
+#endif
if (!gui_chat_console->isOpenInhibited()) {
showPauseMenu();
}
@@ -1888,7 +1887,7 @@ void Game::processKeyInput()
} else if (wasKeyDown(KeyType::CMD)) {
openConsole(0.2, L"/");
} else if (wasKeyDown(KeyType::CMD_LOCAL)) {
- if (client->moddingEnabled())
+ if (client->modsLoaded())
openConsole(0.2, L".");
else
m_game_ui->showStatusText(wgettext("Client side scripting is disabled"));
@@ -1979,7 +1978,7 @@ void Game::processItemSelection(u16 *new_playeritem)
/* Item selection using mouse wheel
*/
- *new_playeritem = client->getPlayerItem();
+ *new_playeritem = player->getWieldIndex();
s32 wheel = input->getMouseWheel();
u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
@@ -2021,7 +2020,7 @@ void Game::dropSelectedItem(bool single_item)
a->count = single_item ? 1 : 0;
a->from_inv.setCurrentPlayer();
a->from_list = "main";
- a->from_i = client->getPlayerItem();
+ a->from_i = client->getEnv().getLocalPlayer()->getWieldIndex();
client->inventoryAction(a);
}
@@ -2044,13 +2043,14 @@ void Game::openInventory()
InventoryLocation inventoryloc;
inventoryloc.setCurrentPlayer();
- if (!client->moddingEnabled()
+ if (!client->modsLoaded()
|| !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) {
TextDest *txt_dst = new TextDestPlayerInventory(client);
- GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src,
+ auto *&formspec = m_game_ui->updateFormspec("");
+ GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
txt_dst, client->getFormspecPrepend());
- cur_formname = "";
- current_formspec->setFormSpec(fs_src->getForm(), inventoryloc);
+
+ formspec->setFormSpec(fs_src->getForm(), inventoryloc);
}
}
@@ -2079,6 +2079,7 @@ void Game::handleAndroidChatInput()
if (m_android_chat_open && porting::getInputDialogState() == 0) {
std::string text = porting::getInputDialogValue();
client->typeChatMessage(utf8_to_wide(text));
+ m_android_chat_open = false;
}
}
#endif
@@ -2348,7 +2349,7 @@ void Game::toggleFullViewRange()
void Game::checkZoomEnabled()
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
- if (player->getZoomFOV() < 0.001f)
+ if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
}
@@ -2479,6 +2480,13 @@ void Game::updatePlayerControl(const CameraOrientation &cam)
keypress_bits |= 1U << 4;
}
+ // autoforward if set: simulate "up" key
+ if (player->getPlayerSettings().continuous_forward &&
+ client->activeObjectsReceived() && !player->isDead()) {
+ control.up = true;
+ keypress_bits |= 1U << 0;
+ }
+
client->setPlayerControl(control);
player->keyPressed = keypress_bits;
@@ -2491,7 +2499,7 @@ inline void Game::step(f32 *dtime)
bool can_be_and_is_paused =
(simple_singleplayer_mode && g_menumgr.pausesGame());
- if (can_be_and_is_paused) { // This is for a singleplayer server
+ if (can_be_and_is_paused) { // This is for a singleplayer server
*dtime = 0; // No time passes
} else {
if (server)
@@ -2526,9 +2534,8 @@ void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam)
void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam)
{
- if (client->moddingEnabled()) {
+ if (client->modsLoaded())
client->getScript()->on_damage_taken(event->player_damage.amount);
- }
// Damage flash and hurt tilt are not used at death
if (client->getHP() > 0) {
@@ -2556,7 +2563,7 @@ void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *
{
// If client scripting is enabled, deathscreen is handled by CSM code in
// builtin/client/init.lua
- if (client->moddingEnabled())
+ if (client->modsLoaded())
client->getScript()->on_death();
else
showDeathFormspec();
@@ -2571,9 +2578,10 @@ void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *
void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
{
if (event->show_formspec.formspec->empty()) {
- if (current_formspec && (event->show_formspec.formname->empty()
- || *(event->show_formspec.formname) == cur_formname)) {
- current_formspec->quitMenu();
+ auto formspec = m_game_ui->getFormspecGUI();
+ if (formspec && (event->show_formspec.formname->empty()
+ || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) {
+ formspec->quitMenu();
}
} else {
FormspecFormSource *fs_src =
@@ -2581,9 +2589,9 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation
TextDestPlayerInventory *txt_dst =
new TextDestPlayerInventory(client, *(event->show_formspec.formname));
- GUIFormSpecMenu::create(current_formspec, client, &input->joystick,
+ auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname));
+ GUIFormSpecMenu::create(formspec, client, &input->joystick,
fs_src, txt_dst, client->getFormspecPrepend());
- cur_formname = *(event->show_formspec.formname);
}
delete event->show_formspec.formspec;
@@ -2595,7 +2603,7 @@ void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrienta
FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec);
LocalFormspecHandler *txt_dst =
new LocalFormspecHandler(*event->show_formspec.formname, client);
- GUIFormSpecMenu::create(current_formspec, client, &input->joystick,
+ GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, &input->joystick,
fs_src, txt_dst, client->getFormspecPrepend());
delete event->show_formspec.formspec;
@@ -2840,19 +2848,10 @@ void Game::updateCamera(u32 busy_time, f32 dtime)
*/
ItemStack playeritem;
{
- InventoryList *mlist = local_inventory->getList("main");
-
- if (mlist && client->getPlayerItem() < mlist->getSize())
- playeritem = mlist->getItem(client->getPlayerItem());
- }
-
- if (playeritem.getDefinition(itemdef_manager).name.empty()) { // override the hand
- InventoryList *hlist = local_inventory->getList("hand");
- if (hlist)
- playeritem = hlist->getItem(0);
+ ItemStack selected, hand;
+ playeritem = player->getWieldedItem(&selected, &hand);
}
-
ToolCapabilities playeritem_toolcap =
playeritem.getToolCapabilities(itemdef_manager);
@@ -2933,7 +2932,7 @@ void Game::updateSound(f32 dtime)
soundmaker->step(dtime);
ClientMap &map = client->getEnv().getClientMap();
- MapNode n = map.getNodeNoEx(player->getFootstepNodePos());
+ MapNode n = map.getNode(player->getFootstepNodePos());
soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
}
@@ -2942,46 +2941,35 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
- ItemStack playeritem;
- {
- InventoryList *mlist = local_inventory->getList("main");
-
- if (mlist && client->getPlayerItem() < mlist->getSize())
- playeritem = mlist->getItem(client->getPlayerItem());
- }
-
- const ItemDefinition &playeritem_def =
- playeritem.getDefinition(itemdef_manager);
- InventoryList *hlist = local_inventory->getList("hand");
- const ItemDefinition &hand_def =
- hlist ? hlist->getItem(0).getDefinition(itemdef_manager) : itemdef_manager->get("");
-
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;
/*
Calculate what block is the crosshair pointing to
*/
- f32 d = playeritem_def.range; // max. distance
- f32 d_hand = hand_def.range;
+ ItemStack selected_item, hand_item;
+ const ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
- if (d < 0 && d_hand >= 0)
- d = d_hand;
- else if (d < 0)
- d = 4.0;
+ const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager);
+ f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager));
core::line3d<f32> shootline;
if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) {
- shootline = core::line3d<f32>(camera_position,
- camera_position + camera_direction * BS * d);
+ shootline = core::line3d<f32>(player_eye_position,
+ player_eye_position + camera_direction * BS * d);
} else {
- // prevent player pointing anything in front-view
- shootline = core::line3d<f32>(camera_position,camera_position);
+ // prevent player pointing anything in front-view
+ shootline = core::line3d<f32>(camera_position, camera_position);
}
#ifdef HAVE_TOUCHSCREENGUI
@@ -2998,7 +2986,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
#endif
PointedThing pointed = updatePointedThing(shootline,
- playeritem_def.liquids_pointable,
+ selected_def.liquids_pointable,
!runData.ldown_for_dig,
camera_offset);
@@ -3020,7 +3008,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
if (runData.digging) {
if (input->getLeftReleased()) {
infostream << "Left button released"
- << " (stopped digging)" << std::endl;
+ << " (stopped digging)" << std::endl;
runData.digging = false;
} else if (pointed != runData.pointed_old) {
if (pointed.type == POINTEDTHING_NODE
@@ -3031,14 +3019,14 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
// Don't reset.
} else {
infostream << "Pointing away from node"
- << " (stopped digging)" << std::endl;
+ << " (stopped digging)" << std::endl;
runData.digging = false;
hud->updateSelectionMesh(camera_offset);
}
}
if (!runData.digging) {
- client->interact(1, runData.pointed_old);
+ client->interact(INTERACT_STOP_DIGGING, runData.pointed_old);
client->setCrack(-1, v3s16(0, 0, 0));
runData.dig_time = 0.0;
}
@@ -3062,30 +3050,19 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug)
else
runData.repeat_rightclick_timer = 0;
- if (playeritem_def.usable && input->getLeftState()) {
- if (input->getLeftClicked() && (!client->moddingEnabled()
- || !client->getScript()->on_item_use(playeritem, pointed)))
- client->interact(4, pointed);
+ if (selected_def.usable && input->getLeftState()) {
+ if (input->getLeftClicked() && (!client->modsLoaded()
+ || !client->getScript()->on_item_use(selected_item, pointed)))
+ client->interact(INTERACT_USE, pointed);
} else if (pointed.type == POINTEDTHING_NODE) {
- ToolCapabilities playeritem_toolcap =
- playeritem.getToolCapabilities(itemdef_manager);
- if (playeritem.name.empty()) {
- const ToolCapabilities *handToolcap = hlist
- ? &hlist->getItem(0).getToolCapabilities(itemdef_manager)
- : itemdef_manager->get("").tool_capabilities;
-
- if (handToolcap != nullptr)
- playeritem_toolcap = *handToolcap;
- }
- handlePointingAtNode(pointed, playeritem_def, playeritem,
- playeritem_toolcap, dtime);
+ handlePointingAtNode(pointed, selected_item, hand_item, dtime);
} else if (pointed.type == POINTEDTHING_OBJECT) {
- handlePointingAtObject(pointed, playeritem, player_position, show_debug);
+ handlePointingAtObject(pointed, tool_item, player_position, show_debug);
} else if (input->getLeftState()) {
// When button is held down in air, show continuous animation
runData.left_punch = true;
} else if (input->getRightClicked()) {
- handlePointingAtNothing(playeritem);
+ handlePointingAtNothing(selected_item);
}
runData.pointed_old = pointed;
@@ -3133,7 +3110,7 @@ PointedThing Game::updatePointedThing(
}
} else if (result.type == POINTEDTHING_NODE) {
// Update selection boxes
- MapNode n = map.getNodeNoEx(result.node_undersurface);
+ MapNode n = map.getNode(result.node_undersurface);
std::vector<aabb3f> boxes;
n.getSelectionBoxes(nodedef, &boxes,
n.getNeighbors(result.node_undersurface, &map));
@@ -3160,12 +3137,12 @@ PointedThing Game::updatePointedThing(
v3s16 p = floatToInt(pf, BS);
// Get selection mesh light level
- MapNode n = map.getNodeNoEx(p);
+ MapNode n = map.getNode(p);
u16 node_light = getInteriorLight(n, -1, nodedef);
u16 light_level = node_light;
for (const v3s16 &dir : g_6dirs) {
- n = map.getNodeNoEx(p + dir);
+ n = map.getNode(p + dir);
node_light = getInteriorLight(n, -1, nodedef);
if (node_light > light_level)
light_level = node_light;
@@ -3197,13 +3174,12 @@ void Game::handlePointingAtNothing(const ItemStack &playerItem)
infostream << "Right Clicked in Air" << std::endl;
PointedThing fauxPointed;
fauxPointed.type = POINTEDTHING_NOTHING;
- client->interact(5, fauxPointed);
+ client->interact(INTERACT_ACTIVATE, fauxPointed);
}
void Game::handlePointingAtNode(const PointedThing &pointed,
- const ItemDefinition &playeritem_def, const ItemStack &playeritem,
- const ToolCapabilities &playeritem_toolcap, f32 dtime)
+ const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
{
v3s16 nodepos = pointed.node_undersurface;
v3s16 neighbourpos = pointed.node_abovesurface;
@@ -3217,7 +3193,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
if (runData.nodig_delay_timer <= 0.0 && input->getLeftState()
&& !runData.digging_blocked
&& client->checkPrivilege("interact")) {
- handleDigging(pointed, nodepos, playeritem_toolcap, dtime);
+ handleDigging(pointed, nodepos, selected_item, hand_item, dtime);
}
// This should be done after digging handling
@@ -3227,7 +3203,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
m_game_ui->setInfoText(unescape_translate(utf8_to_wide(
meta->getString("infotext"))));
} else {
- MapNode n = map.getNodeNoEx(nodepos);
+ MapNode n = map.getNode(nodepos);
if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
m_game_ui->setInfoText(L"Unknown node: " +
@@ -3241,213 +3217,219 @@ void Game::handlePointingAtNode(const PointedThing &pointed,
runData.repeat_rightclick_timer = 0;
infostream << "Ground right-clicked" << std::endl;
- if (meta && !meta->getString("formspec").empty() && !random_input
- && !isKeyDown(KeyType::SNEAK)) {
- // Report right click to server
- if (nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) {
- client->interact(3, pointed);
- }
-
- infostream << "Launching custom inventory view" << std::endl;
-
- InventoryLocation inventoryloc;
- inventoryloc.setNodeMeta(nodepos);
+ camera->setDigging(1); // right click animation (always shown for feedback)
- NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
- &client->getEnv().getClientMap(), nodepos);
- TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
+ soundmaker->m_player_rightpunch_sound = SimpleSoundSpec();
- GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src,
- txt_dst, client->getFormspecPrepend());
- cur_formname.clear();
+ // If the wielded item has node placement prediction,
+ // make that happen
+ // And also set the sound and send the interact
+ // But first check for meta formspec and rightclickable
+ auto &def = selected_item.getDefinition(itemdef_manager);
+ bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos,
+ pointed, meta);
- current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
- } else {
- // Report right click to server
-
- camera->setDigging(1); // right click animation (always shown for feedback)
-
- // If the wielded item has node placement prediction,
- // make that happen
- bool placed = nodePlacementPrediction(playeritem_def, playeritem, nodepos,
- neighbourpos);
-
- if (placed) {
- // Report to server
- client->interact(3, pointed);
- // Read the sound
- soundmaker->m_player_rightpunch_sound =
- playeritem_def.sound_place;
-
- if (client->moddingEnabled())
- client->getScript()->on_placenode(pointed, playeritem_def);
- } else {
- soundmaker->m_player_rightpunch_sound =
- SimpleSoundSpec();
-
- if (playeritem_def.node_placement_prediction.empty() ||
- nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable) {
- client->interact(3, pointed); // Report to server
- } else {
- soundmaker->m_player_rightpunch_sound =
- playeritem_def.sound_place_failed;
- }
- }
- }
+ if (placed && client->modsLoaded())
+ client->getScript()->on_placenode(pointed, def);
}
}
-bool Game::nodePlacementPrediction(const ItemDefinition &playeritem_def,
- const ItemStack &playeritem, const v3s16 &nodepos, const v3s16 &neighbourpos)
+bool Game::nodePlacement(const ItemDefinition &selected_def,
+ const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
+ const PointedThing &pointed, const NodeMetadata *meta)
{
- std::string prediction = playeritem_def.node_placement_prediction;
+ std::string prediction = selected_def.node_placement_prediction;
const NodeDefManager *nodedef = client->ndef();
ClientMap &map = client->getEnv().getClientMap();
MapNode node;
bool is_valid_position;
- node = map.getNodeNoEx(nodepos, &is_valid_position);
- if (!is_valid_position)
+ node = map.getNode(nodepos, &is_valid_position);
+ if (!is_valid_position) {
+ soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
+ return false;
+ }
+
+ // formspec in meta
+ if (meta && !meta->getString("formspec").empty() && !random_input
+ && !isKeyDown(KeyType::SNEAK)) {
+ // on_rightclick callbacks are called anyway
+ if (nodedef_manager->get(map.getNode(nodepos)).rightclickable)
+ client->interact(INTERACT_PLACE, pointed);
+
+ infostream << "Launching custom inventory view" << std::endl;
+
+ InventoryLocation inventoryloc;
+ inventoryloc.setNodeMeta(nodepos);
+
+ NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
+ &client->getEnv().getClientMap(), nodepos);
+ TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
+
+ auto *&formspec = m_game_ui->updateFormspec("");
+ GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src,
+ txt_dst, client->getFormspecPrepend());
+
+ formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
return false;
+ }
+
+ // on_rightclick callback
+ if (prediction.empty() || (nodedef->get(node).rightclickable &&
+ !isKeyDown(KeyType::SNEAK))) {
+ // Report to server
+ client->interact(INTERACT_PLACE, pointed);
+ return false;
+ }
- if (!prediction.empty() && !nodedef->get(node).rightclickable) {
- verbosestream << "Node placement prediction for "
- << playeritem_def.name << " is "
- << prediction << std::endl;
- v3s16 p = neighbourpos;
-
- // Place inside node itself if buildable_to
- MapNode n_under = map.getNodeNoEx(nodepos, &is_valid_position);
- if (is_valid_position)
- {
- if (nodedef->get(n_under).buildable_to)
- p = nodepos;
- else {
- node = map.getNodeNoEx(p, &is_valid_position);
- if (is_valid_position &&!nodedef->get(node).buildable_to)
- return false;
+ verbosestream << "Node placement prediction for "
+ << selected_def.name << " is "
+ << prediction << std::endl;
+ v3s16 p = neighbourpos;
+
+ // Place inside node itself if buildable_to
+ MapNode n_under = map.getNode(nodepos, &is_valid_position);
+ if (is_valid_position) {
+ if (nodedef->get(n_under).buildable_to) {
+ p = nodepos;
+ } else {
+ node = map.getNode(p, &is_valid_position);
+ if (is_valid_position && !nodedef->get(node).buildable_to) {
+ // Report to server
+ client->interact(INTERACT_PLACE, pointed);
+ return false;
}
}
+ }
- // Find id of predicted node
- content_t id;
- bool found = nodedef->getId(prediction, id);
+ // Find id of predicted node
+ content_t id;
+ bool found = nodedef->getId(prediction, id);
- if (!found) {
- errorstream << "Node placement prediction failed for "
- << playeritem_def.name << " (places "
- << prediction
- << ") - Name not known" << std::endl;
- return false;
- }
+ if (!found) {
+ errorstream << "Node placement prediction failed for "
+ << selected_def.name << " (places "
+ << prediction
+ << ") - Name not known" << std::endl;
+ // Handle this as if prediction was empty
+ // Report to server
+ client->interact(INTERACT_PLACE, pointed);
+ return false;
+ }
- const ContentFeatures &predicted_f = nodedef->get(id);
+ const ContentFeatures &predicted_f = nodedef->get(id);
- // Predict param2 for facedir and wallmounted nodes
- u8 param2 = 0;
+ // Predict param2 for facedir and wallmounted nodes
+ u8 param2 = 0;
- if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
+ if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
- v3s16 dir = nodepos - neighbourpos;
+ v3s16 dir = nodepos - neighbourpos;
- if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
- param2 = dir.Y < 0 ? 1 : 0;
- } else if (abs(dir.X) > abs(dir.Z)) {
- param2 = dir.X < 0 ? 3 : 2;
- } else {
- param2 = dir.Z < 0 ? 5 : 4;
- }
+ if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
+ param2 = dir.Y < 0 ? 1 : 0;
+ } else if (abs(dir.X) > abs(dir.Z)) {
+ param2 = dir.X < 0 ? 3 : 2;
+ } else {
+ param2 = dir.Z < 0 ? 5 : 4;
}
+ }
- if (predicted_f.param_type_2 == CPT2_FACEDIR ||
+ if (predicted_f.param_type_2 == CPT2_FACEDIR ||
predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
- v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
+ v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
- if (abs(dir.X) > abs(dir.Z)) {
- param2 = dir.X < 0 ? 3 : 1;
- } else {
- param2 = dir.Z < 0 ? 2 : 0;
- }
+ if (abs(dir.X) > abs(dir.Z)) {
+ param2 = dir.X < 0 ? 3 : 1;
+ } else {
+ param2 = dir.Z < 0 ? 2 : 0;
}
+ }
+
+ assert(param2 <= 5);
- assert(param2 <= 5);
-
- //Check attachment if node is in group attached_node
- if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
- static v3s16 wallmounted_dirs[8] = {
- v3s16(0, 1, 0),
- v3s16(0, -1, 0),
- v3s16(1, 0, 0),
- v3s16(-1, 0, 0),
- v3s16(0, 0, 1),
- v3s16(0, 0, -1),
- };
- v3s16 pp;
-
- if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
+ //Check attachment if node is in group attached_node
+ if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
+ static v3s16 wallmounted_dirs[8] = {
+ v3s16(0, 1, 0),
+ v3s16(0, -1, 0),
+ v3s16(1, 0, 0),
+ v3s16(-1, 0, 0),
+ v3s16(0, 0, 1),
+ v3s16(0, 0, -1),
+ };
+ v3s16 pp;
+
+ if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)
- pp = p + wallmounted_dirs[param2];
- else
- pp = p + v3s16(0, -1, 0);
+ pp = p + wallmounted_dirs[param2];
+ else
+ pp = p + v3s16(0, -1, 0);
- if (!nodedef->get(map.getNodeNoEx(pp)).walkable)
- return false;
+ if (!nodedef->get(map.getNode(pp)).walkable) {
+ // Report to server
+ client->interact(INTERACT_PLACE, pointed);
+ return false;
}
+ }
- // Apply color
- if ((predicted_f.param_type_2 == CPT2_COLOR
+ // Apply color
+ if ((predicted_f.param_type_2 == CPT2_COLOR
|| predicted_f.param_type_2 == CPT2_COLORED_FACEDIR
|| predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) {
- const std::string &indexstr = playeritem.metadata.getString(
- "palette_index", 0);
- if (!indexstr.empty()) {
- s32 index = mystoi(indexstr);
- if (predicted_f.param_type_2 == CPT2_COLOR) {
- param2 = index;
- } else if (predicted_f.param_type_2
- == CPT2_COLORED_WALLMOUNTED) {
- // param2 = pure palette index + other
- param2 = (index & 0xf8) | (param2 & 0x07);
- } else if (predicted_f.param_type_2
- == CPT2_COLORED_FACEDIR) {
- // param2 = pure palette index + other
- param2 = (index & 0xe0) | (param2 & 0x1f);
- }
+ const std::string &indexstr = selected_item.metadata.getString(
+ "palette_index", 0);
+ if (!indexstr.empty()) {
+ s32 index = mystoi(indexstr);
+ if (predicted_f.param_type_2 == CPT2_COLOR) {
+ param2 = index;
+ } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
+ // param2 = pure palette index + other
+ param2 = (index & 0xf8) | (param2 & 0x07);
+ } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
+ // param2 = pure palette index + other
+ param2 = (index & 0xe0) | (param2 & 0x1f);
}
}
+ }
- // Add node to client map
- MapNode n(id, 0, param2);
+ // Add node to client map
+ MapNode n(id, 0, param2);
- try {
- LocalPlayer *player = client->getEnv().getLocalPlayer();
+ try {
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
- // Dont place node when player would be inside new node
- // NOTE: This is to be eventually implemented by a mod as client-side Lua
- if (!nodedef->get(n).walkable ||
+ // Dont place node when player would be inside new node
+ // NOTE: This is to be eventually implemented by a mod as client-side Lua
+ if (!nodedef->get(n).walkable ||
g_settings->getBool("enable_build_where_you_stand") ||
(client->checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
(nodedef->get(n).walkable &&
neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
-
- // This triggers the required mesh update too
- client->addNode(p, n);
- return true;
- }
- } catch (InvalidPositionException &e) {
- errorstream << "Node placement prediction failed for "
- << playeritem_def.name << " (places "
- << prediction
- << ") - Position not loaded" << std::endl;
+ // This triggers the required mesh update too
+ client->addNode(p, n);
+ // Report to server
+ client->interact(INTERACT_PLACE, pointed);
+ // A node is predicted, also play a sound
+ soundmaker->m_player_rightpunch_sound = selected_def.sound_place;
+ return true;
+ } else {
+ soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
+ return false;
}
+ } catch (InvalidPositionException &e) {
+ errorstream << "Node placement prediction failed for "
+ << selected_def.name << " (places "
+ << prediction
+ << ") - Position not loaded" << std::endl;
+ soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
+ return false;
}
-
- return false;
}
-void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem,
- const v3f &player_position, bool show_debug)
+void Game::handlePointingAtObject(const PointedThing &pointed,
+ const ItemStack &tool_item, const v3f &player_position, bool show_debug)
{
std::wstring infotext = unescape_translate(
utf8_to_wide(runData.selected_object->infoText()));
@@ -3483,50 +3465,39 @@ void Game::handlePointingAtObject(const PointedThing &pointed, const ItemStack &
// Report direct punch
v3f objpos = runData.selected_object->getPosition();
v3f dir = (objpos - player_position).normalize();
- ItemStack item = playeritem;
- if (playeritem.name.empty()) {
- InventoryList *hlist = local_inventory->getList("hand");
- if (hlist) {
- item = hlist->getItem(0);
- }
- }
bool disable_send = runData.selected_object->directReportPunch(
- dir, &item, runData.time_from_last_punch);
+ dir, &tool_item, runData.time_from_last_punch);
runData.time_from_last_punch = 0;
if (!disable_send)
- client->interact(0, pointed);
+ client->interact(INTERACT_START_DIGGING, pointed);
}
} else if (input->getRightClicked()) {
infostream << "Right-clicked object" << std::endl;
- client->interact(3, pointed); // place
+ client->interact(INTERACT_PLACE, pointed); // place
}
}
void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
- const ToolCapabilities &playeritem_toolcap, f32 dtime)
+ const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime)
{
+ // See also: serverpackethandle.cpp, action == 2
LocalPlayer *player = client->getEnv().getLocalPlayer();
ClientMap &map = client->getEnv().getClientMap();
- MapNode n = client->getEnv().getClientMap().getNodeNoEx(nodepos);
+ MapNode n = client->getEnv().getClientMap().getNode(nodepos);
// NOTE: Similar piece of code exists on the server side for
// cheat detection.
// Get digging parameters
DigParams params = getDigParams(nodedef_manager->get(n).groups,
- &playeritem_toolcap);
+ &selected_item.getToolCapabilities(itemdef_manager));
// If can't dig, try hand
if (!params.diggable) {
- InventoryList *hlist = local_inventory->getList("hand");
- const ToolCapabilities *tp = hlist
- ? &hlist->getItem(0).getToolCapabilities(itemdef_manager)
- : itemdef_manager->get("").tool_capabilities;
-
- if (tp)
- params = getDigParams(nodedef_manager->get(n).groups, tp);
+ params = getDigParams(nodedef_manager->get(n).groups,
+ &hand_item.getToolCapabilities(itemdef_manager));
}
if (!params.diggable) {
@@ -3545,9 +3516,9 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
if (!runData.digging) {
infostream << "Started digging" << std::endl;
runData.dig_instantly = runData.dig_time_complete == 0;
- if (client->moddingEnabled() && client->getScript()->on_punchnode(nodepos, n))
+ if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n))
return;
- client->interact(0, pointed);
+ client->interact(INTERACT_START_DIGGING, pointed);
runData.digging = true;
runData.ldown_for_dig = true;
}
@@ -3603,10 +3574,10 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
runData.nodig_delay_timer = 0.15;
bool is_valid_position;
- MapNode wasnode = map.getNodeNoEx(nodepos, &is_valid_position);
+ MapNode wasnode = map.getNode(nodepos, &is_valid_position);
if (is_valid_position) {
- if (client->moddingEnabled() &&
- client->getScript()->on_dignode(nodepos, wasnode)) {
+ if (client->modsLoaded() &&
+ client->getScript()->on_dignode(nodepos, wasnode)) {
return;
}
@@ -3622,7 +3593,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
// implicit else: no prediction
}
- client->interact(2, pointed);
+ client->interact(INTERACT_DIGGING_COMPLETED, pointed);
if (m_cache_enable_particles) {
const ContentFeatures &features =
@@ -3650,6 +3621,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
const CameraOrientation &cam)
{
+ TimeTaker tt_update("Game::updateFrame()");
LocalPlayer *player = client->getEnv().getLocalPlayer();
/*
@@ -3674,7 +3646,6 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
direct_brightness = time_brightness;
sunlight_seen = true;
} else {
- ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG);
float old_brightness = sky->getBrightness();
direct_brightness = client->getEnv().getClientMap()
.getBackgroundBrightness(MYMIN(runData.fog_range * 1.2, 60 * BS),
@@ -3780,31 +3751,14 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
Inventory
*/
- if (client->getPlayerItem() != runData.new_playeritem)
- client->selectPlayerItem(runData.new_playeritem);
+ if (player->getWieldIndex() != runData.new_playeritem)
+ client->setPlayerItem(runData.new_playeritem);
- // Update local inventory if it has changed
- if (client->getLocalInventoryUpdated()) {
- //infostream<<"Updating local inventory"<<std::endl;
- client->getLocalInventory(*local_inventory);
- runData.update_wielded_item_trigger = true;
- }
-
- if (runData.update_wielded_item_trigger) {
+ if (client->updateWieldedItem()) {
// Update wielded tool
- InventoryList *mlist = local_inventory->getList("main");
-
- if (mlist && (client->getPlayerItem() < mlist->getSize())) {
- ItemStack item = mlist->getItem(client->getPlayerItem());
- if (item.getDefinition(itemdef_manager).name.empty()) { // override the hand
- InventoryList *hlist = local_inventory->getList("hand");
- if (hlist)
- item = hlist->getItem(0);
- }
- camera->wield(item);
- }
-
- runData.update_wielded_item_trigger = false;
+ ItemStack selected_item, hand_item;
+ ItemStack &tool_item = player->getWieldedItem(&selected_item, &hand_item);
+ camera->wield(tool_item);
}
/*
@@ -3822,28 +3776,42 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
runData.update_draw_list_last_cam_dir = camera_direction;
}
- m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, dtime);
+ m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
/*
make sure menu is on top
1. Delete formspec menu reference if menu was removed
2. Else, make sure formspec menu is on top
*/
- if (current_formspec) {
- if (current_formspec->getReferenceCount() == 1) {
- current_formspec->drop();
- current_formspec = NULL;
- } else if (isMenuActive()) {
- guiroot->bringToFront(current_formspec);
+ auto formspec = m_game_ui->getFormspecGUI();
+ do { // breakable. only runs for one iteration
+ if (!formspec)
+ break;
+
+ if (formspec->getReferenceCount() == 1) {
+ m_game_ui->deleteFormspec();
+ break;
}
- }
+
+ auto &loc = formspec->getFormspecLocation();
+ if (loc.type == InventoryLocation::NODEMETA) {
+ NodeMetadata *meta = client->getEnv().getClientMap().getNodeMetadata(loc.p);
+ if (!meta || meta->getString("formspec").empty()) {
+ formspec->quitMenu();
+ break;
+ }
+ }
+
+ if (isMenuActive())
+ guiroot->bringToFront(formspec);
+ } while (false);
/*
Drawing begins
*/
const video::SColor &skycolor = sky->getSkyColor();
- TimeTaker tt_draw("mainloop: draw");
+ TimeTaker tt_draw("Draw scene");
driver->beginScene(true, true, skycolor);
bool draw_wield_tool = (m_game_ui->m_flags.show_hud &&
@@ -3903,7 +3871,8 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
driver->endScene();
stats->drawtime = tt_draw.stop(true);
- g_profiler->graphAdd("mainloop_draw", stats->drawtime / 1000.0f);
+ g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime);
+ g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true));
}
/* Log times and stuff for visualization */
@@ -4033,8 +4002,8 @@ void Game::extendedResourceCleanup()
void Game::showDeathFormspec()
{
- static std::string formspec =
- std::string(FORMSPEC_VERSION_STRING) +
+ static std::string formspec_str =
+ std::string("formspec_version[1]") +
SIZE_TAG
"bgcolor[#320000b4;true]"
"label[4.85,1.35;" + gettext("You died") + "]"
@@ -4044,12 +4013,13 @@ void Game::showDeathFormspec()
/* Create menu */
/* Note: FormspecFormSource and LocalFormspecHandler *
* are deleted by guiFormSpecMenu */
- FormspecFormSource *fs_src = new FormspecFormSource(formspec);
+ FormspecFormSource *fs_src = new FormspecFormSource(formspec_str);
LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
- GUIFormSpecMenu::create(current_formspec, client, &input->joystick, fs_src,
- txt_dst, client->getFormspecPrepend());
- current_formspec->setFocus("btn_respawn");
+ auto *&formspec = m_game_ui->getFormspecGUI();
+ GUIFormSpecMenu::create(formspec, client, &input->joystick,
+ fs_src, txt_dst, client->getFormspecPrepend());
+ formspec->setFocus("btn_respawn");
}
#define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name())
@@ -4107,7 +4077,7 @@ void Game::showPauseMenu()
float ypos = simple_singleplayer_mode ? 0.7f : 0.1f;
std::ostringstream os;
- os << FORMSPEC_VERSION_STRING << SIZE_TAG
+ os << "formspec_version[1]" << SIZE_TAG
<< "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
<< strgettext("Continue") << "]";
@@ -4173,10 +4143,11 @@ void Game::showPauseMenu()
FormspecFormSource *fs_src = new FormspecFormSource(os.str());
LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
- GUIFormSpecMenu::create(current_formspec, client, &input->joystick,
+ auto *&formspec = m_game_ui->getFormspecGUI();
+ GUIFormSpecMenu::create(formspec, client, &input->joystick,
fs_src, txt_dst, client->getFormspecPrepend());
- current_formspec->setFocus("btn_continue");
- current_formspec->doPause = true;
+ formspec->setFocus("btn_continue");
+ formspec->doPause = true;
}
/****************************************************************************/
@@ -4227,7 +4198,8 @@ void the_game(bool *kill,
error_message = e.what();
errorstream << "ServerError: " << error_message << std::endl;
} catch (ModError &e) {
- error_message = e.what() + strgettext("\nCheck debug.txt for details.");
- errorstream << "ModError: " << error_message << std::endl;
+ error_message = std::string("ModError: ") + e.what() +
+ strgettext("\nCheck debug.txt for details.");
+ errorstream << error_message << std::endl;
}
}
diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp
index 1f433e49a..674d07fa6 100644
--- a/src/client/gameui.cpp
+++ b/src/client/gameui.cpp
@@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <irrlicht_changes/static_text.h>
#include <gettext.h>
#include "gui/mainmenumanager.h"
+#include "gui/guiChatConsole.h"
#include "util/pointedthing.h"
#include "client.h"
#include "clientmap.h"
@@ -79,13 +80,15 @@ void GameUI::init()
// Profiler text (size is updated when text is updated)
m_guitext_profiler = gui::StaticText::add(guienv, L"<Profiler>",
core::rect<s32>(0, 0, 0, 0), false, false, guiroot);
+ m_guitext_profiler->setOverrideFont(g_fontengine->getFont(
+ g_fontengine->getDefaultFontSize() * 0.9f, FM_Mono));
m_guitext_profiler->setBackgroundColor(video::SColor(120, 0, 0, 0));
m_guitext_profiler->setVisible(false);
- m_guitext_profiler->setWordWrap(true);
}
void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_control,
- const CameraOrientation &cam, const PointedThing &pointed_old, float dtime)
+ const CameraOrientation &cam, const PointedThing &pointed_old,
+ const GUIChatConsole *chat_console, float dtime)
{
v2u32 screensize = RenderingEngine::get_instance()->getWindowSize();
@@ -97,17 +100,17 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
std::ostringstream os(std::ios_base::binary);
os << std::fixed
<< PROJECT_NAME_C " " << g_version_hash
- << ", FPS: " << fps
+ << " | FPS: " << fps
<< std::setprecision(0)
- << ", drawtime: " << drawtime_avg << "ms"
+ << " | drawtime: " << drawtime_avg << "ms"
<< std::setprecision(1)
- << ", dtime jitter: "
+ << " | dtime jitter: "
<< (stats.dtime_jitter.max_fraction * 100.0) << "%"
<< std::setprecision(1)
- << ", view range: "
+ << " | view range: "
<< (draw_control->range_all ? "All" : itos(draw_control->wanted_range))
<< std::setprecision(3)
- << ", RTT: " << client->getRTT() << "s";
+ << " | RTT: " << client->getRTT() << "s";
setStaticText(m_guitext, utf8_to_wide(os.str()).c_str());
m_guitext->setRelativePosition(core::rect<s32>(5, 5, screensize.X,
@@ -126,14 +129,15 @@ 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)) << "° "
<< yawToDirectionString(cam.camera_yaw)
- << ", seed: " << ((u64)client->getMapSeed());
+ << " | pitch: " << (-wrapDegrees_180(cam.camera_pitch)) << "°"
+ << " | seed: " << ((u64)client->getMapSeed());
if (pointed_old.type == POINTEDTHING_NODE) {
ClientMap &map = client->getEnv().getClientMap();
const NodeDefManager *nodedef = client->getNodeDefManager();
- MapNode n = map.getNodeNoEx(pointed_old.node_undersurface);
+ MapNode n = map.getNode(pointed_old.node_undersurface);
if (n.getContent() != CONTENT_IGNORE && nodedef->get(n).name != "unknown") {
os << ", pointed: " << nodedef->get(n).name
@@ -185,6 +189,9 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
m_guitext_status->setOverrideColor(fade_color);
m_guitext_status->enableOverrideColor(true);
}
+
+ // Hide chat when console is visible
+ m_guitext_chat->setVisible(isChatVisible() && !chat_console->isVisible());
}
void GameUI::initFlags()
@@ -226,38 +233,28 @@ void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count)
m_guitext_chat->setRelativePosition(core::rect<s32>(10, chat_y, width,
chat_y + m_guitext_chat->getTextHeight()));
- // Don't show chat if disabled or empty or profiler is enabled
- m_guitext_chat->setVisible(m_flags.show_chat &&
- recent_chat_count != 0 && m_profiler_current_page == 0);
+ m_recent_chat_count = recent_chat_count;
}
void GameUI::updateProfiler()
{
if (m_profiler_current_page != 0) {
std::ostringstream os(std::ios_base::binary);
- g_profiler->printPage(os, m_profiler_current_page, m_profiler_max_page);
-
- std::wstring text = translate_string(utf8_to_wide(os.str()));
- setStaticText(m_guitext_profiler, text.c_str());
-
- s32 w = g_fontengine->getTextWidth(text);
-
- if (w < 400)
- w = 400;
-
- u32 text_height = g_fontengine->getTextHeight();
-
- core::position2di upper_left, lower_right;
+ os << " Profiler page " << (int)m_profiler_current_page <<
+ ", elapsed: " << g_profiler->getElapsedMs() << " ms)" << std::endl;
- upper_left.X = 6;
- upper_left.Y = (text_height + 5) * 2;
- lower_right.X = 12 + w;
- lower_right.Y = upper_left.Y + (text_height + 1) * MAX_PROFILER_TEXT_ROWS;
+ int lines = g_profiler->print(os, m_profiler_current_page, m_profiler_max_page);
+ ++lines;
- s32 screen_height = RenderingEngine::get_video_driver()->getScreenSize().Height;
+ std::wstring text = utf8_to_wide(os.str());
+ setStaticText(m_guitext_profiler, text.c_str());
- if (lower_right.Y > screen_height * 2 / 3)
- lower_right.Y = screen_height * 2 / 3;
+ core::dimension2d<u32> size = m_guitext_profiler->getOverrideFont()->
+ getDimension(text.c_str());
+ core::position2di upper_left(6, 50);
+ core::position2di lower_right = upper_left;
+ lower_right.X += size.Width + 10;
+ lower_right.Y += size.Height;
m_guitext_profiler->setRelativePosition(core::rect<s32>(upper_left, lower_right));
}
@@ -301,3 +298,14 @@ void GameUI::toggleProfiler()
showTranslatedStatusText("Profiler hidden");
}
}
+
+
+void GameUI::deleteFormspec()
+{
+ if (m_formspec) {
+ m_formspec->drop();
+ m_formspec = nullptr;
+ }
+
+ m_formname.clear();
+}
diff --git a/src/client/gameui.h b/src/client/gameui.h
index b6b54562a..67c6a9921 100644
--- a/src/client/gameui.h
+++ b/src/client/gameui.h
@@ -21,12 +21,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include <IGUIEnvironment.h>
+#include "gui/guiFormSpecMenu.h"
#include "util/enriched_string.h"
#include "util/pointedthing.h"
#include "game.h"
using namespace irr;
class Client;
+class GUIChatConsole;
struct MapDrawControl;
/*
@@ -62,7 +64,7 @@ public:
void init();
void update(const RunStats &stats, Client *client, MapDrawControl *draw_control,
const CameraOrientation &cam, const PointedThing &pointed_old,
- float dtime);
+ const GUIChatConsole *chat_console, float dtime);
void initFlags();
const Flags &getFlags() const { return m_flags; }
@@ -80,6 +82,10 @@ public:
void showTranslatedStatusText(const char *str);
inline void clearStatusText() { m_statustext.clear(); }
+ const bool isChatVisible()
+ {
+ return m_flags.show_chat && m_recent_chat_count != 0 && m_profiler_current_page == 0;
+ }
void setChatText(const EnrichedString &chat_text, u32 recent_chat_count);
void updateProfiler();
@@ -88,6 +94,16 @@ public:
void toggleHud();
void toggleProfiler();
+ GUIFormSpecMenu *&updateFormspec(const std::string &formname)
+ {
+ m_formname = formname;
+ return m_formspec;
+ }
+
+ const std::string &getFormspecName() { return m_formname; }
+ GUIFormSpecMenu *&getFormspecGUI() { return m_formspec; }
+ void deleteFormspec();
+
private:
Flags m_flags;
@@ -103,8 +119,14 @@ private:
video::SColor m_statustext_initial_color;
gui::IGUIStaticText *m_guitext_chat = nullptr; // Chat text
+ u32 m_recent_chat_count = 0;
gui::IGUIStaticText *m_guitext_profiler = nullptr; // Profiler text
u8 m_profiler_current_page = 0;
const u8 m_profiler_max_page = 3;
+
+ // Default: "". If other than "": Empty show_formspec packets will only
+ // close the formspec when the formname matches
+ std::string m_formname;
+ GUIFormSpecMenu *m_formspec = nullptr;
};
diff --git a/src/client/guiscalingfilter.cpp b/src/client/guiscalingfilter.cpp
index 3b4377da5..2ff57ab74 100644
--- a/src/client/guiscalingfilter.cpp
+++ b/src/client/guiscalingfilter.cpp
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/numeric.h"
#include <cstdio>
#include "client/renderingengine.h"
+#include "client/tile.h" // hasNPotSupport()
/* Maintain a static cache to store the images that correspond to textures
* in a format that's manipulable by code. Some platforms exhibit issues
@@ -39,7 +40,7 @@ std::map<io::path, video::ITexture *> g_txrCache;
/* Manually insert an image into the cache, useful to avoid texture-to-image
* conversion whenever we can intercept it.
*/
-void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value)
+void guiScalingCache(const io::path &key, video::IVideoDriver *driver, video::IImage *value)
{
if (!g_settings->getBool("gui_scaling_filter"))
return;
@@ -113,17 +114,18 @@ video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver,
(u32)destrect.getHeight()));
imageScaleNNAA(srcimg, srcrect, destimg);
-#ifdef __ANDROID__
- // Android is very picky about textures being powers of 2, so expand
- // the image dimensions to the next power of 2, if necessary, for
- // that platform.
- video::IImage *po2img = driver->createImage(src->getColorFormat(),
- core::dimension2d<u32>(npot2((u32)destrect.getWidth()),
- npot2((u32)destrect.getHeight())));
- po2img->fill(video::SColor(0, 0, 0, 0));
- destimg->copyTo(po2img);
- destimg->drop();
- destimg = po2img;
+#if ENABLE_GLES
+ // Some platforms are picky about textures being powers of 2, so expand
+ // the image dimensions to the next power of 2, if necessary.
+ if (!hasNPotSupport()) {
+ video::IImage *po2img = driver->createImage(src->getColorFormat(),
+ core::dimension2d<u32>(npot2((u32)destrect.getWidth()),
+ npot2((u32)destrect.getHeight())));
+ po2img->fill(video::SColor(0, 0, 0, 0));
+ destimg->copyTo(po2img);
+ destimg->drop();
+ destimg = po2img;
+ }
#endif
// Convert the scaled image back into a texture.
@@ -167,3 +169,62 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha);
}
+
+void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture,
+ const core::rect<s32> &rect, const core::rect<s32> &middle)
+{
+ const video::SColor color(255,255,255,255);
+ const video::SColor colors[] = {color,color,color,color};
+
+ auto originalSize = texture->getOriginalSize();
+ core::vector2di lowerRightOffset = core::vector2di(originalSize.Width, originalSize.Height) - middle.LowerRightCorner;
+
+ for (int y = 0; y < 3; ++y) {
+ for (int x = 0; x < 3; ++x) {
+ core::rect<s32> src({0, 0}, originalSize);
+ core::rect<s32> dest = rect;
+
+ switch (x) {
+ case 0:
+ dest.LowerRightCorner.X = rect.UpperLeftCorner.X + middle.UpperLeftCorner.X;
+ src.LowerRightCorner.X = middle.UpperLeftCorner.X;
+ break;
+
+ case 1:
+ dest.UpperLeftCorner.X += middle.UpperLeftCorner.X;
+ dest.LowerRightCorner.X -= lowerRightOffset.X;
+ src.UpperLeftCorner.X = middle.UpperLeftCorner.X;
+ src.LowerRightCorner.X = middle.LowerRightCorner.X;
+ break;
+
+ case 2:
+ dest.UpperLeftCorner.X = rect.LowerRightCorner.X - lowerRightOffset.X;
+ src.UpperLeftCorner.X = middle.LowerRightCorner.X;
+ break;
+ }
+
+ switch (y) {
+ case 0:
+ dest.LowerRightCorner.Y = rect.UpperLeftCorner.Y + middle.UpperLeftCorner.Y;
+ src.LowerRightCorner.Y = middle.UpperLeftCorner.Y;
+ break;
+
+ case 1:
+ dest.UpperLeftCorner.Y += middle.UpperLeftCorner.Y;
+ dest.LowerRightCorner.Y -= lowerRightOffset.Y;
+ src.UpperLeftCorner.Y = middle.UpperLeftCorner.Y;
+ src.LowerRightCorner.Y = middle.LowerRightCorner.Y;
+ break;
+
+ case 2:
+ dest.UpperLeftCorner.Y = rect.LowerRightCorner.Y - lowerRightOffset.Y;
+ src.UpperLeftCorner.Y = middle.LowerRightCorner.Y;
+ break;
+ }
+
+ draw2DImageFilterScaled(driver, texture, dest,
+ src,
+ NULL/*&AbsoluteClippingRect*/, colors, true);
+ }
+ }
+}
diff --git a/src/client/guiscalingfilter.h b/src/client/guiscalingfilter.h
index 4661bf8da..181009551 100644
--- a/src/client/guiscalingfilter.h
+++ b/src/client/guiscalingfilter.h
@@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
/* Manually insert an image into the cache, useful to avoid texture-to-image
* conversion whenever we can intercept it.
*/
-void guiScalingCache(io::path key, video::IVideoDriver *driver, video::IImage *value);
+void guiScalingCache(const io::path &key, video::IVideoDriver *driver, video::IImage *value);
// Manually clear the cache, e.g. when switching to different worlds.
void guiScalingCacheClear();
@@ -48,3 +48,9 @@ void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr,
const core::rect<s32> &destrect, const core::rect<s32> &srcrect,
const core::rect<s32> *cliprect = 0, const video::SColor *const colors = 0,
bool usealpha = false);
+
+/*
+ * 9-slice / segment drawing
+ */
+void draw2DImage9Slice(video::IVideoDriver *driver, video::ITexture *texture,
+ const core::rect<s32> &rect, const core::rect<s32> &middle);
diff --git a/src/client/hud.cpp b/src/client/hud.cpp
index 1a2287a13..291d03816 100644
--- a/src/client/hud.cpp
+++ b/src/client/hud.cpp
@@ -372,7 +372,7 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
}
-void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture,
+void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &texture,
s32 count, v2s32 offset, v2s32 size)
{
const video::SColor color(255, 255, 255, 255);
@@ -649,10 +649,31 @@ void drawItemStack(video::IVideoDriver *driver,
core::rect<s32> oldViewPort = driver->getViewPort();
core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
+ core::rect<s32> viewrect = rect;
+ if (clip)
+ viewrect.clipAgainst(*clip);
+
core::matrix4 ProjMatrix;
- ProjMatrix.buildProjectionMatrixOrthoLH(2, 2, -1, 100);
+ ProjMatrix.buildProjectionMatrixOrthoLH(2.0f, 2.0f, -1.0f, 100.0f);
+
+ core::matrix4 ViewMatrix;
+ ViewMatrix.buildProjectionMatrixOrthoLH(
+ 2.0f * viewrect.getWidth() / rect.getWidth(),
+ 2.0f * viewrect.getHeight() / rect.getHeight(),
+ -1.0f,
+ 100.0f);
+ ViewMatrix.setTranslation(core::vector3df(
+ 1.0f * (rect.LowerRightCorner.X + rect.UpperLeftCorner.X -
+ viewrect.LowerRightCorner.X - viewrect.UpperLeftCorner.X) /
+ viewrect.getWidth(),
+ 1.0f * (viewrect.LowerRightCorner.Y + viewrect.UpperLeftCorner.Y -
+ rect.LowerRightCorner.Y - rect.UpperLeftCorner.Y) /
+ viewrect.getHeight(),
+ 0.0f));
+
driver->setTransform(video::ETS_PROJECTION, ProjMatrix);
- driver->setTransform(video::ETS_VIEW, ProjMatrix);
+ driver->setTransform(video::ETS_VIEW, ViewMatrix);
+
core::matrix4 matrix;
matrix.makeIdentity();
@@ -662,7 +683,7 @@ void drawItemStack(video::IVideoDriver *driver,
}
driver->setTransform(video::ETS_WORLD, matrix);
- driver->setViewPort(rect);
+ driver->setViewPort(viewrect);
video::SColor basecolor =
client->idef()->getItemstackColor(item, client);
@@ -693,6 +714,16 @@ void drawItemStack(video::IVideoDriver *driver,
driver->setTransform(video::ETS_VIEW, oldViewMat);
driver->setTransform(video::ETS_PROJECTION, oldProjMat);
driver->setViewPort(oldViewPort);
+
+ // draw the inventory_overlay
+ if (def.type == ITEM_NODE && def.inventory_image.empty() &&
+ !def.inventory_overlay.empty()) {
+ ITextureSource *tsrc = client->getTextureSource();
+ video::ITexture *overlay_texture = tsrc->getTexture(def.inventory_overlay);
+ core::dimension2d<u32> dimens = overlay_texture->getOriginalSize();
+ core::rect<s32> srcrect(0, 0, dimens.Width, dimens.Height);
+ draw2DImageFilterScaled(driver, overlay_texture, rect, srcrect, clip, 0, true);
+ }
}
if(def.type == ITEM_TOOL && item.wear != 0)
diff --git a/src/client/hud.h b/src/client/hud.h
index e9bcdf4e2..693d2adee 100644
--- a/src/client/hud.h
+++ b/src/client/hud.h
@@ -81,7 +81,7 @@ public:
void drawLuaElements(const v3s16 &camera_offset);
private:
- void drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture,
+ void drawStatbar(v2s32 pos, u16 corner, u16 drawdir, const std::string &texture,
s32 count, v2s32 offset, v2s32 size = v2s32());
void drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp
index 7e7b1a867..c086d860a 100644
--- a/src/client/localplayer.cpp
+++ b/src/client/localplayer.cpp
@@ -41,7 +41,7 @@ LocalPlayer::LocalPlayer(Client *client, const char *name):
static aabb3f getNodeBoundingBox(const std::vector<aabb3f> &nodeboxes)
{
if (nodeboxes.empty())
- return aabb3f(0, 0, 0, 0, 0, 0);
+ return aabb3f(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
aabb3f b_max;
@@ -56,7 +56,7 @@ static aabb3f getNodeBoundingBox(const std::vector<aabb3f> &nodeboxes)
}
bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
- const v3f &sneak_max)
+ const v3f &sneak_max)
{
static const v3s16 dir9_center[9] = {
v3s16( 0, 0, 0),
@@ -76,17 +76,17 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
bool new_sneak_node_exists = m_sneak_node_exists;
// We want the top of the sneak node to be below the players feet
- f32 position_y_mod = 0.05 * BS;
+ f32 position_y_mod = 0.05f * BS;
if (m_sneak_node_exists)
position_y_mod = m_sneak_node_bb_top.MaxEdge.Y - position_y_mod;
// Get position of current standing node
- const v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS);
+ const v3s16 current_node = floatToInt(position - v3f(0.0f, position_y_mod, 0.0f), BS);
if (current_node != m_sneak_node) {
new_sneak_node_exists = false;
} else {
- node = map->getNodeNoEx(current_node, &is_valid_position);
+ node = map->getNode(current_node, &is_valid_position);
if (!is_valid_position || !nodemgr->get(node).walkable)
new_sneak_node_exists = false;
}
@@ -97,7 +97,7 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
// Get new sneak node
m_sneak_ladder_detected = false;
- f32 min_distance_f = 100000.0 * BS;
+ f32 min_distance_f = 100000.0f * BS;
for (const auto &d : dir9_center) {
const v3s16 p = current_node + d;
@@ -106,23 +106,22 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
f32 distance_f = diff.getLength();
if (distance_f > min_distance_f ||
- fabs(diff.X) > (.5 + .1) * BS + sneak_max.X ||
- fabs(diff.Y) > (.5 + .1) * BS + sneak_max.Z)
+ fabs(diff.X) > (0.5f + 0.1f) * BS + sneak_max.X ||
+ fabs(diff.Y) > (0.5f + 0.1f) * BS + sneak_max.Z)
continue;
// The node to be sneaked on has to be walkable
- node = map->getNodeNoEx(p, &is_valid_position);
+ node = map->getNode(p, &is_valid_position);
if (!is_valid_position || !nodemgr->get(node).walkable)
continue;
// And the node(s) above have to be nonwalkable
bool ok = true;
if (!physics_override_sneak_glitch) {
- u16 height = ceilf(
- (m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS
- );
+ u16 height =
+ ceilf((m_collisionbox.MaxEdge.Y - m_collisionbox.MinEdge.Y) / BS);
for (u16 y = 1; y <= height; y++) {
- node = map->getNodeNoEx(p + v3s16(0, y, 0), &is_valid_position);
+ node = map->getNode(p + v3s16(0, y, 0), &is_valid_position);
if (!is_valid_position || nodemgr->get(node).walkable) {
ok = false;
break;
@@ -130,7 +129,7 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
}
} else {
// legacy behaviour: check just one node
- node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position);
+ node = map->getNode(p + v3s16(0, 1, 0), &is_valid_position);
ok = is_valid_position && !nodemgr->get(node).walkable;
}
if (!ok)
@@ -145,7 +144,7 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
return false;
// Update saved top bounding box of sneak node
- node = map->getNodeNoEx(m_sneak_node);
+ node = map->getNode(m_sneak_node);
std::vector<aabb3f> nodeboxes;
node.getCollisionBoxes(nodemgr, &nodeboxes);
m_sneak_node_bb_top = getNodeBoundingBox(nodeboxes);
@@ -153,11 +152,11 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
if (physics_override_sneak_glitch) {
// Detect sneak ladder:
// Node two meters above sneak node must be solid
- node = map->getNodeNoEx(m_sneak_node + v3s16(0, 2, 0),
+ node = map->getNode(m_sneak_node + v3s16(0, 2, 0),
&is_valid_position);
if (is_valid_position && nodemgr->get(node).walkable) {
// Node three meters above: must be non-solid
- node = map->getNodeNoEx(m_sneak_node + v3s16(0, 3, 0),
+ node = map->getNode(m_sneak_node + v3s16(0, 3, 0),
&is_valid_position);
m_sneak_ladder_detected = is_valid_position &&
!nodemgr->get(node).walkable;
@@ -169,10 +168,9 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position,
void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
std::vector<CollisionInfo> *collision_info)
{
- if (!collision_info || collision_info->empty()) {
- // Node below the feet, update each ClientEnvironment::step()
- m_standing_node = floatToInt(m_position, BS) - v3s16(0, 1, 0);
- }
+ // Node at feet position, update each ClientEnvironment::step()
+ if (!collision_info || collision_info->empty())
+ m_standing_node = floatToInt(m_position, BS);
// Temporary option for old move code
if (!physics_override_new_move) {
@@ -188,6 +186,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
// Copy parent position if local player is attached
if (isAttached) {
setPosition(overridePosition);
+ added_velocity = v3f(0.0f); // ignored
return;
}
@@ -201,9 +200,13 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
if (noclip && free_move) {
position += m_speed * dtime;
setPosition(position);
+ added_velocity = v3f(0.0f); // ignored
return;
}
+ m_speed += added_velocity;
+ added_velocity = v3f(0.0f);
+
/*
Collision detection
*/
@@ -219,20 +222,19 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
// If in liquid, the threshold of coming out is at higher y
if (in_liquid)
{
- pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
- node = map->getNodeNoEx(pp, &is_valid_position);
+ pp = floatToInt(position + v3f(0.0f, BS * 0.1f, 0.0f), BS);
+ node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
in_liquid = nodemgr->get(node.getContent()).isLiquid();
liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
} else {
in_liquid = false;
}
- }
- // If not in liquid, the threshold of going in is at lower y
- else
- {
- pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
- node = map->getNodeNoEx(pp, &is_valid_position);
+ } else {
+ // If not in liquid, the threshold of going in is at lower y
+
+ pp = floatToInt(position + v3f(0.0f, BS * 0.5f, 0.0f), BS);
+ node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
in_liquid = nodemgr->get(node.getContent()).isLiquid();
liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
@@ -245,8 +247,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
/*
Check if player is in liquid (the stable value)
*/
- pp = floatToInt(position + v3f(0,0,0), BS);
- node = map->getNodeNoEx(pp, &is_valid_position);
+ pp = floatToInt(position + v3f(0.0f), BS);
+ node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
} else {
@@ -254,21 +256,20 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
}
/*
- Check if player is climbing
+ Check if player is climbing
*/
-
- pp = floatToInt(position + v3f(0,0.5*BS,0), BS);
- v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS);
- node = map->getNodeNoEx(pp, &is_valid_position);
+ pp = floatToInt(position + v3f(0.0f, 0.5f * BS, 0.0f), BS);
+ v3s16 pp2 = floatToInt(position + v3f(0.0f, -0.2f * BS, 0.0f), BS);
+ node = map->getNode(pp, &is_valid_position);
bool is_valid_position2;
- MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2);
+ MapNode node2 = map->getNode(pp2, &is_valid_position2);
if (!(is_valid_position && is_valid_position2)) {
is_climbing = false;
} else {
- is_climbing = (nodemgr->get(node.getContent()).climbable
- || nodemgr->get(node2.getContent()).climbable) && !free_move;
+ is_climbing = (nodemgr->get(node.getContent()).climbable ||
+ nodemgr->get(node2.getContent()).climbable) && !free_move;
}
/*
@@ -277,7 +278,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
*/
//f32 d = pos_max_d * 1.1;
// A fairly large value in here makes moving smoother
- f32 d = 0.15*BS;
+ f32 d = 0.15f * BS;
// This should always apply, otherwise there are glitches
sanity_check(d > pos_max_d);
@@ -287,7 +288,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
float player_stepheight = (m_cao == nullptr) ? 0.0f :
(touching_ground ? m_cao->getStepHeight() : (0.2f * BS));
- v3f accel_f = v3f(0,0,0);
+ v3f accel_f;
const v3f initial_position = position;
const v3f initial_speed = m_speed;
@@ -309,7 +310,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
collision_info->push_back(colinfo);
if (colinfo.type != COLLISION_NODE ||
- colinfo.new_speed.Y != 0 ||
+ colinfo.axis != COLLISION_AXIS_Y ||
(could_sneak && m_sneak_node_exists))
continue;
@@ -320,6 +321,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
if (is_first || len < distance) {
m_standing_node = colinfo.node_p;
distance = len;
+ is_first = false;
}
}
}
@@ -340,7 +342,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
if (m_sneak_ladder_detected) {
// restore legacy behaviour (this makes the m_speed.Y hack necessary)
- sneak_max = v3f(0.4 * BS, 0, 0.4 * BS);
+ sneak_max = v3f(0.4f * BS, 0.0f, 0.4f * BS);
}
/*
@@ -364,12 +366,12 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
bmin.Z - sneak_max.Z, bmax.Z + sneak_max.Z);
if (position.X != old_pos.X)
- m_speed.X = 0;
+ m_speed.X = 0.0f;
if (position.Z != old_pos.Z)
- m_speed.Z = 0;
+ m_speed.Z = 0.0f;
}
- if (y_diff > 0 && m_speed.Y <= 0 &&
+ if (y_diff > 0 && m_speed.Y <= 0.0f &&
(physics_override_sneak_glitch || y_diff < BS * 0.6f)) {
// Move player to the maximal height when falling or when
// the ledge is climbed on the next step.
@@ -377,11 +379,11 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
// Smoothen the movement (based on 'position.Y = bmax.Y')
position.Y += y_diff * dtime * 22.0f + BS * 0.01f;
position.Y = std::min(position.Y, bmax.Y);
- m_speed.Y = 0;
+ m_speed.Y = 0.0f;
}
// Allow jumping on node edges while sneaking
- if (m_speed.Y == 0 || m_sneak_ladder_detected)
+ if (m_speed.Y == 0.0f || m_sneak_ladder_detected)
sneak_can_jump = true;
if (collision_info &&
@@ -413,7 +415,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
Report collisions
*/
- if(!result.standing_on_object && !touching_ground_was && touching_ground) {
+ if (!result.standing_on_object && !touching_ground_was && touching_ground) {
m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND));
// Set camera impact value to be used for view bobbing
@@ -423,31 +425,29 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d,
{
camera_barely_in_ceiling = false;
v3s16 camera_np = floatToInt(getEyePosition(), BS);
- MapNode n = map->getNodeNoEx(camera_np);
- if(n.getContent() != CONTENT_IGNORE){
- if(nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2){
+ MapNode n = map->getNode(camera_np);
+ if (n.getContent() != CONTENT_IGNORE) {
+ if (nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2)
camera_barely_in_ceiling = true;
- }
}
}
/*
Check properties of the node on which the player is standing
*/
- const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(m_standing_node));
+ const ContentFeatures &f = nodemgr->get(map->getNode(m_standing_node));
+
// Determine if jumping is possible
- m_can_jump = (touching_ground && !in_liquid && !is_climbing)
- || sneak_can_jump;
- if (itemgroup_get(f.groups, "disable_jump"))
- m_can_jump = false;
+ m_disable_jump = itemgroup_get(f.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
if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") &&
- m_speed.Y >= -0.5 * BS) {
+ m_speed.Y >= -0.5f * BS) {
float jumpspeed = movement_speed_jump * physics_override_jump;
- if (m_speed.Y > 1) {
+ if (m_speed.Y > 1.0f) {
// Reduce boost when speed already is high
- m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 ));
+ m_speed.Y += jumpspeed / (1.0f + (m_speed.Y / 16.0f));
} else {
m_speed.Y += jumpspeed;
}
@@ -474,19 +474,17 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
setYaw(control.yaw);
// Nullify speed and don't run positioning code if the player is attached
- if(isAttached)
- {
- setSpeed(v3f(0,0,0));
+ if (isAttached) {
+ setSpeed(v3f(0.0f));
return;
}
PlayerSettings &player_settings = getPlayerSettings();
// All vectors are relative to the player's yaw,
- // (and pitch if pitch fly mode enabled),
+ // (and pitch if pitch move mode enabled),
// and will be rotated at the end
- v3f speedH = v3f(0,0,0); // Horizontal (X, Z)
- v3f speedV = v3f(0,0,0); // Vertical (Y)
+ v3f speedH, speedV; // Horizontal (X, Z) and Vertical (Y)
bool fly_allowed = m_client->checkLocalPrivilege("fly");
bool fast_allowed = m_client->checkLocalPrivilege("fast");
@@ -496,7 +494,6 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
bool pitch_move = (free_move || in_liquid) && player_settings.pitch_move;
// When aux1_descends is enabled the fast key is used to go down, so fast isn't possible
bool fast_climb = fast_move && control.aux1 && !player_settings.aux1_descends;
- bool continuous_forward = player_settings.continuous_forward;
bool always_fly_fast = player_settings.always_fly_fast;
// Whether superspeed mode is used or not
@@ -506,76 +503,58 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
superspeed = true;
// Old descend control
- if (player_settings.aux1_descends)
- {
+ if (player_settings.aux1_descends) {
// If free movement and fast movement, always move fast
- if(free_move && fast_move)
+ if (free_move && fast_move)
superspeed = true;
// Auxiliary button 1 (E)
- if(control.aux1)
- {
- if(free_move)
- {
+ if (control.aux1) {
+ if (free_move) {
// In free movement mode, aux1 descends
- if(fast_move)
+ if (fast_move)
speedV.Y = -movement_speed_fast;
else
speedV.Y = -movement_speed_walk;
- }
- else if(in_liquid || in_liquid_stable)
- {
+ } else if (in_liquid || in_liquid_stable) {
speedV.Y = -movement_speed_walk;
swimming_vertical = true;
- }
- else if(is_climbing)
- {
+ } else if (is_climbing) {
speedV.Y = -movement_speed_climb;
- }
- else
- {
+ } else {
// If not free movement but fast is allowed, aux1 is
// "Turbo button"
- if(fast_move)
+ if (fast_move)
superspeed = true;
}
}
- }
- // New minecraft-like descend control
- else
- {
+ } else {
+ // New minecraft-like descend control
+
// Auxiliary button 1 (E)
- if(control.aux1)
- {
- if(!is_climbing)
- {
+ if (control.aux1) {
+ if (!is_climbing) {
// aux1 is "Turbo button"
- if(fast_move)
+ if (fast_move)
superspeed = true;
}
}
- if(control.sneak)
- {
- if(free_move)
- {
+ if (control.sneak) {
+ if (free_move) {
// In free movement mode, sneak descends
if (fast_move && (control.aux1 || always_fly_fast))
speedV.Y = -movement_speed_fast;
else
speedV.Y = -movement_speed_walk;
- }
- else if(in_liquid || in_liquid_stable)
- {
- if(fast_climb)
+ } else if (in_liquid || in_liquid_stable) {
+ if (fast_climb)
speedV.Y = -movement_speed_fast;
else
speedV.Y = -movement_speed_walk;
swimming_vertical = true;
- }
- else if(is_climbing)
- {
- if(fast_climb)
+ } else if (is_climbing) {
+ if (fast_climb)
speedV.Y = -movement_speed_fast;
else
speedV.Y = -movement_speed_climb;
@@ -583,42 +562,32 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
}
}
- if (continuous_forward)
- speedH += v3f(0,0,1);
+ if (control.up)
+ speedH += v3f(0.0f, 0.0f, 1.0f);
+
+ if (control.down)
+ speedH -= v3f(0.0f, 0.0f, 1.0f);
+
+ if (!control.up && !control.down)
+ speedH -= v3f(0.0f, 0.0f, 1.0f) * (control.forw_move_joystick_axis / 32767.f);
+
+ if (control.left)
+ speedH += v3f(-1.0f, 0.0f, 0.0f);
+
+ if (control.right)
+ speedH += v3f(1.0f, 0.0f, 0.0f);
+
+ if (!control.left && !control.right)
+ speedH += v3f(1.0f, 0.0f, 0.0f) * (control.sidew_move_joystick_axis / 32767.f);
- if (control.up) {
- if (continuous_forward) {
- if (fast_move)
- superspeed = true;
- } else {
- speedH += v3f(0,0,1);
- }
- }
- if (control.down) {
- speedH -= v3f(0,0,1);
- }
- if (!control.up && !control.down) {
- speedH -= v3f(0,0,1) *
- (control.forw_move_joystick_axis / 32767.f);
- }
- if (control.left) {
- speedH += v3f(-1,0,0);
- }
- if (control.right) {
- speedH += v3f(1,0,0);
- }
- if (!control.left && !control.right) {
- speedH += v3f(1,0,0) *
- (control.sidew_move_joystick_axis / 32767.f);
- }
if (m_autojump) {
// release autojump after a given time
m_autojump_time -= dtime;
if (m_autojump_time <= 0.0f)
m_autojump = false;
}
- if(control.jump)
- {
+
+ if (control.jump) {
if (free_move) {
if (player_settings.aux1_descends || always_fly_fast) {
if (fast_move)
@@ -626,37 +595,31 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
else
speedV.Y = movement_speed_walk;
} else {
- if(fast_move && control.aux1)
+ if (fast_move && control.aux1)
speedV.Y = movement_speed_fast;
else
speedV.Y = movement_speed_walk;
}
- }
- else if(m_can_jump)
- {
+ } else if (m_can_jump) {
/*
NOTE: The d value in move() affects jump height by
raising the height at which the jump speed is kept
at its starting value
*/
v3f speedJ = getSpeed();
- if(speedJ.Y >= -0.5 * BS) {
+ if (speedJ.Y >= -0.5f * BS) {
speedJ.Y = movement_speed_jump * physics_override_jump;
setSpeed(speedJ);
m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_JUMP));
}
- }
- else if(in_liquid)
- {
- if(fast_climb)
+ } else if (in_liquid && !m_disable_jump) {
+ if (fast_climb)
speedV.Y = movement_speed_fast;
else
speedV.Y = movement_speed_walk;
swimming_vertical = true;
- }
- else if(is_climbing)
- {
- if(fast_climb)
+ } else if (is_climbing && !m_disable_jump) {
+ if (fast_climb)
speedV.Y = movement_speed_fast;
else
speedV.Y = movement_speed_climb;
@@ -664,29 +627,31 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
}
// The speed of the player (Y is ignored)
- if(superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb))
+ if (superspeed || (is_climbing && fast_climb) ||
+ ((in_liquid || in_liquid_stable) && fast_climb))
speedH = speedH.normalize() * movement_speed_fast;
- else if(control.sneak && !free_move && !in_liquid && !in_liquid_stable)
+ else if (control.sneak && !free_move && !in_liquid && !in_liquid_stable)
speedH = speedH.normalize() * movement_speed_crouch;
else
speedH = speedH.normalize() * movement_speed_walk;
// Acceleration increase
- f32 incH = 0; // Horizontal (X, Z)
- f32 incV = 0; // Vertical (Y)
- if((!touching_ground && !free_move && !is_climbing && !in_liquid) || (!free_move && m_can_jump && control.jump))
- {
+ f32 incH = 0.0f; // Horizontal (X, Z)
+ f32 incV = 0.0f; // Vertical (Y)
+ if ((!touching_ground && !free_move && !is_climbing && !in_liquid) ||
+ (!free_move && m_can_jump && control.jump)) {
// Jumping and falling
- if(superspeed || (fast_move && control.aux1))
+ if (superspeed || (fast_move && control.aux1))
incH = movement_acceleration_fast * BS * dtime;
else
incH = movement_acceleration_air * BS * dtime;
- incV = 0; // No vertical acceleration in air
- }
- else if (superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb))
+ incV = 0.0f; // No vertical acceleration in air
+ } else if (superspeed || (is_climbing && fast_climb) ||
+ ((in_liquid || in_liquid_stable) && fast_climb)) {
incH = incV = movement_acceleration_fast * BS * dtime;
- else
+ } else {
incH = incV = movement_acceleration_default * BS * dtime;
+ }
float slip_factor = 1.0f;
if (!free_move && !in_liquid && !in_liquid_stable)
@@ -701,47 +666,55 @@ void LocalPlayer::applyControl(float dtime, Environment *env)
// Accelerate to target speed with maximum increment
accelerate((speedH + speedV) * physics_override_speed,
- incH * physics_override_speed * slip_factor, incV * physics_override_speed,
- pitch_move);
+ incH * physics_override_speed * slip_factor, incV * physics_override_speed,
+ pitch_move);
}
v3s16 LocalPlayer::getStandingNodePos()
{
- if(m_sneak_node_exists)
+ if (m_sneak_node_exists)
return m_sneak_node;
+
return m_standing_node;
}
v3s16 LocalPlayer::getFootstepNodePos()
{
+ // Emit swimming sound if the player is in liquid
if (in_liquid_stable)
- // Emit swimming sound if the player is in liquid
return floatToInt(getPosition(), BS);
+
+ // BS * 0.05 below the player's feet ensures a 1/16th height
+ // nodebox is detected instead of the node below it.
if (touching_ground)
- // BS * 0.05 below the player's feet ensures a 1/16th height
- // nodebox is detected instead of the node below it.
- return floatToInt(getPosition() - v3f(0, BS * 0.05f, 0), BS);
+ return floatToInt(getPosition() - v3f(0.0f, BS * 0.05f, 0.0f), BS);
+
// A larger distance below is necessary for a footstep sound
// when landing after a jump or fall. BS * 0.5 ensures water
// sounds when swimming in 1 node deep water.
- return floatToInt(getPosition() - v3f(0, BS * 0.5f, 0), BS);
+ return floatToInt(getPosition() - v3f(0.0f, BS * 0.5f, 0.0f), BS);
}
v3s16 LocalPlayer::getLightPosition() const
{
- return floatToInt(m_position + v3f(0,BS+BS/2,0), BS);
+ return floatToInt(m_position + v3f(0.0f, BS * 1.5f, 0.0f), BS);
}
v3f LocalPlayer::getEyeOffset() const
{
- float eye_height = camera_barely_in_ceiling ?
- m_eye_height - 0.125f : m_eye_height;
- return v3f(0, BS * eye_height, 0);
+ float eye_height = camera_barely_in_ceiling ? m_eye_height - 0.125f : m_eye_height;
+ return v3f(0.0f, BS * eye_height, 0.0f);
+}
+
+bool LocalPlayer::isDead() const
+{
+ FATAL_ERROR_IF(!getCAO(), "LocalPlayer's CAO isn't initialized");
+ return !getCAO()->isImmortal() && hp == 0;
}
// 3D acceleration
void LocalPlayer::accelerate(const v3f &target_speed, const f32 max_increase_H,
- const f32 max_increase_V, const bool use_pitch)
+ const f32 max_increase_V, const bool use_pitch)
{
const f32 yaw = getYaw();
const f32 pitch = getPitch();
@@ -752,18 +725,18 @@ void LocalPlayer::accelerate(const v3f &target_speed, const f32 max_increase_H,
flat_speed.rotateYZBy(-pitch);
v3f d_wanted = target_speed - flat_speed;
- v3f d = v3f(0,0,0);
+ v3f d;
// Then compare the horizontal and vertical components with the wanted speed
- if (max_increase_H > 0) {
- v3f d_wanted_H = d_wanted * v3f(1,0,1);
+ if (max_increase_H > 0.0f) {
+ v3f d_wanted_H = d_wanted * v3f(1.0f, 0.0f, 1.0f);
if (d_wanted_H.getLength() > max_increase_H)
d += d_wanted_H.normalize() * max_increase_H;
else
d += d_wanted_H;
}
- if (max_increase_V > 0) {
+ if (max_increase_V > 0.0f) {
f32 d_wanted_V = d_wanted.Y;
if (d_wanted_V > max_increase_V)
d.Y += max_increase_V;
@@ -783,7 +756,7 @@ void LocalPlayer::accelerate(const v3f &target_speed, const f32 max_increase_H,
// Temporary option for old move code
void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
- std::vector<CollisionInfo> *collision_info)
+ std::vector<CollisionInfo> *collision_info)
{
Map *map = &env->getMap();
const NodeDefManager *nodemgr = m_client->ndef();
@@ -794,6 +767,7 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
if (isAttached) {
setPosition(overridePosition);
m_sneak_node_exists = false;
+ added_velocity = v3f(0.0f);
return;
}
@@ -807,9 +781,13 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
position += m_speed * dtime;
setPosition(position);
m_sneak_node_exists = false;
+ added_velocity = v3f(0.0f);
return;
}
+ m_speed += added_velocity;
+ added_velocity = v3f(0.0f);
+
/*
Collision detection
*/
@@ -822,8 +800,8 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
*/
if (in_liquid) {
// If in liquid, the threshold of coming out is at higher y
- pp = floatToInt(position + v3f(0, BS * 0.1, 0), BS);
- node = map->getNodeNoEx(pp, &is_valid_position);
+ pp = floatToInt(position + v3f(0.0f, BS * 0.1f, 0.0f), BS);
+ node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
in_liquid = nodemgr->get(node.getContent()).isLiquid();
liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
@@ -832,8 +810,8 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
}
} else {
// If not in liquid, the threshold of going in is at lower y
- pp = floatToInt(position + v3f(0, BS * 0.5, 0), BS);
- node = map->getNodeNoEx(pp, &is_valid_position);
+ pp = floatToInt(position + v3f(0.0f, BS * 0.5f, 0.0f), BS);
+ node = map->getNode(pp, &is_valid_position);
if (is_valid_position) {
in_liquid = nodemgr->get(node.getContent()).isLiquid();
liquid_viscosity = nodemgr->get(node.getContent()).liquid_viscosity;
@@ -845,8 +823,8 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
/*
Check if player is in liquid (the stable value)
*/
- pp = floatToInt(position + v3f(0, 0, 0), BS);
- node = map->getNodeNoEx(pp, &is_valid_position);
+ pp = floatToInt(position + v3f(0.0f), BS);
+ node = map->getNode(pp, &is_valid_position);
if (is_valid_position)
in_liquid_stable = nodemgr->get(node.getContent()).isLiquid();
else
@@ -855,17 +833,17 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
/*
Check if player is climbing
*/
- pp = floatToInt(position + v3f(0, 0.5 * BS, 0), BS);
- v3s16 pp2 = floatToInt(position + v3f(0, -0.2 * BS, 0), BS);
- node = map->getNodeNoEx(pp, &is_valid_position);
+ pp = floatToInt(position + v3f(0.0f, 0.5f * BS, 0.0f), BS);
+ v3s16 pp2 = floatToInt(position + v3f(0.0f, -0.2f * BS, 0.0f), BS);
+ node = map->getNode(pp, &is_valid_position);
bool is_valid_position2;
- MapNode node2 = map->getNodeNoEx(pp2, &is_valid_position2);
+ MapNode node2 = map->getNode(pp2, &is_valid_position2);
if (!(is_valid_position && is_valid_position2))
is_climbing = false;
else
is_climbing = (nodemgr->get(node.getContent()).climbable ||
- nodemgr->get(node2.getContent()).climbable) && !free_move;
+ nodemgr->get(node2.getContent()).climbable) && !free_move;
/*
Collision uncertainty radius
@@ -873,11 +851,11 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
*/
//f32 d = pos_max_d * 1.1;
// A fairly large value in here makes moving smoother
- f32 d = 0.15 * BS;
+ f32 d = 0.15f * BS;
// This should always apply, otherwise there are glitches
sanity_check(d > pos_max_d);
// Maximum distance over border for sneaking
- f32 sneak_max = BS * 0.4;
+ f32 sneak_max = BS * 0.4f;
/*
If sneaking, keep in range from the last walked node and don't
@@ -886,14 +864,14 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
if (control.sneak && m_sneak_node_exists &&
!(fly_allowed && player_settings.free_move) && !in_liquid &&
physics_override_sneak) {
- f32 maxd = 0.5 * BS + sneak_max;
+ f32 maxd = 0.5f * BS + sneak_max;
v3f lwn_f = intToFloat(m_sneak_node, BS);
position.X = rangelim(position.X, lwn_f.X - maxd, lwn_f.X + maxd);
position.Z = rangelim(position.Z, lwn_f.Z - maxd, lwn_f.Z + maxd);
if (!is_climbing) {
// Move up if necessary
- f32 new_y = (lwn_f.Y - 0.5 * BS) + m_sneak_node_bb_ymax;
+ f32 new_y = (lwn_f.Y - 0.5f * BS) + m_sneak_node_bb_ymax;
if (position.Y < new_y)
position.Y = new_y;
/*
@@ -901,15 +879,15 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
sneaking over the edges of current sneaking_node.
TODO (when fixed): Set Y-speed only to 0 when position.Y < new_y.
*/
- if (m_speed.Y < 0)
- m_speed.Y = 0;
+ if (m_speed.Y < 0.0f)
+ m_speed.Y = 0.0f;
}
}
- // this shouldn't be hardcoded but transmitted from server
- float player_stepheight = touching_ground ? (BS * 0.6) : (BS * 0.2);
+ // TODO: This shouldn't be hardcoded but decided by the server
+ float player_stepheight = touching_ground ? (BS * 0.6f) : (BS * 0.2f);
- v3f accel_f = v3f(0, 0, 0);
+ v3f accel_f;
const v3f initial_position = position;
const v3f initial_speed = m_speed;
@@ -917,6 +895,12 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
pos_max_d, m_collisionbox, player_stepheight, dtime,
&position, &m_speed, accel_f);
+ // Positition was slightly changed; update standing node pos
+ if (touching_ground)
+ m_standing_node = floatToInt(m_position - v3f(0.0f, 0.1f * BS, 0.0f), BS);
+ else
+ m_standing_node = floatToInt(m_position, BS);
+
/*
If the player's feet touch the topside of any node, this is
set to true.
@@ -926,35 +910,35 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
bool touching_ground_was = touching_ground;
touching_ground = result.touching_ground;
- //bool standing_on_unloaded = result.standing_on_unloaded;
+ //bool standing_on_unloaded = result.standing_on_unloaded;
/*
Check the nodes under the player to see from which node the
- player is sneaking from, if any. If the node from under
+ player is sneaking from, if any. If the node from under
the player has been removed, the player falls.
*/
- f32 position_y_mod = 0.05 * BS;
- if (m_sneak_node_bb_ymax > 0)
+ f32 position_y_mod = 0.05f * BS;
+ if (m_sneak_node_bb_ymax > 0.0f)
position_y_mod = m_sneak_node_bb_ymax - position_y_mod;
- v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS);
+ v3s16 current_node = floatToInt(position - v3f(0.0f, position_y_mod, 0.0f), BS);
if (m_sneak_node_exists &&
- nodemgr->get(map->getNodeNoEx(m_old_node_below)).name == "air" &&
+ nodemgr->get(map->getNode(m_old_node_below)).name == "air" &&
m_old_node_below_type != "air") {
// Old node appears to have been removed; that is,
// it wasn't air before but now it is
m_need_to_get_new_sneak_node = false;
m_sneak_node_exists = false;
- } else if (nodemgr->get(map->getNodeNoEx(current_node)).name != "air") {
+ } else if (nodemgr->get(map->getNode(current_node)).name != "air") {
// We are on something, so make sure to recalculate the sneak
// node.
m_need_to_get_new_sneak_node = true;
}
if (m_need_to_get_new_sneak_node && physics_override_sneak) {
- m_sneak_node_bb_ymax = 0;
- v3s16 pos_i_bottom = floatToInt(position - v3f(0, position_y_mod, 0), BS);
+ m_sneak_node_bb_ymax = 0.0f;
+ v3s16 pos_i_bottom = floatToInt(position - v3f(0.0f, position_y_mod, 0.0f), BS);
v2f player_p2df(position.X, position.Z);
- f32 min_distance_f = 100000.0 * BS;
+ f32 min_distance_f = 100000.0f * BS;
// If already seeking from some node, compare to it.
v3s16 new_sneak_node = m_sneak_node;
for (s16 x= -1; x <= 1; x++)
@@ -964,24 +948,24 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
v2f node_p2df(pf.X, pf.Z);
f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
f32 max_axis_distance_f = MYMAX(
- std::fabs(player_p2df.X - node_p2df.X),
- std::fabs(player_p2df.Y - node_p2df.Y));
+ std::fabs(player_p2df.X - node_p2df.X),
+ std::fabs(player_p2df.Y - node_p2df.Y));
if (distance_f > min_distance_f ||
- max_axis_distance_f > 0.5 * BS + sneak_max + 0.1 * BS)
+ max_axis_distance_f > 0.5f * BS + sneak_max + 0.1f * BS)
continue;
// The node to be sneaked on has to be walkable
- node = map->getNodeNoEx(p, &is_valid_position);
+ node = map->getNode(p, &is_valid_position);
if (!is_valid_position || !nodemgr->get(node).walkable)
continue;
// And the node above it has to be nonwalkable
- node = map->getNodeNoEx(p + v3s16(0, 1, 0), &is_valid_position);
+ node = map->getNode(p + v3s16(0, 1, 0), &is_valid_position);
if (!is_valid_position || nodemgr->get(node).walkable)
continue;
// If not 'sneak_glitch' the node 2 nodes above it has to be nonwalkable
if (!physics_override_sneak_glitch) {
- node =map->getNodeNoEx(p + v3s16(0, 2, 0), &is_valid_position);
+ node = map->getNode(p + v3s16(0, 2, 0), &is_valid_position);
if (!is_valid_position || nodemgr->get(node).walkable)
continue;
}
@@ -990,14 +974,14 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
new_sneak_node = p;
}
- bool sneak_node_found = (min_distance_f < 100000.0 * BS * 0.9);
+ bool sneak_node_found = (min_distance_f < 100000.0f * BS * 0.9f);
m_sneak_node = new_sneak_node;
m_sneak_node_exists = sneak_node_found;
if (sneak_node_found) {
- f32 cb_max = 0;
- MapNode n = map->getNodeNoEx(m_sneak_node);
+ f32 cb_max = 0.0f;
+ MapNode n = map->getNode(m_sneak_node);
std::vector<aabb3f> nodeboxes;
n.getCollisionBoxes(nodemgr, &nodeboxes);
for (const auto &box : nodeboxes) {
@@ -1025,7 +1009,7 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
/*
Report collisions
*/
- // Dont report if flying
+ // Don't report if flying
if (collision_info && !(player_settings.free_move && fly_allowed)) {
for (const auto &info : result.collisions) {
collision_info->push_back(info);
@@ -1035,13 +1019,13 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
if (!result.standing_on_object && !touching_ground_was && touching_ground) {
m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND));
// Set camera impact value to be used for view bobbing
- camera_impact = getSpeed().Y * -1;
+ camera_impact = getSpeed().Y * -1.0f;
}
{
camera_barely_in_ceiling = false;
v3s16 camera_np = floatToInt(getEyePosition(), BS);
- MapNode n = map->getNodeNoEx(camera_np);
+ MapNode n = map->getNode(camera_np);
if (n.getContent() != CONTENT_IGNORE) {
if (nodemgr->get(n).walkable && nodemgr->get(n).solidness == 2)
camera_barely_in_ceiling = true;
@@ -1051,24 +1035,25 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d,
/*
Update the node last under the player
*/
- m_old_node_below = floatToInt(position - v3f(0, BS / 2, 0), BS);
- m_old_node_below_type = nodemgr->get(map->getNodeNoEx(m_old_node_below)).name;
+ m_old_node_below = floatToInt(position - v3f(0.0f, BS / 2.0f, 0.0f), BS);
+ m_old_node_below_type = nodemgr->get(map->getNode(m_old_node_below)).name;
/*
Check properties of the node on which the player is standing
*/
- const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(getStandingNodePos()));
+ const ContentFeatures &f = nodemgr->get(map->getNode(getStandingNodePos()));
+
// Determine if jumping is possible
- m_can_jump = touching_ground && !in_liquid;
- if (itemgroup_get(f.groups, "disable_jump"))
- m_can_jump = false;
+ m_disable_jump = itemgroup_get(f.groups, "disable_jump");
+ m_can_jump = touching_ground && !m_disable_jump;
+
// Jump key pressed while jumping off from a bouncy block
if (m_can_jump && control.jump && itemgroup_get(f.groups, "bouncy") &&
- m_speed.Y >= -0.5 * BS) {
+ m_speed.Y >= -0.5f * BS) {
float jumpspeed = movement_speed_jump * physics_override_jump;
- if (m_speed.Y > 1) {
+ if (m_speed.Y > 1.0f) {
// Reduce boost when speed already is high
- m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 ));
+ m_speed.Y += jumpspeed / (1.0f + (m_speed.Y / 16.0f));
} else {
m_speed.Y += jumpspeed;
}
@@ -1085,24 +1070,23 @@ float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH)
// Slip on slippery nodes
const NodeDefManager *nodemgr = env->getGameDef()->ndef();
Map *map = &env->getMap();
- const ContentFeatures &f = nodemgr->get(map->getNodeNoEx(
- getStandingNodePos()));
+ const ContentFeatures &f = nodemgr->get(map->getNode(getStandingNodePos()));
int slippery = 0;
if (f.walkable)
slippery = itemgroup_get(f.groups, "slippery");
if (slippery >= 1) {
- if (speedH == v3f(0.0f)) {
- slippery = slippery * 2;
- }
+ if (speedH == v3f(0.0f))
+ slippery *= 2;
+
return core::clamp(1.0f / (slippery + 1), 0.001f, 1.0f);
}
return 1.0f;
}
void LocalPlayer::handleAutojump(f32 dtime, Environment *env,
- const collisionMoveResult &result, const v3f &initial_position,
- const v3f &initial_speed, f32 pos_max_d)
+ const collisionMoveResult &result, const v3f &initial_position,
+ const v3f &initial_speed, f32 pos_max_d)
{
PlayerSettings &player_settings = getPlayerSettings();
if (!player_settings.autojump)
@@ -1111,11 +1095,13 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env,
if (m_autojump)
return;
- bool control_forward = control.up || player_settings.continuous_forward ||
- (!control.up && !control.down &&
- control.forw_move_joystick_axis < -0.05);
+ bool control_forward = control.up ||
+ (!control.up && !control.down &&
+ control.forw_move_joystick_axis < -0.05f);
+
bool could_autojump =
- m_can_jump && !control.jump && !control.sneak && control_forward;
+ m_can_jump && !control.jump && !control.sneak && control_forward;
+
if (!could_autojump)
return;
@@ -1139,9 +1125,9 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env,
v3s16 ceilpos_max = floatToInt(headpos_max, BS) + v3s16(0, 1, 0);
const NodeDefManager *ndef = env->getGameDef()->ndef();
bool is_position_valid;
- for (s16 z = ceilpos_min.Z; z <= ceilpos_max.Z; z++) {
- for (s16 x = ceilpos_min.X; x <= ceilpos_max.X; x++) {
- MapNode n = env->getMap().getNodeNoEx(v3s16(x, ceilpos_max.Y, z), &is_position_valid);
+ for (s16 z = ceilpos_min.Z; z <= ceilpos_max.Z; ++z) {
+ for (s16 x = ceilpos_min.X; x <= ceilpos_max.X; ++x) {
+ MapNode n = env->getMap().getNode(v3s16(x, ceilpos_max.Y, z), &is_position_valid);
if (!is_position_valid)
break; // won't collide with the void outside
@@ -1159,8 +1145,7 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env,
// try at peak of jump, zero step height
collisionMoveResult jump_result = collisionMoveSimple(env, m_client, pos_max_d,
- m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed,
- v3f(0, 0, 0));
+ m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, v3f(0.0f));
// see if we can get a little bit farther horizontally if we had
// jumped
diff --git a/src/client/localplayer.h b/src/client/localplayer.h
index b1fc1fbc8..45dc6776e 100644
--- a/src/client/localplayer.h
+++ b/src/client/localplayer.h
@@ -100,7 +100,7 @@ public:
bool makes_footstep_sound = true;
int last_animation = NO_ANIM;
- float last_animation_speed;
+ float last_animation_speed = 0.0f;
std::string hotbar_image = "";
std::string hotbar_selected_image = "";
@@ -149,15 +149,22 @@ public:
bool getAutojump() const { return m_autojump; }
+ bool isDead() const;
+
+ inline void addVelocity(const v3f &vel)
+ {
+ added_velocity += vel;
+ }
+
private:
void accelerate(const v3f &target_speed, const f32 max_increase_H,
- const f32 max_increase_V, const bool use_pitch);
+ const f32 max_increase_V, const bool use_pitch);
bool updateSneakNode(Map *map, const v3f &position, const v3f &sneak_max);
float getSlipFactor(Environment *env, const v3f &speedH);
void handleAutojump(f32 dtime, Environment *env,
- const collisionMoveResult &result,
- const v3f &position_before_move, const v3f &speed_before_move,
- f32 pos_max_d);
+ const collisionMoveResult &result,
+ const v3f &position_before_move, const v3f &speed_before_move,
+ f32 pos_max_d);
v3f m_position;
v3s16 m_standing_node;
@@ -183,17 +190,21 @@ private:
// ***** End of variables for temporary option *****
bool m_can_jump = false;
+ bool m_disable_jump = false;
u16 m_breath = PLAYER_MAX_BREATH_DEFAULT;
f32 m_yaw = 0.0f;
f32 m_pitch = 0.0f;
bool camera_barely_in_ceiling = false;
aabb3f m_collisionbox = aabb3f(-BS * 0.30f, 0.0f, -BS * 0.30f, BS * 0.30f,
- BS * 1.75f, BS * 0.30f);
+ BS * 1.75f, BS * 0.30f);
float m_eye_height = 1.625f;
float m_zoom_fov = 0.0f;
bool m_autojump = false;
float m_autojump_time = 0.0f;
+ v3f added_velocity = v3f(0.0f); // cleared on each move()
+ // TODO: Rename to adhere to convention: added_velocity --> m_added_velocity
+
GenericCAO *m_cao = nullptr;
Client *m_client;
};
diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp
index 6b5ba9f9d..2bfaa7a4f 100644
--- a/src/client/mapblock_mesh.cpp
+++ b/src/client/mapblock_mesh.cpp
@@ -942,10 +942,7 @@ static void updateFastFaceRow(
makeFastFace(tile, lights[0], lights[1], lights[2], lights[3],
pf, sp, face_dir_corrected, scale, dest);
-
- g_profiler->avg("Meshgen: faces drawn by tiling", 0);
- for (int i = 1; i < continuous_tiles_count; i++)
- g_profiler->avg("Meshgen: faces drawn by tiling", 1);
+ g_profiler->avg("Meshgen: Tiles per face [#]", continuous_tiles_count);
}
continuous_tiles_count = 1;
diff --git a/src/client/mesh.cpp b/src/client/mesh.cpp
index 7fc7531f2..4d73ead8a 100644
--- a/src/client/mesh.cpp
+++ b/src/client/mesh.cpp
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "debug.h"
#include "log.h"
#include "irrMap.h"
+#include <cmath>
#include <iostream>
#include <IAnimatedMesh.h>
#include <SAnimatedMesh.h>
@@ -202,6 +203,20 @@ void setMeshColor(scene::IMesh *mesh, const video::SColor &color)
setMeshBufferColor(mesh->getMeshBuffer(j), color);
}
+template <typename F>
+static void applyToMesh(scene::IMesh *mesh, const F &fn)
+{
+ u16 mc = mesh->getMeshBufferCount();
+ for (u16 j = 0; j < mc; j++) {
+ scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
+ const u32 stride = getVertexPitchFromType(buf->getVertexType());
+ u32 vertex_count = buf->getVertexCount();
+ char *vertices = reinterpret_cast<char *>(buf->getVertices());
+ for (u32 i = 0; i < vertex_count; i++)
+ fn(reinterpret_cast<video::S3DVertex *>(vertices + i * stride));
+ }
+}
+
void colorizeMeshBuffer(scene::IMeshBuffer *buf, const video::SColor *buffercolor)
{
const u32 stride = getVertexPitchFromType(buf->getVertexType());
@@ -222,28 +237,20 @@ void setMeshColorByNormalXYZ(scene::IMesh *mesh,
const video::SColor &colorY,
const video::SColor &colorZ)
{
- if (mesh == NULL)
+ if (!mesh)
return;
-
- u16 mc = mesh->getMeshBufferCount();
- for (u16 j = 0; j < mc; j++) {
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
- const u32 stride = getVertexPitchFromType(buf->getVertexType());
- u32 vertex_count = buf->getVertexCount();
- u8 *vertices = (u8 *)buf->getVertices();
- for (u32 i = 0; i < vertex_count; i++) {
- video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
- f32 x = fabs(vertex->Normal.X);
- f32 y = fabs(vertex->Normal.Y);
- f32 z = fabs(vertex->Normal.Z);
- if (x >= y && x >= z)
- vertex->Color = colorX;
- else if (y >= z)
- vertex->Color = colorY;
- else
- vertex->Color = colorZ;
- }
- }
+ auto colorizator = [=] (video::S3DVertex *vertex) {
+ f32 x = fabs(vertex->Normal.X);
+ f32 y = fabs(vertex->Normal.Y);
+ f32 z = fabs(vertex->Normal.Z);
+ if (x >= y && x >= z)
+ vertex->Color = colorX;
+ else if (y >= z)
+ vertex->Color = colorY;
+ else
+ vertex->Color = colorZ;
+ };
+ applyToMesh(mesh, colorizator);
}
void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal,
@@ -251,132 +258,58 @@ void setMeshColorByNormal(scene::IMesh *mesh, const v3f &normal,
{
if (!mesh)
return;
+ auto colorizator = [normal, color] (video::S3DVertex *vertex) {
+ if (vertex->Normal == normal)
+ vertex->Color = color;
+ };
+ applyToMesh(mesh, colorizator);
+}
- u16 mc = mesh->getMeshBufferCount();
- for (u16 j = 0; j < mc; j++) {
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
- const u32 stride = getVertexPitchFromType(buf->getVertexType());
- u32 vertex_count = buf->getVertexCount();
- u8 *vertices = (u8 *)buf->getVertices();
- for (u32 i = 0; i < vertex_count; i++) {
- video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
- if (normal == vertex->Normal) {
- vertex->Color = color;
- }
- }
- }
+template <float v3f::*U, float v3f::*V>
+static void rotateMesh(scene::IMesh *mesh, float degrees)
+{
+ degrees *= M_PI / 180.0f;
+ float c = std::cos(degrees);
+ float s = std::sin(degrees);
+ auto rotator = [c, s] (video::S3DVertex *vertex) {
+ float u = vertex->Pos.*U;
+ float v = vertex->Pos.*V;
+ vertex->Pos.*U = c * u - s * v;
+ vertex->Pos.*V = s * u + c * v;
+ };
+ applyToMesh(mesh, rotator);
}
void rotateMeshXYby(scene::IMesh *mesh, f64 degrees)
{
- u16 mc = mesh->getMeshBufferCount();
- for (u16 j = 0; j < mc; j++) {
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
- const u32 stride = getVertexPitchFromType(buf->getVertexType());
- u32 vertex_count = buf->getVertexCount();
- u8 *vertices = (u8 *)buf->getVertices();
- for (u32 i = 0; i < vertex_count; i++)
- ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXYBy(degrees);
- }
+ rotateMesh<&v3f::X, &v3f::Y>(mesh, degrees);
}
void rotateMeshXZby(scene::IMesh *mesh, f64 degrees)
{
- u16 mc = mesh->getMeshBufferCount();
- for (u16 j = 0; j < mc; j++) {
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
- const u32 stride = getVertexPitchFromType(buf->getVertexType());
- u32 vertex_count = buf->getVertexCount();
- u8 *vertices = (u8 *)buf->getVertices();
- for (u32 i = 0; i < vertex_count; i++)
- ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateXZBy(degrees);
- }
+ rotateMesh<&v3f::X, &v3f::Z>(mesh, degrees);
}
void rotateMeshYZby(scene::IMesh *mesh, f64 degrees)
{
- u16 mc = mesh->getMeshBufferCount();
- for (u16 j = 0; j < mc; j++) {
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
- const u32 stride = getVertexPitchFromType(buf->getVertexType());
- u32 vertex_count = buf->getVertexCount();
- u8 *vertices = (u8 *)buf->getVertices();
- for (u32 i = 0; i < vertex_count; i++)
- ((video::S3DVertex *)(vertices + i * stride))->Pos.rotateYZBy(degrees);
- }
+ rotateMesh<&v3f::Y, &v3f::Z>(mesh, degrees);
}
void rotateMeshBy6dFacedir(scene::IMesh *mesh, int facedir)
{
int axisdir = facedir >> 2;
facedir &= 0x03;
-
- u16 mc = mesh->getMeshBufferCount();
- for (u16 j = 0; j < mc; j++) {
- scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
- const u32 stride = getVertexPitchFromType(buf->getVertexType());
- u32 vertex_count = buf->getVertexCount();
- u8 *vertices = (u8 *)buf->getVertices();
- for (u32 i = 0; i < vertex_count; i++) {
- video::S3DVertex *vertex = (video::S3DVertex *)(vertices + i * stride);
- switch (axisdir) {
- case 0:
- if (facedir == 1)
- vertex->Pos.rotateXZBy(-90);
- else if (facedir == 2)
- vertex->Pos.rotateXZBy(180);
- else if (facedir == 3)
- vertex->Pos.rotateXZBy(90);
- break;
- case 1: // z+
- vertex->Pos.rotateYZBy(90);
- if (facedir == 1)
- vertex->Pos.rotateXYBy(90);
- else if (facedir == 2)
- vertex->Pos.rotateXYBy(180);
- else if (facedir == 3)
- vertex->Pos.rotateXYBy(-90);
- break;
- case 2: //z-
- vertex->Pos.rotateYZBy(-90);
- if (facedir == 1)
- vertex->Pos.rotateXYBy(-90);
- else if (facedir == 2)
- vertex->Pos.rotateXYBy(180);
- else if (facedir == 3)
- vertex->Pos.rotateXYBy(90);
- break;
- case 3: //x+
- vertex->Pos.rotateXYBy(-90);
- if (facedir == 1)
- vertex->Pos.rotateYZBy(90);
- else if (facedir == 2)
- vertex->Pos.rotateYZBy(180);
- else if (facedir == 3)
- vertex->Pos.rotateYZBy(-90);
- break;
- case 4: //x-
- vertex->Pos.rotateXYBy(90);
- if (facedir == 1)
- vertex->Pos.rotateYZBy(-90);
- else if (facedir == 2)
- vertex->Pos.rotateYZBy(180);
- else if (facedir == 3)
- vertex->Pos.rotateYZBy(90);
- break;
- case 5:
- vertex->Pos.rotateXYBy(-180);
- if (facedir == 1)
- vertex->Pos.rotateXZBy(90);
- else if (facedir == 2)
- vertex->Pos.rotateXZBy(180);
- else if (facedir == 3)
- vertex->Pos.rotateXZBy(-90);
- break;
- default:
- break;
- }
- }
+ switch (facedir) {
+ case 1: rotateMeshXZby(mesh, -90); break;
+ case 2: rotateMeshXZby(mesh, 180); break;
+ case 3: rotateMeshXZby(mesh, 90); break;
+ }
+ switch (axisdir) {
+ case 1: rotateMeshYZby(mesh, 90); break; // z+
+ case 2: rotateMeshYZby(mesh, -90); break; // z-
+ case 3: rotateMeshXYby(mesh, -90); break; // x+
+ case 4: rotateMeshXYby(mesh, 90); break; // x-
+ case 5: rotateMeshXYby(mesh, -180); break;
}
}
@@ -410,8 +343,8 @@ scene::IMeshBuffer* cloneMeshBuffer(scene::IMeshBuffer *mesh_buffer)
video::S3DVertex2TCoords *v =
(video::S3DVertex2TCoords *) mesh_buffer->getVertices();
u16 *indices = mesh_buffer->getIndices();
- scene::SMeshBufferTangents *cloned_buffer =
- new scene::SMeshBufferTangents();
+ scene::SMeshBufferLightMap *cloned_buffer =
+ new scene::SMeshBufferLightMap();
cloned_buffer->append(v, mesh_buffer->getVertexCount(), indices,
mesh_buffer->getIndexCount());
return cloned_buffer;
diff --git a/src/client/mesh_generator_thread.cpp b/src/client/mesh_generator_thread.cpp
index be4bcc1f4..53b980eeb 100644
--- a/src/client/mesh_generator_thread.cpp
+++ b/src/client/mesh_generator_thread.cpp
@@ -98,7 +98,7 @@ void MeshUpdateQueue::addBlock(Map *map, v3s16 p, bool ack_block_to_server, bool
&cache_hit_counter);
cached_blocks.push_back(cached_block);
}
- g_profiler->avg("MeshUpdateQueue MapBlock cache hit %",
+ g_profiler->avg("MeshUpdateQueue: MapBlocks from cache [%]",
100.0f * cache_hit_counter / cached_blocks.size());
/*
@@ -162,39 +162,36 @@ QueuedMeshUpdate *MeshUpdateQueue::pop()
CachedMapBlockData* MeshUpdateQueue::cacheBlock(Map *map, v3s16 p, UpdateMode mode,
size_t *cache_hit_counter)
{
+ CachedMapBlockData *cached_block = nullptr;
std::map<v3s16, CachedMapBlockData*>::iterator it =
m_cache.find(p);
+
if (it != m_cache.end()) {
- // Already in cache
- CachedMapBlockData *cached_block = it->second;
+ cached_block = it->second;
+
if (mode == SKIP_UPDATE_IF_ALREADY_CACHED) {
if (cache_hit_counter)
(*cache_hit_counter)++;
return cached_block;
}
- MapBlock *b = map->getBlockNoCreateNoEx(p);
- if (b) {
- if (cached_block->data == NULL)
- cached_block->data =
- new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE];
- memcpy(cached_block->data, b->getData(),
- MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode));
- } else {
- delete[] cached_block->data;
- cached_block->data = NULL;
- }
- return cached_block;
}
- // Not yet in cache
- CachedMapBlockData *cached_block = new CachedMapBlockData();
- m_cache[p] = cached_block;
+ if (!cached_block) {
+ // Not yet in cache
+ cached_block = new CachedMapBlockData();
+ m_cache[p] = cached_block;
+ }
+
MapBlock *b = map->getBlockNoCreateNoEx(p);
if (b) {
- cached_block->data =
- new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE];
+ if (!cached_block->data)
+ cached_block->data =
+ new MapNode[MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE];
memcpy(cached_block->data, b->getData(),
MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE * sizeof(MapNode));
+ } else {
+ delete[] cached_block->data;
+ cached_block->data = nullptr;
}
return cached_block;
}
@@ -292,7 +289,7 @@ void MeshUpdateThread::doUpdate()
while ((q = m_queue_in.pop())) {
if (m_generation_interval)
sleep_ms(m_generation_interval);
- ScopeProfiler sp(g_profiler, "Client: Mesh making");
+ ScopeProfiler sp(g_profiler, "Client: Mesh making (sum)");
MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset);
diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp
index 4d83c088a..68770ec19 100644
--- a/src/client/minimap.cpp
+++ b/src/client/minimap.cpp
@@ -569,7 +569,7 @@ void Minimap::updateActiveMarkers()
m_active_markers.clear();
for (Nametag *nametag : nametags) {
- v3s16 pos = floatToInt(nametag->parent_node->getPosition() +
+ v3s16 pos = floatToInt(nametag->parent_node->getAbsolutePosition() +
intToFloat(client->getCamera()->getOffset(), BS), BS);
pos -= data->pos - v3s16(data->map_size / 2,
data->scan_height / 2,
diff --git a/src/client/particles.cpp b/src/client/particles.cpp
index ebd52f0f0..a0e4e54eb 100644
--- a/src/client/particles.cpp
+++ b/src/client/particles.cpp
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cmath>
#include "client.h"
#include "collision.h"
+#include "client/content_cao.h"
#include "client/clientevent.h"
#include "client/renderingengine.h"
#include "util/numeric.h"
@@ -38,9 +39,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
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);
+ 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);
}
Particle::Particle(
@@ -99,8 +101,13 @@ Particle::Particle(
m_glow = glow;
// Irrlicht stuff
- m_collisionbox = aabb3f
- (-size/2,-size/2,-size/2,size/2,size/2,size/2);
+ m_collisionbox = aabb3f(
+ -size / 2,
+ -size / 2,
+ -size / 2,
+ size / 2,
+ size / 2,
+ size / 2);
this->setAutomaticCulling(scene::EAC_OFF);
// Init lighting
@@ -120,7 +127,7 @@ void Particle::OnRegisterSceneNode()
void Particle::render()
{
- video::IVideoDriver* driver = SceneManager->getVideoDriver();
+ video::IVideoDriver *driver = SceneManager->getVideoDriver();
driver->setMaterial(m_material);
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
@@ -181,7 +188,7 @@ void Particle::updateLight()
floor(m_pos.Y+0.5),
floor(m_pos.Z+0.5)
);
- MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
+ MapNode n = m_env->getClientMap().getNode(p, &pos_ok);
if (pos_ok)
light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
else
@@ -291,24 +298,29 @@ ParticleSpawner::ParticleSpawner(
m_animation = anim;
m_glow = glow;
- for (u16 i = 0; i<=m_amount; i++)
+ for (u16 i = 0; i <= m_amount; i++)
{
- float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
+ float spawntime = (float)rand() / (float)RAND_MAX * m_spawntime;
m_spawntimes.push_back(spawntime);
}
}
void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
- bool is_attached, const v3f &attached_pos, float attached_yaw)
+ const core::matrix4 *attached_absolute_pos_rot_matrix)
{
v3f ppos = m_player->getPosition() / BS;
v3f pos = random_v3f(m_minpos, m_maxpos);
// Need to apply this first or the following check
// will be wrong for attached spawners
- if (is_attached) {
- pos.rotateXZBy(attached_yaw);
- pos += attached_pos;
+ if (attached_absolute_pos_rot_matrix) {
+ pos *= BS;
+ attached_absolute_pos_rot_matrix->transformVect(pos);
+ pos /= BS;
+ v3s16 camera_offset = m_particlemanager->m_env->getCameraOffset();
+ pos.X += camera_offset.X;
+ pos.Y += camera_offset.Y;
+ pos.Z += camera_offset.Z;
}
if (pos.getDistanceFrom(ppos) > radius)
@@ -317,18 +329,19 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
v3f vel = random_v3f(m_minvel, m_maxvel);
v3f acc = random_v3f(m_minacc, m_maxacc);
- if (is_attached) {
- // Apply attachment yaw
- vel.rotateXZBy(attached_yaw);
- acc.rotateXZBy(attached_yaw);
+ if (attached_absolute_pos_rot_matrix) {
+ // Apply attachment rotation
+ attached_absolute_pos_rot_matrix->rotateVect(vel);
+ attached_absolute_pos_rot_matrix->rotateVect(acc);
}
float exptime = rand() / (float)RAND_MAX
- * (m_maxexptime - m_minexptime)
- + m_minexptime;
+ * (m_maxexptime - m_minexptime)
+ + m_minexptime;
+
float size = rand() / (float)RAND_MAX
- * (m_maxsize - m_minsize)
- + m_minsize;
+ * (m_maxsize - m_minsize)
+ + m_minsize;
m_particlemanager->addParticle(new Particle(
m_gamedef,
@@ -351,7 +364,7 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
));
}
-void ParticleSpawner::step(float dtime, ClientEnvironment* env)
+void ParticleSpawner::step(float dtime, ClientEnvironment *env)
{
m_time += dtime;
@@ -359,14 +372,10 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env)
g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
bool unloaded = false;
- bool is_attached = false;
- v3f attached_pos = v3f(0,0,0);
- float attached_yaw = 0;
- if (m_attached_id != 0) {
- if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
- attached_pos = attached->getPosition() / BS;
- attached_yaw = attached->getYaw();
- is_attached = true;
+ 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();
} else {
unloaded = true;
}
@@ -377,12 +386,12 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env)
for (std::vector<float>::iterator i = m_spawntimes.begin();
i != m_spawntimes.end();) {
if ((*i) <= m_time && m_amount > 0) {
- m_amount--;
+ --m_amount;
// Pretend to, but don't actually spawn a particle if it is
// attached to an unloaded object or distant from player.
if (!unloaded)
- spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
+ spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
i = m_spawntimes.erase(i);
} else {
@@ -398,13 +407,13 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env)
for (int i = 0; i <= m_amount; i++) {
if (rand() / (float)RAND_MAX < dtime)
- spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
+ spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
}
}
}
-ParticleManager::ParticleManager(ClientEnvironment* env) :
+ParticleManager::ParticleManager(ClientEnvironment *env) :
m_env(env)
{}
@@ -419,7 +428,7 @@ void ParticleManager::step(float dtime)
stepSpawners (dtime);
}
-void ParticleManager::stepSpawners (float dtime)
+void ParticleManager::stepSpawners(float dtime)
{
MutexAutoLock lock(m_spawner_list_lock);
for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
@@ -433,7 +442,7 @@ void ParticleManager::stepSpawners (float dtime)
}
}
-void ParticleManager::stepParticles (float dtime)
+void ParticleManager::stepParticles(float dtime)
{
MutexAutoLock lock(m_particle_list_lock);
for (auto i = m_particles.begin(); i != m_particles.end();) {
@@ -448,7 +457,7 @@ void ParticleManager::stepParticles (float dtime)
}
}
-void ParticleManager::clearAll ()
+void ParticleManager::clearAll()
{
MutexAutoLock lock(m_spawner_list_lock);
MutexAutoLock lock2(m_particle_list_lock);
@@ -457,9 +466,7 @@ void ParticleManager::clearAll ()
m_particle_spawners.erase(i++);
}
- for(std::vector<Particle*>::iterator i =
- m_particles.begin();
- i != m_particles.end();)
+ for(auto i = m_particles.begin(); i != m_particles.end();)
{
(*i)->remove();
delete *i;
@@ -568,7 +575,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
// The final burst of particles when a node is finally dug, *not* particles
// spawned during the digging of a node.
-void ParticleManager::addDiggingParticles(IGameDef* gamedef,
+void ParticleManager::addDiggingParticles(IGameDef *gamedef,
LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
{
// No particles for "airlike" nodes
@@ -583,7 +590,7 @@ void ParticleManager::addDiggingParticles(IGameDef* gamedef,
// During the digging of a node particles are spawned individually by this
// function, called from Game::handleDigging() in game.cpp.
-void ParticleManager::addNodeParticle(IGameDef* gamedef,
+void ParticleManager::addNodeParticle(IGameDef *gamedef,
LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
{
// No particles for "airlike" nodes
@@ -635,7 +642,7 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef,
else
n.getColor(f, &color);
- Particle* toadd = new Particle(
+ Particle *toadd = new Particle(
gamedef,
player,
m_env,
@@ -658,7 +665,7 @@ void ParticleManager::addNodeParticle(IGameDef* gamedef,
addParticle(toadd);
}
-void ParticleManager::addParticle(Particle* toadd)
+void ParticleManager::addParticle(Particle *toadd)
{
MutexAutoLock lock(m_particle_list_lock);
m_particles.push_back(toadd);
diff --git a/src/client/particles.h b/src/client/particles.h
index 353743372..e7b8cbe24 100644
--- a/src/client/particles.h
+++ b/src/client/particles.h
@@ -144,8 +144,7 @@ public:
private:
void spawnParticle(ClientEnvironment *env, float radius,
- bool is_attached, const v3f &attached_pos,
- float attached_yaw);
+ const core::matrix4 *attached_absolute_pos_rot_matrix);
ParticleManager *m_particlemanager;
float m_time;
diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp
index 8c70b36c6..bf5aa6c2c 100644
--- a/src/client/render/core.cpp
+++ b/src/client/render/core.cpp
@@ -86,7 +86,7 @@ void RenderingCore::drawHUD()
if (show_hud) {
if (draw_crosshair)
hud->drawCrosshair();
- hud->drawHotbar(client->getPlayerItem());
+ hud->drawHotbar(client->getEnv().getLocalPlayer()->getWieldIndex());
hud->drawLuaElements(camera->getOffset());
camera->drawNametags();
if (mapper && show_minimap)
diff --git a/src/client/render/plain.cpp b/src/client/render/plain.cpp
index cc17c98c3..a130a14eb 100644
--- a/src/client/render/plain.cpp
+++ b/src/client/render/plain.cpp
@@ -35,7 +35,7 @@ RenderingCorePlain::RenderingCorePlain(
void RenderingCorePlain::initTextures()
{
- if (!scale)
+ if (scale <= 1)
return;
v2u32 size{scaledown(scale, screensize.X), scaledown(scale, screensize.Y)};
lowres = driver->addRenderTargetTexture(
@@ -44,21 +44,21 @@ void RenderingCorePlain::initTextures()
void RenderingCorePlain::clearTextures()
{
- if (!scale)
+ if (scale <= 1)
return;
driver->removeTexture(lowres);
}
void RenderingCorePlain::beforeDraw()
{
- if (!scale)
+ if (scale <= 1)
return;
driver->setRenderTarget(lowres, true, true, skycolor);
}
void RenderingCorePlain::upscale()
{
- if (!scale)
+ if (scale <= 1)
return;
driver->setRenderTarget(0, true, true);
v2u32 size{scaledown(scale, screensize.X), scaledown(scale, screensize.Y)};
diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp
index b18e91f6e..6e6509eeb 100644
--- a/src/client/renderingengine.cpp
+++ b/src/client/renderingengine.cpp
@@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "render/factory.h"
#include "inputhandler.h"
#include "gettext.h"
+#include "../gui/guiSkin.h"
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__ANDROID__) && \
!defined(SERVER) && !defined(__HAIKU__)
@@ -44,14 +45,38 @@ 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 __ANDROID__
+#if ENABLE_GLES
#include "filesys.h"
#endif
RenderingEngine *RenderingEngine::s_singleton = nullptr;
+
+static gui::GUISkin *createSkin(gui::IGUIEnvironment *environment,
+ gui::EGUI_SKIN_TYPE type, video::IVideoDriver *driver)
+{
+ gui::GUISkin *skin = new gui::GUISkin(type, driver);
+
+ gui::IGUIFont *builtinfont = environment->getBuiltInFont();
+ gui::IGUIFontBitmap *bitfont = nullptr;
+ if (builtinfont && builtinfont->getType() == gui::EGFT_BITMAP)
+ bitfont = (gui::IGUIFontBitmap*)builtinfont;
+
+ gui::IGUISpriteBank *bank = 0;
+ skin->setFont(builtinfont);
+
+ if (bitfont)
+ bank = bitfont->getSpriteBank();
+
+ skin->setSpriteBank(bank);
+
+ return skin;
+}
+
+
RenderingEngine::RenderingEngine(IEventReceiver *receiver)
{
sanity_check(!s_singleton);
@@ -77,7 +102,7 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
u32 i;
for (i = 0; i != drivers.size(); i++) {
if (!strcasecmp(driverstring.c_str(),
- RenderingEngine::getVideoDriverName(drivers[i]))) {
+ RenderingEngine::getVideoDriverName(drivers[i]))) {
driverType = drivers[i];
break;
}
@@ -106,12 +131,22 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver)
params.OGLES2ShaderPath = std::string(porting::path_user + DIR_DELIM + "media" +
DIR_DELIM + "Shaders" + DIR_DELIM).c_str();
// clang-format on
+#elif ENABLE_GLES
+ // there is no standardized path for these on desktop
+ std::string rel_path = std::string("client") + DIR_DELIM
+ + "shaders" + DIR_DELIM + "Irrlicht";
+ params.OGLES2ShaderPath = (porting::path_share + DIR_DELIM + rel_path + DIR_DELIM).c_str();
#endif
m_device = createDeviceEx(params);
driver = m_device->getVideoDriver();
s_singleton = this;
+
+ auto skin = createSkin(m_device->getGUIEnvironment(),
+ gui::EGST_WINDOWS_METALLIC, driver);
+ m_device->getGUIEnvironment()->setSkin(skin);
+ skin->drop();
}
RenderingEngine::~RenderingEngine()
@@ -193,7 +228,7 @@ bool RenderingEngine::setupTopLevelWindow(const std::string &name)
// 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.
-
+
/* Setting Xorg properties for the top level window */
setupTopLevelXorgWindow(name);
/* Done with Xorg properties */
@@ -211,7 +246,7 @@ bool RenderingEngine::setupTopLevelWindow(const std::string &name)
/* Done with general properties */
// FIXME: setWindowIcon returns a bool result but it is unused.
- // For now continue to return this result.
+ // For now continue to return this result.
return result;
}
@@ -223,7 +258,7 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name)
Display *x11_dpl = reinterpret_cast<Display *>(exposedData.OpenGLLinux.X11Display);
if (x11_dpl == NULL) {
warningstream << "Client: Could not find X11 Display in ExposedVideoData"
- << std::endl;
+ << std::endl;
return;
}
@@ -244,30 +279,30 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name)
// FIXME: In the future WMNormalHints should be set ... e.g see the
// gtk/gdk code (gdk/x11/gdksurface-x11.c) for the setup_top_level
- // method. But for now (as it would require some significant changes)
- // leave the code as is.
-
+ // method. But for now (as it would require some significant changes)
+ // leave the code as is.
+
// The following is borrowed from the above gdk source for setting top
// level windows. The source indicates and the Xlib docs suggest that
- // this will set the WM_CLIENT_MACHINE and WM_LOCAL_NAME. This will not
- // set the WM_CLIENT_MACHINE to a Fully Qualified Domain Name (FQDN) which is
+ // this will set the WM_CLIENT_MACHINE and WM_LOCAL_NAME. This will not
+ // set the WM_CLIENT_MACHINE to a Fully Qualified Domain Name (FQDN) which is
// required by the Extended Window Manager Hints (EWMH) spec when setting
// the _NET_WM_PID (see further down) but running Minetest in an env
// where the window manager is on another machine from Minetest (therefore
// making the PID useless) is not expected to be a problem. Further
// more, using gtk/gdk as the model it would seem that not using a FQDN is
// not an issue for modern Xorg window managers.
-
+
verbosestream << "Client: Setting Xorg window manager Properties"
<< std::endl;
XSetWMProperties (x11_dpl, x11_win, NULL, NULL, NULL, 0, NULL, NULL, NULL);
- // Set the _NET_WM_PID window property according to the EWMH spec. _NET_WM_PID
- // (in conjunction with WM_CLIENT_MACHINE) can be used by window managers to
- // force a shutdown of an application if it doesn't respond to the destroy
- // window message.
-
+ // Set the _NET_WM_PID window property according to the EWMH spec. _NET_WM_PID
+ // (in conjunction with WM_CLIENT_MACHINE) can be used by window managers to
+ // force a shutdown of an application if it doesn't respond to the destroy
+ // window message.
+
verbosestream << "Client: Setting Xorg _NET_WM_PID extened window manager property"
<< std::endl;
@@ -277,12 +312,12 @@ void RenderingEngine::setupTopLevelXorgWindow(const std::string &name)
infostream << "Client: PID is '" << static_cast<long>(pid) << "'"
<< std::endl;
- XChangeProperty(x11_dpl, x11_win, NET_WM_PID,
- XA_CARDINAL, 32, PropModeReplace,
+ XChangeProperty(x11_dpl, x11_win, NET_WM_PID,
+ XA_CARDINAL, 32, PropModeReplace,
reinterpret_cast<unsigned char *>(&pid),1);
// Set the WM_CLIENT_LEADER window property here. Minetest has only one
- // window and that window will always be the leader.
+ // window and that window will always be the leader.
verbosestream << "Client: Setting Xorg WM_CLIENT_LEADER property"
<< std::endl;
diff --git a/src/client/shader.cpp b/src/client/shader.cpp
index 3b49a36ba..f36ff3d85 100644
--- a/src/client/shader.cpp
+++ b/src/client/shader.cpp
@@ -527,15 +527,18 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp
switch (material_type) {
case TILE_MATERIAL_OPAQUE:
case TILE_MATERIAL_LIQUID_OPAQUE:
+ case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
shaderinfo.base_material = video::EMT_SOLID;
break;
case TILE_MATERIAL_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_WAVING_LEAVES:
case TILE_MATERIAL_WAVING_PLANTS:
+ case TILE_MATERIAL_WAVING_LIQUID_BASIC:
shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
break;
}
@@ -631,10 +634,13 @@ ShaderInfo generate_shader(const std::string &name, u8 material_type, u8 drawtyp
"TILE_MATERIAL_LIQUID_OPAQUE",
"TILE_MATERIAL_WAVING_LEAVES",
"TILE_MATERIAL_WAVING_PLANTS",
- "TILE_MATERIAL_OPAQUE"
+ "TILE_MATERIAL_OPAQUE",
+ "TILE_MATERIAL_WAVING_LIQUID_BASIC",
+ "TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT",
+ "TILE_MATERIAL_WAVING_LIQUID_OPAQUE",
};
- for (int i = 0; i < 7; i++){
+ for (int i = 0; i < 10; i++){
shaders_header += "#define ";
shaders_header += materialTypes[i];
shaders_header += " ";
diff --git a/src/client/sky.cpp b/src/client/sky.cpp
index ff968444d..346cd0642 100644
--- a/src/client/sky.cpp
+++ b/src/client/sky.cpp
@@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/renderingengine.h"
#include "settings.h"
#include "camera.h" // CameraModes
+#include "config.h"
Sky::Sky(s32 id, ITextureSource *tsrc):
@@ -44,7 +45,7 @@ Sky::Sky(s32 id, ITextureSource *tsrc):
video::SMaterial mat;
mat.Lighting = false;
-#ifdef __ANDROID__
+#if ENABLE_GLES
mat.ZBuffer = video::ECFN_DISABLED;
#else
mat.ZBuffer = video::ECFN_NEVER;
@@ -118,8 +119,8 @@ void Sky::render()
if (!m_visible)
return;
- video::IVideoDriver* driver = SceneManager->getVideoDriver();
- scene::ICameraSceneNode* camera = SceneManager->getActiveCamera();
+ video::IVideoDriver *driver = SceneManager->getVideoDriver();
+ scene::ICameraSceneNode *camera = SceneManager->getActiveCamera();
if (!camera || !driver)
return;
@@ -273,7 +274,7 @@ void Sky::render()
// Stars are only drawn when brighter than skycolor
if (starcolor.getBlue() < m_skycolor.getBlue())
break;
-#ifdef __ANDROID__
+#if ENABLE_GLES
u16 indices[SKY_STAR_COUNT * 3];
video::S3DVertex vertices[SKY_STAR_COUNT * 3];
for (u32 i = 0; i < SKY_STAR_COUNT; i++) {
@@ -337,7 +338,7 @@ void Sky::render()
indices, SKY_STAR_COUNT, video::EVT_STANDARD,
scene::EPT_QUADS, video::EIT_16BIT);
#endif
- } while(false);
+ } while (false);
// Draw sunrise/sunset horizon glow texture (textures/base/pack/sunrisebg.png)
{
@@ -366,154 +367,12 @@ void Sky::render()
// Draw sun
if (wicked_time_of_day > 0.15 && wicked_time_of_day < 0.85) {
- if (!m_sun_texture) {
- driver->setMaterial(m_materials[1]);
- float d = sunsize * 1.7;
- video::SColor c = suncolor;
- c.setAlpha(0.05 * 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- d = sunsize * 1.2;
- c = suncolor;
- c.setAlpha(0.15 * 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- d = sunsize;
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, suncolor, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, suncolor, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- d = sunsize * 0.7;
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, suncolor2, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, suncolor2, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, suncolor2, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, suncolor2, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- } else {
- driver->setMaterial(m_materials[3]);
- float d = sunsize * 1.7;
- video::SColor c;
- if (m_sun_tonemap)
- c = video::SColor (0, 0, 0, 0);
- else
- c = video::SColor (255, 255, 255, 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to +X (east)
- vertex.Pos.rotateXZBy(90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- }
+ draw_sun(driver, sunsize, suncolor, suncolor2, wicked_time_of_day);
}
// Draw moon
if (wicked_time_of_day < 0.3 || wicked_time_of_day > 0.7) {
- if (!m_moon_texture) {
- driver->setMaterial(m_materials[1]);
- float d = moonsize * 1.9;
- video::SColor c = mooncolor;
- c.setAlpha(0.05 * 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- d = moonsize * 1.3;
- c = mooncolor;
- c.setAlpha(0.15 * 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- d = moonsize;
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, mooncolor, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, mooncolor, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, mooncolor, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, mooncolor, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
-
- float d2 = moonsize * 0.6;
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, mooncolor2, t, t);
- vertices[1] = video::S3DVertex( d2,-d, -1, 0, 0, 1, mooncolor2, o, t);
- vertices[2] = video::S3DVertex( d2, d2, -1, 0, 0, 1, mooncolor2, o, o);
- vertices[3] = video::S3DVertex(-d, d2, -1, 0, 0, 1, mooncolor2, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- } else {
- driver->setMaterial(m_materials[4]);
- float d = moonsize * 1.9;
- video::SColor c;
- if (m_moon_tonemap)
- c = video::SColor (0, 0, 0, 0);
- else
- c = video::SColor (255, 255, 255, 255);
- vertices[0] = video::S3DVertex(-d, -d, -1, 0, 0, 1, c, t, t);
- vertices[1] = video::S3DVertex( d, -d, -1, 0, 0, 1, c, o, t);
- vertices[2] = video::S3DVertex( d, d, -1, 0, 0, 1, c, o, o);
- vertices[3] = video::S3DVertex(-d, d, -1, 0, 0, 1, c, t, o);
- for (video::S3DVertex &vertex : vertices) {
- // Switch from -Z (south) to -X (west)
- vertex.Pos.rotateXZBy(-90);
- vertex.Pos.rotateXYBy(wicked_time_of_day * 360 - 90);
- }
- driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
- }
+ draw_moon(driver, moonsize, mooncolor, mooncolor2, wicked_time_of_day);
}
// Draw far cloudy fog thing below all horizons in front of sun, moon
@@ -555,8 +414,8 @@ void Sky::render()
void Sky::update(float time_of_day, float time_brightness,
- float direct_brightness, bool sunlight_seen,
- CameraMode cam_mode, float yaw, float pitch)
+ float direct_brightness, bool sunlight_seen,
+ CameraMode cam_mode, float yaw, float pitch)
{
// Stabilize initial brightness and color values by flooding updates
if (m_first_update) {
@@ -766,3 +625,128 @@ void Sky::update(float time_of_day, float time_brightness,
video::SColorf(pointcolor), m_horizon_blend() * 0.25);
}
}
+
+void Sky::draw_sun(video::IVideoDriver *driver, float sunsize, const video::SColor &suncolor,
+ const video::SColor &suncolor2, float wicked_time_of_day)
+ /* Draw sun in the sky.
+ * driver: Video driver object used to draw
+ * sunsize: the default size of the sun
+ * suncolor: main sun color
+ * suncolor2: second sun color
+ * wicked_time_of_day: current time of day, to know where should be the sun in the sky
+ */
+{
+ static const u16 indices[4] = {0, 1, 2, 3};
+ std::array<video::S3DVertex, 4> vertices;
+ if (!m_sun_texture) {
+ driver->setMaterial(m_materials[1]);
+ const float sunsizes[4] = {sunsize * 1.7f, sunsize * 1.2f, sunsize, sunsize * 0.7f};
+ video::SColor c1 = suncolor;
+ video::SColor c2 = suncolor;
+ c1.setAlpha(0.05 * 255);
+ c2.setAlpha(0.15 * 255);
+ const video::SColor colors[4] = {c1, c2, suncolor, suncolor2};
+ for (int i = 0; i < 4; i++) {
+ draw_sky_body(vertices, -sunsizes[i], sunsizes[i], colors[i]);
+ place_sky_body(vertices, 90, wicked_time_of_day * 360 - 90);
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+ }
+ } else {
+ driver->setMaterial(m_materials[3]);
+ float d = sunsize * 1.7;
+ video::SColor c;
+ if (m_sun_tonemap)
+ c = video::SColor(0, 0, 0, 0);
+ else
+ c = video::SColor(255, 255, 255, 255);
+ draw_sky_body(vertices, -d, d, c);
+ place_sky_body(vertices, 90, wicked_time_of_day * 360 - 90);
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+ }
+}
+
+
+void Sky::draw_moon(video::IVideoDriver *driver, float moonsize, const video::SColor &mooncolor,
+ const video::SColor &mooncolor2, float wicked_time_of_day)
+ /*
+ * Draw moon in the sky.
+ * driver: Video driver object used to draw
+ * moonsize: the default size of the moon
+ * mooncolor: main moon color
+ * mooncolor2: second moon color
+ * wicked_time_of_day: current time of day, to know where should be the moon in the sky
+ */
+{
+ static const u16 indices[4] = {0, 1, 2, 3};
+ std::array<video::S3DVertex, 4> vertices;
+ if (!m_moon_texture) {
+ driver->setMaterial(m_materials[1]);
+ const float moonsizes_1[4] = {
+ -moonsize * 1.9f,
+ -moonsize * 1.3f,
+ -moonsize,
+ -moonsize
+ };
+ const float moonsizes_2[4] = {
+ moonsize * 1.9f,
+ moonsize * 1.3f,
+ moonsize,
+ moonsize * 0.6f
+ };
+ video::SColor c1 = mooncolor;
+ video::SColor c2 = mooncolor;
+ c1.setAlpha(0.05 * 255);
+ c2.setAlpha(0.15 * 255);
+ const video::SColor colors[4] = {c1, c2, mooncolor, mooncolor2};
+ for (int i = 0; i < 4; i++) {
+ draw_sky_body(vertices, moonsizes_1[i], moonsizes_2[i], colors[i]);
+ place_sky_body(vertices, -90, wicked_time_of_day * 360 - 90);
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+ }
+ } else {
+ driver->setMaterial(m_materials[4]);
+ float d = moonsize * 1.9;
+ video::SColor c;
+ if (m_moon_tonemap)
+ c = video::SColor(0, 0, 0, 0);
+ else
+ c = video::SColor(255, 255, 255, 255);
+ draw_sky_body(vertices, -d, d, c);
+ place_sky_body(vertices, -90, wicked_time_of_day * 360 - 90);
+ driver->drawIndexedTriangleFan(&vertices[0], 4, indices, 2);
+ }
+}
+
+
+void Sky::draw_sky_body(std::array<video::S3DVertex, 4> &vertices, float pos_1, float pos_2, const video::SColor &c)
+{
+ /*
+ * Create an array of vertices with the dimensions specified.
+ * pos_1, pos_2: position of the body's vertices
+ * c: color of the body
+ */
+
+ const f32 t = 1.0f;
+ const f32 o = 0.0f;
+ vertices[0] = video::S3DVertex(pos_1, pos_1, -1, 0, 0, 1, c, t, t);
+ vertices[1] = video::S3DVertex(pos_2, pos_1, -1, 0, 0, 1, c, o, t);
+ vertices[2] = video::S3DVertex(pos_2, pos_2, -1, 0, 0, 1, c, o, o);
+ vertices[3] = video::S3DVertex(pos_1, pos_2, -1, 0, 0, 1, c, t, o);
+}
+
+
+void Sky::place_sky_body(
+ std::array<video::S3DVertex, 4> &vertices, float horizon_position, float day_position)
+ /*
+ * Place body in the sky.
+ * vertices: The body as a rectangle of 4 vertices
+ * horizon_position: turn the body around the Y axis
+ * day_position: turn the body around the Z axis, to place it depending of the time of the day
+ */
+{
+ for (video::S3DVertex &vertex : vertices) {
+ // Body is directed to -Z (south) by default
+ vertex.Pos.rotateXZBy(horizon_position);
+ vertex.Pos.rotateXYBy(day_position);
+ }
+}
diff --git a/src/client/sky.h b/src/client/sky.h
index b66a4990f..9cff20e08 100644
--- a/src/client/sky.h
+++ b/src/client/sky.h
@@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include <ISceneNode.h>
+#include <array>
#include "camera.h"
#include "irrlichttypes_extrabloated.h"
@@ -145,4 +146,13 @@ private:
video::ITexture *m_moon_texture;
video::ITexture *m_sun_tonemap;
video::ITexture *m_moon_tonemap;
+ void draw_sun(video::IVideoDriver *driver, float sunsize, const video::SColor &suncolor,
+ const video::SColor &suncolor2, float wicked_time_of_day);
+ void draw_moon(video::IVideoDriver *driver, float moonsize, const video::SColor &mooncolor,
+ const video::SColor &mooncolor2, float wicked_time_of_day);
+ void draw_sky_body(std::array<video::S3DVertex, 4> &vertices,
+ float pos_1, float pos_2, const video::SColor &c);
+ void place_sky_body(
+ std::array<video::S3DVertex, 4> &vertices, float horizon_position,
+ float day_position);
};
diff --git a/src/client/tile.cpp b/src/client/tile.cpp
index 4911013ae..3d9e2470a 100644
--- a/src/client/tile.cpp
+++ b/src/client/tile.cpp
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <algorithm>
#include <ICameraSceneNode.h>
+#include <IrrCompileConfig.h>
#include "util/string.h"
#include "util/container.h"
#include "util/thread.h"
@@ -34,8 +35,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "renderingengine.h"
-#ifdef __ANDROID__
+#if ENABLE_GLES
+#ifdef _IRR_COMPILE_WITH_OGLES1_
#include <GLES/gl.h>
+#else
+#include <GLES2/gl2.h>
+#endif
#endif
/*
@@ -117,9 +122,14 @@ std::string getImagePath(std::string path)
Utilizes a thread-safe cache.
*/
-std::string getTexturePath(const std::string &filename)
+std::string getTexturePath(const std::string &filename, bool *is_base_pack)
{
std::string fullpath;
+
+ // This can set a wrong value on cached textures, but is irrelevant because
+ // is_base_pack is only passed when initializing the textures the first time
+ if (is_base_pack)
+ *is_base_pack = false;
/*
Check from cache
*/
@@ -131,7 +141,8 @@ std::string getTexturePath(const std::string &filename)
Check from texture_path
*/
for (const auto &path : getTextureDirs()) {
- std::string testpath = path + DIR_DELIM + filename;
+ std::string testpath = path + DIR_DELIM;
+ testpath.append(filename);
// Check all filename extensions. Returns "" if not found.
fullpath = getImagePath(testpath);
if (!fullpath.empty())
@@ -148,6 +159,8 @@ std::string getTexturePath(const std::string &filename)
std::string testpath = base_path + DIR_DELIM + filename;
// Check all filename extensions. Returns "" if not found.
fullpath = getImagePath(testpath);
+ if (is_base_pack && !fullpath.empty())
+ *is_base_pack = true;
}
// Add to cache (also an empty result is cached)
@@ -209,9 +222,11 @@ public:
bool need_to_grab = true;
// Try to use local texture instead if asked to
- if (prefer_local){
- std::string path = getTexturePath(name);
- if (!path.empty()) {
+ if (prefer_local) {
+ bool is_base_pack;
+ std::string path = getTexturePath(name, &is_base_pack);
+ // Ignore base pack
+ if (!path.empty() && !is_base_pack) {
video::IImage *img2 = RenderingEngine::get_video_driver()->
createImageFromFile(path.c_str());
if (img2){
@@ -594,7 +609,7 @@ u32 TextureSource::generateTexture(const std::string &name)
video::ITexture *tex = NULL;
if (img != NULL) {
-#ifdef __ANDROID__
+#if ENABLE_GLES
img = Align2Npot2(img, driver);
#endif
// Create texture from resulting image
@@ -751,7 +766,7 @@ void TextureSource::rebuildImagesAndTextures()
// Recreate textures
for (TextureInfo &ti : m_textureinfo_cache) {
video::IImage *img = generateImage(ti.name);
-#ifdef __ANDROID__
+#if ENABLE_GLES
img = Align2Npot2(img, driver);
#endif
// Create texture from resulting image
@@ -988,8 +1003,30 @@ video::IImage* TextureSource::generateImage(const std::string &name)
return baseimg;
}
-#ifdef __ANDROID__
-#include <GLES/gl.h>
+#if ENABLE_GLES
+
+
+static inline u16 get_GL_major_version()
+{
+ const GLubyte *gl_version = glGetString(GL_VERSION);
+ return (u16) (gl_version[0] - '0');
+}
+
+/**
+ * Check if hardware requires npot2 aligned textures
+ * @return true if alignment NOT(!) requires, false otherwise
+ */
+
+bool hasNPotSupport()
+{
+ // Only GLES2 is trusted to correctly report npot support
+ // Note: we cache the boolean result, the GL context will never change.
+ static const bool supported = get_GL_major_version() > 1 &&
+ glGetString(GL_EXTENSIONS) &&
+ strstr((char *)glGetString(GL_EXTENSIONS), "GL_OES_texture_npot");
+ return supported;
+}
+
/**
* Check and align image to npot2 if required by hardware
* @param image image to check for npot2 alignment
@@ -997,53 +1034,33 @@ video::IImage* TextureSource::generateImage(const std::string &name)
* @return image or copy of image aligned to npot2
*/
-inline u16 get_GL_major_version()
-{
- const GLubyte *gl_version = glGetString(GL_VERSION);
- return (u16) (gl_version[0] - '0');
-}
-
video::IImage * Align2Npot2(video::IImage * image,
video::IVideoDriver* driver)
{
- if (image == NULL) {
+ if (image == NULL)
return image;
- }
- core::dimension2d<u32> dim = image->getDimension();
-
- // Only GLES2 is trusted to correctly report npot support
- // Note: we cache the boolean result. GL context will never change on Android.
- static const bool hasNPotSupport = get_GL_major_version() > 1 &&
- glGetString(GL_EXTENSIONS) &&
- strstr((char *)glGetString(GL_EXTENSIONS), "GL_OES_texture_npot");
-
- if (hasNPotSupport)
+ if (hasNPotSupport())
return image;
+ core::dimension2d<u32> dim = image->getDimension();
unsigned int height = npot2(dim.Height);
unsigned int width = npot2(dim.Width);
- if ((dim.Height == height) &&
- (dim.Width == width)) {
+ if (dim.Height == height && dim.Width == width)
return image;
- }
- if (dim.Height > height) {
+ if (dim.Height > height)
height *= 2;
- }
-
- if (dim.Width > width) {
+ if (dim.Width > width)
width *= 2;
- }
video::IImage *targetimage =
driver->createImage(video::ECF_A8R8G8B8,
core::dimension2d<u32>(width, height));
- if (targetimage != NULL) {
+ if (targetimage != NULL)
image->copyToScaling(targetimage);
- }
image->drop();
return targetimage;
}
@@ -1077,7 +1094,7 @@ bool TextureSource::generateImagePart(std::string part_of_name,
// Stuff starting with [ are special commands
if (part_of_name.empty() || part_of_name[0] != '[') {
video::IImage *image = m_sourcecache.getOrLoad(part_of_name);
-#ifdef __ANDROID__
+#if ENABLE_GLES
image = Align2Npot2(image, driver);
#endif
if (image == NULL) {
diff --git a/src/client/tile.h b/src/client/tile.h
index 2a33dd160..533df676e 100644
--- a/src/client/tile.h
+++ b/src/client/tile.h
@@ -27,8 +27,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <SMaterial.h>
#include <memory>
#include "util/numeric.h"
+#include "config.h"
-#if __ANDROID__
+#if ENABLE_GLES
#include <IVideoDriver.h>
#endif
@@ -63,7 +64,7 @@ std::string getImagePath(std::string path);
Utilizes a thread-safe cache.
*/
-std::string getTexturePath(const std::string &filename);
+std::string getTexturePath(const std::string &filename, bool *is_base_pack = nullptr);
void clearTextureNameCache();
@@ -133,7 +134,8 @@ public:
IWritableTextureSource *createTextureSource();
-#ifdef __ANDROID__
+#if ENABLE_GLES
+bool hasNPotSupport();
video::IImage * Align2Npot2(video::IImage * image, irr::video::IVideoDriver* driver);
#endif
@@ -144,7 +146,10 @@ enum MaterialType{
TILE_MATERIAL_LIQUID_OPAQUE,
TILE_MATERIAL_WAVING_LEAVES,
TILE_MATERIAL_WAVING_PLANTS,
- TILE_MATERIAL_OPAQUE
+ TILE_MATERIAL_OPAQUE,
+ TILE_MATERIAL_WAVING_LIQUID_BASIC,
+ TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT,
+ TILE_MATERIAL_WAVING_LIQUID_OPAQUE,
};
// Material flags
@@ -208,16 +213,19 @@ struct TileLayer
switch (material_type) {
case TILE_MATERIAL_OPAQUE:
case TILE_MATERIAL_LIQUID_OPAQUE:
+ case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
material.MaterialType = video::EMT_SOLID;
break;
case TILE_MATERIAL_BASIC:
case TILE_MATERIAL_WAVING_LEAVES:
case TILE_MATERIAL_WAVING_PLANTS:
+ case TILE_MATERIAL_WAVING_LIQUID_BASIC:
material.MaterialTypeParam = 0.5;
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
break;
case TILE_MATERIAL_ALPHA:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
+ case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
break;
default:
diff --git a/src/clientiface.cpp b/src/clientiface.cpp
index a55e0f7b5..dceaa64f2 100644
--- a/src/clientiface.cpp
+++ b/src/clientiface.cpp
@@ -636,7 +636,7 @@ ClientInterface::~ClientInterface()
Delete clients
*/
{
- MutexAutoLock clientslock(m_clients_mutex);
+ RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (auto &client_it : m_clients) {
// Delete client
@@ -648,7 +648,7 @@ ClientInterface::~ClientInterface()
std::vector<session_t> ClientInterface::getClientIDs(ClientState min_state)
{
std::vector<session_t> reply;
- MutexAutoLock clientslock(m_clients_mutex);
+ RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (const auto &m_client : m_clients) {
if (m_client.second->getState() >= min_state)
@@ -660,7 +660,7 @@ std::vector<session_t> ClientInterface::getClientIDs(ClientState min_state)
void ClientInterface::markBlockposAsNotSent(const v3s16 &pos)
{
- MutexAutoLock clientslock(m_clients_mutex);
+ RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (const auto &client : m_clients) {
if (client.second->getState() >= CS_Active)
client.second->SetBlockNotSent(pos);
@@ -705,7 +705,7 @@ void ClientInterface::UpdatePlayerList()
infostream << "* " << player->getName() << "\t";
{
- MutexAutoLock clientslock(m_clients_mutex);
+ RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClient* client = lockedGetClientNoEx(i);
if (client)
client->PrintInfo(infostream);
@@ -724,7 +724,7 @@ void ClientInterface::send(session_t peer_id, u8 channelnum,
void ClientInterface::sendToAll(NetworkPacket *pkt)
{
- MutexAutoLock clientslock(m_clients_mutex);
+ RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (auto &client_it : m_clients) {
RemoteClient *client = client_it.second;
@@ -739,7 +739,7 @@ void ClientInterface::sendToAll(NetworkPacket *pkt)
void ClientInterface::sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacypkt,
u16 min_proto_ver)
{
- MutexAutoLock clientslock(m_clients_mutex);
+ RecursiveMutexAutoLock clientslock(m_clients_mutex);
for (auto &client_it : m_clients) {
RemoteClient *client = client_it.second;
NetworkPacket *pkt_to_send = nullptr;
@@ -763,7 +763,7 @@ void ClientInterface::sendToAllCompat(NetworkPacket *pkt, NetworkPacket *legacyp
RemoteClient* ClientInterface::getClientNoEx(session_t peer_id, ClientState state_min)
{
- MutexAutoLock clientslock(m_clients_mutex);
+ RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClientMap::const_iterator n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
@@ -792,7 +792,7 @@ RemoteClient* ClientInterface::lockedGetClientNoEx(session_t peer_id, ClientStat
ClientState ClientInterface::getClientState(session_t peer_id)
{
- MutexAutoLock clientslock(m_clients_mutex);
+ RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClientMap::const_iterator n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
@@ -804,7 +804,7 @@ ClientState ClientInterface::getClientState(session_t peer_id)
void ClientInterface::setPlayerName(session_t peer_id, const std::string &name)
{
- MutexAutoLock clientslock(m_clients_mutex);
+ RecursiveMutexAutoLock clientslock(m_clients_mutex);
RemoteClientMap::iterator n = m_clients.find(peer_id);
// The client may not exist; clients are immediately removed if their
// access is denied, and this event occurs later then.
@@ -814,7 +814,7 @@ void ClientInterface::setPlayerName(session_t peer_id, const std::string &name)
void ClientInterface::DeleteClient(session_t peer_id)
{
- MutexAutoLock conlock(m_clients_mutex);
+ RecursiveMutexAutoLock conlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
@@ -844,7 +844,7 @@ void ClientInterface::DeleteClient(session_t peer_id)
void ClientInterface::CreateClient(session_t peer_id)
{
- MutexAutoLock conlock(m_clients_mutex);
+ RecursiveMutexAutoLock conlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
@@ -860,7 +860,7 @@ void ClientInterface::CreateClient(session_t peer_id)
void ClientInterface::event(session_t peer_id, ClientStateEvent event)
{
{
- MutexAutoLock clientlock(m_clients_mutex);
+ RecursiveMutexAutoLock clientlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
@@ -881,7 +881,7 @@ void ClientInterface::event(session_t peer_id, ClientStateEvent event)
u16 ClientInterface::getProtocolVersion(session_t peer_id)
{
- MutexAutoLock conlock(m_clients_mutex);
+ RecursiveMutexAutoLock conlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
@@ -896,7 +896,7 @@ u16 ClientInterface::getProtocolVersion(session_t peer_id)
void ClientInterface::setClientVersion(session_t peer_id, u8 major, u8 minor, u8 patch,
const std::string &full)
{
- MutexAutoLock conlock(m_clients_mutex);
+ RecursiveMutexAutoLock conlock(m_clients_mutex);
// Error check
RemoteClientMap::iterator n = m_clients.find(peer_id);
diff --git a/src/clientiface.h b/src/clientiface.h
index 5335fa644..2b0ccfbb1 100644
--- a/src/clientiface.h
+++ b/src/clientiface.h
@@ -501,7 +501,7 @@ private:
// Connection
std::shared_ptr<con::Connection> m_con;
- std::mutex m_clients_mutex;
+ std::recursive_mutex m_clients_mutex;
// Connected clients (behind the con mutex)
RemoteClientMap m_clients;
std::vector<std::string> m_clients_names; //for announcing masterserver
diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in
index 81b8c2695..cb54cb488 100644
--- a/src/cmake_config.h.in
+++ b/src/cmake_config.h.in
@@ -26,6 +26,7 @@
#cmakedefine01 USE_SPATIAL
#cmakedefine01 USE_SYSTEM_GMP
#cmakedefine01 USE_REDIS
+#cmakedefine01 ENABLE_GLES
#cmakedefine01 HAVE_ENDIAN_H
#cmakedefine01 CURSES_HAVE_CURSES_H
#cmakedefine01 CURSES_HAVE_NCURSES_H
diff --git a/src/collision.cpp b/src/collision.cpp
index a07899e65..a443be7ab 100644
--- a/src/collision.cpp
+++ b/src/collision.cpp
@@ -59,7 +59,7 @@ struct NearbyCollisionInfo {
// Checks for collision of a moving aabbox with a static aabbox
// Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision
// The time after which the collision occurs is stored in dtime.
-int axisAlignedCollision(
+CollisionAxis axisAlignedCollision(
const aabb3f &staticbox, const aabb3f &movingbox,
const v3f &speed, f32 d, f32 *dtime)
{
@@ -86,11 +86,11 @@ int axisAlignedCollision(
(relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO) &&
(relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
(relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
- return 0;
+ return COLLISION_AXIS_X;
}
else if(relbox.MinEdge.X > xsize)
{
- return -1;
+ return COLLISION_AXIS_NONE;
}
}
else if(speed.X < 0) // Check for collision with X+ plane
@@ -101,11 +101,11 @@ int axisAlignedCollision(
(relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO) &&
(relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
(relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
- return 0;
+ return COLLISION_AXIS_X;
}
else if(relbox.MaxEdge.X < 0)
{
- return -1;
+ return COLLISION_AXIS_NONE;
}
}
@@ -119,11 +119,11 @@ int axisAlignedCollision(
(relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
(relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
(relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
- return 1;
+ return COLLISION_AXIS_Y;
}
else if(relbox.MinEdge.Y > ysize)
{
- return -1;
+ return COLLISION_AXIS_NONE;
}
}
else if(speed.Y < 0) // Check for collision with Y+ plane
@@ -134,11 +134,11 @@ int axisAlignedCollision(
(relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
(relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) &&
(relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO))
- return 1;
+ return COLLISION_AXIS_Y;
}
else if(relbox.MaxEdge.Y < 0)
{
- return -1;
+ return COLLISION_AXIS_NONE;
}
}
@@ -152,11 +152,11 @@ int axisAlignedCollision(
(relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
(relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) &&
(relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO))
- return 2;
+ return COLLISION_AXIS_Z;
}
//else if(relbox.MinEdge.Z > zsize)
//{
- // return -1;
+ // return COLLISION_AXIS_NONE;
//}
}
else if(speed.Z < 0) // Check for collision with Z+ plane
@@ -167,15 +167,15 @@ int axisAlignedCollision(
(relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) &&
(relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) &&
(relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO))
- return 2;
+ return COLLISION_AXIS_Z;
}
//else if(relbox.MaxEdge.Z < 0)
//{
- // return -1;
+ // return COLLISION_AXIS_NONE;
//}
}
- return -1;
+ return COLLISION_AXIS_NONE;
}
// Helper function:
@@ -206,7 +206,7 @@ bool wouldCollideWithCeiling(
static inline void getNeighborConnectingFace(const v3s16 &p,
const NodeDefManager *nodedef, Map *map, MapNode n, int v, int *neighbors)
{
- MapNode n2 = map->getNodeNoEx(p);
+ MapNode n2 = map->getNode(p);
if (nodedef->nodeboxConnects(n, n2, v))
*neighbors |= v;
}
@@ -220,8 +220,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
{
static bool time_notification_done = false;
Map *map = &env->getMap();
- //TimeTaker tt("collisionMoveSimple");
- ScopeProfiler sp(g_profiler, "collisionMoveSimple avg", SPT_AVG);
+
+ ScopeProfiler sp(g_profiler, "collisionMoveSimple()", SPT_AVG);
collisionMoveResult result;
@@ -255,7 +255,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
std::vector<NearbyCollisionInfo> cinfo;
{
//TimeTaker tt2("collisionMoveSimple collect boxes");
- ScopeProfiler sp2(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG);
+ ScopeProfiler sp2(g_profiler, "collisionMoveSimple(): collect boxes", SPT_AVG);
v3f newpos_f = *pos_f + *speed_f * dtime;
v3f minpos_f(
@@ -278,7 +278,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
for (p.Y = min.Y; p.Y <= max.Y; p.Y++)
for (p.Z = min.Z; p.Z <= max.Z; p.Z++) {
bool is_position_valid;
- MapNode n = map->getNodeNoEx(p, &is_position_valid);
+ MapNode n = map->getNode(p, &is_position_valid);
if (is_position_valid && n.getContent() != CONTENT_IGNORE) {
// Object collides into walkable nodes
@@ -351,9 +351,6 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
if(collideWithObjects)
{
- ScopeProfiler sp2(g_profiler, "collisionMoveSimple objects avg", SPT_AVG);
- //TimeTaker tt3("collisionMoveSimple collect object boxes");
-
/* add object boxes to cinfo */
std::vector<ActiveObject*> objects;
@@ -428,9 +425,6 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
int loopcount = 0;
while(dtime > BS * 1e-10f) {
- //TimeTaker tt3("collisionMoveSimple dtime loop");
- ScopeProfiler sp2(g_profiler, "collisionMoveSimple dtime loop avg", SPT_AVG);
-
// Avoid infinite loop
loopcount++;
if (loopcount >= 100) {
@@ -442,7 +436,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
movingbox.MinEdge += *pos_f;
movingbox.MaxEdge += *pos_f;
- int nearest_collided = -1;
+ CollisionAxis nearest_collided = COLLISION_AXIS_NONE;
f32 nearest_dtime = dtime;
int nearest_boxindex = -1;
@@ -457,7 +451,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
// Find nearest collision of the two boxes (raytracing-like)
f32 dtime_tmp;
- int collided = axisAlignedCollision(box_info.box,
+ CollisionAxis collided = axisAlignedCollision(box_info.box,
movingbox, *speed_f, d, &dtime_tmp);
if (collided == -1 || dtime_tmp >= nearest_dtime)
@@ -468,7 +462,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
nearest_boxindex = boxindex;
}
- if (nearest_collided == -1) {
+ if (nearest_collided == COLLISION_AXIS_NONE) {
// No collision with any collision box.
*pos_f += *speed_f * dtime;
dtime = 0; // Set to 0 to avoid "infinite" loop due to small FP numbers
@@ -477,7 +471,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
NearbyCollisionInfo &nearest_info = cinfo[nearest_boxindex];
const aabb3f& cbox = nearest_info.box;
// Check for stairs.
- bool step_up = (nearest_collided != 1) && // must not be Y direction
+ 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,
@@ -491,11 +485,11 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
if (nearest_dtime < 0) {
// Handle negative nearest_dtime (can be caused by the d allowance)
if (!step_up) {
- if (nearest_collided == 0)
+ if (nearest_collided == COLLISION_AXIS_X)
pos_f->X += speed_f->X * nearest_dtime;
- if (nearest_collided == 1)
+ if (nearest_collided == COLLISION_AXIS_Y)
pos_f->Y += speed_f->Y * nearest_dtime;
- if (nearest_collided == 2)
+ if (nearest_collided == COLLISION_AXIS_Z)
pos_f->Z += speed_f->Z * nearest_dtime;
}
} else {
@@ -522,19 +516,19 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
// Special case: Handle stairs
nearest_info.is_step_up = true;
is_collision = false;
- } else if (nearest_collided == 0) { // X
+ } else if (nearest_collided == COLLISION_AXIS_X) {
if (fabs(speed_f->X) > BS * 3)
speed_f->X *= bounce;
else
speed_f->X = 0;
result.collides = true;
- } else if (nearest_collided == 1) { // Y
+ } else if (nearest_collided == COLLISION_AXIS_Y) {
if(fabs(speed_f->Y) > BS * 3)
speed_f->Y *= bounce;
else
speed_f->Y = 0;
result.collides = true;
- } else if (nearest_collided == 2) { // Z
+ } else if (nearest_collided == COLLISION_AXIS_Z) {
if (fabs(speed_f->Z) > BS * 3)
speed_f->Z *= bounce;
else
@@ -547,6 +541,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
is_collision = false;
if (is_collision) {
+ info.axis = nearest_collided;
result.collisions.push_back(info);
}
}
diff --git a/src/collision.h b/src/collision.h
index 4c5594528..85df02c96 100644
--- a/src/collision.h
+++ b/src/collision.h
@@ -33,11 +33,20 @@ enum CollisionType
COLLISION_OBJECT,
};
+enum CollisionAxis
+{
+ COLLISION_AXIS_NONE = -1,
+ COLLISION_AXIS_X,
+ COLLISION_AXIS_Y,
+ COLLISION_AXIS_Z,
+};
+
struct CollisionInfo
{
CollisionInfo() = default;
CollisionType type = COLLISION_NODE;
+ CollisionAxis axis = COLLISION_AXIS_NONE;
v3s16 node_p = v3s16(-32768,-32768,-32768); // COLLISION_NODE
v3f old_speed;
v3f new_speed;
@@ -66,7 +75,7 @@ collisionMoveResult collisionMoveSimple(Environment *env,IGameDef *gamedef,
// Checks for collision of a moving aabbox with a static aabbox
// Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision
// dtime receives time until first collision, invalid if -1 is returned
-int axisAlignedCollision(
+CollisionAxis axisAlignedCollision(
const aabb3f &staticbox, const aabb3f &movingbox,
const v3f &speed, f32 d, f32 *dtime);
diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp
index 4b0e37f7c..39cdc056f 100644
--- a/src/content/subgames.cpp
+++ b/src/content/subgames.cpp
@@ -67,15 +67,19 @@ SubgameSpec findSubgame(const std::string &id)
std::vector<GameFindPath> find_paths;
while (!search_paths.at_end()) {
std::string path = search_paths.next(PATH_DELIM);
- find_paths.emplace_back(path + DIR_DELIM + id, false);
- find_paths.emplace_back(path + DIR_DELIM + id + "_game", false);
+ path.append(DIR_DELIM).append(id);
+ find_paths.emplace_back(path, false);
+ path.append("_game");
+ find_paths.emplace_back(path, false);
}
- find_paths.emplace_back(
- user + DIR_DELIM + "games" + DIR_DELIM + id + "_game", true);
- find_paths.emplace_back(user + DIR_DELIM + "games" + DIR_DELIM + id, true);
- find_paths.emplace_back(
- share + DIR_DELIM + "games" + DIR_DELIM + id + "_game", false);
- find_paths.emplace_back(share + DIR_DELIM + "games" + DIR_DELIM + id, false);
+
+ std::string game_base = DIR_DELIM;
+ game_base = game_base.append("games").append(DIR_DELIM).append(id);
+ std::string game_suffixed = game_base + "_game";
+ find_paths.emplace_back(user + game_suffixed, true);
+ find_paths.emplace_back(user + game_base, true);
+ find_paths.emplace_back(share + game_suffixed, false);
+ find_paths.emplace_back(share + game_base, false);
// Find game directory
std::string game_path;
diff --git a/src/content_sao.cpp b/src/content_sao.cpp
index cb0a969eb..87e6466a9 100644
--- a/src/content_sao.cpp
+++ b/src/content_sao.cpp
@@ -134,7 +134,7 @@ void UnitSAO::setArmorGroups(const ItemGroupList &armor_groups)
m_armor_groups_sent = false;
}
-const ItemGroupList &UnitSAO::getArmorGroups()
+const ItemGroupList &UnitSAO::getArmorGroups() const
{
return m_armor_groups;
}
@@ -200,7 +200,7 @@ void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position
}
void UnitSAO::getAttachment(int *parent_id, std::string *bone, v3f *position,
- v3f *rotation)
+ v3f *rotation) const
{
*parent_id = m_attachment_parent_id;
*bone = m_attachment_bone;
@@ -242,7 +242,7 @@ void UnitSAO::removeAttachmentChild(int child_id)
m_attachment_child_ids.erase(child_id);
}
-const std::unordered_set<int> &UnitSAO::getAttachmentChildIds()
+const std::unordered_set<int> &UnitSAO::getAttachmentChildIds() const
{
return m_attachment_child_ids;
}
@@ -331,7 +331,7 @@ void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
if(m_registered){
// Get properties
m_env->getScriptIface()->
- luaentity_GetProperties(m_id, &m_prop);
+ luaentity_GetProperties(m_id, this, &m_prop);
// Initialize HP from properties
m_hp = m_prop.hp_max;
// Activate entity, supplying serialized state
@@ -454,14 +454,19 @@ void LuaEntitySAO::step(float dtime, bool send_recommended)
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_delta =
- dtime * m_prop.automatic_face_movement_max_rotation_per_sec;
-
- m_rotation.Y = wrapDegrees_0_360(m_rotation.Y);
- wrappedApproachShortest(m_rotation.Y, target_yaw, max_rotation_delta, 360.f);
+ 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;
+ }
}
}
@@ -570,6 +575,8 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
(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)));
}
@@ -617,7 +624,7 @@ void LuaEntitySAO::getStaticData(std::string *result) const
*result = os.str();
}
-int LuaEntitySAO::punch(v3f dir,
+u16 LuaEntitySAO::punch(v3f dir,
const ToolCapabilities *toolcap,
ServerActiveObject *puncher,
float time_from_last_punch)
@@ -628,17 +635,16 @@ int LuaEntitySAO::punch(v3f dir,
return 0;
}
- ItemStack *punchitem = NULL;
- ItemStack punchitem_static;
- if (puncher) {
- punchitem_static = puncher->getWieldedItem();
- punchitem = &punchitem_static;
- }
+ 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,
- punchitem,
+ &tool_item,
time_from_last_punch);
bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
@@ -649,14 +655,6 @@ int LuaEntitySAO::punch(v3f dir,
setHP((s32)getHP() - result.damage,
PlayerHPChangeReason(PlayerHPChangeReason::SET_HP));
- if (result.damage > 0) {
- std::string punchername = puncher ? puncher->getDescription() : "nil";
-
- actionstream << getDescription() << " punched by "
- << punchername << ", damage " << result.damage
- << " hp, health now " << getHP() << " hp" << std::endl;
- }
-
std::string str = gob_cmd_punched(getHP());
// create message and add to list
ActiveObjectMessage aom(getId(), true, str);
@@ -671,6 +669,12 @@ int LuaEntitySAO::punch(v3f dir,
m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
}
+ 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 result.wear;
}
@@ -888,12 +892,9 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p
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;
-}
-PlayerSAO::~PlayerSAO()
-{
- if(m_inventory != &m_player->inventory)
- delete m_inventory;
+ if (!g_settings->getBool("enable_damage"))
+ m_armor_groups["immortal"] = 1;
}
void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
@@ -901,7 +902,6 @@ void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &priv
assert(player);
m_player = player;
m_privs = privs;
- m_inventory = &m_player->inventory;
}
v3f PlayerSAO::getEyeOffset() const
@@ -990,10 +990,10 @@ void PlayerSAO::getStaticData(std::string * result) const
void PlayerSAO::step(float dtime, bool send_recommended)
{
- if (m_drowning_interval.step(dtime, 2.0f)) {
+ 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().getNodeNoEx(p);
+ 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) {
@@ -1009,19 +1009,20 @@ void PlayerSAO::step(float dtime, bool send_recommended)
}
}
- if (m_breathing_interval.step(dtime, 0.5f)) {
+ 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().getNodeNoEx(p);
+ MapNode n = m_env->getMap().getNode(p);
const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
- // If player is alive & no drowning & not in ignore, breathe
- if (m_breath < m_prop.breath_max &&
- c.drowning == 0 && n.getContent() != CONTENT_IGNORE && m_hp > 0)
+ // 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 (m_node_hurt_interval.step(dtime, 1.0f)) {
+ 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;
@@ -1030,21 +1031,27 @@ void PlayerSAO::step(float dtime, bool send_recommended)
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().getNodeNoEx(p);
- damage_per_second = std::max(damage_per_second,
- m_env->getGameDef()->ndef()->get(n).damage_per_second);
+ 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().getNodeNoEx(ptop);
- damage_per_second = std::max(damage_per_second,
- m_env->getGameDef()->ndef()->get(ntop).damage_per_second);
+ 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);
+ PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
setHP(newhp, reason);
m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
}
@@ -1056,6 +1063,7 @@ void PlayerSAO::step(float dtime, bool send_recommended)
// 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.
@@ -1084,6 +1092,7 @@ void PlayerSAO::step(float dtime, bool send_recommended)
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.
@@ -1098,14 +1107,14 @@ void PlayerSAO::step(float dtime, bool send_recommended)
if (!send_recommended)
return;
- // If the object is attached client-side, don't waste bandwidth sending its
- // position or rotation to clients.
- if (m_position_not_sent && !isAttached()) {
+ if (m_position_not_sent) {
m_position_not_sent = false;
float update_interval = m_env->getSendRecommendedInterval();
v3f pos;
- if (isAttached()) // Just in case we ever do send attachment position too
- pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
+ // 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;
@@ -1193,6 +1202,10 @@ 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;
@@ -1260,7 +1273,7 @@ void PlayerSAO::setLookPitchAndSend(const float pitch)
m_env->getGameDef()->SendMovePlayer(m_peer_id);
}
-int PlayerSAO::punch(v3f dir,
+u16 PlayerSAO::punch(v3f dir,
const ToolCapabilities *toolcap,
ServerActiveObject *puncher,
float time_from_last_punch)
@@ -1268,8 +1281,10 @@ int PlayerSAO::punch(v3f dir,
if (!toolcap)
return 0;
- // No effect if PvP disabled
- if (!g_settings->getBool("enable_pvp")) {
+ 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
@@ -1279,14 +1294,10 @@ int PlayerSAO::punch(v3f dir,
}
}
+ s32 old_hp = getHP();
HitParams hitparams = getHitParams(m_armor_groups, toolcap,
time_from_last_punch);
- std::string punchername = "nil";
-
- if (puncher != 0)
- punchername = puncher->getDescription();
-
PlayerSAO *playersao = m_player->getPlayerSAO();
bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
@@ -1305,15 +1316,11 @@ int PlayerSAO::punch(v3f dir,
}
}
-
- actionstream << "Player " << m_player->getName() << " punched by "
- << punchername;
- if (!damage_handled) {
- actionstream << ", damage " << hitparams.hp << " HP";
- } else {
- actionstream << ", damage handled by lua";
- }
- actionstream << std::endl;
+ 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;
}
@@ -1322,13 +1329,17 @@ void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
{
s32 oldhp = m_hp;
- s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason);
- if (hp_change == 0)
- return;
+ hp = rangelim(hp, 0, m_prop.hp_max);
- hp = rangelim(oldhp + hp_change, 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 && !g_settings->getBool("enable_damage"))
+ if (hp < oldhp && isImmortal())
return;
m_hp = hp;
@@ -1343,19 +1354,15 @@ void PlayerSAO::setBreath(const u16 breath, bool send)
if (m_player && breath != m_breath)
m_player->setDirty(true);
- m_breath = MYMIN(breath, m_prop.breath_max);
+ m_breath = rangelim(breath, 0, m_prop.breath_max);
if (send)
m_env->getGameDef()->SendPlayerBreath(this);
}
-Inventory* PlayerSAO::getInventory()
+Inventory *PlayerSAO::getInventory() const
{
- return m_inventory;
-}
-const Inventory* PlayerSAO::getInventory() const
-{
- return m_inventory;
+ return m_player ? &m_player->inventory : nullptr;
}
InventoryLocation PlayerSAO::getInventoryLocation() const
@@ -1365,61 +1372,26 @@ InventoryLocation PlayerSAO::getInventoryLocation() const
return loc;
}
-std::string PlayerSAO::getWieldList() const
-{
- return "main";
-}
-
-ItemStack PlayerSAO::getWieldedItem() const
+u16 PlayerSAO::getWieldIndex() const
{
- const Inventory *inv = getInventory();
- ItemStack ret;
- const InventoryList *mlist = inv->getList(getWieldList());
- if (mlist && getWieldIndex() < (s32)mlist->getSize())
- ret = mlist->getItem(getWieldIndex());
- return ret;
+ return m_player->getWieldIndex();
}
-ItemStack PlayerSAO::getWieldedItemOrHand() const
+ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
{
- const Inventory *inv = getInventory();
- ItemStack ret;
- const InventoryList *mlist = inv->getList(getWieldList());
- if (mlist && getWieldIndex() < (s32)mlist->getSize())
- ret = mlist->getItem(getWieldIndex());
- if (ret.name.empty()) {
- const InventoryList *hlist = inv->getList("hand");
- if (hlist)
- ret = hlist->getItem(0);
- }
- return ret;
+ return m_player->getWieldedItem(selected, hand);
}
bool PlayerSAO::setWieldedItem(const ItemStack &item)
{
- Inventory *inv = getInventory();
- if (inv) {
- InventoryList *mlist = inv->getList(getWieldList());
- if (mlist) {
- mlist->changeItem(getWieldIndex(), item);
- return true;
- }
+ InventoryList *mlist = m_player->inventory.getList(getWieldList());
+ if (mlist) {
+ mlist->changeItem(m_player->getWieldIndex(), item);
+ return true;
}
return false;
}
-int PlayerSAO::getWieldIndex() const
-{
- return m_wield_index;
-}
-
-void PlayerSAO::setWieldIndex(int i)
-{
- if(i != m_wield_index) {
- m_wield_index = i;
- }
-}
-
void PlayerSAO::disconnected()
{
m_peer_id = 0;
@@ -1441,6 +1413,19 @@ std::string PlayerSAO::getPropertyPacket()
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 ||
@@ -1460,6 +1445,14 @@ bool PlayerSAO::checkMovementCheat()
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
@@ -1468,10 +1461,13 @@ bool PlayerSAO::checkMovementCheat()
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)
diff --git a/src/content_sao.h b/src/content_sao.h
index f54bc16c2..e9047daf0 100644
--- a/src/content_sao.h
+++ b/src/content_sao.h
@@ -46,20 +46,24 @@ public:
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 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);
+ 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 std::unordered_set<int> &getAttachmentChildIds() const;
ServerActiveObject *getParent() const;
ObjectProperties* accessObjectProperties();
void notifyObjectPropertiesModified();
@@ -104,7 +108,7 @@ class LuaEntitySAO : public UnitSAO
{
public:
LuaEntitySAO(ServerEnvironment *env, v3f pos,
- const std::string &name, const std::string &state);
+ const std::string &name, const std::string &state);
~LuaEntitySAO();
ActiveObjectType getType() const
{ return ACTIVEOBJECT_TYPE_LUAENTITY; }
@@ -112,16 +116,16 @@ public:
{ return ACTIVEOBJECT_TYPE_GENERIC; }
virtual void addedToEnvironment(u32 dtime_s);
static ServerActiveObject* create(ServerEnvironment *env, v3f pos,
- const std::string &data);
+ 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;
- int punch(v3f dir,
- const ToolCapabilities *toolcap=NULL,
- ServerActiveObject *puncher=NULL,
- float time_from_last_punch=1000000);
+ 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);
@@ -129,6 +133,7 @@ public:
std::string getDescription();
void setHP(s32 hp, const PlayerHPChangeReason &reason);
u16 getHP() const;
+
/* LuaEntitySAO-specific */
void setVelocity(v3f velocity);
void addVelocity(v3f velocity)
@@ -214,7 +219,7 @@ class PlayerSAO : public UnitSAO
public:
PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
bool is_singleplayer);
- ~PlayerSAO();
+
ActiveObjectType getType() const
{ return ACTIVEOBJECT_TYPE_PLAYER; }
ActiveObjectType getSendType() const
@@ -253,7 +258,7 @@ public:
Interaction interface
*/
- int punch(v3f dir,
+ u16 punch(v3f dir,
const ToolCapabilities *toolcap,
ServerActiveObject *puncher,
float time_from_last_punch);
@@ -267,16 +272,13 @@ public:
/*
Inventory interface
*/
-
- Inventory* getInventory();
- const Inventory* getInventory() const;
+ Inventory *getInventory() const;
InventoryLocation getInventoryLocation() const;
- std::string getWieldList() const;
- ItemStack getWieldedItem() const;
- ItemStack getWieldedItemOrHand() const;
+ void setInventoryModified() {}
+ std::string getWieldList() const { return "main"; }
+ u16 getWieldIndex() const;
+ ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const;
bool setWieldedItem(const ItemStack &item);
- int getWieldIndex() const;
- void setWieldIndex(int i);
/*
PlayerSAO-specific
@@ -320,6 +322,7 @@ public:
{
return m_dig_pool;
}
+ void setMaxSpeedOverride(const v3f &vel);
// Returns true if cheated
bool checkMovementCheat();
@@ -350,7 +353,6 @@ private:
RemotePlayer *m_player = nullptr;
session_t m_peer_id = 0;
- Inventory *m_inventory = nullptr;
// Cheat prevention
LagPool m_dig_pool;
@@ -360,13 +362,14 @@ private:
float m_time_from_last_punch = 0.0f;
v3s16 m_nocheat_dig_pos = v3s16(32767, 32767, 32767);
float m_nocheat_dig_time = 0.0f;
+ float m_max_speed_override_time = 0.0f;
+ v3f m_max_speed_override = v3f(0.0f, 0.0f, 0.0f);
// Timers
IntervalLimiter m_breathing_interval;
IntervalLimiter m_drowning_interval;
IntervalLimiter m_node_hurt_interval;
- int m_wield_index = 0;
bool m_position_not_sent = false;
// Cached privileges for enforcement
@@ -401,10 +404,14 @@ struct PlayerHPChangeReason {
};
Type type = SET_HP;
- ServerActiveObject *object;
bool from_mod = false;
int lua_reference = -1;
+ // For PLAYER_PUNCH
+ ServerActiveObject *object = nullptr;
+ // For NODE_DAMAGE
+ std::string node;
+
inline bool hasLuaReference() const
{
return lua_reference >= 0;
@@ -450,7 +457,15 @@ struct PlayerHPChangeReason {
}
}
- PlayerHPChangeReason(Type type, ServerActiveObject *object=NULL):
+ PlayerHPChangeReason(Type type):
+ type(type)
+ {}
+
+ PlayerHPChangeReason(Type type, ServerActiveObject *object):
type(type), object(object)
{}
+
+ PlayerHPChangeReason(Type type, std::string node):
+ type(type), node(node)
+ {}
};
diff --git a/src/craftdef.cpp b/src/craftdef.cpp
index 077d7e044..0181ceb60 100644
--- a/src/craftdef.cpp
+++ b/src/craftdef.cpp
@@ -37,6 +37,15 @@ inline bool isGroupRecipeStr(const std::string &rec_name)
return str_starts_with(rec_name, std::string("group:"));
}
+static bool hasGroupItem(const std::vector<std::string> &recipe)
+{
+ for (const auto &item : recipe) {
+ if (isGroupRecipeStr(item))
+ return true;
+ }
+ return false;
+}
+
inline u64 getHashForString(const std::string &recipe_str)
{
/*errorstream << "Hashing craft string \"" << recipe_str << '"';*/
@@ -278,6 +287,15 @@ std::string craftDumpMatrix(const std::vector<ItemStack> &items,
CraftInput
*/
+bool CraftInput::empty() const
+{
+ for (const auto &item : items) {
+ if (!item.empty())
+ return false;
+ }
+ return true;
+}
+
std::string CraftInput::dump() const
{
std::ostringstream os(std::ios::binary);
@@ -320,6 +338,19 @@ std::string CraftReplacements::dump() const
CraftDefinitionShaped
*/
+CraftDefinitionShaped::CraftDefinitionShaped(
+ const std::string &output_,
+ unsigned int width_,
+ const std::vector<std::string> &recipe_,
+ const CraftReplacements &replacements_):
+ output(output_), width(width_), recipe(recipe_), replacements(replacements_)
+{
+ if (hasGroupItem(recipe))
+ priority = PRIORITY_SHAPED_AND_GROUPS;
+ else
+ priority = PRIORITY_SHAPED;
+}
+
std::string CraftDefinitionShaped::getName() const
{
return "shaped";
@@ -407,22 +438,6 @@ void CraftDefinitionShaped::decrementInput(CraftInput &input, std::vector<ItemSt
craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
}
-CraftHashType CraftDefinitionShaped::getHashType() const
-{
- assert(hash_inited); // Pre-condition
- bool has_group = false;
- for (const auto &recipe_name : recipe_names) {
- if (isGroupRecipeStr(recipe_name)) {
- has_group = true;
- break;
- }
- }
- if (has_group)
- return CRAFT_HASH_TYPE_COUNT;
-
- return CRAFT_HASH_TYPE_ITEM_NAMES;
-}
-
u64 CraftDefinitionShaped::getHash(CraftHashType type) const
{
assert(hash_inited); // Pre-condition
@@ -440,6 +455,11 @@ void CraftDefinitionShaped::initHash(IGameDef *gamedef)
return;
hash_inited = true;
recipe_names = craftGetItemNames(recipe, gamedef);
+
+ if (hasGroupItem(recipe_names))
+ hash_type = CRAFT_HASH_TYPE_COUNT;
+ else
+ hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
}
std::string CraftDefinitionShaped::dump() const
@@ -455,6 +475,18 @@ std::string CraftDefinitionShaped::dump() const
CraftDefinitionShapeless
*/
+CraftDefinitionShapeless::CraftDefinitionShapeless(
+ const std::string &output_,
+ const std::vector<std::string> &recipe_,
+ const CraftReplacements &replacements_):
+ output(output_), recipe(recipe_), replacements(replacements_)
+{
+ if (hasGroupItem(recipe))
+ priority = PRIORITY_SHAPELESS_AND_GROUPS;
+ else
+ priority = PRIORITY_SHAPELESS;
+}
+
std::string CraftDefinitionShapeless::getName() const
{
return "shapeless";
@@ -527,22 +559,6 @@ void CraftDefinitionShapeless::decrementInput(CraftInput &input, std::vector<Ite
craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
}
-CraftHashType CraftDefinitionShapeless::getHashType() const
-{
- assert(hash_inited); // Pre-condition
- bool has_group = false;
- for (const auto &recipe_name : recipe_names) {
- if (isGroupRecipeStr(recipe_name)) {
- has_group = true;
- break;
- }
- }
- if (has_group)
- return CRAFT_HASH_TYPE_COUNT;
-
- return CRAFT_HASH_TYPE_ITEM_NAMES;
-}
-
u64 CraftDefinitionShapeless::getHash(CraftHashType type) const
{
assert(hash_inited); // Pre-condition
@@ -558,6 +574,11 @@ void CraftDefinitionShapeless::initHash(IGameDef *gamedef)
hash_inited = true;
recipe_names = craftGetItemNames(recipe, gamedef);
std::sort(recipe_names.begin(), recipe_names.end());
+
+ if (hasGroupItem(recipe_names))
+ hash_type = CRAFT_HASH_TYPE_COUNT;
+ else
+ hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
}
std::string CraftDefinitionShapeless::dump() const
@@ -573,6 +594,12 @@ std::string CraftDefinitionShapeless::dump() const
CraftDefinitionToolRepair
*/
+CraftDefinitionToolRepair::CraftDefinitionToolRepair(float additional_wear_):
+ additional_wear(additional_wear_)
+{
+ priority = PRIORITY_TOOLREPAIR;
+}
+
static ItemStack craftToolRepair(
const ItemStack &item1,
const ItemStack &item2,
@@ -667,6 +694,19 @@ std::string CraftDefinitionToolRepair::dump() const
CraftDefinitionCooking
*/
+CraftDefinitionCooking::CraftDefinitionCooking(
+ const std::string &output_,
+ const std::string &recipe_,
+ float cooktime_,
+ const CraftReplacements &replacements_):
+ output(output_), recipe(recipe_), cooktime(cooktime_), replacements(replacements_)
+{
+ if (isGroupRecipeStr(recipe))
+ priority = PRIORITY_SHAPELESS_AND_GROUPS;
+ else
+ priority = PRIORITY_SHAPELESS;
+}
+
std::string CraftDefinitionCooking::getName() const
{
return "cooking";
@@ -715,14 +755,6 @@ void CraftDefinitionCooking::decrementInput(CraftInput &input, std::vector<ItemS
craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
}
-CraftHashType CraftDefinitionCooking::getHashType() const
-{
- if (isGroupRecipeStr(recipe_name))
- return CRAFT_HASH_TYPE_COUNT;
-
- return CRAFT_HASH_TYPE_ITEM_NAMES;
-}
-
u64 CraftDefinitionCooking::getHash(CraftHashType type) const
{
if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
@@ -744,6 +776,11 @@ void CraftDefinitionCooking::initHash(IGameDef *gamedef)
return;
hash_inited = true;
recipe_name = craftGetItemName(recipe, gamedef);
+
+ if (isGroupRecipeStr(recipe_name))
+ hash_type = CRAFT_HASH_TYPE_COUNT;
+ else
+ hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
}
std::string CraftDefinitionCooking::dump() const
@@ -760,6 +797,18 @@ std::string CraftDefinitionCooking::dump() const
CraftDefinitionFuel
*/
+CraftDefinitionFuel::CraftDefinitionFuel(
+ const std::string &recipe_,
+ float burntime_,
+ const CraftReplacements &replacements_):
+ recipe(recipe_), burntime(burntime_), replacements(replacements_)
+{
+ if (isGroupRecipeStr(recipe_name))
+ priority = PRIORITY_SHAPELESS_AND_GROUPS;
+ else
+ priority = PRIORITY_SHAPELESS;
+}
+
std::string CraftDefinitionFuel::getName() const
{
return "fuel";
@@ -808,14 +857,6 @@ void CraftDefinitionFuel::decrementInput(CraftInput &input, std::vector<ItemStac
craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef);
}
-CraftHashType CraftDefinitionFuel::getHashType() const
-{
- if (isGroupRecipeStr(recipe_name))
- return CRAFT_HASH_TYPE_COUNT;
-
- return CRAFT_HASH_TYPE_ITEM_NAMES;
-}
-
u64 CraftDefinitionFuel::getHash(CraftHashType type) const
{
if (type == CRAFT_HASH_TYPE_ITEM_NAMES) {
@@ -837,7 +878,13 @@ void CraftDefinitionFuel::initHash(IGameDef *gamedef)
return;
hash_inited = true;
recipe_name = craftGetItemName(recipe, gamedef);
+
+ if (isGroupRecipeStr(recipe_name))
+ hash_type = CRAFT_HASH_TYPE_COUNT;
+ else
+ hash_type = CRAFT_HASH_TYPE_ITEM_NAMES;
}
+
std::string CraftDefinitionFuel::dump() const
{
std::ostringstream os(std::ios::binary);
@@ -868,25 +915,18 @@ public:
std::vector<ItemStack> &output_replacement, bool decrementInput,
IGameDef *gamedef) const
{
- output.item = "";
- output.time = 0;
-
- // If all input items are empty, abort.
- bool all_empty = true;
- for (const auto &item : input.items) {
- if (!item.empty()) {
- all_empty = false;
- break;
- }
- }
- if (all_empty)
+ if (input.empty())
return false;
std::vector<std::string> input_names;
input_names = craftGetItemNames(input.items, gamedef);
std::sort(input_names.begin(), input_names.end());
- // Try hash types with increasing collision rate, and return if found.
+ // Try hash types with increasing collision rate
+ // while remembering the latest, highest priority recipe.
+ CraftDefinition::RecipePriority priority_best =
+ CraftDefinition::PRIORITY_NO_RECIPE;
+ CraftDefinition *def_best = nullptr;
for (int type = 0; type <= craft_hash_type_max; type++) {
u64 hash = getHashForGrid((CraftHashType) type, input_names);
@@ -909,7 +949,9 @@ public:
/*errorstream << "Checking " << input.dump() << std::endl
<< " against " << def->dump() << std::endl;*/
- if (def->check(input, gamedef)) {
+ CraftDefinition::RecipePriority priority = def->getPriority();
+ if (priority > priority_best
+ && def->check(input, gamedef)) {
// Check if the crafted node/item exists
CraftOutput out = def->getOutput(input, gamedef);
ItemStack is;
@@ -920,17 +962,17 @@ public:
continue;
}
- // Get output, then decrement input (if requested)
output = out;
-
- if (decrementInput)
- def->decrementInput(input, output_replacement, gamedef);
- /*errorstream << "Check RETURNS TRUE" << std::endl;*/
- return true;
+ priority_best = priority;
+ def_best = def;
}
}
}
- return false;
+ if (priority_best == CraftDefinition::PRIORITY_NO_RECIPE)
+ return false;
+ if (decrementInput)
+ def_best->decrementInput(input, output_replacement, gamedef);
+ return true;
}
virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output,
@@ -958,84 +1000,48 @@ public:
return recipes;
}
- virtual bool clearCraftRecipesByOutput(const CraftOutput &output, IGameDef *gamedef)
+ virtual bool clearCraftsByOutput(const CraftOutput &output, IGameDef *gamedef)
{
- auto vec_iter = m_output_craft_definitions.find(output.item);
+ auto to_clear = m_output_craft_definitions.find(output.item);
- if (vec_iter == m_output_craft_definitions.end())
+ if (to_clear == m_output_craft_definitions.end())
return false;
- std::vector<CraftDefinition*> &vec = vec_iter->second;
- for (auto def : vec) {
+ for (auto def : to_clear->second) {
// Recipes are not yet hashed at this point
- std::vector<CraftDefinition*> &unhashed_inputs_vec = m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0];
- std::vector<CraftDefinition*> new_vec_by_input;
- /* We will preallocate necessary memory addresses, so we don't need to reallocate them later.
- This would save us some performance. */
- new_vec_by_input.reserve(unhashed_inputs_vec.size());
- for (auto &i2 : unhashed_inputs_vec) {
- if (def != i2) {
- new_vec_by_input.push_back(i2);
- }
- }
- m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].swap(new_vec_by_input);
+ std::vector<CraftDefinition *> &defs = m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0];
+ defs.erase(std::remove(defs.begin(), defs.end(), def), defs.end());
+ delete def;
}
- m_output_craft_definitions.erase(output.item);
+ m_output_craft_definitions.erase(to_clear);
return true;
}
- virtual bool clearCraftRecipesByInput(CraftMethod craft_method, unsigned int craft_grid_width,
- const std::vector<std::string> &recipe, IGameDef *gamedef)
+ virtual bool clearCraftsByInput(const CraftInput &input, IGameDef *gamedef)
{
- bool all_empty = true;
- for (const auto &i : recipe) {
- if (!i.empty()) {
- all_empty = false;
- break;
- }
- }
- if (all_empty)
+ if (input.empty())
return false;
- CraftInput input(craft_method, craft_grid_width, craftGetItems(recipe, gamedef));
// Recipes are not yet hashed at this point
- std::vector<CraftDefinition*> &unhashed_inputs_vec = m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0];
- std::vector<CraftDefinition*> new_vec_by_input;
+ std::vector<CraftDefinition *> &defs = m_craft_defs[(int)CRAFT_HASH_TYPE_UNHASHED][0];
+ std::vector<CraftDefinition *> new_defs;
bool got_hit = false;
- for (std::vector<CraftDefinition*>::size_type
- i = unhashed_inputs_vec.size(); i > 0; i--) {
- CraftDefinition *def = unhashed_inputs_vec[i - 1];
- /* If the input doesn't match the recipe definition, this recipe definition later
- will be added back in source map. */
+ for (auto def : defs) {
if (!def->check(input, gamedef)) {
- new_vec_by_input.push_back(def);
+ new_defs.push_back(def);
continue;
}
- CraftOutput output = def->getOutput(input, gamedef);
got_hit = true;
- auto vec_iter = m_output_craft_definitions.find(output.item);
- if (vec_iter == m_output_craft_definitions.end())
+ std::string output = def->getOutput(input, gamedef).item;
+ delete def;
+ auto it = m_output_craft_definitions.find(craftGetItemName(output, gamedef));
+ if (it == m_output_craft_definitions.end())
continue;
- std::vector<CraftDefinition*> &vec = vec_iter->second;
- std::vector<CraftDefinition*> new_vec_by_output;
- /* We will preallocate necessary memory addresses, so we don't need
- to reallocate them later. This would save us some performance. */
- new_vec_by_output.reserve(vec.size());
- for (auto &vec_i : vec) {
- /* If pointers from map by input and output are not same,
- we will add 'CraftDefinition*' to a new vector. */
- if (def != vec_i) {
- /* Adding dereferenced iterator value (which are
- 'CraftDefinition' reference) to a new vector. */
- new_vec_by_output.push_back(vec_i);
- }
- }
- // Swaps assigned to current key value with new vector for output map.
- m_output_craft_definitions[output.item].swap(new_vec_by_output);
+ std::vector<CraftDefinition *> &outdefs = it->second;
+ outdefs.erase(std::remove(outdefs.begin(), outdefs.end(), def), outdefs.end());
}
if (got_hit)
- // Swaps value with new vector for input map.
- m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].swap(new_vec_by_input);
+ defs.swap(new_defs);
return got_hit;
}
@@ -1099,9 +1105,10 @@ public:
unhashed.clear();
}
private:
- //TODO: change both maps to unordered_map when c++11 can be used
- std::vector<std::map<u64, std::vector<CraftDefinition*> > > m_craft_defs;
- std::map<std::string, std::vector<CraftDefinition*> > m_output_craft_definitions;
+ std::vector<std::unordered_map<u64, std::vector<CraftDefinition*> > >
+ m_craft_defs;
+ std::unordered_map<std::string, std::vector<CraftDefinition*> >
+ m_output_craft_definitions;
};
IWritableCraftDefManager* createCraftDefManager()
diff --git a/src/craftdef.h b/src/craftdef.h
index 46ee2164e..5971a89bf 100644
--- a/src/craftdef.h
+++ b/src/craftdef.h
@@ -80,6 +80,9 @@ struct CraftInput
method(method_), width(width_), items(items_)
{}
+ // Returns true if all items are empty.
+ bool empty() const;
+
std::string dump() const;
};
@@ -132,6 +135,23 @@ struct CraftReplacements
class CraftDefinition
{
public:
+ /*
+ Craft recipe priorities, from low to high
+
+ Recipes are searched from latest to first.
+ If a recipe with higher priority than a previous found one is
+ encountered, it is selected instead.
+ */
+ enum RecipePriority
+ {
+ PRIORITY_NO_RECIPE,
+ PRIORITY_TOOLREPAIR,
+ PRIORITY_SHAPELESS_AND_GROUPS,
+ PRIORITY_SHAPELESS,
+ PRIORITY_SHAPED_AND_GROUPS,
+ PRIORITY_SHAPED,
+ };
+
CraftDefinition() = default;
virtual ~CraftDefinition() = default;
@@ -140,6 +160,10 @@ public:
// Checks whether the recipe is applicable
virtual bool check(const CraftInput &input, IGameDef *gamedef) const=0;
+ RecipePriority getPriority() const
+ {
+ return priority;
+ }
// Returns the output structure, meaning depends on crafting method
// The implementation can assume that check(input) returns true
virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const=0;
@@ -149,13 +173,20 @@ public:
virtual void decrementInput(CraftInput &input,
std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const=0;
- virtual CraftHashType getHashType() const = 0;
+ CraftHashType getHashType() const
+ {
+ return hash_type;
+ }
virtual u64 getHash(CraftHashType type) const = 0;
// to be called after all mods are loaded, so that we catch all aliases
virtual void initHash(IGameDef *gamedef) = 0;
virtual std::string dump() const=0;
+
+protected:
+ CraftHashType hash_type;
+ RecipePriority priority;
};
/*
@@ -168,15 +199,12 @@ class CraftDefinitionShaped: public CraftDefinition
{
public:
CraftDefinitionShaped() = delete;
-
CraftDefinitionShaped(
- const std::string &output_,
- unsigned int width_,
- const std::vector<std::string> &recipe_,
- const CraftReplacements &replacements_):
- output(output_), width(width_), recipe(recipe_),
- replacements(replacements_)
- {}
+ const std::string &output_,
+ unsigned int width_,
+ const std::vector<std::string> &recipe_,
+ const CraftReplacements &replacements_);
+
virtual ~CraftDefinitionShaped() = default;
virtual std::string getName() const;
@@ -186,7 +214,6 @@ public:
virtual void decrementInput(CraftInput &input,
std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const;
- virtual CraftHashType getHashType() const;
virtual u64 getHash(CraftHashType type) const;
virtual void initHash(IGameDef *gamedef);
@@ -218,11 +245,10 @@ class CraftDefinitionShapeless: public CraftDefinition
public:
CraftDefinitionShapeless() = delete;
CraftDefinitionShapeless(
- const std::string &output_,
- const std::vector<std::string> &recipe_,
- const CraftReplacements &replacements_):
- output(output_), recipe(recipe_), replacements(replacements_)
- {}
+ const std::string &output_,
+ const std::vector<std::string> &recipe_,
+ const CraftReplacements &replacements_);
+
virtual ~CraftDefinitionShapeless() = default;
virtual std::string getName() const;
@@ -232,7 +258,6 @@ public:
virtual void decrementInput(CraftInput &input,
std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const;
- virtual CraftHashType getHashType() const;
virtual u64 getHash(CraftHashType type) const;
virtual void initHash(IGameDef *gamedef);
@@ -262,9 +287,8 @@ class CraftDefinitionToolRepair: public CraftDefinition
{
public:
CraftDefinitionToolRepair() = delete;
- CraftDefinitionToolRepair(float additional_wear_):
- additional_wear(additional_wear_)
- {}
+ CraftDefinitionToolRepair(float additional_wear_);
+
virtual ~CraftDefinitionToolRepair() = default;
virtual std::string getName() const;
@@ -274,10 +298,12 @@ public:
virtual void decrementInput(CraftInput &input,
std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const;
- virtual CraftHashType getHashType() const { return CRAFT_HASH_TYPE_COUNT; }
virtual u64 getHash(CraftHashType type) const { return 2; }
- virtual void initHash(IGameDef *gamedef) {}
+ virtual void initHash(IGameDef *gamedef)
+ {
+ hash_type = CRAFT_HASH_TYPE_COUNT;
+ }
virtual std::string dump() const;
@@ -299,12 +325,11 @@ class CraftDefinitionCooking: public CraftDefinition
public:
CraftDefinitionCooking() = delete;
CraftDefinitionCooking(
- const std::string &output_,
- const std::string &recipe_,
- float cooktime_,
- const CraftReplacements &replacements_):
- output(output_), recipe(recipe_), cooktime(cooktime_), replacements(replacements_)
- {}
+ const std::string &output_,
+ const std::string &recipe_,
+ float cooktime_,
+ const CraftReplacements &replacements_);
+
virtual ~CraftDefinitionCooking() = default;
virtual std::string getName() const;
@@ -314,7 +339,6 @@ public:
virtual void decrementInput(CraftInput &input,
std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const;
- virtual CraftHashType getHashType() const;
virtual u64 getHash(CraftHashType type) const;
virtual void initHash(IGameDef *gamedef);
@@ -344,11 +368,11 @@ class CraftDefinitionFuel: public CraftDefinition
{
public:
CraftDefinitionFuel() = delete;
- CraftDefinitionFuel(const std::string &recipe_,
- float burntime_,
- const CraftReplacements &replacements_):
- recipe(recipe_), burntime(burntime_), replacements(replacements_)
- {}
+ CraftDefinitionFuel(
+ const std::string &recipe_,
+ float burntime_,
+ const CraftReplacements &replacements_);
+
virtual ~CraftDefinitionFuel() = default;
virtual std::string getName() const;
@@ -358,7 +382,6 @@ public:
virtual void decrementInput(CraftInput &input,
std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const;
- virtual CraftHashType getHashType() const;
virtual u64 getHash(CraftHashType type) const;
virtual void initHash(IGameDef *gamedef);
@@ -411,9 +434,8 @@ public:
virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output,
IGameDef *gamedef, unsigned limit=0) const=0;
- virtual bool clearCraftRecipesByOutput(const CraftOutput &output, IGameDef *gamedef) = 0;
- virtual bool clearCraftRecipesByInput(CraftMethod craft_method,
- unsigned int craft_grid_width, const std::vector<std::string> &recipe, IGameDef *gamedef) = 0;
+ virtual bool clearCraftsByOutput(const CraftOutput &output, IGameDef *gamedef) = 0;
+ virtual bool clearCraftsByInput(const CraftInput &input, IGameDef *gamedef) = 0;
// Print crafting recipes for debugging
virtual std::string dump() const=0;
diff --git a/src/daynightratio.h b/src/daynightratio.h
index f959cdf6c..5986da5fc 100644
--- a/src/daynightratio.h
+++ b/src/daynightratio.h
@@ -22,45 +22,51 @@ with this program; if not, write to the Free Software Foundation, Inc.,
inline u32 time_to_daynight_ratio(float time_of_day, bool smooth)
{
float t = time_of_day;
- if(t < 0)
- t += ((int)(-t)/24000)*24000;
- if(t >= 24000)
- t -= ((int)(t)/24000)*24000;
- if(t > 12000)
- t = 24000 - t;
- float values[][2] = {
- {4250+125, 150},
- {4500+125, 150},
- {4750+125, 250},
- {5000+125, 350},
- {5250+125, 500},
- {5500+125, 675},
- {5750+125, 875},
- {6000+125, 1000},
- {6250+125, 1000},
+ if (t < 0.0f)
+ t += ((int)(-t) / 24000) * 24000.0f;
+ if (t >= 24000.0f)
+ t -= ((int)(t) / 24000) * 24000.0f;
+ if (t > 12000.0f)
+ t = 24000.0f - t;
+
+ const float values[9][2] = {
+ {4250.0f + 125.0f, 150.0f},
+ {4500.0f + 125.0f, 150.0f},
+ {4750.0f + 125.0f, 250.0f},
+ {5000.0f + 125.0f, 350.0f},
+ {5250.0f + 125.0f, 500.0f},
+ {5500.0f + 125.0f, 675.0f},
+ {5750.0f + 125.0f, 875.0f},
+ {6000.0f + 125.0f, 1000.0f},
+ {6250.0f + 125.0f, 1000.0f},
};
- if(!smooth){
+
+ if (!smooth) {
float lastt = values[0][0];
- for(u32 i=1; i<sizeof(values)/sizeof(*values); i++){
+ for (u32 i = 1; i < 9; i++) {
float t0 = values[i][0];
- float switch_t = (t0 + lastt) / 2;
+ float switch_t = (t0 + lastt) / 2.0f;
lastt = t0;
- if(switch_t <= t)
+ if (switch_t <= t)
continue;
+
return values[i][1];
}
return 1000;
}
- for (u32 i=0; i < sizeof(values) / sizeof(*values); i++) {
- if (values[i][0] <= t)
- continue;
- if (i == 0)
- return values[i][1];
- float td0 = values[i][0] - values[i-1][0];
- float f = (t - values[i-1][0]) / td0;
- return f * values[i][1] + (1.0 - f) * values[i-1][1];
- }
+ if (t <= 4625.0f) // 4500 + 125
+ return values[0][1];
+ else if (t >= 6125.0f) // 6000 + 125
return 1000;
+ for (u32 i = 0; i < 9; i++) {
+ if (values[i][0] <= t)
+ continue;
+
+ float td0 = values[i][0] - values[i - 1][0];
+ float f = (t - values[i - 1][0]) / td0;
+ return f * values[i][1] + (1.0f - f) * values[i - 1][1];
+ }
+ return 1000;
}
diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp
index 5a56f3d77..01ee97a33 100644
--- a/src/defaultsettings.cpp
+++ b/src/defaultsettings.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 <IrrCompileConfig.h>
#include "settings.h"
#include "porting.h"
#include "filesys.h"
@@ -82,7 +83,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("keymap_console", "KEY_F10");
settings->setDefault("keymap_rangeselect", "KEY_KEY_R");
settings->setDefault("keymap_freemove", "KEY_KEY_K");
- settings->setDefault("keymap_pitchmove", "KEY_KEY_L");
+ settings->setDefault("keymap_pitchmove", "KEY_KEY_P");
settings->setDefault("keymap_fastmove", "KEY_KEY_J");
settings->setDefault("keymap_noclip", "KEY_KEY_H");
settings->setDefault("keymap_hotbar_next", "KEY_KEY_N");
@@ -182,7 +183,15 @@ void set_default_settings(Settings *settings)
settings->setDefault("lighting_boost_spread", "0.2");
settings->setDefault("texture_path", "");
settings->setDefault("shader_path", "");
+#if ENABLE_GLES
+#ifdef _IRR_COMPILE_WITH_OGLES1_
+ settings->setDefault("video_driver", "ogles1");
+#else
+ settings->setDefault("video_driver", "ogles2");
+#endif
+#else
settings->setDefault("video_driver", "opengl");
+#endif
settings->setDefault("cinematic", "false");
settings->setDefault("camera_smoothing", "0");
settings->setDefault("cinematic_camera_smoothing", "0.7");
@@ -217,7 +226,11 @@ void set_default_settings(Settings *settings)
settings->setDefault("texture_clean_transparent", "false");
settings->setDefault("texture_min_size", "64");
settings->setDefault("ambient_occlusion_gamma", "2.2");
+#if ENABLE_GLES
+ settings->setDefault("enable_shaders", "false");
+#else
settings->setDefault("enable_shaders", "true");
+#endif
settings->setDefault("enable_particles", "true");
settings->setDefault("arm_inertia", "true");
@@ -343,6 +356,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("kick_msg_crash", "This server has experienced an internal error. You will now be disconnected.");
settings->setDefault("ask_reconnect_on_crash", "false");
+ settings->setDefault("chat_message_format", "<@name> @message");
settings->setDefault("profiler_print_interval", "0");
settings->setDefault("active_object_send_range_blocks", "4");
settings->setDefault("active_block_range", "3");
@@ -355,7 +369,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("csm_restriction_noderange", "0");
settings->setDefault("max_clearobjects_extra_loaded_blocks", "4096");
settings->setDefault("time_speed", "72");
- settings->setDefault("world_start_time", "5250");
+ settings->setDefault("world_start_time", "6125");
settings->setDefault("server_unload_unused_data_timeout", "29");
settings->setDefault("max_objects_per_block", "64");
settings->setDefault("server_map_save_interval", "5.3");
@@ -371,6 +385,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("ignore_world_load_errors", "false");
settings->setDefault("remote_media", "");
settings->setDefault("debug_log_level", "action");
+ settings->setDefault("debug_log_size_max", "50");
settings->setDefault("emergequeue_limit_total", "512");
settings->setDefault("emergequeue_limit_diskonly", "64");
settings->setDefault("emergequeue_limit_generate", "64");
@@ -403,10 +418,9 @@ void set_default_settings(Settings *settings)
settings->setDefault("water_level", "1");
settings->setDefault("mapgen_limit", "31000");
settings->setDefault("chunksize", "5");
- settings->setDefault("mg_flags", "dungeons");
+ settings->setDefault("mg_flags", "caves,dungeons,light,decorations,biomes");
settings->setDefault("fixed_map_seed", "");
settings->setDefault("max_block_generate_distance", "8");
- settings->setDefault("projecting_dungeons", "true");
settings->setDefault("enable_mapgen_debug_info", "false");
// Server list announcing
@@ -429,11 +443,9 @@ void set_default_settings(Settings *settings)
#ifdef __ANDROID__
settings->setDefault("screen_w", "0");
settings->setDefault("screen_h", "0");
- settings->setDefault("enable_shaders", "false");
settings->setDefault("fullscreen", "true");
- settings->setDefault("video_driver", "ogles1");
settings->setDefault("touchtarget", "true");
- settings->setDefault("TMPFolder","/sdcard/" PROJECT_NAME_C "/tmp/");
+ settings->setDefault("TMPFolder", porting::getDataPath("tmp" DIR_DELIM));
settings->setDefault("touchscreen_threshold","20");
settings->setDefault("fixed_virtual_joystick", "false");
settings->setDefault("virtual_joystick_triggers_aux", "false");
@@ -450,8 +462,8 @@ void set_default_settings(Settings *settings)
settings->setDefault("server_map_save_interval", "15");
settings->setDefault("client_mapblock_limit", "1000");
settings->setDefault("active_block_range", "2");
- settings->setDefault("chunksize", "5");
settings->setDefault("viewing_range", "50");
+ settings->setDefault("leaves_style", "simple");
settings->setDefault("curl_verify_cert","false");
// Apply settings according to screen size
diff --git a/src/emerge.cpp b/src/emerge.cpp
index 0a1852985..fc1da4ee7 100644
--- a/src/emerge.cpp
+++ b/src/emerge.cpp
@@ -184,33 +184,28 @@ EmergeManager::~EmergeManager()
}
-bool EmergeManager::initMapgens(MapgenParams *params)
+void EmergeManager::initMapgens(MapgenParams *params)
{
- if (!m_mapgens.empty())
- return false;
+ FATAL_ERROR_IF(!m_mapgens.empty(), "Mapgen already initialised.");
- this->mgparams = params;
+ mgparams = params;
- for (u32 i = 0; i != m_threads.size(); i++) {
- Mapgen *mg = Mapgen::createMapgen(params->mgtype, i, params, this);
- m_mapgens.push_back(mg);
- }
-
- return true;
+ for (u32 i = 0; i != m_threads.size(); i++)
+ m_mapgens.push_back(Mapgen::createMapgen(params->mgtype, params, this));
}
Mapgen *EmergeManager::getCurrentMapgen()
{
if (!m_threads_active)
- return NULL;
+ return nullptr;
for (u32 i = 0; i != m_threads.size(); i++) {
if (m_threads[i]->isCurrentThread())
return m_threads[i]->m_mapgen;
}
- return NULL;
+ return nullptr;
}
@@ -642,12 +637,8 @@ void *EmergeThread::run()
{
ScopeProfiler sp(g_profiler,
"EmergeThread: Mapgen::makeChunk", SPT_AVG);
- TimeTaker t("mapgen::make_block()");
m_mapgen->makeChunk(&bmdata);
-
- if (!enable_mapgen_debug_info)
- t.stop(true); // Hide output
}
block = finishGen(pos, &bmdata, &modified_blocks);
diff --git a/src/emerge.h b/src/emerge.h
index d4a34451d..df849e542 100644
--- a/src/emerge.h
+++ b/src/emerge.h
@@ -117,7 +117,7 @@ public:
~EmergeManager();
DISABLE_CLASS_COPY(EmergeManager);
- bool initMapgens(MapgenParams *mgparams);
+ void initMapgens(MapgenParams *mgparams);
void startThreads();
void stopThreads();
diff --git a/src/environment.cpp b/src/environment.cpp
index ac7b7ce69..906f35219 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -159,7 +159,7 @@ void Environment::continueRaycast(RaycastState *state, PointedThing *result)
v3s16 np(x, y, z);
bool is_valid_position;
- n = map.getNodeNoEx(np, &is_valid_position);
+ n = map.getNode(np, &is_valid_position);
if (!(is_valid_position && isPointableNode(n, nodedef,
state->m_liquids_pointable))) {
continue;
diff --git a/src/gettime.h b/src/gettime.h
index f20728dfd..66efef1d7 100644
--- a/src/gettime.h
+++ b/src/gettime.h
@@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
-#include "irrlichttypes.h"
#include <ctime>
#include <string>
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 4bc451825..2307856a4 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -1,4 +1,5 @@
set(gui_SRCS
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiButton.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiConfirmRegistration.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiEditBoxWithScrollbar.cpp
@@ -7,6 +8,8 @@ set(gui_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiScrollBar.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiSkin.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp
${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp
${CMAKE_CURRENT_SOURCE_DIR}/intlGUIEditBox.cpp
diff --git a/src/gui/StyleSpec.h b/src/gui/StyleSpec.h
new file mode 100644
index 000000000..29aae0836
--- /dev/null
+++ b/src/gui/StyleSpec.h
@@ -0,0 +1,135 @@
+/*
+Minetest
+Copyright (C) 2019 rubenwardy
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "irrlichttypes_extrabloated.h"
+#include <array>
+
+#pragma once
+
+class StyleSpec
+{
+public:
+ enum Property
+ {
+ TEXTCOLOR,
+ BGCOLOR,
+ NOCLIP,
+ BORDER,
+ BGIMG,
+ BGIMG_PRESSED,
+ ALPHA,
+ NUM_PROPERTIES,
+ NONE
+ };
+
+private:
+ std::array<bool, NUM_PROPERTIES> property_set;
+ std::array<std::string, NUM_PROPERTIES> properties;
+
+public:
+ static Property GetPropertyByName(const std::string &name)
+ {
+ if (name == "textcolor") {
+ return TEXTCOLOR;
+ } else if (name == "bgcolor") {
+ return BGCOLOR;
+ } else if (name == "noclip") {
+ return NOCLIP;
+ } else if (name == "border") {
+ return BORDER;
+ } else if (name == "bgimg") {
+ return BGIMG;
+ } else if (name == "bgimg_pressed") {
+ return BGIMG_PRESSED;
+ } else if (name == "alpha") {
+ return ALPHA;
+ } else {
+ return NONE;
+ }
+ }
+
+ std::string get(Property prop, std::string def) const
+ {
+ const auto &val = properties[prop];
+ return val.empty() ? def : val;
+ }
+
+ void set(Property prop, const std::string &value)
+ {
+ properties[prop] = value;
+ property_set[prop] = true;
+ }
+
+ video::SColor getColor(Property prop, video::SColor def) const
+ {
+ const auto &val = properties[prop];
+ if (val.empty()) {
+ return def;
+ }
+
+ parseColorString(val, def, false, 0xFF);
+ return def;
+ }
+
+ video::SColor getColor(Property prop) const
+ {
+ const auto &val = properties[prop];
+ FATAL_ERROR_IF(val.empty(), "Unexpected missing property");
+
+ video::SColor color;
+ parseColorString(val, color, false, 0xFF);
+ return color;
+ }
+
+ bool getBool(Property prop, bool def) const
+ {
+ const auto &val = properties[prop];
+ if (val.empty()) {
+ return def;
+ }
+
+ return is_yes(val);
+ }
+
+ inline bool isNotDefault(Property prop) const
+ {
+ return !properties[prop].empty();
+ }
+
+ inline bool hasProperty(Property prop) const { return property_set[prop]; }
+
+ StyleSpec &operator|=(const StyleSpec &other)
+ {
+ for (size_t i = 0; i < NUM_PROPERTIES; i++) {
+ auto prop = (Property)i;
+ if (other.hasProperty(prop)) {
+ set(prop, other.get(prop, ""));
+ }
+ }
+
+ return *this;
+ }
+
+ StyleSpec operator|(const StyleSpec &other) const
+ {
+ StyleSpec newspec = *this;
+ newspec |= other;
+ return newspec;
+ }
+};
diff --git a/src/gui/guiButton.cpp b/src/gui/guiButton.cpp
new file mode 100644
index 000000000..60d330f4a
--- /dev/null
+++ b/src/gui/guiButton.cpp
@@ -0,0 +1,649 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#include "guiButton.h"
+
+
+#include "IGUISkin.h"
+#include "IGUIEnvironment.h"
+#include "IVideoDriver.h"
+#include "IGUIFont.h"
+#include "porting.h"
+
+using namespace irr;
+using namespace gui;
+
+//! constructor
+GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent,
+ s32 id, core::rect<s32> rectangle, 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)
+{
+ setNotClipped(noclip);
+
+ // This element can be tabbed.
+ setTabStop(true);
+ setTabOrder(-1);
+
+ // PATCH
+ for (size_t i = 0; i < 4; i++) {
+ Colors[i] = Environment->getSkin()->getColor((EGUI_DEFAULT_COLOR)i);
+ }
+ // END PATCH
+}
+
+//! destructor
+GUIButton::~GUIButton()
+{
+ if (OverrideFont)
+ OverrideFont->drop();
+
+ if (SpriteBank)
+ SpriteBank->drop();
+}
+
+
+//! Sets if the images should be scaled to fit the button
+void GUIButton::setScaleImage(bool scaleImage)
+{
+ ScaleImage = scaleImage;
+}
+
+
+//! Returns whether the button scale the used images
+bool GUIButton::isScalingImage() const
+{
+ return ScaleImage;
+}
+
+
+//! Sets if the button should use the skin to draw its border
+void GUIButton::setDrawBorder(bool border)
+{
+ DrawBorder = border;
+}
+
+
+void GUIButton::setSpriteBank(IGUISpriteBank* sprites)
+{
+ if (sprites)
+ sprites->grab();
+
+ if (SpriteBank)
+ SpriteBank->drop();
+
+ SpriteBank = sprites;
+}
+
+void GUIButton::setSprite(EGUI_BUTTON_STATE state, s32 index, video::SColor color, bool loop, bool scale)
+{
+ ButtonSprites[(u32)state].Index = index;
+ ButtonSprites[(u32)state].Color = color;
+ ButtonSprites[(u32)state].Loop = loop;
+ ButtonSprites[(u32)state].Scale = scale;
+}
+
+//! Get the sprite-index for the given state or -1 when no sprite is set
+s32 GUIButton::getSpriteIndex(EGUI_BUTTON_STATE state) const
+{
+ return ButtonSprites[(u32)state].Index;
+}
+
+//! Get the sprite color for the given state. Color is only used when a sprite is set.
+video::SColor GUIButton::getSpriteColor(EGUI_BUTTON_STATE state) const
+{
+ return ButtonSprites[(u32)state].Color;
+}
+
+//! Returns if the sprite in the given state does loop
+bool GUIButton::getSpriteLoop(EGUI_BUTTON_STATE state) const
+{
+ return ButtonSprites[(u32)state].Loop;
+}
+
+//! Returns if the sprite in the given state is scaled
+bool GUIButton::getSpriteScale(EGUI_BUTTON_STATE state) const
+{
+ return ButtonSprites[(u32)state].Scale;
+}
+
+//! called if an event happened.
+bool GUIButton::OnEvent(const SEvent& event)
+{
+ if (!isEnabled())
+ return IGUIElement::OnEvent(event);
+
+ switch(event.EventType)
+ {
+ case EET_KEY_INPUT_EVENT:
+ if (event.KeyInput.PressedDown &&
+ (event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE))
+ {
+ if (!IsPushButton)
+ setPressed(true);
+ else
+ setPressed(!Pressed);
+
+ return true;
+ }
+ if (Pressed && !IsPushButton && event.KeyInput.PressedDown && event.KeyInput.Key == KEY_ESCAPE)
+ {
+ setPressed(false);
+ return true;
+ }
+ else
+ if (!event.KeyInput.PressedDown && Pressed &&
+ (event.KeyInput.Key == KEY_RETURN || event.KeyInput.Key == KEY_SPACE))
+ {
+
+ if (!IsPushButton)
+ setPressed(false);
+
+ if (Parent)
+ {
+ ClickShiftState = event.KeyInput.Shift;
+ ClickControlState = event.KeyInput.Control;
+
+ SEvent newEvent;
+ newEvent.EventType = EET_GUI_EVENT;
+ newEvent.GUIEvent.Caller = this;
+ newEvent.GUIEvent.Element = 0;
+ newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED;
+ Parent->OnEvent(newEvent);
+ }
+ return true;
+ }
+ break;
+ case EET_GUI_EVENT:
+ if (event.GUIEvent.Caller == this)
+ {
+ if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
+ {
+ if (!IsPushButton)
+ setPressed(false);
+ FocusTime = (u32)porting::getTimeMs();
+ }
+ else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUSED)
+ {
+ FocusTime = (u32)porting::getTimeMs();
+ }
+ else if (event.GUIEvent.EventType == EGET_ELEMENT_HOVERED || event.GUIEvent.EventType == EGET_ELEMENT_LEFT)
+ {
+ HoverTime = (u32)porting::getTimeMs();
+ }
+ }
+ break;
+ case EET_MOUSE_INPUT_EVENT:
+ if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
+ {
+ if (!IsPushButton)
+ setPressed(true);
+
+ return true;
+ }
+ else
+ if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
+ {
+ bool wasPressed = Pressed;
+
+ if ( !AbsoluteClippingRect.isPointInside( core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y ) ) )
+ {
+ if (!IsPushButton)
+ setPressed(false);
+ return true;
+ }
+
+ if (!IsPushButton)
+ setPressed(false);
+ else
+ {
+ setPressed(!Pressed);
+ }
+
+ if ((!IsPushButton && wasPressed && Parent) ||
+ (IsPushButton && wasPressed != Pressed))
+ {
+ ClickShiftState = event.MouseInput.Shift;
+ ClickControlState = event.MouseInput.Control;
+
+ SEvent newEvent;
+ newEvent.EventType = EET_GUI_EVENT;
+ newEvent.GUIEvent.Caller = this;
+ newEvent.GUIEvent.Element = 0;
+ newEvent.GUIEvent.EventType = EGET_BUTTON_CLICKED;
+ Parent->OnEvent(newEvent);
+ }
+
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return Parent ? Parent->OnEvent(event) : false;
+}
+
+
+//! draws the element and its children
+void GUIButton::draw()
+{
+ if (!IsVisible)
+ return;
+
+ // PATCH
+ GUISkin* skin = dynamic_cast<GUISkin*>(Environment->getSkin());
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+ // END PATCH
+
+ if (DrawBorder)
+ {
+ if (!Pressed)
+ {
+ // PATCH
+ skin->drawColored3DButtonPaneStandard(this, AbsoluteRect, &AbsoluteClippingRect, Colors);
+ // END PATCH
+ }
+ else
+ {
+ // PATCH
+ skin->drawColored3DButtonPanePressed(this, AbsoluteRect, &AbsoluteClippingRect, Colors);
+ // END PATCH
+ }
+ }
+
+ const core::position2di buttonCenter(AbsoluteRect.getCenter());
+ EGUI_BUTTON_IMAGE_STATE imageState = getImageState(Pressed);
+ if ( ButtonImages[(u32)imageState].Texture )
+ {
+ core::position2d<s32> pos(buttonCenter);
+ core::rect<s32> sourceRect(ButtonImages[(u32)imageState].SourceRect);
+ if ( sourceRect.getWidth() == 0 && sourceRect.getHeight() == 0 )
+ sourceRect = core::rect<s32>(core::position2di(0,0), ButtonImages[(u32)imageState].Texture->getOriginalSize());
+
+ pos.X -= sourceRect.getWidth() / 2;
+ pos.Y -= sourceRect.getHeight() / 2;
+
+ if ( Pressed )
+ {
+ // Create a pressed-down effect by moving the image when it looks identical to the unpressed state image
+ EGUI_BUTTON_IMAGE_STATE unpressedState = getImageState(false);
+ if ( unpressedState == imageState || ButtonImages[(u32)imageState] == ButtonImages[(u32)unpressedState] )
+ {
+ pos.X += skin->getSize(EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X);
+ pos.Y += skin->getSize(EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y);
+ }
+ }
+
+ driver->draw2DImage(ButtonImages[(u32)imageState].Texture,
+ ScaleImage? AbsoluteRect : core::rect<s32>(pos, sourceRect.getSize()),
+ sourceRect, &AbsoluteClippingRect,
+ 0, UseAlphaChannel);
+ }
+
+ if (SpriteBank)
+ {
+ core::position2di pos(buttonCenter);
+
+ if (isEnabled())
+ {
+ // pressed / unpressed animation
+ EGUI_BUTTON_STATE state = Pressed ? EGBS_BUTTON_DOWN : EGBS_BUTTON_UP;
+ drawSprite(state, ClickTime, pos);
+
+ // focused / unfocused animation
+ state = Environment->hasFocus(this) ? EGBS_BUTTON_FOCUSED : EGBS_BUTTON_NOT_FOCUSED;
+ drawSprite(state, FocusTime, pos);
+
+ // mouse over / off animation
+ state = Environment->getHovered() == this ? EGBS_BUTTON_MOUSE_OVER : EGBS_BUTTON_MOUSE_OFF;
+ drawSprite(state, HoverTime, pos);
+ }
+ else
+ {
+ // draw disabled
+// drawSprite(EGBS_BUTTON_DISABLED, 0, pos);
+ }
+ }
+
+ if (Text.size())
+ {
+ IGUIFont* font = getActiveFont();
+
+ core::rect<s32> rect = AbsoluteRect;
+ if (Pressed)
+ {
+ rect.UpperLeftCorner.X += skin->getSize(EGDS_BUTTON_PRESSED_TEXT_OFFSET_X);
+ rect.UpperLeftCorner.Y += skin->getSize(EGDS_BUTTON_PRESSED_TEXT_OFFSET_Y);
+ }
+
+ if (font)
+ font->draw(Text.c_str(), rect,
+ OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT),
+ true, true, &AbsoluteClippingRect);
+ }
+
+ IGUIElement::draw();
+}
+
+void GUIButton::drawSprite(EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center)
+{
+ u32 stateIdx = (u32)state;
+
+ if (ButtonSprites[stateIdx].Index != -1)
+ {
+ if ( ButtonSprites[stateIdx].Scale )
+ {
+ const video::SColor colors[] = {ButtonSprites[stateIdx].Color,ButtonSprites[stateIdx].Color,ButtonSprites[stateIdx].Color,ButtonSprites[stateIdx].Color};
+ SpriteBank->draw2DSprite(ButtonSprites[stateIdx].Index, AbsoluteRect.UpperLeftCorner,
+ &AbsoluteClippingRect, colors[0], // FIXME: remove [0]
+ porting::getTimeMs()-startTime, ButtonSprites[stateIdx].Loop);
+ }
+ else
+ {
+ SpriteBank->draw2DSprite(ButtonSprites[stateIdx].Index, center,
+ &AbsoluteClippingRect, ButtonSprites[stateIdx].Color, startTime, porting::getTimeMs(),
+ ButtonSprites[stateIdx].Loop, true);
+ }
+ }
+}
+
+EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const
+{
+ // figure state we should have
+ EGUI_BUTTON_IMAGE_STATE state = EGBIS_IMAGE_DISABLED;
+ bool focused = Environment->hasFocus((IGUIElement*)this);
+ bool mouseOver = static_cast<const IGUIElement*>(Environment->getHovered()) == this; // (static cast for Borland)
+ if (isEnabled())
+ {
+ if ( pressed )
+ {
+ if ( focused && mouseOver )
+ state = EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER;
+ else if ( focused )
+ state = EGBIS_IMAGE_DOWN_FOCUSED;
+ else if ( mouseOver )
+ state = EGBIS_IMAGE_DOWN_MOUSEOVER;
+ else
+ state = EGBIS_IMAGE_DOWN;
+ }
+ else // !pressed
+ {
+ if ( focused && mouseOver )
+ state = EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER;
+ else if ( focused )
+ state = EGBIS_IMAGE_UP_FOCUSED;
+ else if ( mouseOver )
+ state = EGBIS_IMAGE_UP_MOUSEOVER;
+ else
+ state = EGBIS_IMAGE_UP;
+ }
+ }
+
+ // find a compatible state that has images
+ while ( state != EGBIS_IMAGE_UP && !ButtonImages[(u32)state].Texture )
+ {
+ switch ( state )
+ {
+ case EGBIS_IMAGE_UP_FOCUSED:
+ state = EGBIS_IMAGE_UP_MOUSEOVER;
+ break;
+ case EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER:
+ state = EGBIS_IMAGE_UP_FOCUSED;
+ break;
+ case EGBIS_IMAGE_DOWN_MOUSEOVER:
+ state = EGBIS_IMAGE_DOWN;
+ break;
+ case EGBIS_IMAGE_DOWN_FOCUSED:
+ state = EGBIS_IMAGE_DOWN_MOUSEOVER;
+ break;
+ case EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER:
+ state = EGBIS_IMAGE_DOWN_FOCUSED;
+ break;
+ case EGBIS_IMAGE_DISABLED:
+ if ( pressed )
+ state = EGBIS_IMAGE_DOWN;
+ else
+ state = EGBIS_IMAGE_UP;
+ break;
+ default:
+ state = EGBIS_IMAGE_UP;
+ }
+ }
+
+ return state;
+}
+
+//! sets another skin independent font. if this is set to zero, the button uses the font of the skin.
+void GUIButton::setOverrideFont(IGUIFont* font)
+{
+ if (OverrideFont == font)
+ return;
+
+ if (OverrideFont)
+ OverrideFont->drop();
+
+ OverrideFont = font;
+
+ if (OverrideFont)
+ OverrideFont->grab();
+}
+
+//! Gets the override font (if any)
+IGUIFont * GUIButton::getOverrideFont() const
+{
+ return OverrideFont;
+}
+
+//! Get the font which is used right now for drawing
+IGUIFont* GUIButton::getActiveFont() const
+{
+ if ( OverrideFont )
+ return OverrideFont;
+ IGUISkin* skin = Environment->getSkin();
+ if (skin)
+ return skin->getFont(EGDF_BUTTON);
+ return 0;
+}
+
+//! Sets another color for the text.
+void GUIButton::setOverrideColor(video::SColor color)
+{
+ OverrideColor = color;
+ OverrideColorEnabled = true;
+}
+
+video::SColor GUIButton::getOverrideColor() const
+{
+ return OverrideColor;
+}
+
+void GUIButton::enableOverrideColor(bool enable)
+{
+ OverrideColorEnabled = enable;
+}
+
+bool GUIButton::isOverrideColorEnabled() const
+{
+ return OverrideColorEnabled;
+}
+
+void GUIButton::setImage(EGUI_BUTTON_IMAGE_STATE state, video::ITexture* image, const core::rect<s32>& sourceRect)
+{
+ if ( state >= EGBIS_COUNT )
+ return;
+
+ if ( image )
+ image->grab();
+
+ u32 stateIdx = (u32)state;
+ if ( ButtonImages[stateIdx].Texture )
+ ButtonImages[stateIdx].Texture->drop();
+
+ ButtonImages[stateIdx].Texture = image;
+ ButtonImages[stateIdx].SourceRect = sourceRect;
+}
+
+//! Sets if the button should behave like a push button. Which means it
+//! can be in two states: Normal or Pressed. With a click on the button,
+//! the user can change the state of the button.
+void GUIButton::setIsPushButton(bool isPushButton)
+{
+ IsPushButton = isPushButton;
+}
+
+
+//! Returns if the button is currently pressed
+bool GUIButton::isPressed() const
+{
+ return Pressed;
+}
+
+
+//! Sets the pressed state of the button if this is a pushbutton
+void GUIButton::setPressed(bool pressed)
+{
+ if (Pressed != pressed)
+ {
+ ClickTime = porting::getTimeMs();
+ Pressed = pressed;
+ }
+}
+
+
+//! Returns whether the button is a push button
+bool GUIButton::isPushButton() const
+{
+ return IsPushButton;
+}
+
+
+//! Sets if the alpha channel should be used for drawing images on the button (default is false)
+void GUIButton::setUseAlphaChannel(bool useAlphaChannel)
+{
+ UseAlphaChannel = useAlphaChannel;
+}
+
+
+//! Returns if the alpha channel should be used for drawing images on the button
+bool GUIButton::isAlphaChannelUsed() const
+{
+ return UseAlphaChannel;
+}
+
+
+bool GUIButton::isDrawingBorder() const
+{
+ return DrawBorder;
+}
+
+
+//! Writes attributes of the element.
+void GUIButton::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
+{
+ IGUIButton::serializeAttributes(out,options);
+
+ out->addBool ("PushButton", IsPushButton );
+ if (IsPushButton)
+ out->addBool("Pressed", Pressed);
+
+ for ( u32 i=0; i<(u32)EGBIS_COUNT; ++i )
+ {
+ if ( ButtonImages[i].Texture )
+ {
+ core::stringc name( GUIButtonImageStateNames[i] );
+ out->addTexture(name.c_str(), ButtonImages[i].Texture);
+ name += "Rect";
+ out->addRect(name.c_str(), ButtonImages[i].SourceRect);
+ }
+ }
+
+ out->addBool ("UseAlphaChannel", UseAlphaChannel);
+ out->addBool ("Border", DrawBorder);
+ out->addBool ("ScaleImage", ScaleImage);
+
+ for ( u32 i=0; i<(u32)EGBS_COUNT; ++i )
+ {
+ if ( ButtonSprites[i].Index >= 0 )
+ {
+ core::stringc nameIndex( GUIButtonStateNames[i] );
+ nameIndex += "Index";
+ out->addInt(nameIndex.c_str(), ButtonSprites[i].Index );
+
+ core::stringc nameColor( GUIButtonStateNames[i] );
+ nameColor += "Color";
+ out->addColor(nameColor.c_str(), ButtonSprites[i].Color );
+
+ core::stringc nameLoop( GUIButtonStateNames[i] );
+ nameLoop += "Loop";
+ out->addBool(nameLoop.c_str(), ButtonSprites[i].Loop );
+
+ core::stringc nameScale( GUIButtonStateNames[i] );
+ nameScale += "Scale";
+ out->addBool(nameScale.c_str(), ButtonSprites[i].Scale );
+ }
+ }
+
+ // out->addString ("OverrideFont", OverrideFont);
+}
+
+
+//! Reads attributes of the element
+void GUIButton::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
+{
+ IGUIButton::deserializeAttributes(in,options);
+
+ IsPushButton = in->getAttributeAsBool("PushButton");
+ Pressed = IsPushButton ? in->getAttributeAsBool("Pressed") : false;
+
+ core::rect<s32> rec = in->getAttributeAsRect("ImageRect");
+ if (rec.isValid())
+ setImage( in->getAttributeAsTexture("Image"), rec);
+ else
+ setImage( in->getAttributeAsTexture("Image") );
+
+ rec = in->getAttributeAsRect("PressedImageRect");
+ if (rec.isValid())
+ setPressedImage( in->getAttributeAsTexture("PressedImage"), rec);
+ else
+ setPressedImage( in->getAttributeAsTexture("PressedImage") );
+
+ setDrawBorder(in->getAttributeAsBool("Border"));
+ setUseAlphaChannel(in->getAttributeAsBool("UseAlphaChannel"));
+ setScaleImage(in->getAttributeAsBool("ScaleImage"));
+
+ // setOverrideFont(in->getAttributeAsString("OverrideFont"));
+
+ updateAbsolutePosition();
+}
+
+// PATCH
+GUIButton* GUIButton::addButton(IGUIEnvironment *environment, const core::rect<s32>& rectangle,
+ IGUIElement* parent, s32 id, const wchar_t* text, const wchar_t *tooltiptext)
+{
+ GUIButton* button = new GUIButton(environment, parent ? parent : environment->getRootGUIElement(), id, rectangle);
+ if (text)
+ button->setText(text);
+
+ if ( tooltiptext )
+ button->setToolTipText ( tooltiptext );
+
+ button->drop();
+ return button;
+}
+
+void GUIButton::setColor(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);
+ Colors[i] = base.getInterpolated(color, d);
+ }
+}
+// END PATCH
diff --git a/src/gui/guiButton.h b/src/gui/guiButton.h
new file mode 100644
index 000000000..63e29ccfc
--- /dev/null
+++ b/src/gui/guiButton.h
@@ -0,0 +1,310 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#pragma once
+
+#include "IrrCompileConfig.h"
+
+#include "IGUIButton.h"
+#include "IGUISpriteBank.h"
+#include "ITexture.h"
+#include "SColor.h"
+#include "guiSkin.h"
+
+using namespace irr;
+
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8)
+ namespace irr { namespace gui {
+
+ //! State of buttons used for drawing texture images.
+ //! Note that only a single state is active at a time
+ //! Also when no image is defined for a state it will use images from another state
+ //! and if that state is not set from the replacement for that,etc.
+ //! So in many cases setting EGBIS_IMAGE_UP and EGBIS_IMAGE_DOWN is sufficient.
+ enum EGUI_BUTTON_IMAGE_STATE {
+ //! When no other states have images they will all use this one.
+ EGBIS_IMAGE_UP,
+ //! When not set EGBIS_IMAGE_UP is used.
+ EGBIS_IMAGE_UP_MOUSEOVER,
+ //! When not set EGBIS_IMAGE_UP_MOUSEOVER is used.
+ EGBIS_IMAGE_UP_FOCUSED,
+ //! When not set EGBIS_IMAGE_UP_FOCUSED is used.
+ EGBIS_IMAGE_UP_FOCUSED_MOUSEOVER,
+ //! When not set EGBIS_IMAGE_UP is used.
+ EGBIS_IMAGE_DOWN,
+ //! When not set EGBIS_IMAGE_DOWN is used.
+ EGBIS_IMAGE_DOWN_MOUSEOVER,
+ //! When not set EGBIS_IMAGE_DOWN_MOUSEOVER is used.
+ EGBIS_IMAGE_DOWN_FOCUSED,
+ //! When not set EGBIS_IMAGE_DOWN_FOCUSED is used.
+ EGBIS_IMAGE_DOWN_FOCUSED_MOUSEOVER,
+ //! When not set EGBIS_IMAGE_UP or EGBIS_IMAGE_DOWN are used (depending on button state).
+ EGBIS_IMAGE_DISABLED,
+ //! not used, counts the number of enumerated items
+ EGBIS_COUNT
+ };
+
+ //! Names for gui button image states
+ const c8 *const GUIButtonImageStateNames[EGBIS_COUNT + 1] =
+ {
+ "Image", // not "ImageUp" as it otherwise breaks serialization of old files
+ "ImageUpOver",
+ "ImageUpFocused",
+ "ImageUpFocusedOver",
+ "PressedImage", // not "ImageDown" as it otherwise breaks serialization of old files
+ "ImageDownOver",
+ "ImageDownFocused",
+ "ImageDownFocusedOver",
+ "ImageDisabled",
+ 0 // count
+ };
+
+ }}
+
+#endif
+
+class GUIButton : public gui::IGUIButton
+{
+public:
+
+ //! constructor
+ GUIButton(gui::IGUIEnvironment* environment, gui::IGUIElement* parent,
+ s32 id, core::rect<s32> rectangle, bool noclip=false);
+
+ //! destructor
+ virtual ~GUIButton();
+
+ //! called if an event happened.
+ virtual bool OnEvent(const SEvent& event) override;
+
+ //! draws the element and its children
+ virtual void draw() override;
+
+ //! sets another skin independent font. if this is set to zero, the button uses the font of the skin.
+ virtual void setOverrideFont(gui::IGUIFont* font=0) override;
+
+ //! Gets the override font (if any)
+ virtual gui::IGUIFont* getOverrideFont() const override;
+
+ //! Get the font which is used right now for drawing
+ virtual gui::IGUIFont* getActiveFont() const override;
+
+ //! Sets another color for the button text.
+ virtual void setOverrideColor(video::SColor color);
+
+ //! Gets the override color
+ virtual video::SColor getOverrideColor(void) const;
+
+ //! Sets if the button text should use the override color or the color in the gui skin.
+ virtual void enableOverrideColor(bool enable);
+
+ //! Checks if an override color is enabled
+ virtual bool isOverrideColorEnabled(void) const;
+
+ //! Sets an image which should be displayed on the button when it is in the given state.
+ virtual void setImage(gui::EGUI_BUTTON_IMAGE_STATE state,
+ video::ITexture* image=0,
+ const core::rect<s32>& sourceRect=core::rect<s32>(0,0,0,0));
+
+ //! Sets an image which should be displayed on the button when it is in normal state.
+ virtual void setImage(video::ITexture* image=0) override
+ {
+ setImage(gui::EGBIS_IMAGE_UP, image);
+ }
+
+ //! Sets an image which should be displayed on the button when it is in normal state.
+ virtual void setImage(video::ITexture* image, const core::rect<s32>& pos) override
+ {
+ setImage(gui::EGBIS_IMAGE_UP, image, pos);
+ }
+
+ //! Sets an image which should be displayed on the button when it is in pressed state.
+ virtual void setPressedImage(video::ITexture* image=0) override
+ {
+ setImage(gui::EGBIS_IMAGE_DOWN, image);
+ }
+
+ //! 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
+ {
+ setImage(gui::EGBIS_IMAGE_DOWN, image, pos);
+ }
+
+ //! Sets the sprite bank used by the button
+ virtual void setSpriteBank(gui::IGUISpriteBank* bank=0) override;
+
+ //! Sets the animated sprite for a specific button state
+ /** \param index: Number of the sprite within the sprite bank, use -1 for no sprite
+ \param state: State of the button to set the sprite for
+ \param index: The sprite number from the current sprite bank
+ \param color: The color of the sprite
+ */
+ virtual void setSprite(gui::EGUI_BUTTON_STATE state, s32 index,
+ video::SColor color=video::SColor(255,255,255,255),
+ bool loop=false, bool scale=false);
+
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8)
+ void setSprite(gui::EGUI_BUTTON_STATE state, s32 index, video::SColor color, bool loop) override {
+ setSprite(state, index, color, loop, false);
+ }
+#endif
+
+ //! Get the sprite-index for the given state or -1 when no sprite is set
+ virtual s32 getSpriteIndex(gui::EGUI_BUTTON_STATE state) const;
+
+ //! Get the sprite color for the given state. Color is only used when a sprite is set.
+ virtual video::SColor getSpriteColor(gui::EGUI_BUTTON_STATE state) const;
+
+ //! Returns if the sprite in the given state does loop
+ virtual bool getSpriteLoop(gui::EGUI_BUTTON_STATE state) const;
+
+ //! Returns if the sprite in the given state is scaled
+ virtual bool getSpriteScale(gui::EGUI_BUTTON_STATE state) const;
+
+ //! Sets if the button should behave like a push button. Which means it
+ //! can be in two states: Normal or Pressed. With a click on the button,
+ //! the user can change the state of the button.
+ virtual void setIsPushButton(bool isPushButton=true) override;
+
+ //! Checks whether the button is a push button
+ virtual bool isPushButton() const override;
+
+ //! Sets the pressed state of the button if this is a pushbutton
+ virtual void setPressed(bool pressed=true) override;
+
+ //! Returns if the button is currently pressed
+ virtual bool isPressed() const override;
+
+ //! Sets if the button should use the skin to draw its border
+ virtual void setDrawBorder(bool border=true) override;
+
+ //! Checks if the button face and border are being drawn
+ virtual bool isDrawingBorder() const override;
+
+ //! Sets if the alpha channel should be used for drawing images on the button (default is false)
+ virtual void setUseAlphaChannel(bool useAlphaChannel=true) override;
+
+ //! Checks if the alpha channel should be used for drawing images on the button
+ virtual bool isAlphaChannelUsed() const override;
+
+ //! Sets if the button should scale the button images to fit
+ virtual void setScaleImage(bool scaleImage=true) override;
+
+ //! Checks whether the button scales the used images
+ virtual bool isScalingImage() const override;
+
+ //! Get if the shift key was pressed in last EGET_BUTTON_CLICKED event
+ virtual bool getClickShiftState() const
+ {
+ return ClickShiftState;
+ }
+
+ //! Get if the control key was pressed in last EGET_BUTTON_CLICKED event
+ virtual bool getClickControlState() const
+ {
+ return ClickControlState;
+ }
+
+ //! Writes attributes of the element.
+ virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const override;
+
+ //! Reads attributes of the element
+ virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options) override;
+
+
+
+ void setColor(video::SColor color);
+
+
+ //! 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"");
+
+protected:
+ void drawSprite(gui::EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center);
+ gui::EGUI_BUTTON_IMAGE_STATE getImageState(bool pressed) const;
+
+private:
+
+ struct ButtonSprite
+ {
+ ButtonSprite() : Index(-1), Loop(false), Scale(false)
+ {
+ }
+
+ bool operator==(const ButtonSprite& other) const
+ {
+ return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale;
+ }
+
+ s32 Index;
+ video::SColor Color;
+ bool Loop;
+ bool Scale;
+ };
+
+ ButtonSprite ButtonSprites[gui::EGBS_COUNT];
+ gui::IGUISpriteBank* SpriteBank;
+
+ struct ButtonImage
+ {
+ ButtonImage() : Texture(0), SourceRect(core::rect<s32>(0,0,0,0))
+ {
+ }
+
+ ButtonImage(const ButtonImage& other) : Texture(0), SourceRect(core::rect<s32>(0,0,0,0))
+ {
+ *this = other;
+ }
+
+ ~ButtonImage()
+ {
+ if ( Texture )
+ Texture->drop();
+ }
+
+ ButtonImage& operator=(const ButtonImage& other)
+ {
+ if ( this == &other )
+ return *this;
+
+ if (other.Texture)
+ other.Texture->grab();
+ if ( Texture )
+ Texture->drop();
+ Texture = other.Texture;
+ SourceRect = other.SourceRect;
+ return *this;
+ }
+
+ bool operator==(const ButtonImage& other) const
+ {
+ return Texture == other.Texture && SourceRect == other.SourceRect;
+ }
+
+
+ video::ITexture* Texture;
+ core::rect<s32> SourceRect;
+ };
+
+ ButtonImage ButtonImages[gui::EGBIS_COUNT];
+
+ gui::IGUIFont* OverrideFont;
+
+ bool OverrideColorEnabled;
+ video::SColor OverrideColor;
+
+ u32 ClickTime, HoverTime, FocusTime;
+
+ bool ClickShiftState;
+ bool ClickControlState;
+
+ bool IsPushButton;
+ bool Pressed;
+ bool UseAlphaChannel;
+ bool DrawBorder;
+ bool ScaleImage;
+
+ video::SColor Colors[4];
+};
diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp
index 1ccb4e6d1..e67fae3c6 100644
--- a/src/gui/guiChatConsole.cpp
+++ b/src/gui/guiChatConsole.cpp
@@ -77,7 +77,7 @@ GUIChatConsole::GUIChatConsole(
m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono);
if (!m_font) {
- errorstream << "GUIChatConsole: Unable to load mono font ";
+ errorstream << "GUIChatConsole: Unable to load mono font" << std::endl;
} else {
core::dimension2d<u32> dim = m_font->getDimension(L"M");
m_fontsize = v2u32(dim.Width, dim.Height);
@@ -139,7 +139,7 @@ f32 GUIChatConsole::getDesiredHeight() const
return m_desired_height_fraction;
}
-void GUIChatConsole::replaceAndAddToHistory(std::wstring line)
+void GUIChatConsole::replaceAndAddToHistory(const std::wstring &line)
{
ChatPrompt& prompt = m_chat_backend->getPrompt();
prompt.addToHistory(prompt.getLine());
@@ -322,9 +322,9 @@ void GUIChatConsole::drawText()
core::rect<s32> destrect(
x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
-
- #if USE_FREETYPE
- // Draw colored text if FreeType is enabled
+#if USE_FREETYPE
+ if (m_font->getType() == irr::gui::EGFT_CUSTOM) {
+ // Draw colored text if FreeType is enabled
irr::gui::CGUITTFont *tmp = dynamic_cast<irr::gui::CGUITTFont *>(m_font);
tmp->draw(
fragment.text,
@@ -333,8 +333,10 @@ void GUIChatConsole::drawText()
false,
false,
&AbsoluteClippingRect);
- #else
- // Otherwise use standard text
+ } else
+#endif
+ {
+ // Otherwise use standard text
m_font->draw(
fragment.text.c_str(),
destrect,
@@ -342,7 +344,7 @@ void GUIChatConsole::drawText()
false,
false,
&AbsoluteClippingRect);
- #endif
+ }
}
}
}
diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h
index ef8a87673..7be40e27c 100644
--- a/src/gui/guiChatConsole.h
+++ b/src/gui/guiChatConsole.h
@@ -60,7 +60,7 @@ public:
f32 getDesiredHeight() const;
// Replace actual line when adding the actual to the history (if there is any)
- void replaceAndAddToHistory(std::wstring line);
+ void replaceAndAddToHistory(const std::wstring &line);
// Change how the cursor looks
void setCursor(
diff --git a/src/gui/guiConfirmRegistration.cpp b/src/gui/guiConfirmRegistration.cpp
index 6e6b7ad16..6fe2a4fc4 100644
--- a/src/gui/guiConfirmRegistration.cpp
+++ b/src/gui/guiConfirmRegistration.cpp
@@ -38,10 +38,10 @@ const int ID_cancel = 265;
GUIConfirmRegistration::GUIConfirmRegistration(gui::IGUIEnvironment *env,
gui::IGUIElement *parent, s32 id, IMenuManager *menumgr, Client *client,
const std::string &playername, const std::string &password,
- const std::string &address, bool *aborted) :
+ bool *aborted) :
GUIModalMenu(env, parent, id, menumgr),
m_client(client), m_playername(playername), m_password(password),
- m_address(address), m_aborted(aborted)
+ m_aborted(aborted)
{
#ifdef __ANDROID__
m_touchscreen_visible = false;
@@ -62,6 +62,7 @@ void GUIConfirmRegistration::removeChildren()
for (gui::IGUIElement *i : children_copy)
i->remove();
}
+
void GUIConfirmRegistration::regenerateGui(v2u32 screensize)
{
acceptInput();
@@ -119,6 +120,7 @@ void GUIConfirmRegistration::regenerateGui(v2u32 screensize)
gui::IGUIEditBox *e = Environment->addEditBox(m_pass_confirm.c_str(),
rect2, true, this, ID_confirmPassword);
e->setPasswordBox(true);
+ Environment->setFocus(e);
}
ypos += 60 * s;
@@ -218,8 +220,7 @@ bool GUIConfirmRegistration::OnEvent(const SEvent &event)
if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) {
if (!canTakeFocus(event.GUIEvent.Element)) {
- dstream << "GUIConfirmRegistration: Not allowing focus "
- "change."
+ dstream << "GUIConfirmRegistration: Not allowing focus change."
<< std::endl;
// Returning true disables focus change
return true;
diff --git a/src/gui/guiConfirmRegistration.h b/src/gui/guiConfirmRegistration.h
index 2f2066c21..42c07e4ed 100644
--- a/src/gui/guiConfirmRegistration.h
+++ b/src/gui/guiConfirmRegistration.h
@@ -32,7 +32,7 @@ public:
GUIConfirmRegistration(gui::IGUIEnvironment *env, gui::IGUIElement *parent,
s32 id, IMenuManager *menumgr, Client *client,
const std::string &playername, const std::string &password,
- const std::string &address, bool *aborted);
+ bool *aborted);
~GUIConfirmRegistration();
void removeChildren();
@@ -61,7 +61,6 @@ private:
Client *m_client = nullptr;
const std::string &m_playername;
const std::string &m_password;
- const std::string &m_address;
bool *m_aborted = nullptr;
std::wstring m_pass_confirm = L"";
};
diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp
index 4a821c7b4..2f909f54f 100644
--- a/src/gui/guiEditBoxWithScrollbar.cpp
+++ b/src/gui/guiEditBoxWithScrollbar.cpp
@@ -13,7 +13,6 @@
#include "porting.h"
#include "Keycodes.h"
-
/*
todo:
optional scrollbars [done]
@@ -76,7 +75,8 @@ GUIEditBoxWithScrollBar::~GUIEditBoxWithScrollBar()
if (m_operator)
m_operator->drop();
- m_vscrollbar->remove();
+ if (m_vscrollbar)
+ m_vscrollbar->drop();
}
@@ -1400,7 +1400,9 @@ void GUIEditBoxWithScrollBar::createVScrollBar()
irr::core::rect<s32> scrollbarrect = m_frame_rect;
scrollbarrect.UpperLeftCorner.X += m_frame_rect.getWidth() - m_scrollbar_width;
- m_vscrollbar = Environment->addScrollBar(false, scrollbarrect, getParent(), getID());
+ m_vscrollbar = new GUIScrollBar(Environment, getParent(), -1,
+ scrollbarrect, false, true);
+
m_vscrollbar->setVisible(false);
m_vscrollbar->setSmallStep(1);
m_vscrollbar->setLargeStep(1);
@@ -1422,6 +1424,7 @@ void GUIEditBoxWithScrollBar::updateVScrollBar()
if (scrollymax != m_vscrollbar->getMax()) {
// manage a newline or a deleted line
m_vscrollbar->setMax(scrollymax);
+ m_vscrollbar->setPageSize(s32(getTextDimension().Height));
calculateScrollPos();
} else {
// manage a newline or a deleted line
@@ -1436,6 +1439,7 @@ void GUIEditBoxWithScrollBar::updateVScrollBar()
s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight();
if (scrollymax != m_vscrollbar->getMax()) {
m_vscrollbar->setMax(scrollymax);
+ m_vscrollbar->setPageSize(s32(getTextDimension().Height));
}
if (!m_vscrollbar->isVisible()) {
@@ -1448,10 +1452,10 @@ void GUIEditBoxWithScrollBar::updateVScrollBar()
m_vscroll_pos = 0;
m_vscrollbar->setPos(0);
m_vscrollbar->setMax(1);
+ m_vscrollbar->setPageSize(s32(getTextDimension().Height));
}
}
-
}
//! set true if this editbox is writable
diff --git a/src/gui/guiEditBoxWithScrollbar.h b/src/gui/guiEditBoxWithScrollbar.h
index cedffd82f..77538e2f7 100644
--- a/src/gui/guiEditBoxWithScrollbar.h
+++ b/src/gui/guiEditBoxWithScrollbar.h
@@ -7,7 +7,7 @@
#include "IGUIEditBox.h"
#include "IOSOperator.h"
-#include "IGUIScrollBar.h"
+#include "guiScrollBar.h"
#include <vector>
using namespace irr;
@@ -116,7 +116,7 @@ public:
//! Updates the absolute position, splits text if required
virtual void updateAbsolutePosition();
-
+
virtual void setWritable(bool writable);
//! Change the background color
@@ -187,7 +187,7 @@ protected:
core::rect<s32> m_current_text_rect, m_frame_rect; // temporary values
u32 m_scrollbar_width;
- IGUIScrollBar *m_vscrollbar;
+ GUIScrollBar *m_vscrollbar;
bool m_writable;
bool m_bg_color_used;
diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp
index 6030a5bfb..3107d64cd 100644
--- a/src/gui/guiEngine.cpp
+++ b/src/gui/guiEngine.cpp
@@ -39,9 +39,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/guiscalingfilter.h"
#include "irrlicht_changes/static_text.h"
-#ifdef __ANDROID__
+#if ENABLE_GLES
#include "client/tile.h"
-#include <GLES/gl.h>
#endif
@@ -78,7 +77,7 @@ video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
m_to_delete.insert(name);
-#ifdef __ANDROID__
+#if ENABLE_GLES
video::ITexture *retval = m_driver->findTexture(name.c_str());
if (retval)
return retval;
@@ -391,6 +390,15 @@ void GUIEngine::cloudPostProcess()
}
/******************************************************************************/
+void GUIEngine::setFormspecPrepend(const std::string &fs)
+{
+ if (m_menu) {
+ m_menu->setFormspecPrepend(fs);
+ }
+}
+
+
+/******************************************************************************/
void GUIEngine::drawBackground(video::IVideoDriver *driver)
{
v2u32 screensize = driver->getScreenSize();
@@ -518,7 +526,7 @@ void GUIEngine::drawFooter(video::IVideoDriver *driver)
}
/******************************************************************************/
-bool GUIEngine::setTexture(texture_layer layer, std::string texturepath,
+bool GUIEngine::setTexture(texture_layer layer, const std::string &texturepath,
bool tile_image, unsigned int minsize)
{
video::IVideoDriver *driver = RenderingEngine::get_video_driver();
@@ -593,7 +601,7 @@ void GUIEngine::updateTopLeftTextSize()
}
/******************************************************************************/
-s32 GUIEngine::playSound(SimpleSoundSpec spec, bool looped)
+s32 GUIEngine::playSound(const SimpleSoundSpec &spec, bool looped)
{
s32 handle = m_sound_manager->playSound(spec, looped);
return handle;
@@ -611,4 +619,3 @@ unsigned int GUIEngine::queueAsync(const std::string &serialized_func,
{
return m_script->queueAsync(serialized_func, serialized_params);
}
-
diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h
index 409ba94c4..e55531bbc 100644
--- a/src/gui/guiEngine.h
+++ b/src/gui/guiEngine.h
@@ -221,6 +221,8 @@ private:
/** script basefolder */
std::string m_scriptdir = "";
+ void setFormspecPrepend(const std::string &fs);
+
/**
* draw background layer
* @param driver to use for drawing
@@ -247,7 +249,7 @@ private:
* @param layer draw layer to specify texture
* @param texturepath full path of texture to load
*/
- bool setTexture(texture_layer layer, std::string texturepath,
+ bool setTexture(texture_layer layer, const std::string &texturepath,
bool tile_image, unsigned int minsize);
/**
@@ -296,7 +298,7 @@ private:
clouddata m_cloud;
/** start playing a sound and return handle */
- s32 playSound(SimpleSoundSpec spec, bool looped);
+ s32 playSound(const SimpleSoundSpec &spec, bool looped);
/** stop playing a sound started with playSound() */
void stopSound(s32 handle);
diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp
index 6c404728f..aee7da869 100644
--- a/src/gui/guiFormSpecMenu.cpp
+++ b/src/gui/guiFormSpecMenu.cpp
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iterator>
#include <sstream>
#include <limits>
+#include "guiButton.h"
#include "guiFormSpecMenu.h"
#include "guiTable.h"
#include "constants.h"
@@ -66,8 +67,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define MY_CHECKGEOM(a,b) \
if (v_geom.size() != 2) { \
- errorstream<< "Invalid pos for element " << a << "specified: \"" \
- << parts[b] << "\"" << std::endl; \
+ errorstream<< "Invalid geometry for element " << a << \
+ "specified: \"" << parts[b] << "\"" << std::endl; \
return; \
}
/*
@@ -86,7 +87,7 @@ inline u32 clamp_u8(s32 value)
GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick,
gui::IGUIElement *parent, s32 id, IMenuManager *menumgr,
Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst,
- std::string formspecPrepend,
+ const std::string &formspecPrepend,
bool remap_dbl_click):
GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr),
m_invmgr(client),
@@ -270,6 +271,25 @@ v2s32 GUIFormSpecMenu::getElementBasePos(bool absolute,
return v2s32(pos_f.X, pos_f.Y);
}
+v2s32 GUIFormSpecMenu::getRealCoordinateBasePos(bool absolute,
+ const std::vector<std::string> &v_pos)
+{
+ v2f32 pos_f = v2f32(0.0f, 0.0f);
+
+ pos_f.X += stof(v_pos[0]) + pos_offset.X;
+ pos_f.Y += stof(v_pos[1]) + pos_offset.Y;
+
+ if (absolute)
+ return v2s32(pos_f.X * imgsize.X + AbsoluteRect.UpperLeftCorner.X,
+ pos_f.Y * imgsize.Y + AbsoluteRect.UpperLeftCorner.Y);
+ return v2s32(pos_f.X * imgsize.X, pos_f.Y * imgsize.Y);
+}
+
+v2s32 GUIFormSpecMenu::getRealCoordinateGeometry(const std::vector<std::string> &v_geom)
+{
+ return v2s32(stof(v_geom[0]) * imgsize.X, stof(v_geom[1]) * imgsize.Y);
+}
+
void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element)
{
std::vector<std::string> parts = split(element,',');
@@ -306,8 +326,8 @@ void GUIFormSpecMenu::parseContainer(parserData* data, const std::string &elemen
parts[1] = parts[1].substr(0, parts[1].find(';'));
container_stack.push(pos_offset);
- pos_offset.X += MYMAX(0, stof(parts[0]));
- pos_offset.Y += MYMAX(0, stof(parts[1]));
+ pos_offset.X += stof(parts[0]);
+ pos_offset.Y += stof(parts[1]);
return;
}
errorstream<< "Invalid container start element (" << parts.size() << "): '" << element << "'" << std::endl;
@@ -348,13 +368,19 @@ void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
InventoryLocation loc;
- if(location == "context" || location == "current_name")
+ if (location == "context" || location == "current_name")
loc = m_current_inventory_location;
else
loc.deSerialize(location);
- v2s32 pos = getElementBasePos(true, &v_pos);
+ v2s32 pos;
v2s32 geom;
+
+ if (data->real_coordinates)
+ pos = getRealCoordinateBasePos(true, v_pos);
+ else
+ pos = getElementBasePos(true, &v_pos);
+
geom.X = stoi(v_geom[0]);
geom.Y = stoi(v_geom[1]);
@@ -369,7 +395,7 @@ void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
if(!data->explicit_size)
warningstream<<"invalid use of list without a size[] element"<<std::endl;
- m_inventorylists.emplace_back(loc, listname, pos, geom, start_i);
+ m_inventorylists.emplace_back(loc, listname, pos, geom, start_i, data->real_coordinates);
return;
}
errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl;
@@ -430,20 +456,37 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
MY_CHECKPOS("checkbox",0);
- v2s32 pos = getElementBasePos(false, &v_pos);
-
bool fselected = false;
if (selected == "true")
fselected = true;
std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
- s32 spacing = Environment->getSkin()->getSize(gui::EGDS_CHECK_BOX_WIDTH) + 7;
+ const core::dimension2d<u32> label_size = m_font->getDimension(wlabel.c_str());
+ s32 cb_size = Environment->getSkin()->getSize(gui::EGDS_CHECK_BOX_WIDTH);
+ s32 y_center = (std::max(label_size.Height, (u32)cb_size) + 1) / 2;
+
+ v2s32 pos;
+ core::rect<s32> rect;
- core::rect<s32> rect = core::rect<s32>(
- pos.X, pos.Y + ((imgsize.Y / 2) - m_btn_height),
- pos.X + m_font->getDimension(wlabel.c_str()).Width + spacing,
- pos.Y + ((imgsize.Y / 2) + m_btn_height));
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+
+ rect = core::rect<s32>(
+ pos.X,
+ pos.Y - y_center,
+ pos.X + label_size.Width + cb_size + 7,
+ pos.Y + y_center
+ );
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+ rect = core::rect<s32>(
+ pos.X,
+ pos.Y + imgsize.Y / 2 - y_center,
+ pos.X + label_size.Width + cb_size + 7,
+ pos.Y + imgsize.Y / 2 + y_center
+ );
+ }
FieldSpec spec(
name,
@@ -457,6 +500,9 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element
gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this,
spec.fid, spec.flabel.c_str());
+ auto style = getStyleForElement("checkbox", name);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
}
@@ -474,24 +520,25 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen
if (parts.size() >= 5) {
std::vector<std::string> v_pos = split(parts[0],',');
- std::vector<std::string> v_dim = split(parts[1],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
std::string name = parts[3];
std::string value = parts[4];
MY_CHECKPOS("scrollbar",0);
+ MY_CHECKGEOM("scrollbar",1);
- v2s32 pos = getElementBasePos(false, &v_pos);
+ v2s32 pos;
+ v2s32 dim;
- if (v_dim.size() != 2) {
- errorstream<< "Invalid size for element " << "scrollbar"
- << "specified: \"" << parts[1] << "\"" << std::endl;
- return;
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+ dim = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+ dim.X = stof(v_geom[0]) * spacing.X;
+ dim.Y = stof(v_geom[1]) * spacing.Y;
}
- v2s32 dim;
- dim.X = stof(v_dim[0]) * spacing.X;
- dim.Y = stof(v_dim[1]) * spacing.Y;
-
core::rect<s32> rect =
core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y);
@@ -512,6 +559,9 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &elemen
gui::IGUIScrollBar* e =
Environment->addScrollBar(is_horizontal,rect,this,spec.fid);
+ auto style = getStyleForElement("scrollbar", name);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+
e->setMax(1000);
e->setMin(0);
e->setPos(stoi(parts[4]));
@@ -539,10 +589,17 @@ void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
MY_CHECKPOS("image", 0);
MY_CHECKGEOM("image", 1);
- v2s32 pos = getElementBasePos(true, &v_pos);
+ v2s32 pos;
v2s32 geom;
- geom.X = stof(v_geom[0]) * (float)imgsize.X;
- geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(true, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(true, &v_pos);
+ geom.X = stof(v_geom[0]) * (float)imgsize.X;
+ geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+ }
if (!data->explicit_size)
warningstream<<"invalid use of image without a size[] element"<<std::endl;
@@ -580,10 +637,17 @@ void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &elemen
MY_CHECKPOS("itemimage",0);
MY_CHECKGEOM("itemimage",1);
- v2s32 pos = getElementBasePos(true, &v_pos);
+ v2s32 pos;
v2s32 geom;
- geom.X = stof(v_geom[0]) * (float)imgsize.X;
- geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(true, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(true, &v_pos);
+ geom.X = stof(v_geom[0]) * (float)imgsize.X;
+ geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+ }
if(!data->explicit_size)
warningstream<<"invalid use of item_image without a size[] element"<<std::endl;
@@ -609,14 +673,23 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
MY_CHECKPOS("button",0);
MY_CHECKGEOM("button",1);
- v2s32 pos = getElementBasePos(false, &v_pos);
+ v2s32 pos;
v2s32 geom;
- geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
- pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
+ core::rect<s32> rect;
- core::rect<s32> rect =
- core::rect<s32>(pos.X, pos.Y - m_btn_height,
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
+ pos.Y+geom.Y);
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+ geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
+ pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
+
+ rect = core::rect<s32>(pos.X, pos.Y - m_btn_height,
pos.X + geom.X, pos.Y + m_btn_height);
+ }
if(!data->explicit_size)
warningstream<<"invalid use of button without a size[] element"<<std::endl;
@@ -632,8 +705,38 @@ void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
spec.ftype = f_Button;
if(type == "button_exit")
spec.is_exit = true;
- gui::IGUIButton* e = Environment->addButton(rect, this, spec.fid,
- spec.flabel.c_str());
+
+ GUIButton *e = GUIButton::addButton(Environment, rect, this, spec.fid, spec.flabel.c_str());
+
+ auto style = getStyleForElement(type, name, (type != "button") ? "button" : "");
+ if (style.isNotDefault(StyleSpec::BGCOLOR)) {
+ e->setColor(style.getColor(StyleSpec::BGCOLOR));
+ }
+ if (style.isNotDefault(StyleSpec::TEXTCOLOR)) {
+ e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR));
+ }
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
+
+ if (style.isNotDefault(StyleSpec::BGIMG)) {
+ std::string image_name = style.get(StyleSpec::BGIMG, "");
+ std::string pressed_image_name = style.get(StyleSpec::BGIMG_PRESSED, "");
+
+ video::ITexture *texture = 0;
+ video::ITexture *pressed_texture = 0;
+ texture = m_tsrc->getTexture(image_name);
+ if (!pressed_image_name.empty())
+ pressed_texture = m_tsrc->getTexture(pressed_image_name);
+ else
+ pressed_texture = texture;
+
+ e->setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
+ e->setImage(guiScalingImageButton(
+ Environment->getVideoDriver(), texture, geom.X, geom.Y));
+ e->setPressedImage(guiScalingImageButton(
+ Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
+ e->setScaleImage(true);
+ }
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
@@ -649,9 +752,8 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
{
std::vector<std::string> parts = split(element,';');
- if (((parts.size() == 3) || (parts.size() == 4)) ||
- ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
- {
+ if ((parts.size() >= 3 && parts.size() <= 5) ||
+ (parts.size() > 5 && m_formspec_version > FORMSPEC_API_VERSION)) {
std::vector<std::string> v_pos = split(parts[0],',');
std::vector<std::string> v_geom = split(parts[1],',');
std::string name = unescape_string(parts[2]);
@@ -659,25 +761,58 @@ void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &eleme
MY_CHECKPOS("background",0);
MY_CHECKGEOM("background",1);
- v2s32 pos = getElementBasePos(true, &v_pos);
- pos.X -= (spacing.X - (float)imgsize.X) / 2;
- pos.Y -= (spacing.Y - (float)imgsize.Y) / 2;
-
+ v2s32 pos;
v2s32 geom;
- geom.X = stof(v_geom[0]) * spacing.X;
- geom.Y = stof(v_geom[1]) * spacing.Y;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(true, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(true, &v_pos);
+ pos.X -= (spacing.X - (float)imgsize.X) / 2;
+ pos.Y -= (spacing.Y - (float)imgsize.Y) / 2;
+
+ geom.X = stof(v_geom[0]) * spacing.X;
+ geom.Y = stof(v_geom[1]) * spacing.Y;
+ }
bool clip = false;
- if (parts.size() == 4 && is_yes(parts[3])) {
- pos.X = stoi(v_pos[0]); //acts as offset
- pos.Y = stoi(v_pos[1]); //acts as offset
+ if (parts.size() >= 4 && is_yes(parts[3])) {
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos) * -1;
+ geom = v2s32(0, 0);
+ } else {
+ pos.X = stoi(v_pos[0]); //acts as offset
+ pos.Y = stoi(v_pos[1]);
+ }
clip = true;
}
+ core::rect<s32> middle;
+ if (parts.size() >= 5) {
+ std::vector<std::string> v_middle = split(parts[4], ',');
+ if (v_middle.size() == 1) {
+ s32 x = stoi(v_middle[0]);
+ middle.UpperLeftCorner = core::vector2di(x, x);
+ middle.LowerRightCorner = core::vector2di(-x, -x);
+ } else if (v_middle.size() == 2) {
+ s32 x = stoi(v_middle[0]);
+ s32 y = stoi(v_middle[1]);
+ middle.UpperLeftCorner = core::vector2di(x, y);
+ middle.LowerRightCorner = core::vector2di(-x, -y);
+ // `-x` is interpreted as `w - x`
+ } else if (v_middle.size() == 4) {
+ middle.UpperLeftCorner = core::vector2di(stoi(v_middle[0]), stoi(v_middle[1]));
+ middle.LowerRightCorner = core::vector2di(stoi(v_middle[2]), stoi(v_middle[3]));
+ } else {
+ warningstream << "Invalid rectangle given to middle param of background[] element" << std::endl;
+ }
+ }
+
if (!data->explicit_size && !clip)
warningstream << "invalid use of unclipped background without a size[] element" << std::endl;
- m_backgrounds.emplace_back(name, pos, geom, clip);
+ m_backgrounds.emplace_back(name, pos, geom, middle, clip);
return;
}
@@ -736,10 +871,17 @@ void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
MY_CHECKPOS("table",0);
MY_CHECKGEOM("table",1);
- v2s32 pos = getElementBasePos(false, &v_pos);
+ v2s32 pos;
v2s32 geom;
- geom.X = stof(v_geom[0]) * spacing.X;
- geom.Y = stof(v_geom[1]) * spacing.Y;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+ geom.X = stof(v_geom[0]) * spacing.X;
+ geom.Y = stof(v_geom[1]) * spacing.Y;
+ }
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
@@ -773,6 +915,9 @@ 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);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+
m_tables.emplace_back(spec, e);
m_fields.push_back(spec);
return;
@@ -803,11 +948,17 @@ void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element
MY_CHECKPOS("textlist",0);
MY_CHECKGEOM("textlist",1);
- v2s32 pos = getElementBasePos(false, &v_pos);
+ v2s32 pos;
v2s32 geom;
- geom.X = stof(v_geom[0]) * spacing.X;
- geom.Y = stof(v_geom[1]) * spacing.Y;
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+ geom.X = stof(v_geom[0]) * spacing.X;
+ geom.Y = stof(v_geom[1]) * spacing.Y;
+ }
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
@@ -841,6 +992,9 @@ 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);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+
m_tables.emplace_back(spec, e);
m_fields.push_back(spec);
return;
@@ -864,12 +1018,29 @@ void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element
MY_CHECKPOS("dropdown",0);
- v2s32 pos = getElementBasePos(false, &v_pos);
+ v2s32 pos;
+ v2s32 geom;
+ core::rect<s32> rect;
+
+ if (data->real_coordinates) {
+ std::vector<std::string> v_geom = split(parts[1],',');
+
+ if (v_geom.size() == 1)
+ v_geom.emplace_back("1");
+
+ MY_CHECKGEOM("dropdown",1);
- s32 width = stof(parts[1]) * spacing.Y;
+ pos = getRealCoordinateBasePos(false, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+
+ s32 width = stof(parts[1]) * spacing.Y;
- core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y,
- pos.X + width, pos.Y + (m_btn_height * 2));
+ rect = core::rect<s32>(pos.X, pos.Y,
+ pos.X + width, pos.Y + (m_btn_height * 2));
+ }
FieldSpec spec(
name,
@@ -896,6 +1067,9 @@ 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);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+
m_fields.push_back(spec);
m_dropdowns.emplace_back(spec, std::vector<std::string>());
@@ -934,15 +1108,22 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
MY_CHECKPOS("pwdfield",0);
MY_CHECKGEOM("pwdfield",1);
- v2s32 pos = getElementBasePos(false, &v_pos);
- pos -= padding;
-
+ v2s32 pos;
v2s32 geom;
- geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
- pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
- pos.Y -= m_btn_height;
- geom.Y = m_btn_height*2;
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+ pos -= padding;
+
+ geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
+
+ pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
+ pos.Y -= m_btn_height;
+ geom.Y = m_btn_height*2;
+ }
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
@@ -972,6 +1153,11 @@ void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element
e->setPasswordBox(true,L'*');
+ auto style = getStyleForElement("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)));
+
irr::SEvent evt;
evt.EventType = EET_KEY_INPUT_EVENT;
evt.KeyInput.Key = KEY_END;
@@ -1022,12 +1208,14 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
true, Environment, this, spec.fid, rect, is_editable, is_multiline);
e->drop();
} else {
- if (is_multiline)
+ if (is_multiline) {
e = new GUIEditBoxWithScrollBar(spec.fdefault.c_str(), true,
Environment, this, spec.fid, rect, is_editable, true);
- else if (is_editable)
+ e->drop();
+ } else if (is_editable) {
e = Environment->addEditBox(spec.fdefault.c_str(), rect, true,
this, spec.fid);
+ }
}
if (e) {
@@ -1048,6 +1236,14 @@ void GUIFormSpecMenu::createTextField(parserData *data, FieldSpec &spec,
evt.KeyInput.PressedDown = true;
e->OnEvent(evt);
}
+
+ auto style = getStyleForElement(is_multiline ? "textarea" : "field", spec.fname);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
+ e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
+ if (style.get(StyleSpec::BGCOLOR, "") == "transparent") {
+ e->setDrawBackground(false);
+ }
}
if (!spec.flabel.empty()) {
@@ -1117,23 +1313,29 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>&
MY_CHECKPOS(type,0);
MY_CHECKGEOM(type,1);
- v2s32 pos = getElementBasePos(false, &v_pos);
- pos -= padding;
-
+ v2s32 pos;
v2s32 geom;
- geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+ pos -= padding;
- if (type == "textarea")
- {
- geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y);
- pos.Y += m_btn_height;
- }
- else
- {
- pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
- pos.Y -= m_btn_height;
- geom.Y = m_btn_height*2;
+ geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
+
+ if (type == "textarea")
+ {
+ geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y);
+ pos.Y += m_btn_height;
+ }
+ else
+ {
+ pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
+ pos.Y -= m_btn_height;
+ geom.Y = m_btn_height*2;
+ }
}
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
@@ -1197,46 +1399,79 @@ void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
MY_CHECKPOS("label",0);
- v2s32 pos = getElementBasePos(false, nullptr);
- pos.X += stof(v_pos[0]) * spacing.X;
- pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y;
-
if(!data->explicit_size)
warningstream<<"invalid use of label without a size[] element"<<std::endl;
std::vector<std::string> lines = split(text, '\n');
for (unsigned int i = 0; i != lines.size(); i++) {
- // Lines are spaced at the nominal distance of
- // 2/5 inventory slot, even if the font doesn't
- // quite match that. This provides consistent
- // form layout, at the expense of sometimes
- // having sub-optimal spacing for the font.
- // We multiply by 2 and then divide by 5, rather
- // than multiply by 0.4, to get exact results
- // in the integer cases: 0.4 is not exactly
- // representable in binary floating point.
- s32 posy = pos.Y + ((float)i) * spacing.Y * 2.0 / 5.0;
- std::wstring wlabel = utf8_to_wide(unescape_string(lines[i]));
- core::rect<s32> rect = core::rect<s32>(
- pos.X, posy - m_btn_height,
- pos.X + m_font->getDimension(wlabel.c_str()).Width,
- posy + m_btn_height);
+ std::wstring wlabel_colors = translate_string(
+ utf8_to_wide(unescape_string(lines[i])));
+ // Without color escapes to get the font dimensions
+ std::wstring wlabel_plain = unescape_enriched(wlabel_colors);
+
+ core::rect<s32> rect;
+
+ if (data->real_coordinates) {
+ // Lines are spaced at the distance of 1/2 imgsize.
+ // This alows lines that line up with the new elements
+ // easily without sacrificing good line distance. If
+ // it was one whole imgsize, it would have too much
+ // spacing.
+ v2s32 pos = getRealCoordinateBasePos(false, v_pos);
+
+ // Labels are positioned by their center, not their top.
+ pos.Y += (((float) imgsize.Y) / -2) + (((float) imgsize.Y) * i / 2);
+
+ rect = core::rect<s32>(
+ pos.X, pos.Y,
+ pos.X + m_font->getDimension(wlabel_plain.c_str()).Width,
+ pos.Y + imgsize.Y);
+
+ } else {
+ // Lines are spaced at the nominal distance of
+ // 2/5 inventory slot, even if the font doesn't
+ // quite match that. This provides consistent
+ // form layout, at the expense of sometimes
+ // having sub-optimal spacing for the font.
+ // We multiply by 2 and then divide by 5, rather
+ // than multiply by 0.4, to get exact results
+ // in the integer cases: 0.4 is not exactly
+ // representable in binary floating point.
+
+ v2s32 pos = getElementBasePos(false, nullptr);
+ pos.X += stof(v_pos[0]) * spacing.X;
+ pos.Y += (stof(v_pos[1]) + 7.0f / 30.0f) * spacing.Y;
+
+ pos.Y += ((float) i) * spacing.Y * 2.0 / 5.0;
+
+ rect = core::rect<s32>(
+ pos.X, pos.Y - m_btn_height,
+ pos.X + m_font->getDimension(wlabel_plain.c_str()).Width,
+ pos.Y + m_btn_height);
+ }
+
FieldSpec spec(
"",
- wlabel,
+ wlabel_colors,
L"",
258+m_fields.size()
);
gui::IGUIStaticText *e = gui::StaticText::add(Environment,
spec.flabel.c_str(), rect, false, false, this, spec.fid);
e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_CENTER);
+
+ auto style = getStyleForElement("label", spec.fname);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
+
m_fields.push_back(spec);
}
return;
}
- errorstream<< "Invalid label element(" << parts.size() << "): '" << element << "'" << std::endl;
+ errorstream << "Invalid label element(" << parts.size() << "): '" << element
+ << "'" << std::endl;
}
void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element)
@@ -1252,15 +1487,35 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen
MY_CHECKPOS("vertlabel",1);
- v2s32 pos = getElementBasePos(false, &v_pos);
+ v2s32 pos;
+ core::rect<s32> rect;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+
+ // Vertlabels are positioned by center, not left.
+ pos.X -= imgsize.X / 2;
+
+ // We use text.length + 1 because without it, the rect
+ // isn't quite tall enough and cuts off the text.
+ rect = core::rect<s32>(pos.X, pos.Y,
+ pos.X + imgsize.X,
+ pos.Y + font_line_height(m_font) *
+ (text.length() + 1));
- core::rect<s32> rect = core::rect<s32>(
- pos.X, pos.Y+((imgsize.Y/2)- m_btn_height),
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+
+ // As above, the length must be one longer. The width of
+ // the rect (15 pixels) seems rather arbitrary, but
+ // changing it might break something.
+ rect = core::rect<s32>(
+ pos.X, pos.Y+((imgsize.Y/2) - m_btn_height),
pos.X+15, pos.Y +
- font_line_height(m_font)
- * (text.length()+1)
- +((imgsize.Y/2)- m_btn_height));
- //actually text.length() would be correct but adding +1 avoids to break all mods
+ font_line_height(m_font) *
+ (text.length() + 1) +
+ ((imgsize.Y/2) - m_btn_height));
+ }
if(!data->explicit_size)
warningstream<<"invalid use of label without a size[] element"<<std::endl;
@@ -1278,9 +1533,14 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &elemen
L"",
258+m_fields.size()
);
- gui::IGUIStaticText *t = gui::StaticText::add(Environment, spec.flabel.c_str(),
+ gui::IGUIStaticText *e = gui::StaticText::add(Environment, spec.flabel.c_str(),
rect, false, false, this, spec.fid);
- t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
+ e->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
+
+ auto style = getStyleForElement("vertlabel", spec.fname, "label");
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ e->setOverrideColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
+
m_fields.push_back(spec);
return;
}
@@ -1304,11 +1564,6 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
MY_CHECKPOS("imagebutton",0);
MY_CHECKGEOM("imagebutton",1);
- v2s32 pos = getElementBasePos(false, &v_pos);
- v2s32 geom;
- geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
- geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
-
bool noclip = false;
bool drawborder = true;
std::string pressed_image_name;
@@ -1324,9 +1579,22 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
pressed_image_name = parts[7];
}
- core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+ v2s32 pos;
+ v2s32 geom;
- if(!data->explicit_size)
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+ geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
+ geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
+ }
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
+ pos.Y+geom.Y);
+
+ if (!data->explicit_size)
warningstream<<"invalid use of image_button without a size[] element"<<std::endl;
image_name = unescape_string(image_name);
@@ -1341,7 +1609,7 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
258+m_fields.size()
);
spec.ftype = f_Button;
- if(type == "image_button_exit")
+ if (type == "image_button_exit")
spec.is_exit = true;
video::ITexture *texture = 0;
@@ -1358,14 +1626,21 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
Environment->setFocus(e);
}
- e->setUseAlphaChannel(true);
+ auto style = getStyleForElement("image_button", spec.fname);
+
+ e->setUseAlphaChannel(style.getBool(StyleSpec::ALPHA, true));
e->setImage(guiScalingImageButton(
Environment->getVideoDriver(), texture, geom.X, geom.Y));
e->setPressedImage(guiScalingImageButton(
Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
e->setScaleImage(true);
- e->setNotClipped(noclip);
- e->setDrawBorder(drawborder);
+ if (parts.size() >= 7) {
+ e->setNotClipped(noclip);
+ e->setDrawBorder(drawborder);
+ } else {
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
+ }
m_fields.push_back(spec);
return;
@@ -1376,25 +1651,43 @@ void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &elem
void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element)
{
- std::vector<std::string> parts = split(element,';');
+ std::vector<std::string> parts = split(element, ';');
- if (((parts.size() == 4) || (parts.size() == 6)) ||
- ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ if (((parts.size() == 4) || (parts.size() == 6)) || (parts.size() == 7 &&
+ data->real_coordinates) || ((parts.size() > 6) &&
+ (m_formspec_version > FORMSPEC_API_VERSION)))
{
std::vector<std::string> v_pos = split(parts[0],',');
- std::string name = parts[1];
- std::vector<std::string> buttons = split(parts[2],',');
- std::string str_index = parts[3];
+
+ // If we're using real coordinates, add an extra field for height.
+ // 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
+ bool auto_width = true;
+ if (parts.size() == 7) {
+ i++;
+
+ v_geom = split(parts[1], ',');
+ if (v_geom.size() == 1)
+ v_geom.insert(v_geom.begin(), "1"); // Dummy value
+ else
+ auto_width = false;
+ }
+
+ std::string name = parts[i+1];
+ std::vector<std::string> buttons = split(parts[i+2], ',');
+ std::string str_index = parts[i+3];
bool show_background = true;
bool show_border = true;
- int tab_index = stoi(str_index) -1;
+ int tab_index = stoi(str_index) - 1;
- MY_CHECKPOS("tabheader",0);
+ MY_CHECKPOS("tabheader", 0);
- if (parts.size() == 6) {
- if (parts[4] == "true")
+ if (parts.size() == 6 + i) {
+ if (parts[4+i] == "true")
show_background = false;
- if (parts[5] == "false")
+ if (parts[5+i] == "false")
show_border = false;
}
@@ -1408,15 +1701,26 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
spec.ftype = f_TabHeader;
v2s32 pos;
- {
+ v2s32 geom;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+
+ geom = getRealCoordinateGeometry(v_geom);
+ pos.Y -= geom.Y; // TabHeader base pos is the bottom, not the top.
+ if (auto_width)
+ geom.X = DesiredRect.getWidth(); // Set automatic width
+
+ MY_CHECKGEOM("tabheader", 1);
+ } else {
v2f32 pos_f = pos_offset * spacing;
pos_f.X += stof(v_pos[0]) * spacing.X;
pos_f.Y += stof(v_pos[1]) * spacing.Y - m_btn_height * 2;
pos = v2s32(pos_f.X, pos_f.Y);
+
+ geom.Y = m_btn_height * 2;
+ geom.X = DesiredRect.getWidth();
}
- v2s32 geom;
- geom.X = DesiredRect.getWidth();
- geom.Y = m_btn_height*2;
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
pos.Y+geom.Y);
@@ -1425,17 +1729,22 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &elemen
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(m_btn_height*2);
+ e->setTabHeight(geom.Y);
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
}
- e->setNotClipped(true);
+ auto style = getStyleForElement("tabheader", name);
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, true));
for (const std::string &button : buttons) {
- e->addTab(unescape_translate(unescape_string(
+ auto tab = e->addTab(unescape_translate(unescape_string(
utf8_to_wide(button))).c_str(), -1);
+ if (style.isNotDefault(StyleSpec::BGCOLOR))
+ tab->setBackgroundColor(style.getColor(StyleSpec::BGCOLOR));
+
+ tab->setTextColor(style.getColor(StyleSpec::TEXTCOLOR, video::SColor(0xFFFFFFFF)));
}
if ((tab_index >= 0) &&
@@ -1476,10 +1785,17 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
MY_CHECKPOS("itemimagebutton",0);
MY_CHECKGEOM("itemimagebutton",1);
- v2s32 pos = getElementBasePos(false, &v_pos);
+ v2s32 pos;
v2s32 geom;
- geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
- geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(false, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(false, &v_pos);
+ geom.X = (stof(v_geom[0]) * spacing.X) - (spacing.X - imgsize.X);
+ geom.Y = (stof(v_geom[1]) * spacing.Y) - (spacing.Y - imgsize.Y);
+ }
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
@@ -1504,6 +1820,10 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, L"");
+ auto style = getStyleForElement("item_image_button", spec.fname, "image_button");
+ e->setNotClipped(style.getBool(StyleSpec::NOCLIP, false));
+ e->setDrawBorder(style.getBool(StyleSpec::BORDER, true));
+
if (spec.fname == data->focused_fieldname) {
Environment->setFocus(e);
}
@@ -1513,7 +1833,11 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &
spec.rect=rect;
m_fields.push_back(spec);
- pos = getElementBasePos(true, &v_pos);
+ if (data->real_coordinates)
+ pos = getRealCoordinateBasePos(true, v_pos);
+ else
+ pos = getElementBasePos(true, &v_pos);
+
m_itemimages.emplace_back("", item_name, e, pos, geom);
m_static_texts.emplace_back(utf8_to_wide(label), rect, e);
return;
@@ -1534,10 +1858,17 @@ void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
MY_CHECKPOS("box",0);
MY_CHECKGEOM("box",1);
- v2s32 pos = getElementBasePos(true, &v_pos);
+ v2s32 pos;
v2s32 geom;
- geom.X = stof(v_geom[0]) * spacing.X;
- geom.Y = stof(v_geom[1]) * spacing.Y;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(true, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(true, &v_pos);
+ geom.X = stof(v_geom[0]) * spacing.X;
+ geom.Y = stof(v_geom[1]) * spacing.Y;
+ }
video::SColor tmp_color;
@@ -1640,13 +1971,20 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
std::vector<std::string> v_pos = split(parts[0], ',');
std::vector<std::string> v_geom = split(parts[1], ',');
- MY_CHECKPOS("tooltip", 0);
+ MY_CHECKPOS("tooltip", 0);
MY_CHECKGEOM("tooltip", 1);
- v2s32 pos = getElementBasePos(true, &v_pos);
+ v2s32 pos;
v2s32 geom;
- geom.X = stof(v_geom[0]) * spacing.X;
- geom.Y = stof(v_geom[1]) * spacing.Y;
+
+ if (data->real_coordinates) {
+ pos = getRealCoordinateBasePos(true, v_pos);
+ geom = getRealCoordinateGeometry(v_geom);
+ } else {
+ pos = getElementBasePos(true, &v_pos);
+ geom.X = stof(v_geom[0]) * spacing.X;
+ geom.Y = stof(v_geom[1]) * spacing.Y;
+ }
irr::core::rect<s32> rect(pos, pos + geom);
m_tooltip_rects.emplace_back(rect, spec);
@@ -1772,12 +2110,70 @@ void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element)
<< "'" << std::endl;
}
+bool GUIFormSpecMenu::parseStyle(parserData *data, const std::string &element, bool style_type)
+{
+ std::vector<std::string> parts = split(element, ';');
+
+ if (parts.size() < 2) {
+ errorstream << "Invalid style element (" << parts.size() << "): '" << element
+ << "'" << std::endl;
+ return false;
+ }
+
+ std::string selector = trim(parts[0]);
+ if (selector.empty()) {
+ errorstream << "Invalid style element (Selector required): '" << element
+ << "'" << std::endl;
+ return false;
+ }
+
+ StyleSpec spec;
+
+ for (size_t i = 1; i < parts.size(); i++) {
+ size_t equal_pos = parts[i].find('=');
+ if (equal_pos == std::string::npos) {
+ errorstream << "Invalid style element (Property missing value): '" << element
+ << "'" << std::endl;
+ return false;
+ }
+
+ std::string propname = trim(parts[i].substr(0, equal_pos));
+ std::string value = trim(unescape_string(parts[i].substr(equal_pos + 1)));
+
+ std::transform(propname.begin(), propname.end(), propname.begin(), ::tolower);
+
+ StyleSpec::Property prop = StyleSpec::GetPropertyByName(propname);
+ if (prop == StyleSpec::NONE) {
+ if (property_warned.find(propname) != property_warned.end()) {
+ warningstream << "Invalid style element (Unknown property " << propname << "): '"
+ << element
+ << "'" << std::endl;
+ property_warned.insert(propname);
+ }
+ return false;
+ }
+
+ spec.set(prop, value);
+ }
+
+ if (style_type) {
+ theme_by_type[selector] |= spec;
+ } else {
+ theme_by_name[selector] |= spec;
+ }
+
+ return true;
+}
+
void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
{
//some prechecks
if (element.empty())
return;
+ if (parseVersionDirect(element))
+ return;
+
std::vector<std::string> parts = split(element,'[');
// ugly workaround to keep compatibility
@@ -1837,8 +2233,8 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
return;
}
- if (type == "background") {
- parseBackground(data,description);
+ if (type == "background" || type == "background9") {
+ parseBackground(data, description);
return;
}
@@ -1932,6 +2328,21 @@ void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
return;
}
+ if (type == "real_coordinates") {
+ data->real_coordinates = is_yes(description);
+ return;
+ }
+
+ if (type == "style") {
+ parseStyle(data, description, false);
+ return;
+ }
+
+ if (type == "style_type") {
+ parseStyle(data, description, true);
+ return;
+ }
+
// Ignore others
infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
<< std::endl;
@@ -2002,6 +2413,8 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
m_inventory_rings.clear();
m_static_texts.clear();
m_dropdowns.clear();
+ theme_by_name.clear();
+ theme_by_type.clear();
m_bgfullscreen = false;
@@ -2096,6 +2509,17 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
break;
}
+ /* Copy of the "real_coordinates" element for after the form size. */
+ mydata.real_coordinates = m_formspec_version >= 2;
+ for (; i < elements.size(); i++) {
+ std::vector<std::string> parts = split(elements[i], '[');
+ std::string name = trim(parts[0]);
+ if (name != "real_coordinates" || parts.size() != 2)
+ break; // Invalid format
+
+ mydata.real_coordinates = is_yes(trim(parts[1]));
+ }
+
if (mydata.explicit_size) {
// compute scaling for specified form size
if (m_lock) {
@@ -2186,10 +2610,18 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
m_font = g_fontengine->getFont();
- mydata.size = v2s32(
- padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X,
- padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0
- );
+ if (mydata.real_coordinates) {
+ mydata.size = v2s32(
+ mydata.invsize.X*imgsize.X,
+ mydata.invsize.Y*imgsize.Y
+ );
+ } else {
+ mydata.size = v2s32(
+ padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X,
+ padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0
+ );
+ }
+
DesiredRect = mydata.rect = core::rect<s32>(
(s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * (f32)mydata.size.X) + offset.X,
(s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * (f32)mydata.size.Y) + offset.Y,
@@ -2221,9 +2653,17 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
pos_offset = v2f32();
if (enable_prepends) {
+ // Backup the coordinates so that prepends can use the coordinates of choice.
+ bool rc_backup = mydata.real_coordinates;
+ u16 version_backup = m_formspec_version;
+ mydata.real_coordinates = false; // Old coordinates by default.
+
std::vector<std::string> prepend_elements = split(m_formspec_prepend, ']');
for (const auto &element : prepend_elements)
parseElement(&mydata, element);
+
+ m_formspec_version = version_backup;
+ mydata.real_coordinates = rc_backup; // Restore coordinates
}
for (; i< elements.size(); i++) {
@@ -2313,8 +2753,16 @@ GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
for(s32 i=0; i<s.geom.X*s.geom.Y; i++) {
s32 item_i = i + s.start_item_i;
- s32 x = (i%s.geom.X) * spacing.X;
- s32 y = (i/s.geom.X) * spacing.Y;
+
+ s32 x;
+ s32 y;
+ if (s.real_coordinates) {
+ x = (i%s.geom.X) * (imgsize.X * 1.25);
+ y = (i/s.geom.X) * (imgsize.Y * 1.25);
+ } else {
+ x = (i%s.geom.X) * spacing.X;
+ y = (i/s.geom.X) * spacing.Y;
+ }
v2s32 p0(x,y);
core::rect<s32> rect = imgrect + s.pos + p0;
if(rect.isPointInside(p))
@@ -2356,8 +2804,15 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int layer,
if (item_i >= (s32)ilist->getSize())
break;
- s32 x = (i%s.geom.X) * spacing.X;
- s32 y = (i/s.geom.X) * spacing.Y;
+ s32 x;
+ s32 y;
+ if (s.real_coordinates) {
+ x = (i%s.geom.X) * (imgsize.X * 1.25);
+ y = (i/s.geom.X) * (imgsize.Y * 1.25);
+ } else {
+ x = (i%s.geom.X) * spacing.X;
+ y = (i/s.geom.X) * spacing.Y;
+ }
v2s32 p(x,y);
core::rect<s32> rect = imgrect + s.pos + p;
ItemStack item = ilist->getItem(item_i);
@@ -2401,37 +2856,23 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int layer,
}
if (layer == 1) {
- // Draw item stack
if (selected)
item.takeItem(m_selected_amount);
if (!item.empty()) {
+ // Draw item stack
drawItemStack(driver, m_font, item,
rect, &AbsoluteClippingRect, m_client,
rotation_kind);
- }
-
- // Draw tooltip
- std::wstring tooltip_text;
- if (hovering && !m_selected_item) {
- const std::string &desc = item.metadata.getString("description");
- if (desc.empty())
- tooltip_text =
- utf8_to_wide(item.getDefinition(m_client->idef()).description);
- else
- tooltip_text = utf8_to_wide(desc);
-
- if (!item.name.empty()) {
- if (tooltip_text.empty())
- tooltip_text = utf8_to_wide(item.name);
- else if (m_tooltip_append_itemname)
- tooltip_text += utf8_to_wide(" [" + item.name + "]");
+ // Draw tooltip
+ if (hovering && !m_selected_item) {
+ std::string tooltip = item.getDescription(m_client->idef());
+ if (m_tooltip_append_itemname)
+ tooltip += "\n[" + item.name + "]";
+ showTooltip(utf8_to_wide(tooltip), m_default_tooltip_color,
+ m_default_tooltip_bgcolor);
}
}
- if (!tooltip_text.empty()) {
- showTooltip(tooltip_text, m_default_tooltip_color,
- m_default_tooltip_bgcolor);
- }
}
}
}
@@ -2510,6 +2951,8 @@ void GUIFormSpecMenu::drawMenu()
core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
// Image rectangle on screen
core::rect<s32> rect = imgrect + spec.pos;
+ // Middle rect for 9-slicing
+ core::rect<s32> middle = spec.middle;
if (spec.clip) {
core::dimension2d<s32> absrec_size = AbsoluteRect.getSize();
@@ -2519,12 +2962,23 @@ void GUIFormSpecMenu::drawMenu()
AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y);
}
- const video::SColor color(255,255,255,255);
- const video::SColor colors[] = {color,color,color,color};
- draw2DImageFilterScaled(driver, texture, rect,
- core::rect<s32>(core::position2d<s32>(0,0),
- core::dimension2di(texture->getOriginalSize())),
- NULL/*&AbsoluteClippingRect*/, colors, true);
+ if (middle.getArea() == 0) {
+ const video::SColor color(255, 255, 255, 255);
+ const video::SColor colors[] = {color, color, color, color};
+ draw2DImageFilterScaled(driver, texture, rect,
+ core::rect<s32>(core::position2d<s32>(0, 0),
+ core::dimension2di(texture->getOriginalSize())),
+ NULL/*&AbsoluteClippingRect*/, colors, true);
+ } else {
+ // `-x` is interpreted as `w - x`
+ if (middle.LowerRightCorner.X < 0) {
+ middle.LowerRightCorner.X += texture->getOriginalSize().Width;
+ }
+ if (middle.LowerRightCorner.Y < 0) {
+ middle.LowerRightCorner.Y += texture->getOriginalSize().Height;
+ }
+ draw2DImage9Slice(driver, texture, rect, middle);
+ }
} else {
errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
errorstream << "\t" << spec.name << std::endl;
@@ -3406,7 +3860,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
// Mouse has been moved and rmb is down and mouse pointer just
// entered a new inventory field (checked in the entry-if, this
// is the only action here that is generated by mouse movement)
- if (m_selected_item && s.isValid()) {
+ if (m_selected_item && s.isValid() && s.listname != "craftpreview") {
// Move 1 item
// TODO: middle mouse to move 10 items might be handy
if (m_auto_place) {
@@ -3740,3 +4194,27 @@ std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
}
return L"";
}
+
+StyleSpec GUIFormSpecMenu::getStyleForElement(const std::string &type,
+ const std::string &name, const std::string &parent_type) {
+ StyleSpec ret;
+
+ if (!parent_type.empty()) {
+ auto it = theme_by_type.find(parent_type);
+ if (it != theme_by_type.end()) {
+ ret |= it->second;
+ }
+ }
+
+ auto it = theme_by_type.find(type);
+ if (it != theme_by_type.end()) {
+ ret |= it->second;
+ }
+
+ it = theme_by_name.find(name);
+ if (it != theme_by_name.end()) {
+ ret |= it->second;
+ }
+
+ return ret;
+}
diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h
index d75a108d4..46df0930c 100644
--- a/src/gui/guiFormSpecMenu.h
+++ b/src/gui/guiFormSpecMenu.h
@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <utility>
#include <stack>
+#include <unordered_set>
#include "irrlichttypes_extrabloated.h"
#include "inventorymanager.h"
@@ -30,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client/joystick_controller.h"
#include "util/string.h"
#include "util/enriched_string.h"
+#include "StyleSpec.h"
class InventoryManager;
class ISimpleTextureSource;
@@ -99,12 +101,14 @@ class GUIFormSpecMenu : public GUIModalMenu
ListDrawSpec(const InventoryLocation &a_inventoryloc,
const std::string &a_listname,
- v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i):
+ v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i,
+ bool a_real_coordinates):
inventoryloc(a_inventoryloc),
listname(a_listname),
pos(a_pos),
geom(a_geom),
- start_item_i(a_start_item_i)
+ start_item_i(a_start_item_i),
+ real_coordinates(a_real_coordinates)
{
}
@@ -113,6 +117,7 @@ class GUIFormSpecMenu : public GUIModalMenu
v2s32 pos;
v2s32 geom;
s32 start_item_i;
+ bool real_coordinates;
};
struct ListRingSpec
@@ -177,6 +182,18 @@ class GUIFormSpecMenu : public GUIModalMenu
}
ImageDrawSpec(const std::string &a_name,
+ const v2s32 &a_pos, const v2s32 &a_geom, const core::rect<s32> &middle, bool clip=false):
+ name(a_name),
+ parent_button(NULL),
+ pos(a_pos),
+ geom(a_geom),
+ middle(middle),
+ scale(true),
+ clip(clip)
+ {
+ }
+
+ ImageDrawSpec(const std::string &a_name,
const v2s32 &a_pos):
name(a_name),
parent_button(NULL),
@@ -191,6 +208,7 @@ class GUIFormSpecMenu : public GUIModalMenu
gui::IGUIButton *parent_button;
v2s32 pos;
v2s32 geom;
+ core::rect<s32> middle;
bool scale;
bool clip;
};
@@ -287,7 +305,7 @@ public:
ISimpleTextureSource *tsrc,
IFormSource* fs_src,
TextDest* txt_dst,
- std::string formspecPrepend,
+ const std::string &formspecPrepend,
bool remap_dbl_click = true);
~GUIFormSpecMenu();
@@ -304,6 +322,11 @@ public:
regenerateGui(m_screensize_old);
}
+ const InventoryLocation &getFormspecLocation()
+ {
+ return m_current_inventory_location;
+ }
+
void setFormspecPrepend(const std::string &formspecPrepend)
{
m_formspec_prepend = formspecPrepend;
@@ -376,6 +399,16 @@ protected:
std::string getNameByID(s32 id);
v2s32 getElementBasePos(bool absolute,
const std::vector<std::string> *v_pos);
+ v2s32 getRealCoordinateBasePos(bool absolute,
+ 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_set<std::string> property_warned;
+
+ StyleSpec getStyleForElement(const std::string &type,
+ const std::string &name="", const std::string &parent_type="");
v2s32 padding;
v2f32 spacing;
@@ -439,12 +472,13 @@ protected:
private:
IFormSource *m_form_src;
TextDest *m_text_dst;
- u32 m_formspec_version = 0;
+ u16 m_formspec_version = 1;
std::string m_focused_element = "";
JoystickController *m_joystick;
typedef struct {
bool explicit_size;
+ bool real_coordinates;
v2f invsize;
v2s32 size;
v2f32 offset;
@@ -512,6 +546,7 @@ private:
void parsePosition(parserData *data, const std::string &element);
bool parseAnchorDirect(parserData *data, const std::string &element);
void parseAnchor(parserData *data, const std::string &element);
+ bool parseStyle(parserData *data, const std::string &element, bool style_type);
void tryClose();
@@ -542,7 +577,6 @@ private:
* and the default value for the setting is true.
*/
bool m_remap_dbl_click;
-
};
class FormspecFormSource: public IFormSource
@@ -557,7 +591,7 @@ public:
void setForm(const std::string &formspec)
{
- m_formspec = FORMSPEC_VERSION_STRING + formspec;
+ m_formspec = formspec;
}
const std::string &getForm() const
diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp
index 1a41e5828..ca331a7d4 100644
--- a/src/gui/guiKeyChangeMenu.cpp
+++ b/src/gui/guiKeyChangeMenu.cpp
@@ -50,7 +50,7 @@ enum
GUI_ID_KEY_FAST_BUTTON,
GUI_ID_KEY_JUMP_BUTTON,
GUI_ID_KEY_NOCLIP_BUTTON,
- GUI_ID_KEY_CINEMATIC_BUTTON,
+ GUI_ID_KEY_PITCH_MOVE,
GUI_ID_KEY_CHAT_BUTTON,
GUI_ID_KEY_CMD_BUTTON,
GUI_ID_KEY_CMD_LOCAL_BUTTON,
@@ -119,9 +119,9 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
const float s = m_gui_scale;
DesiredRect = core::rect<s32>(
- screensize.X / 2 - 745 * s / 2,
+ screensize.X / 2 - 835 * s / 2,
screensize.Y / 2 - 430 * s / 2,
- screensize.X / 2 + 745 * s / 2,
+ screensize.X / 2 + 835 * s / 2,
screensize.Y / 2 + 430 * s / 2
);
recalculateAbsolutePosition(false);
@@ -155,13 +155,13 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
{
core::rect<s32> rect(0, 0, 100 * s, 30 * s);
- rect += topleft + v2s32(offset.X + 120 * s, offset.Y - 5 * s);
+ rect += topleft + v2s32(offset.X + 150 * s, offset.Y - 5 * s);
const wchar_t *text = wgettext(k->key.name());
k->button = Environment->addButton(rect, this, k->id, text);
delete[] text;
}
if ((i + 1) % KMaxButtonPerColumns == 0) {
- offset.X += 230 * s;
+ offset.X += 260 * s;
offset.Y = 60 * s;
} else {
offset += v2s32(0, 25 * s);
@@ -430,9 +430,9 @@ void GUIKeyChangeMenu::init_keys()
this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON,wgettext("Next item"), "keymap_hotbar_next");
this->add_key(GUI_ID_KEY_ZOOM_BUTTON, wgettext("Zoom"), "keymap_zoom");
this->add_key(GUI_ID_KEY_CAMERA_BUTTON, wgettext("Change camera"), "keymap_camera_mode");
- this->add_key(GUI_ID_KEY_CINEMATIC_BUTTON, wgettext("Toggle Cinematic"), "keymap_cinematic");
this->add_key(GUI_ID_KEY_MINIMAP_BUTTON, wgettext("Toggle minimap"), "keymap_minimap");
this->add_key(GUI_ID_KEY_FLY_BUTTON, wgettext("Toggle fly"), "keymap_freemove");
+ this->add_key(GUI_ID_KEY_PITCH_MOVE, wgettext("Toggle pitchmove"), "keymap_pitchmove");
this->add_key(GUI_ID_KEY_FAST_BUTTON, wgettext("Toggle fast"), "keymap_fastmove");
this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wgettext("Toggle noclip"), "keymap_noclip");
this->add_key(GUI_ID_KEY_MUTE_BUTTON, wgettext("Mute"), "keymap_mute");
@@ -451,4 +451,3 @@ void GUIKeyChangeMenu::init_keys()
this->add_key(GUI_ID_KEY_CHATLOG_BUTTON, wgettext("Toggle chat log"), "keymap_toggle_chat");
this->add_key(GUI_ID_KEY_FOG_BUTTON, wgettext("Toggle fog"), "keymap_toggle_fog");
}
-
diff --git a/src/gui/guiScrollBar.cpp b/src/gui/guiScrollBar.cpp
new file mode 100644
index 000000000..f7218e733
--- /dev/null
+++ b/src/gui/guiScrollBar.cpp
@@ -0,0 +1,425 @@
+/*
+Copyright (C) 2002-2013 Nikolaus Gebhardt
+This file is part of the "Irrlicht Engine".
+For conditions of distribution and use, see copyright notice in irrlicht.h
+
+Modified 2019.05.01 by stujones11, Stuart Jones <stujones111@gmail.com>
+
+This is a heavily modified copy of the Irrlicht CGUIScrollBar class
+which includes automatic scaling of the thumb slider and hiding of
+the arrow buttons where there is insufficient space.
+*/
+
+#include "guiScrollBar.h"
+#include <IGUIButton.h>
+#include <IGUISkin.h>
+
+GUIScrollBar::GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id,
+ core::rect<s32> rectangle, bool horizontal, bool auto_scale) :
+ IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle),
+ up_button(nullptr), down_button(nullptr), is_dragging(false),
+ is_horizontal(horizontal), is_auto_scaling(auto_scale),
+ dragged_by_slider(false), tray_clicked(false), scroll_pos(0),
+ draw_center(0), thumb_size(0), min_pos(0), max_pos(100), small_step(10),
+ large_step(50), last_change(0), drag_offset(0), page_size(100), border_size(0)
+{
+ refreshControls();
+ setNotClipped(false);
+ setTabStop(true);
+ setTabOrder(-1);
+ setPos(0);
+}
+
+bool GUIScrollBar::OnEvent(const SEvent &event)
+{
+ if (isEnabled()) {
+ switch (event.EventType) {
+ case EET_KEY_INPUT_EVENT:
+ if (event.KeyInput.PressedDown) {
+ const s32 old_pos = scroll_pos;
+ bool absorb = true;
+ switch (event.KeyInput.Key) {
+ case KEY_LEFT:
+ case KEY_UP:
+ setPos(scroll_pos - small_step);
+ break;
+ case KEY_RIGHT:
+ case KEY_DOWN:
+ setPos(scroll_pos + small_step);
+ break;
+ case KEY_HOME:
+ setPos(min_pos);
+ break;
+ case KEY_PRIOR:
+ setPos(scroll_pos - large_step);
+ break;
+ case KEY_END:
+ setPos(max_pos);
+ break;
+ case KEY_NEXT:
+ setPos(scroll_pos + large_step);
+ break;
+ default:
+ absorb = false;
+ }
+ if (scroll_pos != old_pos) {
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = nullptr;
+ e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
+ Parent->OnEvent(e);
+ }
+ if (absorb)
+ return true;
+ }
+ break;
+ case EET_GUI_EVENT:
+ if (event.GUIEvent.EventType == EGET_BUTTON_CLICKED) {
+ if (event.GUIEvent.Caller == up_button)
+ setPos(scroll_pos - small_step);
+ else if (event.GUIEvent.Caller == down_button)
+ setPos(scroll_pos + small_step);
+
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = nullptr;
+ e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
+ Parent->OnEvent(e);
+ return true;
+ } else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
+ if (event.GUIEvent.Caller == this)
+ is_dragging = false;
+ break;
+ case EET_MOUSE_INPUT_EVENT: {
+ const core::position2di p(event.MouseInput.X, event.MouseInput.Y);
+ bool is_inside = isPointInside(p);
+ switch (event.MouseInput.Event) {
+ case EMIE_MOUSE_WHEEL:
+ if (Environment->hasFocus(this)) {
+ s8 d = event.MouseInput.Wheel < 0 ? -1 : 1;
+ s8 h = is_horizontal ? 1 : -1;
+ setPos(getPos() + (d * small_step * h));
+
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = nullptr;
+ e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
+ Parent->OnEvent(e);
+ return true;
+ }
+ break;
+ case EMIE_LMOUSE_PRESSED_DOWN: {
+ if (is_inside) {
+ is_dragging = true;
+ dragged_by_slider = slider_rect.isPointInside(p);
+ core::vector2di corner = slider_rect.UpperLeftCorner;
+ drag_offset = is_horizontal ? p.X - corner.X : p.Y - corner.Y;
+ tray_clicked = !dragged_by_slider;
+ if (tray_clicked) {
+ const s32 new_pos = getPosFromMousePos(p);
+ const s32 old_pos = scroll_pos;
+ setPos(new_pos);
+ // drag in the middle
+ drag_offset = thumb_size / 2;
+ // report the scroll event
+ if (scroll_pos != old_pos && Parent) {
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = nullptr;
+ e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
+ Parent->OnEvent(e);
+ }
+ }
+ Environment->setFocus(this);
+ return true;
+ }
+ break;
+ }
+ case EMIE_LMOUSE_LEFT_UP:
+ case EMIE_MOUSE_MOVED: {
+ if (!event.MouseInput.isLeftPressed())
+ is_dragging = false;
+
+ if (!is_dragging) {
+ if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
+ break;
+ return is_inside;
+ }
+
+ if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
+ is_dragging = false;
+
+ // clang-format off
+ if (!dragged_by_slider) {
+ if (is_inside) {
+ dragged_by_slider = slider_rect.isPointInside(p);
+ tray_clicked = !dragged_by_slider;
+ }
+ if (!dragged_by_slider) {
+ tray_clicked = false;
+ if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
+ return is_inside;
+ }
+ }
+ // clang-format on
+
+ const s32 new_pos = getPosFromMousePos(p);
+ const s32 old_pos = scroll_pos;
+
+ setPos(new_pos);
+
+ if (scroll_pos != old_pos && Parent) {
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = nullptr;
+ e.GUIEvent.EventType = EGET_SCROLL_BAR_CHANGED;
+ Parent->OnEvent(e);
+ }
+ return is_inside;
+ }
+ default:
+ break;
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+ return IGUIElement::OnEvent(event);
+}
+
+void GUIScrollBar::draw()
+{
+ if (!IsVisible)
+ return;
+
+ IGUISkin *skin = Environment->getSkin();
+ if (!skin)
+ return;
+
+ video::SColor icon_color = skin->getColor(
+ isEnabled() ? EGDC_WINDOW_SYMBOL : EGDC_GRAY_WINDOW_SYMBOL);
+ if (icon_color != current_icon_color)
+ refreshControls();
+
+ slider_rect = AbsoluteRect;
+ skin->draw2DRectangle(this, skin->getColor(EGDC_SCROLLBAR), slider_rect,
+ &AbsoluteClippingRect);
+
+ if (core::isnotzero(range())) {
+ if (is_horizontal) {
+ slider_rect.UpperLeftCorner.X = AbsoluteRect.UpperLeftCorner.X +
+ draw_center - thumb_size / 2;
+ slider_rect.LowerRightCorner.X =
+ slider_rect.UpperLeftCorner.X + thumb_size;
+ } else {
+ slider_rect.UpperLeftCorner.Y = AbsoluteRect.UpperLeftCorner.Y +
+ draw_center - thumb_size / 2;
+ slider_rect.LowerRightCorner.Y =
+ slider_rect.UpperLeftCorner.Y + thumb_size;
+ }
+ skin->draw3DButtonPaneStandard(this, slider_rect, &AbsoluteClippingRect);
+ }
+ IGUIElement::draw();
+}
+
+void GUIScrollBar::updateAbsolutePosition()
+{
+ IGUIElement::updateAbsolutePosition();
+ refreshControls();
+ setPos(scroll_pos);
+}
+
+s32 GUIScrollBar::getPosFromMousePos(const core::position2di &pos) const
+{
+ s32 w, p;
+ s32 offset = dragged_by_slider ? drag_offset : thumb_size / 2;
+
+ if (is_horizontal) {
+ w = RelativeRect.getWidth() - border_size * 2 - thumb_size;
+ p = pos.X - AbsoluteRect.UpperLeftCorner.X - border_size - offset;
+ } else {
+ w = RelativeRect.getHeight() - border_size * 2 - thumb_size;
+ p = pos.Y - AbsoluteRect.UpperLeftCorner.Y - border_size - offset;
+ }
+ return core::isnotzero(range()) ? s32(f32(p) / f32(w) * range()) + min_pos : 0;
+}
+
+void GUIScrollBar::setPos(const s32 &pos)
+{
+ s32 thumb_area = 0;
+ s32 thumb_min = 0;
+
+ if (is_horizontal) {
+ thumb_min = RelativeRect.getHeight();
+ thumb_area = RelativeRect.getWidth() - border_size * 2;
+ } else {
+ thumb_min = RelativeRect.getWidth();
+ thumb_area = RelativeRect.getHeight() - border_size * 2;
+ }
+
+ if (is_auto_scaling)
+ thumb_size = s32(thumb_area /
+ (f32(page_size) / f32(thumb_area + border_size * 2)));
+
+ thumb_size = core::s32_clamp(thumb_size, thumb_min, thumb_area);
+ scroll_pos = core::s32_clamp(pos, min_pos, max_pos);
+
+ f32 f = core::isnotzero(range()) ? (f32(thumb_area) - f32(thumb_size)) / range()
+ : 1.0f;
+ draw_center = s32((f32(scroll_pos) * f) + (f32(thumb_size) * 0.5f)) + border_size;
+}
+
+void GUIScrollBar::setSmallStep(const s32 &step)
+{
+ small_step = step > 0 ? step : 10;
+}
+
+void GUIScrollBar::setLargeStep(const s32 &step)
+{
+ large_step = step > 0 ? step : 50;
+}
+
+void GUIScrollBar::setMax(const s32 &max)
+{
+ max_pos = max;
+ if (min_pos > max_pos)
+ min_pos = max_pos;
+
+ bool enable = core::isnotzero(range());
+ up_button->setEnabled(enable);
+ down_button->setEnabled(enable);
+ setPos(scroll_pos);
+}
+
+void GUIScrollBar::setMin(const s32 &min)
+{
+ min_pos = min;
+ if (max_pos < min_pos)
+ max_pos = min_pos;
+
+ bool enable = core::isnotzero(range());
+ up_button->setEnabled(enable);
+ down_button->setEnabled(enable);
+ setPos(scroll_pos);
+}
+
+void GUIScrollBar::setPageSize(const s32 &size)
+{
+ page_size = size;
+ setPos(scroll_pos);
+}
+
+s32 GUIScrollBar::getPos() const
+{
+ return scroll_pos;
+}
+
+void GUIScrollBar::refreshControls()
+{
+ IGUISkin *skin = Environment->getSkin();
+ IGUISpriteBank *sprites = nullptr;
+ current_icon_color = video::SColor(255, 255, 255, 255);
+
+ if (skin) {
+ sprites = skin->getSpriteBank();
+ current_icon_color =
+ skin->getColor(isEnabled() ? EGDC_WINDOW_SYMBOL
+ : EGDC_GRAY_WINDOW_SYMBOL);
+ }
+ if (is_horizontal) {
+ s32 h = RelativeRect.getHeight();
+ border_size = RelativeRect.getWidth() < h * 4 ? 0 : h;
+ if (!up_button) {
+ up_button = Environment->addButton(
+ core::rect<s32>(0, 0, h, h), this);
+ up_button->setSubElement(true);
+ up_button->setTabStop(false);
+ }
+ if (sprites) {
+ up_button->setSpriteBank(sprites);
+ up_button->setSprite(EGBS_BUTTON_UP,
+ s32(skin->getIcon(EGDI_CURSOR_LEFT)),
+ current_icon_color);
+ up_button->setSprite(EGBS_BUTTON_DOWN,
+ s32(skin->getIcon(EGDI_CURSOR_LEFT)),
+ current_icon_color);
+ }
+ up_button->setRelativePosition(core::rect<s32>(0, 0, h, h));
+ up_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_UPPERLEFT,
+ EGUIA_LOWERRIGHT);
+ if (!down_button) {
+ down_button = Environment->addButton(
+ core::rect<s32>(RelativeRect.getWidth() - h, 0,
+ RelativeRect.getWidth(), h),
+ this);
+ down_button->setSubElement(true);
+ down_button->setTabStop(false);
+ }
+ if (sprites) {
+ down_button->setSpriteBank(sprites);
+ down_button->setSprite(EGBS_BUTTON_UP,
+ s32(skin->getIcon(EGDI_CURSOR_RIGHT)),
+ current_icon_color);
+ down_button->setSprite(EGBS_BUTTON_DOWN,
+ s32(skin->getIcon(EGDI_CURSOR_RIGHT)),
+ current_icon_color);
+ }
+ down_button->setRelativePosition(
+ core::rect<s32>(RelativeRect.getWidth() - h, 0,
+ RelativeRect.getWidth(), h));
+ down_button->setAlignment(EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT,
+ EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT);
+ } else {
+ s32 w = RelativeRect.getWidth();
+ border_size = RelativeRect.getHeight() < w * 4 ? 0 : w;
+ if (!up_button) {
+ up_button = Environment->addButton(
+ core::rect<s32>(0, 0, w, w), this);
+ up_button->setSubElement(true);
+ up_button->setTabStop(false);
+ }
+ if (sprites) {
+ up_button->setSpriteBank(sprites);
+ up_button->setSprite(EGBS_BUTTON_UP,
+ s32(skin->getIcon(EGDI_CURSOR_UP)),
+ current_icon_color);
+ up_button->setSprite(EGBS_BUTTON_DOWN,
+ s32(skin->getIcon(EGDI_CURSOR_UP)),
+ current_icon_color);
+ }
+ up_button->setRelativePosition(core::rect<s32>(0, 0, w, w));
+ up_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT,
+ EGUIA_UPPERLEFT, EGUIA_UPPERLEFT);
+ if (!down_button) {
+ down_button = Environment->addButton(
+ core::rect<s32>(0, RelativeRect.getHeight() - w,
+ w, RelativeRect.getHeight()),
+ this);
+ down_button->setSubElement(true);
+ down_button->setTabStop(false);
+ }
+ if (sprites) {
+ down_button->setSpriteBank(sprites);
+ down_button->setSprite(EGBS_BUTTON_UP,
+ s32(skin->getIcon(EGDI_CURSOR_DOWN)),
+ current_icon_color);
+ down_button->setSprite(EGBS_BUTTON_DOWN,
+ s32(skin->getIcon(EGDI_CURSOR_DOWN)),
+ current_icon_color);
+ }
+ down_button->setRelativePosition(
+ core::rect<s32>(0, RelativeRect.getHeight() - w, w,
+ RelativeRect.getHeight()));
+ down_button->setAlignment(EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT,
+ EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);
+ }
+ bool visible = (border_size != 0);
+ up_button->setVisible(visible);
+ down_button->setVisible(visible);
+}
diff --git a/src/gui/guiScrollBar.h b/src/gui/guiScrollBar.h
new file mode 100644
index 000000000..349411fc1
--- /dev/null
+++ b/src/gui/guiScrollBar.h
@@ -0,0 +1,69 @@
+/*
+Copyright (C) 2002-2013 Nikolaus Gebhardt
+This file is part of the "Irrlicht Engine".
+For conditions of distribution and use, see copyright notice in irrlicht.h
+
+Modified 2019.05.01 by stujones11, Stuart Jones <stujones111@gmail.com>
+
+This is a heavily modified copy of the Irrlicht CGUIScrollBar class
+which includes automatic scaling of the thumb slider and hiding of
+the arrow buttons where there is insufficient space.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+
+using namespace irr;
+using namespace gui;
+
+class GUIScrollBar : public IGUIElement
+{
+public:
+ GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id,
+ core::rect<s32> rectangle, bool horizontal, bool auto_scale);
+
+ virtual void draw();
+ virtual void updateAbsolutePosition();
+ virtual bool OnEvent(const SEvent &event);
+
+ s32 getMax() const { return max_pos; }
+ s32 getMin() const { return min_pos; }
+ s32 getLargeStep() const { return large_step; }
+ s32 getSmallStep() const { return small_step; }
+ s32 getPos() const;
+
+ void setMax(const s32 &max);
+ void setMin(const s32 &min);
+ void setSmallStep(const s32 &step);
+ void setLargeStep(const s32 &step);
+ void setPos(const s32 &pos);
+ void setPageSize(const s32 &size);
+
+private:
+ void refreshControls();
+ s32 getPosFromMousePos(const core::position2di &p) const;
+ f32 range() const { return f32(max_pos - min_pos); }
+
+ IGUIButton *up_button;
+ IGUIButton *down_button;
+ bool is_dragging;
+ bool is_horizontal;
+ bool is_auto_scaling;
+ bool dragged_by_slider;
+ bool tray_clicked;
+ s32 scroll_pos;
+ s32 draw_center;
+ s32 thumb_size;
+ s32 min_pos;
+ s32 max_pos;
+ s32 small_step;
+ s32 large_step;
+ u32 last_change;
+ s32 drag_offset;
+ s32 page_size;
+ s32 border_size;
+
+ core::rect<s32> slider_rect;
+ video::SColor current_icon_color;
+};
diff --git a/src/gui/guiSkin.cpp b/src/gui/guiSkin.cpp
new file mode 100644
index 000000000..8892a00b4
--- /dev/null
+++ b/src/gui/guiSkin.cpp
@@ -0,0 +1,1084 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// Copyright (C) 2019 Irrlick
+//
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#include "guiSkin.h"
+#ifdef _IRR_COMPILE_WITH_GUI_
+
+#include "IGUIFont.h"
+#include "IGUISpriteBank.h"
+#include "IGUIElement.h"
+#include "IVideoDriver.h"
+#include "IAttributes.h"
+
+namespace irr
+{
+namespace gui
+{
+
+GUISkin::GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver)
+: SpriteBank(0), Driver(driver), Type(type)
+{
+ #ifdef _DEBUG
+ setDebugName("GUISkin");
+ #endif
+
+ if ((Type == EGST_WINDOWS_CLASSIC) || (Type == EGST_WINDOWS_METALLIC))
+ {
+ Colors[EGDC_3D_DARK_SHADOW] = video::SColor(101,50,50,50);
+ Colors[EGDC_3D_SHADOW] = video::SColor(101,130,130,130);
+ Colors[EGDC_3D_FACE] = video::SColor(101,210,210,210);
+ Colors[EGDC_3D_HIGH_LIGHT] = video::SColor(101,255,255,255);
+ Colors[EGDC_3D_LIGHT] = video::SColor(101,210,210,210);
+ Colors[EGDC_ACTIVE_BORDER] = video::SColor(101,16,14,115);
+ Colors[EGDC_ACTIVE_CAPTION] = video::SColor(255,255,255,255);
+ Colors[EGDC_APP_WORKSPACE] = video::SColor(101,100,100,100);
+ Colors[EGDC_BUTTON_TEXT] = video::SColor(240,10,10,10);
+ Colors[EGDC_GRAY_TEXT] = video::SColor(240,130,130,130);
+ Colors[EGDC_HIGH_LIGHT] = video::SColor(101,8,36,107);
+ Colors[EGDC_HIGH_LIGHT_TEXT] = video::SColor(240,255,255,255);
+ Colors[EGDC_INACTIVE_BORDER] = video::SColor(101,165,165,165);
+ Colors[EGDC_INACTIVE_CAPTION] = video::SColor(255,30,30,30);
+ Colors[EGDC_TOOLTIP] = video::SColor(200,0,0,0);
+ Colors[EGDC_TOOLTIP_BACKGROUND] = video::SColor(200,255,255,225);
+ Colors[EGDC_SCROLLBAR] = video::SColor(101,230,230,230);
+ Colors[EGDC_WINDOW] = video::SColor(101,255,255,255);
+ Colors[EGDC_WINDOW_SYMBOL] = video::SColor(200,10,10,10);
+ Colors[EGDC_ICON] = video::SColor(200,255,255,255);
+ Colors[EGDC_ICON_HIGH_LIGHT] = video::SColor(200,8,36,107);
+ Colors[EGDC_GRAY_WINDOW_SYMBOL] = video::SColor(240,100,100,100);
+ Colors[EGDC_EDITABLE] = video::SColor(255,255,255,255);
+ Colors[EGDC_GRAY_EDITABLE] = video::SColor(255,120,120,120);
+ Colors[EGDC_FOCUSED_EDITABLE] = video::SColor(255,240,240,255);
+
+
+ Sizes[EGDS_SCROLLBAR_SIZE] = 14;
+ Sizes[EGDS_MENU_HEIGHT] = 30;
+ Sizes[EGDS_WINDOW_BUTTON_WIDTH] = 15;
+ Sizes[EGDS_CHECK_BOX_WIDTH] = 18;
+ Sizes[EGDS_MESSAGE_BOX_WIDTH] = 500;
+ Sizes[EGDS_MESSAGE_BOX_HEIGHT] = 200;
+ Sizes[EGDS_BUTTON_WIDTH] = 80;
+ Sizes[EGDS_BUTTON_HEIGHT] = 30;
+
+ Sizes[EGDS_TEXT_DISTANCE_X] = 2;
+ Sizes[EGDS_TEXT_DISTANCE_Y] = 0;
+
+ Sizes[EGDS_TITLEBARTEXT_DISTANCE_X] = 2;
+ Sizes[EGDS_TITLEBARTEXT_DISTANCE_Y] = 0;
+ }
+ else
+ {
+ //0x80a6a8af
+ Colors[EGDC_3D_DARK_SHADOW] = 0x60767982;
+ //Colors[EGDC_3D_FACE] = 0xc0c9ccd4; // tab background
+ Colors[EGDC_3D_FACE] = 0xc0cbd2d9; // tab background
+ Colors[EGDC_3D_SHADOW] = 0x50e4e8f1; // tab background, and left-top highlight
+ Colors[EGDC_3D_HIGH_LIGHT] = 0x40c7ccdc;
+ Colors[EGDC_3D_LIGHT] = 0x802e313a;
+ Colors[EGDC_ACTIVE_BORDER] = 0x80404040; // window title
+ Colors[EGDC_ACTIVE_CAPTION] = 0xffd0d0d0;
+ Colors[EGDC_APP_WORKSPACE] = 0xc0646464; // unused
+ Colors[EGDC_BUTTON_TEXT] = 0xd0161616;
+ Colors[EGDC_GRAY_TEXT] = 0x3c141414;
+ Colors[EGDC_HIGH_LIGHT] = 0x6c606060;
+ Colors[EGDC_HIGH_LIGHT_TEXT] = 0xd0e0e0e0;
+ Colors[EGDC_INACTIVE_BORDER] = 0xf0a5a5a5;
+ Colors[EGDC_INACTIVE_CAPTION] = 0xffd2d2d2;
+ Colors[EGDC_TOOLTIP] = 0xf00f2033;
+ Colors[EGDC_TOOLTIP_BACKGROUND] = 0xc0cbd2d9;
+ Colors[EGDC_SCROLLBAR] = 0xf0e0e0e0;
+ Colors[EGDC_WINDOW] = 0xf0f0f0f0;
+ Colors[EGDC_WINDOW_SYMBOL] = 0xd0161616;
+ Colors[EGDC_ICON] = 0xd0161616;
+ Colors[EGDC_ICON_HIGH_LIGHT] = 0xd0606060;
+ Colors[EGDC_GRAY_WINDOW_SYMBOL] = 0x3c101010;
+ Colors[EGDC_EDITABLE] = 0xf0ffffff;
+ Colors[EGDC_GRAY_EDITABLE] = 0xf0cccccc;
+ Colors[EGDC_FOCUSED_EDITABLE] = 0xf0fffff0;
+
+ Sizes[EGDS_SCROLLBAR_SIZE] = 14;
+ Sizes[EGDS_MENU_HEIGHT] = 48;
+ Sizes[EGDS_WINDOW_BUTTON_WIDTH] = 15;
+ Sizes[EGDS_CHECK_BOX_WIDTH] = 18;
+ Sizes[EGDS_MESSAGE_BOX_WIDTH] = 500;
+ Sizes[EGDS_MESSAGE_BOX_HEIGHT] = 200;
+ Sizes[EGDS_BUTTON_WIDTH] = 80;
+ Sizes[EGDS_BUTTON_HEIGHT] = 30;
+
+ Sizes[EGDS_TEXT_DISTANCE_X] = 3;
+ Sizes[EGDS_TEXT_DISTANCE_Y] = 2;
+
+ Sizes[EGDS_TITLEBARTEXT_DISTANCE_X] = 3;
+ Sizes[EGDS_TITLEBARTEXT_DISTANCE_Y] = 2;
+ }
+
+ Sizes[EGDS_MESSAGE_BOX_GAP_SPACE] = 15;
+ Sizes[EGDS_MESSAGE_BOX_MIN_TEXT_WIDTH] = 0;
+ Sizes[EGDS_MESSAGE_BOX_MAX_TEXT_WIDTH] = 500;
+ Sizes[EGDS_MESSAGE_BOX_MIN_TEXT_HEIGHT] = 0;
+ Sizes[EGDS_MESSAGE_BOX_MAX_TEXT_HEIGHT] = 99999;
+
+ Sizes[EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X] = 1;
+ Sizes[EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y] = 1;
+ Sizes[EGDS_BUTTON_PRESSED_TEXT_OFFSET_X] = 0;
+ Sizes[EGDS_BUTTON_PRESSED_TEXT_OFFSET_Y] = 2;
+
+ Texts[EGDT_MSG_BOX_OK] = L"OK";
+ Texts[EGDT_MSG_BOX_CANCEL] = L"Cancel";
+ Texts[EGDT_MSG_BOX_YES] = L"Yes";
+ Texts[EGDT_MSG_BOX_NO] = L"No";
+ Texts[EGDT_WINDOW_CLOSE] = L"Close";
+ Texts[EGDT_WINDOW_RESTORE] = L"Restore";
+ Texts[EGDT_WINDOW_MINIMIZE] = L"Minimize";
+ Texts[EGDT_WINDOW_MAXIMIZE] = L"Maximize";
+
+ Icons[EGDI_WINDOW_MAXIMIZE] = 225;
+ Icons[EGDI_WINDOW_RESTORE] = 226;
+ Icons[EGDI_WINDOW_CLOSE] = 227;
+ Icons[EGDI_WINDOW_MINIMIZE] = 228;
+ Icons[EGDI_CURSOR_UP] = 229;
+ Icons[EGDI_CURSOR_DOWN] = 230;
+ Icons[EGDI_CURSOR_LEFT] = 231;
+ Icons[EGDI_CURSOR_RIGHT] = 232;
+ Icons[EGDI_MENU_MORE] = 232;
+ Icons[EGDI_CHECK_BOX_CHECKED] = 233;
+ Icons[EGDI_DROP_DOWN] = 234;
+ Icons[EGDI_SMALL_CURSOR_UP] = 235;
+ Icons[EGDI_SMALL_CURSOR_DOWN] = 236;
+ Icons[EGDI_RADIO_BUTTON_CHECKED] = 237;
+ Icons[EGDI_MORE_LEFT] = 238;
+ Icons[EGDI_MORE_RIGHT] = 239;
+ Icons[EGDI_MORE_UP] = 240;
+ Icons[EGDI_MORE_DOWN] = 241;
+ Icons[EGDI_WINDOW_RESIZE] = 242;
+ Icons[EGDI_EXPAND] = 243;
+ Icons[EGDI_COLLAPSE] = 244;
+
+ Icons[EGDI_FILE] = 245;
+ Icons[EGDI_DIRECTORY] = 246;
+
+ for (u32 i=0; i<EGDF_COUNT; ++i)
+ Fonts[i] = 0;
+
+ UseGradient = (Type == EGST_WINDOWS_METALLIC) || (Type == EGST_BURNING_SKIN) ;
+}
+
+
+//! destructor
+GUISkin::~GUISkin()
+{
+ for (u32 i=0; i<EGDF_COUNT; ++i)
+ {
+ if (Fonts[i])
+ Fonts[i]->drop();
+ }
+
+ if (SpriteBank)
+ SpriteBank->drop();
+}
+
+
+//! returns default color
+video::SColor GUISkin::getColor(EGUI_DEFAULT_COLOR color) const
+{
+ if ((u32)color < EGDC_COUNT)
+ return Colors[color];
+ else
+ return video::SColor();
+}
+
+
+//! sets a default color
+void GUISkin::setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor)
+{
+ if ((u32)which < EGDC_COUNT)
+ Colors[which] = newColor;
+}
+
+
+//! returns size for the given size type
+s32 GUISkin::getSize(EGUI_DEFAULT_SIZE size) const
+{
+ if ((u32)size < EGDS_COUNT)
+ return Sizes[size];
+ else
+ return 0;
+}
+
+
+//! sets a default size
+void GUISkin::setSize(EGUI_DEFAULT_SIZE which, s32 size)
+{
+ if ((u32)which < EGDS_COUNT)
+ Sizes[which] = size;
+}
+
+
+//! returns the default font
+IGUIFont* GUISkin::getFont(EGUI_DEFAULT_FONT which) const
+{
+ if (((u32)which < EGDF_COUNT) && Fonts[which])
+ return Fonts[which];
+ else
+ return Fonts[EGDF_DEFAULT];
+}
+
+
+//! sets a default font
+void GUISkin::setFont(IGUIFont* font, EGUI_DEFAULT_FONT which)
+{
+ if ((u32)which >= EGDF_COUNT)
+ return;
+
+ if (font)
+ {
+ font->grab();
+ if (Fonts[which])
+ Fonts[which]->drop();
+
+ Fonts[which] = font;
+ }
+}
+
+
+//! gets the sprite bank stored
+IGUISpriteBank* GUISkin::getSpriteBank() const
+{
+ return SpriteBank;
+}
+
+
+//! set a new sprite bank or remove one by passing 0
+void GUISkin::setSpriteBank(IGUISpriteBank* bank)
+{
+ if (bank)
+ bank->grab();
+
+ if (SpriteBank)
+ SpriteBank->drop();
+
+ SpriteBank = bank;
+}
+
+
+//! Returns a default icon
+u32 GUISkin::getIcon(EGUI_DEFAULT_ICON icon) const
+{
+ if ((u32)icon < EGDI_COUNT)
+ return Icons[icon];
+ else
+ return 0;
+}
+
+
+//! Sets a default icon
+void GUISkin::setIcon(EGUI_DEFAULT_ICON icon, u32 index)
+{
+ if ((u32)icon < EGDI_COUNT)
+ Icons[icon] = index;
+}
+
+
+//! Returns a default text. For example for Message box button captions:
+//! "OK", "Cancel", "Yes", "No" and so on.
+const wchar_t* GUISkin::getDefaultText(EGUI_DEFAULT_TEXT text) const
+{
+ if ((u32)text < EGDT_COUNT)
+ return Texts[text].c_str();
+ else
+ return Texts[0].c_str();
+}
+
+
+//! Sets a default text. For example for Message box button captions:
+//! "OK", "Cancel", "Yes", "No" and so on.
+void GUISkin::setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText)
+{
+ if ((u32)which < EGDT_COUNT)
+ Texts[which] = newText;
+}
+
+
+//! draws a standard 3d button pane
+/** Used for drawing for example buttons in normal state.
+It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
+EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
+\param rect: Defining area where to draw.
+\param clip: Clip area.
+\param element: Pointer to the element which wishes to draw this. This parameter
+is usually not used by ISkin, but can be used for example by more complex
+implementations to find out how to draw the part exactly. */
+// PATCH
+void GUISkin::drawColored3DButtonPaneStandard(IGUIElement* element,
+ const core::rect<s32>& r,
+ const core::rect<s32>* clip,
+ const video::SColor* colors)
+{
+ if (!Driver)
+ return;
+
+ if (!colors)
+ colors = Colors;
+
+ core::rect<s32> rect = r;
+
+ if ( Type == EGST_BURNING_SKIN )
+ {
+ rect.UpperLeftCorner.X -= 1;
+ rect.UpperLeftCorner.Y -= 1;
+ rect.LowerRightCorner.X += 1;
+ rect.LowerRightCorner.Y += 1;
+ draw3DSunkenPane(element,
+ colors[ EGDC_WINDOW ].getInterpolated( 0xFFFFFFFF, 0.9f )
+ ,false, true, rect, clip);
+ return;
+ }
+
+ Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip);
+
+ rect.LowerRightCorner.X -= 1;
+ rect.LowerRightCorner.Y -= 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip);
+
+ rect.UpperLeftCorner.X += 1;
+ rect.UpperLeftCorner.Y += 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip);
+
+ rect.LowerRightCorner.X -= 1;
+ rect.LowerRightCorner.Y -= 1;
+
+ if (!UseGradient)
+ {
+ Driver->draw2DRectangle(colors[EGDC_3D_FACE], rect, clip);
+ }
+ else
+ {
+ const video::SColor c1 = colors[EGDC_3D_FACE];
+ const video::SColor c2 = c1.getInterpolated(colors[EGDC_3D_DARK_SHADOW], 0.4f);
+ Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip);
+ }
+}
+// END PATCH
+
+
+//! draws a pressed 3d button pane
+/** Used for drawing for example buttons in pressed state.
+It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
+EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
+\param rect: Defining area where to draw.
+\param clip: Clip area.
+\param element: Pointer to the element which wishes to draw this. This parameter
+is usually not used by ISkin, but can be used for example by more complex
+implementations to find out how to draw the part exactly. */
+// PATCH
+void GUISkin::drawColored3DButtonPanePressed(IGUIElement* element,
+ const core::rect<s32>& r,
+ const core::rect<s32>* clip,
+ const video::SColor* colors)
+{
+ if (!Driver)
+ return;
+
+ if (!colors)
+ colors = Colors;
+
+ core::rect<s32> rect = r;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip);
+
+ rect.LowerRightCorner.X -= 1;
+ rect.LowerRightCorner.Y -= 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip);
+
+ rect.UpperLeftCorner.X += 1;
+ rect.UpperLeftCorner.Y += 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip);
+
+ rect.UpperLeftCorner.X += 1;
+ rect.UpperLeftCorner.Y += 1;
+
+ if (!UseGradient)
+ {
+ Driver->draw2DRectangle(colors[EGDC_3D_FACE], rect, clip);
+ }
+ else
+ {
+ const video::SColor c1 = colors[EGDC_3D_FACE];
+ const video::SColor c2 = c1.getInterpolated(colors[EGDC_3D_DARK_SHADOW], 0.4f);
+ Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip);
+ }
+}
+// END PATCH
+
+
+//! draws a sunken 3d pane
+/** Used for drawing the background of edit, combo or check boxes.
+\param element: Pointer to the element which wishes to draw this. This parameter
+is usually not used by ISkin, but can be used for example by more complex
+implementations to find out how to draw the part exactly.
+\param bgcolor: Background color.
+\param flat: Specifies if the sunken pane should be flat or displayed as sunken
+deep into the ground.
+\param rect: Defining area where to draw.
+\param clip: Clip area. */
+// PATCH
+void GUISkin::drawColored3DSunkenPane(IGUIElement* element, video::SColor bgcolor,
+ bool flat, bool fillBackGround,
+ const core::rect<s32>& r,
+ const core::rect<s32>* clip,
+ const video::SColor* colors)
+{
+ if (!Driver)
+ return;
+
+ if (!colors)
+ colors = Colors;
+
+ core::rect<s32> rect = r;
+
+ if (fillBackGround)
+ Driver->draw2DRectangle(bgcolor, rect, clip);
+
+ if (flat)
+ {
+ // draw flat sunken pane
+
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // top
+
+ ++rect.UpperLeftCorner.Y;
+ rect.LowerRightCorner.Y = r.LowerRightCorner.Y;
+ rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // left
+
+ rect = r;
+ ++rect.UpperLeftCorner.Y;
+ rect.UpperLeftCorner.X = rect.LowerRightCorner.X - 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // right
+
+ rect = r;
+ ++rect.UpperLeftCorner.X;
+ rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1;
+ --rect.LowerRightCorner.X;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // bottom
+ }
+ else
+ {
+ // draw deep sunken pane
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // top
+ ++rect.UpperLeftCorner.X;
+ ++rect.UpperLeftCorner.Y;
+ --rect.LowerRightCorner.X;
+ ++rect.LowerRightCorner.Y;
+ Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip);
+
+ rect.UpperLeftCorner.X = r.UpperLeftCorner.X;
+ rect.UpperLeftCorner.Y = r.UpperLeftCorner.Y+1;
+ rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1;
+ rect.LowerRightCorner.Y = r.LowerRightCorner.Y;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip); // left
+ ++rect.UpperLeftCorner.X;
+ ++rect.UpperLeftCorner.Y;
+ ++rect.LowerRightCorner.X;
+ --rect.LowerRightCorner.Y;
+ Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip);
+
+ rect = r;
+ rect.UpperLeftCorner.X = rect.LowerRightCorner.X - 1;
+ ++rect.UpperLeftCorner.Y;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // right
+ --rect.UpperLeftCorner.X;
+ ++rect.UpperLeftCorner.Y;
+ --rect.LowerRightCorner.X;
+ --rect.LowerRightCorner.Y;
+ Driver->draw2DRectangle(colors[EGDC_3D_LIGHT], rect, clip);
+
+ rect = r;
+ ++rect.UpperLeftCorner.X;
+ rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1;
+ --rect.LowerRightCorner.X;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip); // bottom
+ ++rect.UpperLeftCorner.X;
+ --rect.UpperLeftCorner.Y;
+ --rect.LowerRightCorner.X;
+ --rect.LowerRightCorner.Y;
+ Driver->draw2DRectangle(colors[EGDC_3D_LIGHT], rect, clip);
+ }
+}
+// END PATCH
+
+//! draws a window background
+// return where to draw title bar text.
+// PATCH
+core::rect<s32> GUISkin::drawColored3DWindowBackground(IGUIElement* element,
+ bool drawTitleBar, video::SColor titleBarColor,
+ const core::rect<s32>& r,
+ const core::rect<s32>* clip,
+ core::rect<s32>* checkClientArea,
+ const video::SColor* colors)
+{
+ if (!Driver)
+ {
+ if ( checkClientArea )
+ {
+ *checkClientArea = r;
+ }
+ return r;
+ }
+
+ if (!colors)
+ colors = Colors;
+
+ core::rect<s32> rect = r;
+
+ // top border
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1;
+ if ( !checkClientArea )
+ {
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip);
+ }
+
+ // left border
+ rect.LowerRightCorner.Y = r.LowerRightCorner.Y;
+ rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1;
+ if ( !checkClientArea )
+ {
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip);
+ }
+
+ // right border dark outer line
+ rect.UpperLeftCorner.X = r.LowerRightCorner.X - 1;
+ rect.LowerRightCorner.X = r.LowerRightCorner.X;
+ rect.UpperLeftCorner.Y = r.UpperLeftCorner.Y;
+ rect.LowerRightCorner.Y = r.LowerRightCorner.Y;
+ if ( !checkClientArea )
+ {
+ Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip);
+ }
+
+ // right border bright innner line
+ rect.UpperLeftCorner.X -= 1;
+ rect.LowerRightCorner.X -= 1;
+ rect.UpperLeftCorner.Y += 1;
+ rect.LowerRightCorner.Y -= 1;
+ if ( !checkClientArea )
+ {
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip);
+ }
+
+ // bottom border dark outer line
+ rect.UpperLeftCorner.X = r.UpperLeftCorner.X;
+ rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1;
+ rect.LowerRightCorner.Y = r.LowerRightCorner.Y;
+ rect.LowerRightCorner.X = r.LowerRightCorner.X;
+ if ( !checkClientArea )
+ {
+ Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip);
+ }
+
+ // bottom border bright inner line
+ rect.UpperLeftCorner.X += 1;
+ rect.LowerRightCorner.X -= 1;
+ rect.UpperLeftCorner.Y -= 1;
+ rect.LowerRightCorner.Y -= 1;
+ if ( !checkClientArea )
+ {
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip);
+ }
+
+ // client area for background
+ rect = r;
+ rect.UpperLeftCorner.X +=1;
+ rect.UpperLeftCorner.Y +=1;
+ rect.LowerRightCorner.X -= 2;
+ rect.LowerRightCorner.Y -= 2;
+ if (checkClientArea)
+ {
+ *checkClientArea = rect;
+ }
+
+ if ( !checkClientArea )
+ {
+ if (!UseGradient)
+ {
+ Driver->draw2DRectangle(colors[EGDC_3D_FACE], rect, clip);
+ }
+ else if ( Type == EGST_BURNING_SKIN )
+ {
+ const video::SColor c1 = colors[EGDC_WINDOW].getInterpolated ( 0xFFFFFFFF, 0.9f );
+ const video::SColor c2 = colors[EGDC_WINDOW].getInterpolated ( 0xFFFFFFFF, 0.8f );
+
+ Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip);
+ }
+ else
+ {
+ const video::SColor c2 = colors[EGDC_3D_SHADOW];
+ const video::SColor c1 = colors[EGDC_3D_FACE];
+ Driver->draw2DRectangle(rect, c1, c1, c1, c2, clip);
+ }
+ }
+
+ // title bar
+ rect = r;
+ rect.UpperLeftCorner.X += 2;
+ rect.UpperLeftCorner.Y += 2;
+ rect.LowerRightCorner.X -= 2;
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + getSize(EGDS_WINDOW_BUTTON_WIDTH) + 2;
+
+ if (drawTitleBar )
+ {
+ if (checkClientArea)
+ {
+ (*checkClientArea).UpperLeftCorner.Y = rect.LowerRightCorner.Y;
+ }
+ else
+ {
+ // draw title bar
+ //if (!UseGradient)
+ // Driver->draw2DRectangle(titleBarColor, rect, clip);
+ //else
+ if ( Type == EGST_BURNING_SKIN )
+ {
+ const video::SColor c = titleBarColor.getInterpolated( video::SColor(titleBarColor.getAlpha(),255,255,255), 0.8f);
+ Driver->draw2DRectangle(rect, titleBarColor, titleBarColor, c, c, clip);
+ }
+ else
+ {
+ const video::SColor c = titleBarColor.getInterpolated(video::SColor(titleBarColor.getAlpha(),0,0,0), 0.2f);
+ Driver->draw2DRectangle(rect, titleBarColor, c, titleBarColor, c, clip);
+ }
+ }
+ }
+
+ return rect;
+}
+// END PATCH
+
+
+//! draws a standard 3d menu pane
+/** Used for drawing for menus and context menus.
+It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
+EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
+\param element: Pointer to the element which wishes to draw this. This parameter
+is usually not used by ISkin, but can be used for example by more complex
+implementations to find out how to draw the part exactly.
+\param rect: Defining area where to draw.
+\param clip: Clip area. */
+// PATCH
+void GUISkin::drawColored3DMenuPane(IGUIElement* element,
+ const core::rect<s32>& r, const core::rect<s32>* clip,
+ const video::SColor* colors)
+{
+ if (!Driver)
+ return;
+
+ if (!colors)
+ colors = Colors;
+
+ core::rect<s32> rect = r;
+
+ if ( Type == EGST_BURNING_SKIN )
+ {
+ rect.UpperLeftCorner.Y -= 3;
+ draw3DButtonPaneStandard(element, rect, clip);
+ return;
+ }
+
+ // in this skin, this is exactly what non pressed buttons look like,
+ // so we could simply call
+ // draw3DButtonPaneStandard(element, rect, clip);
+ // here.
+ // but if the skin is transparent, this doesn't look that nice. So
+ // We draw it a little bit better, with some more draw2DRectangle calls,
+ // but there aren't that much menus visible anyway.
+
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip);
+
+ rect.LowerRightCorner.Y = r.LowerRightCorner.Y;
+ rect.LowerRightCorner.X = rect.UpperLeftCorner.X + 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], rect, clip);
+
+ rect.UpperLeftCorner.X = r.LowerRightCorner.X - 1;
+ rect.LowerRightCorner.X = r.LowerRightCorner.X;
+ rect.UpperLeftCorner.Y = r.UpperLeftCorner.Y;
+ rect.LowerRightCorner.Y = r.LowerRightCorner.Y;
+ Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip);
+
+ rect.UpperLeftCorner.X -= 1;
+ rect.LowerRightCorner.X -= 1;
+ rect.UpperLeftCorner.Y += 1;
+ rect.LowerRightCorner.Y -= 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip);
+
+ rect.UpperLeftCorner.X = r.UpperLeftCorner.X;
+ rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1;
+ rect.LowerRightCorner.Y = r.LowerRightCorner.Y;
+ rect.LowerRightCorner.X = r.LowerRightCorner.X;
+ Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], rect, clip);
+
+ rect.UpperLeftCorner.X += 1;
+ rect.LowerRightCorner.X -= 1;
+ rect.UpperLeftCorner.Y -= 1;
+ rect.LowerRightCorner.Y -= 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip);
+
+ rect = r;
+ rect.UpperLeftCorner.X +=1;
+ rect.UpperLeftCorner.Y +=1;
+ rect.LowerRightCorner.X -= 2;
+ rect.LowerRightCorner.Y -= 2;
+
+ if (!UseGradient)
+ Driver->draw2DRectangle(colors[EGDC_3D_FACE], rect, clip);
+ else
+ {
+ const video::SColor c1 = colors[EGDC_3D_FACE];
+ const video::SColor c2 = colors[EGDC_3D_SHADOW];
+ Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip);
+ }
+}
+// END PATCH
+
+
+//! draws a standard 3d tool bar
+/** Used for drawing for toolbars and menus.
+\param element: Pointer to the element which wishes to draw this. This parameter
+is usually not used by ISkin, but can be used for example by more complex
+implementations to find out how to draw the part exactly.
+\param rect: Defining area where to draw.
+\param clip: Clip area. */
+// PATCH
+void GUISkin::drawColored3DToolBar(IGUIElement* element,
+ const core::rect<s32>& r,
+ const core::rect<s32>* clip,
+ const video::SColor* colors)
+{
+ if (!Driver)
+ return;
+
+ if (!colors)
+ colors = Colors;
+
+ core::rect<s32> rect = r;
+
+ rect.UpperLeftCorner.X = r.UpperLeftCorner.X;
+ rect.UpperLeftCorner.Y = r.LowerRightCorner.Y - 1;
+ rect.LowerRightCorner.Y = r.LowerRightCorner.Y;
+ rect.LowerRightCorner.X = r.LowerRightCorner.X;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], rect, clip);
+
+ rect = r;
+ rect.LowerRightCorner.Y -= 1;
+
+ if (!UseGradient)
+ {
+ Driver->draw2DRectangle(colors[EGDC_3D_FACE], rect, clip);
+ }
+ else
+ if ( Type == EGST_BURNING_SKIN )
+ {
+ const video::SColor c1 = 0xF0000000 | colors[EGDC_3D_FACE].color;
+ const video::SColor c2 = 0xF0000000 | colors[EGDC_3D_SHADOW].color;
+
+ rect.LowerRightCorner.Y += 1;
+ Driver->draw2DRectangle(rect, c1, c2, c1, c2, clip);
+ }
+ else
+ {
+ const video::SColor c1 = colors[EGDC_3D_FACE];
+ const video::SColor c2 = colors[EGDC_3D_SHADOW];
+ Driver->draw2DRectangle(rect, c1, c1, c2, c2, clip);
+ }
+}
+// END PATCH
+
+//! draws a tab button
+/** Used for drawing for tab buttons on top of tabs.
+\param element: Pointer to the element which wishes to draw this. This parameter
+is usually not used by ISkin, but can be used for example by more complex
+implementations to find out how to draw the part exactly.
+\param active: Specifies if the tab is currently active.
+\param rect: Defining area where to draw.
+\param clip: Clip area. */
+// PATCH
+void GUISkin::drawColored3DTabButton(IGUIElement* element, bool active,
+ const core::rect<s32>& frameRect, const core::rect<s32>* clip, EGUI_ALIGNMENT alignment,
+ const video::SColor* colors)
+{
+ if (!Driver)
+ return;
+
+ if (!colors)
+ colors = Colors;
+
+ core::rect<s32> tr = frameRect;
+
+ if ( alignment == EGUIA_UPPERLEFT )
+ {
+ tr.LowerRightCorner.X -= 2;
+ tr.LowerRightCorner.Y = tr.UpperLeftCorner.Y + 1;
+ tr.UpperLeftCorner.X += 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], tr, clip);
+
+ // draw left highlight
+ tr = frameRect;
+ tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1;
+ tr.UpperLeftCorner.Y += 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], tr, clip);
+
+ // draw grey background
+ tr = frameRect;
+ tr.UpperLeftCorner.X += 1;
+ tr.UpperLeftCorner.Y += 1;
+ tr.LowerRightCorner.X -= 2;
+ Driver->draw2DRectangle(colors[EGDC_3D_FACE], tr, clip);
+
+ // draw right middle gray shadow
+ tr.LowerRightCorner.X += 1;
+ tr.UpperLeftCorner.X = tr.LowerRightCorner.X - 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], tr, clip);
+
+ tr.LowerRightCorner.X += 1;
+ tr.UpperLeftCorner.X += 1;
+ tr.UpperLeftCorner.Y += 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], tr, clip);
+ }
+ else
+ {
+ tr.LowerRightCorner.X -= 2;
+ tr.UpperLeftCorner.Y = tr.LowerRightCorner.Y - 1;
+ tr.UpperLeftCorner.X += 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], tr, clip);
+
+ // draw left highlight
+ tr = frameRect;
+ tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1;
+ tr.LowerRightCorner.Y -= 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], tr, clip);
+
+ // draw grey background
+ tr = frameRect;
+ tr.UpperLeftCorner.X += 1;
+ tr.UpperLeftCorner.Y -= 1;
+ tr.LowerRightCorner.X -= 2;
+ tr.LowerRightCorner.Y -= 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_FACE], tr, clip);
+
+ // draw right middle gray shadow
+ tr.LowerRightCorner.X += 1;
+ tr.UpperLeftCorner.X = tr.LowerRightCorner.X - 1;
+ //tr.LowerRightCorner.Y -= 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], tr, clip);
+
+ tr.LowerRightCorner.X += 1;
+ tr.UpperLeftCorner.X += 1;
+ tr.LowerRightCorner.Y -= 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_DARK_SHADOW], tr, clip);
+ }
+}
+// END PATCH
+
+
+//! draws a tab control body
+/** \param element: Pointer to the element which wishes to draw this. This parameter
+is usually not used by ISkin, but can be used for example by more complex
+implementations to find out how to draw the part exactly.
+\param border: Specifies if the border should be drawn.
+\param background: Specifies if the background should be drawn.
+\param rect: Defining area where to draw.
+\param clip: Clip area. */
+// PATCH
+void GUISkin::drawColored3DTabBody(IGUIElement* element, bool border, bool background,
+ const core::rect<s32>& rect, const core::rect<s32>* clip, s32 tabHeight, EGUI_ALIGNMENT alignment,
+ const video::SColor* colors)
+{
+ if (!Driver)
+ return;
+
+ if (!colors)
+ colors = Colors;
+
+ core::rect<s32> tr = rect;
+
+ if ( tabHeight == -1 )
+ tabHeight = getSize(gui::EGDS_BUTTON_HEIGHT);
+
+ // draw border.
+ if (border)
+ {
+ if ( alignment == EGUIA_UPPERLEFT )
+ {
+ // draw left hightlight
+ tr.UpperLeftCorner.Y += tabHeight + 2;
+ tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], tr, clip);
+
+ // draw right shadow
+ tr.UpperLeftCorner.X = rect.LowerRightCorner.X - 1;
+ tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], tr, clip);
+
+ // draw lower shadow
+ tr = rect;
+ tr.UpperLeftCorner.Y = tr.LowerRightCorner.Y - 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], tr, clip);
+ }
+ else
+ {
+ // draw left hightlight
+ tr.LowerRightCorner.Y -= tabHeight + 2;
+ tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], tr, clip);
+
+ // draw right shadow
+ tr.UpperLeftCorner.X = rect.LowerRightCorner.X - 1;
+ tr.LowerRightCorner.X = tr.UpperLeftCorner.X + 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_SHADOW], tr, clip);
+
+ // draw lower shadow
+ tr = rect;
+ tr.LowerRightCorner.Y = tr.UpperLeftCorner.Y + 1;
+ Driver->draw2DRectangle(colors[EGDC_3D_HIGH_LIGHT], tr, clip);
+ }
+ }
+
+ if (background)
+ {
+ if ( alignment == EGUIA_UPPERLEFT )
+ {
+ tr = rect;
+ tr.UpperLeftCorner.Y += tabHeight + 2;
+ tr.LowerRightCorner.X -= 1;
+ tr.UpperLeftCorner.X += 1;
+ tr.LowerRightCorner.Y -= 1;
+ }
+ else
+ {
+ tr = rect;
+ tr.UpperLeftCorner.X += 1;
+ tr.UpperLeftCorner.Y -= 1;
+ tr.LowerRightCorner.X -= 1;
+ tr.LowerRightCorner.Y -= tabHeight + 2;
+ //tr.UpperLeftCorner.X += 1;
+ }
+
+ if (!UseGradient)
+ Driver->draw2DRectangle(colors[EGDC_3D_FACE], tr, clip);
+ else
+ {
+ video::SColor c1 = colors[EGDC_3D_FACE];
+ video::SColor c2 = colors[EGDC_3D_SHADOW];
+ Driver->draw2DRectangle(tr, c1, c1, c2, c2, clip);
+ }
+ }
+}
+// END PATCH
+
+
+//! draws an icon, usually from the skin's sprite bank
+/** \param parent: Pointer to the element which wishes to draw this icon.
+This parameter is usually not used by IGUISkin, but can be used for example
+by more complex implementations to find out how to draw the part exactly.
+\param icon: Specifies the icon to be drawn.
+\param position: The position to draw the icon
+\param starttime: The time at the start of the animation
+\param currenttime: The present time, used to calculate the frame number
+\param loop: Whether the animation should loop or not
+\param clip: Clip area. */
+// PATCH
+void GUISkin::drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
+ const core::position2di position,
+ u32 starttime, u32 currenttime,
+ bool loop, const core::rect<s32>* clip,
+ const video::SColor* colors)
+{
+ if (!SpriteBank)
+ return;
+
+ if (!colors)
+ colors = Colors;
+
+ bool gray = element && !element->isEnabled();
+ SpriteBank->draw2DSprite(Icons[icon], position, clip,
+ colors[gray? EGDC_GRAY_WINDOW_SYMBOL : EGDC_WINDOW_SYMBOL], starttime, currenttime, loop, true);
+}
+// END PATCH
+
+
+EGUI_SKIN_TYPE GUISkin::getType() const
+{
+ return Type;
+}
+
+
+//! draws a 2d rectangle.
+void GUISkin::draw2DRectangle(IGUIElement* element,
+ const video::SColor &color, const core::rect<s32>& pos,
+ const core::rect<s32>* clip)
+{
+ Driver->draw2DRectangle(color, pos, clip);
+}
+
+
+//! Writes attributes of the object.
+//! Implement this to expose the attributes of your scene node animator for
+//! scripting languages, editors, debuggers or xml serialization purposes.
+void GUISkin::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const
+{
+ u32 i;
+ for (i=0; i<EGDC_COUNT; ++i)
+ out->addColor(GUISkinColorNames[i], Colors[i]);
+
+ for (i=0; i<EGDS_COUNT; ++i)
+ out->addInt(GUISkinSizeNames[i], Sizes[i]);
+
+ for (i=0; i<EGDT_COUNT; ++i)
+ out->addString(GUISkinTextNames[i], Texts[i].c_str());
+
+ for (i=0; i<EGDI_COUNT; ++i)
+ out->addInt(GUISkinIconNames[i], Icons[i]);
+}
+
+
+//! Reads attributes of the object.
+//! Implement this to set the attributes of your scene node animator for
+//! scripting languages, editors, debuggers or xml deserialization purposes.
+void GUISkin::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
+{
+ // TODO: This is not nice code for downward compatibility, whenever new values are added and users
+ // load an old skin the corresponding values will be set to 0.
+ u32 i;
+ for (i=0; i<EGDC_COUNT; ++i)
+ Colors[i] = in->getAttributeAsColor(GUISkinColorNames[i]);
+
+ for (i=0; i<EGDS_COUNT; ++i)
+ Sizes[i] = in->getAttributeAsInt(GUISkinSizeNames[i]);
+
+ for (i=0; i<EGDT_COUNT; ++i)
+ Texts[i] = in->getAttributeAsStringW(GUISkinTextNames[i]);
+
+ for (i=0; i<EGDI_COUNT; ++i)
+ Icons[i] = in->getAttributeAsInt(GUISkinIconNames[i]);
+}
+
+
+//! gets the colors
+// PATCH
+void GUISkin::getColors(video::SColor* colors)
+{
+ u32 i;
+ for (i=0; i<EGDC_COUNT; ++i)
+ colors[i] = Colors[i];
+}
+// END PATCH
+
+} // end namespace gui
+} // end namespace irr
+
+
+#endif // _IRR_COMPILE_WITH_GUI_
+
diff --git a/src/gui/guiSkin.h b/src/gui/guiSkin.h
new file mode 100644
index 000000000..bbb900f9f
--- /dev/null
+++ b/src/gui/guiSkin.h
@@ -0,0 +1,376 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#ifndef __GUI_SKIN_H_INCLUDED__
+#define __GUI_SKIN_H_INCLUDED__
+
+#include "IrrCompileConfig.h"
+#ifdef _IRR_COMPILE_WITH_GUI_
+
+#include "IGUISkin.h"
+#include "irrString.h"
+#include <string>
+#include "ITexture.h"
+
+namespace irr
+{
+namespace video
+{
+ class IVideoDriver;
+}
+namespace gui
+{
+ class GUISkin : public IGUISkin
+ {
+ public:
+
+ GUISkin(EGUI_SKIN_TYPE type, video::IVideoDriver* driver);
+
+ //! destructor
+ virtual ~GUISkin();
+
+ //! returns default color
+ virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const;
+
+ //! sets a default color
+ virtual void setColor(EGUI_DEFAULT_COLOR which, video::SColor newColor);
+
+ //! returns size for the given size type
+ virtual s32 getSize(EGUI_DEFAULT_SIZE size) const;
+
+ //! sets a default size
+ virtual void setSize(EGUI_DEFAULT_SIZE which, s32 size);
+
+ //! returns the default font
+ virtual IGUIFont* getFont(EGUI_DEFAULT_FONT which=EGDF_DEFAULT) const;
+
+ //! sets a default font
+ virtual void setFont(IGUIFont* font, EGUI_DEFAULT_FONT which=EGDF_DEFAULT);
+
+ //! sets the sprite bank used for drawing icons
+ virtual void setSpriteBank(IGUISpriteBank* bank);
+
+ //! gets the sprite bank used for drawing icons
+ virtual IGUISpriteBank* getSpriteBank() const;
+
+ //! Returns a default icon
+ /** Returns the sprite index within the sprite bank */
+ virtual u32 getIcon(EGUI_DEFAULT_ICON icon) const;
+
+ //! Sets a default icon
+ /** Sets the sprite index used for drawing icons like arrows,
+ close buttons and ticks in checkboxes
+ \param icon: Enum specifying which icon to change
+ \param index: The sprite index used to draw this icon */
+ virtual void setIcon(EGUI_DEFAULT_ICON icon, u32 index);
+
+ //! Returns a default text.
+ /** For example for Message box button captions:
+ "OK", "Cancel", "Yes", "No" and so on. */
+ virtual const wchar_t* getDefaultText(EGUI_DEFAULT_TEXT text) const;
+
+ //! Sets a default text.
+ /** For example for Message box button captions:
+ "OK", "Cancel", "Yes", "No" and so on. */
+ virtual void setDefaultText(EGUI_DEFAULT_TEXT which, const wchar_t* newText);
+
+ //! draws a standard 3d button pane
+ /** Used for drawing for example buttons in normal state.
+ It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
+ EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
+ \param rect: Defining area where to draw.
+ \param clip: Clip area.
+ \param element: Pointer to the element which wishes to draw this. This parameter
+ is usually not used by ISkin, but can be used for example by more complex
+ implementations to find out how to draw the part exactly. */
+ virtual void draw3DButtonPaneStandard(IGUIElement* element,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip=0)
+ {
+ drawColored3DButtonPaneStandard(element, rect,clip);
+ }
+
+ virtual void drawColored3DButtonPaneStandard(IGUIElement* element,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip=0,
+ const video::SColor* colors=0);
+
+ //! draws a pressed 3d button pane
+ /** Used for drawing for example buttons in pressed state.
+ It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
+ EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
+ \param rect: Defining area where to draw.
+ \param clip: Clip area.
+ \param element: Pointer to the element which wishes to draw this. This parameter
+ is usually not used by ISkin, but can be used for example by more complex
+ implementations to find out how to draw the part exactly. */
+ virtual void draw3DButtonPanePressed(IGUIElement* element,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip=0)
+ {
+ drawColored3DButtonPanePressed(element, rect, clip);
+ }
+
+ virtual void drawColored3DButtonPanePressed(IGUIElement* element,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip=0,
+ const video::SColor* colors=0);
+
+ //! draws a sunken 3d pane
+ /** Used for drawing the background of edit, combo or check boxes.
+ \param element: Pointer to the element which wishes to draw this. This parameter
+ is usually not used by ISkin, but can be used for example by more complex
+ implementations to find out how to draw the part exactly.
+ \param bgcolor: Background color.
+ \param flat: Specifies if the sunken pane should be flat or displayed as sunken
+ deep into the ground.
+ \param rect: Defining area where to draw.
+ \param clip: Clip area. */
+ virtual void draw3DSunkenPane(IGUIElement* element,
+ video::SColor bgcolor, bool flat,
+ bool fillBackGround,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip=0)
+ {
+ drawColored3DSunkenPane(element, bgcolor, flat, fillBackGround, rect, clip);
+ }
+
+ virtual void drawColored3DSunkenPane(IGUIElement* element,
+ video::SColor bgcolor, bool flat,
+ bool fillBackGround,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip=0,
+ const video::SColor* colors=0);
+
+ //! draws a window background
+ /** Used for drawing the background of dialogs and windows.
+ \param element: Pointer to the element which wishes to draw this. This parameter
+ is usually not used by ISkin, but can be used for example by more complex
+ implementations to find out how to draw the part exactly.
+ \param titleBarColor: Title color.
+ \param drawTitleBar: True to enable title drawing.
+ \param rect: Defining area where to draw.
+ \param clip: Clip area.
+ \param checkClientArea: When set to non-null the function will not draw anything,
+ but will instead return the clientArea which can be used for drawing by the calling window.
+ That is the area without borders and without titlebar.
+ \return Returns rect where it would be good to draw title bar text. This will
+ work even when checkClientArea is set to a non-null value.*/
+ virtual core::rect<s32> draw3DWindowBackground(IGUIElement* element,
+ bool drawTitleBar, video::SColor titleBarColor,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip,
+ core::rect<s32>* checkClientArea)
+ {
+ return drawColored3DWindowBackground(element, drawTitleBar, titleBarColor,
+ rect, clip, checkClientArea);
+ }
+
+ virtual core::rect<s32> drawColored3DWindowBackground(IGUIElement* element,
+ bool drawTitleBar, video::SColor titleBarColor,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip,
+ core::rect<s32>* checkClientArea,
+ const video::SColor* colors=0);
+
+ //! draws a standard 3d menu pane
+ /** Used for drawing for menus and context menus.
+ It uses the colors EGDC_3D_DARK_SHADOW, EGDC_3D_HIGH_LIGHT, EGDC_3D_SHADOW and
+ EGDC_3D_FACE for this. See EGUI_DEFAULT_COLOR for details.
+ \param element: Pointer to the element which wishes to draw this. This parameter
+ is usually not used by ISkin, but can be used for example by more complex
+ implementations to find out how to draw the part exactly.
+ \param rect: Defining area where to draw.
+ \param clip: Clip area. */
+ virtual void draw3DMenuPane(IGUIElement* element,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip=0)
+ {
+ drawColored3DMenuPane(element, rect, clip);
+ }
+
+ virtual void drawColored3DMenuPane(IGUIElement* element,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip=0,
+ const video::SColor* colors=0);
+
+ //! draws a standard 3d tool bar
+ /** Used for drawing for toolbars and menus.
+ \param element: Pointer to the element which wishes to draw this. This parameter
+ is usually not used by ISkin, but can be used for example by more complex
+ implementations to find out how to draw the part exactly.
+ \param rect: Defining area where to draw.
+ \param clip: Clip area. */
+ virtual void draw3DToolBar(IGUIElement* element,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip=0)
+ {
+ drawColored3DToolBar(element, rect, clip);
+ }
+
+ virtual void drawColored3DToolBar(IGUIElement* element,
+ const core::rect<s32>& rect,
+ const core::rect<s32>* clip=0,
+ const video::SColor* colors=0);
+
+ //! draws a tab button
+ /** Used for drawing for tab buttons on top of tabs.
+ \param element: Pointer to the element which wishes to draw this. This parameter
+ is usually not used by ISkin, but can be used for example by more complex
+ implementations to find out how to draw the part exactly.
+ \param active: Specifies if the tab is currently active.
+ \param rect: Defining area where to draw.
+ \param clip: Clip area. */
+ virtual void draw3DTabButton(IGUIElement* element, bool active,
+ const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
+ {
+ drawColored3DTabButton(element, active, rect, clip, alignment);
+ }
+
+ virtual void drawColored3DTabButton(IGUIElement* element, bool active,
+ const core::rect<s32>& rect, const core::rect<s32>* clip=0, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
+ const video::SColor* colors=0);
+
+ //! draws a tab control body
+ /** \param element: Pointer to the element which wishes to draw this. This parameter
+ is usually not used by ISkin, but can be used for example by more complex
+ implementations to find out how to draw the part exactly.
+ \param border: Specifies if the border should be drawn.
+ \param background: Specifies if the background should be drawn.
+ \param rect: Defining area where to draw.
+ \param clip: Clip area. */
+ virtual void draw3DTabBody(IGUIElement* element, bool border, bool background,
+ const core::rect<s32>& rect, const core::rect<s32>* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT)
+ {
+ drawColored3DTabBody(element, border, background, rect, clip, tabHeight, alignment);
+ }
+
+ virtual void drawColored3DTabBody(IGUIElement* element, bool border, bool background,
+ const core::rect<s32>& rect, const core::rect<s32>* clip=0, s32 tabHeight=-1, EGUI_ALIGNMENT alignment=EGUIA_UPPERLEFT,
+ const video::SColor* colors=0);
+
+ //! draws an icon, usually from the skin's sprite bank
+ /** \param element: Pointer to the element which wishes to draw this icon.
+ This parameter is usually not used by IGUISkin, but can be used for example
+ by more complex implementations to find out how to draw the part exactly.
+ \param icon: Specifies the icon to be drawn.
+ \param position: The position to draw the icon
+ \param starttime: The time at the start of the animation
+ \param currenttime: The present time, used to calculate the frame number
+ \param loop: Whether the animation should loop or not
+ \param clip: Clip area. */
+ virtual void drawIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
+ const core::position2di position,
+ u32 starttime=0, u32 currenttime=0,
+ bool loop=false, const core::rect<s32>* clip=0)
+ {
+ drawColoredIcon(element, icon, position, starttime, currenttime, loop, clip);
+ }
+
+ virtual void drawColoredIcon(IGUIElement* element, EGUI_DEFAULT_ICON icon,
+ const core::position2di position,
+ u32 starttime=0, u32 currenttime=0,
+ bool loop=false, const core::rect<s32>* clip=0,
+ const video::SColor* colors=0);
+
+ //! draws a 2d rectangle.
+ /** \param element: Pointer to the element which wishes to draw this icon.
+ This parameter is usually not used by IGUISkin, but can be used for example
+ by more complex implementations to find out how to draw the part exactly.
+ \param color: Color of the rectangle to draw. The alpha component specifies how
+ transparent the rectangle will be.
+ \param pos: Position of the rectangle.
+ \param clip: Pointer to rectangle against which the rectangle will be clipped.
+ If the pointer is null, no clipping will be performed. */
+ virtual void draw2DRectangle(IGUIElement* element, const video::SColor &color,
+ const core::rect<s32>& pos, const core::rect<s32>* clip = 0);
+
+
+ //! get the type of this skin
+ virtual EGUI_SKIN_TYPE getType() const;
+
+ //! Writes attributes of the object.
+ //! Implement this to expose the attributes of your scene node animator for
+ //! scripting languages, editors, debuggers or xml serialization purposes.
+ virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const;
+
+ //! Reads attributes of the object.
+ //! Implement this to set the attributes of your scene node animator for
+ //! scripting languages, editors, debuggers or xml deserialization purposes.
+ virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0);
+
+ //! gets the colors
+ virtual void getColors(video::SColor* colors); // ::PATCH:
+
+ private:
+
+ video::SColor Colors[EGDC_COUNT];
+ s32 Sizes[EGDS_COUNT];
+ u32 Icons[EGDI_COUNT];
+ IGUIFont* Fonts[EGDF_COUNT];
+ IGUISpriteBank* SpriteBank;
+ core::stringw Texts[EGDT_COUNT];
+ video::IVideoDriver* Driver;
+ bool UseGradient;
+
+ EGUI_SKIN_TYPE Type;
+ };
+
+ #define set3DSkinColors(skin, button_color) \
+ { \
+ skin->setColor(EGDC_3D_FACE, button_color); \
+ skin->setColor(EGDC_3D_DARK_SHADOW, button_color, 0.25f); \
+ skin->setColor(EGDC_3D_SHADOW, button_color, 0.5f); \
+ skin->setColor(EGDC_3D_LIGHT, button_color); \
+ skin->setColor(EGDC_3D_HIGH_LIGHT, button_color, 1.5f); \
+ }
+
+ #define getElementSkinColor(color) \
+ { \
+ if (!Colors) \
+ { \
+ IGUISkin* skin = Environment->getSkin(); \
+ if (skin) \
+ return skin->getColor(color); \
+ } \
+ return Colors[color]; \
+ }
+
+ #define setElementSkinColor(which, newColor, shading) \
+ { \
+ if (!Colors) \
+ { \
+ Colors = new video::SColor[EGDC_COUNT]; \
+ GUISkin* skin = (GUISkin *)Environment->getSkin(); \
+ if (skin) \
+ skin->getColors(Colors); \
+ } \
+ Colors[which] = newColor; \
+ setShading(Colors[which],shading); \
+ }
+} // end namespace gui
+//! Sets the shading
+inline void setShading(video::SColor &color,f32 s) // :PATCH:
+{
+ if (s < 1.0f)
+ {
+ color.setRed(color.getRed() * s);
+ color.setGreen(color.getGreen() * s);
+ color.setBlue(color.getBlue() * s);
+ }
+ else if (s > 1.0f)
+ {
+ s -= 1.0f;
+
+ color.setRed(color.getRed() + (255 - color.getRed()) * s);
+ color.setGreen(color.getGreen() + (255 - color.getGreen()) * s);
+ color.setBlue(color.getBlue() + (255 - color.getBlue()) * s);
+ }
+}
+} // end namespace irr
+
+
+#endif // _IRR_COMPILE_WITH_GUI_
+
+#endif
diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp
index a123bdd6d..c705e17fb 100644
--- a/src/gui/guiTable.cpp
+++ b/src/gui/guiTable.cpp
@@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <cstring>
#include <IGUISkin.h>
#include <IGUIFont.h>
-#include <IGUIScrollBar.h>
#include "client/renderingengine.h"
#include "debug.h"
#include "log.h"
@@ -62,12 +61,12 @@ GUITable::GUITable(gui::IGUIEnvironment *env,
}
const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
- m_scrollbar = Environment->addScrollBar(false,
+ m_scrollbar = new GUIScrollBar(Environment, this, -1,
core::rect<s32>(RelativeRect.getWidth() - s,
0,
RelativeRect.getWidth(),
RelativeRect.getHeight()),
- this, -1);
+ false, true);
m_scrollbar->setSubElement(true);
m_scrollbar->setTabStop(false);
m_scrollbar->setAlignment(gui::EGUIA_LOWERRIGHT, gui::EGUIA_LOWERRIGHT,
@@ -99,7 +98,8 @@ GUITable::~GUITable()
if (m_font)
m_font->drop();
- m_scrollbar->remove();
+ if (m_scrollbar)
+ m_scrollbar->drop();
}
GUITable::Option GUITable::splitOption(const std::string &str)
@@ -1075,6 +1075,7 @@ void GUITable::updateScrollBar()
m_scrollbar->setMax(scrollmax);
m_scrollbar->setSmallStep(m_rowheight);
m_scrollbar->setLargeStep(2 * m_rowheight);
+ m_scrollbar->setPageSize(totalheight);
}
void GUITable::sendTableEvent(s32 column, bool doubleclick)
diff --git a/src/gui/guiTable.h b/src/gui/guiTable.h
index f9337ff6d..11093ea72 100644
--- a/src/gui/guiTable.h
+++ b/src/gui/guiTable.h
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iostream>
#include "irrlichttypes_extrabloated.h"
+#include "guiScrollBar.h"
class ISimpleTextureSource;
@@ -198,7 +199,7 @@ protected:
video::SColor m_highlight_text = video::SColor(255, 255, 255, 255);
s32 m_rowheight = 1;
gui::IGUIFont *m_font = nullptr;
- gui::IGUIScrollBar *m_scrollbar = nullptr;
+ GUIScrollBar *m_scrollbar = nullptr;
// Allocated strings and images
std::vector<core::stringw> m_strings;
diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp
index e0cb6fa72..45d2ee139 100644
--- a/src/gui/guiVolumeChange.cpp
+++ b/src/gui/guiVolumeChange.cpp
@@ -58,6 +58,9 @@ void GUIVolumeChange::removeChildren()
if (gui::IGUIElement *e = getElementFromId(ID_soundSlider))
e->remove();
+
+ if (gui::IGUIElement *e = getElementFromId(ID_soundMuteButton))
+ e->remove();
}
void GUIVolumeChange::regenerateGui(v2u32 screensize)
@@ -193,4 +196,3 @@ bool GUIVolumeChange::OnEvent(const SEvent& event)
return Parent ? Parent->OnEvent(event) : false;
}
-
diff --git a/src/gui/intlGUIEditBox.cpp b/src/gui/intlGUIEditBox.cpp
index fd3caa0f7..10395423c 100644
--- a/src/gui/intlGUIEditBox.cpp
+++ b/src/gui/intlGUIEditBox.cpp
@@ -113,6 +113,9 @@ intlGUIEditBox::~intlGUIEditBox()
if (Operator)
Operator->drop();
+
+ if (m_vscrollbar)
+ m_vscrollbar->drop();
}
@@ -1479,7 +1482,9 @@ void intlGUIEditBox::createVScrollBar()
irr::core::rect<s32> scrollbarrect = FrameRect;
scrollbarrect.UpperLeftCorner.X += FrameRect.getWidth() - m_scrollbar_width;
- m_vscrollbar = Environment->addScrollBar(false, scrollbarrect, getParent(), getID());
+ m_vscrollbar = new GUIScrollBar(Environment, getParent(), -1,
+ scrollbarrect, false, true);
+
m_vscrollbar->setVisible(false);
m_vscrollbar->setSmallStep(3 * fontHeight);
m_vscrollbar->setLargeStep(10 * fontHeight);
@@ -1501,6 +1506,7 @@ void intlGUIEditBox::updateVScrollBar()
if (scrollymax != m_vscrollbar->getMax()) {
// manage a newline or a deleted line
m_vscrollbar->setMax(scrollymax);
+ m_vscrollbar->setPageSize(s32(getTextDimension().Height));
calculateScrollPos();
} else {
// manage a newline or a deleted line
@@ -1513,6 +1519,7 @@ void intlGUIEditBox::updateVScrollBar()
s32 scrollymax = getTextDimension().Height - FrameRect.getHeight();
if (scrollymax != m_vscrollbar->getMax()) {
m_vscrollbar->setMax(scrollymax);
+ m_vscrollbar->setPageSize(s32(getTextDimension().Height));
}
if (!m_vscrollbar->isVisible() && MultiLine) {
@@ -1527,6 +1534,7 @@ void intlGUIEditBox::updateVScrollBar()
VScrollPos = 0;
m_vscrollbar->setPos(0);
m_vscrollbar->setMax(1);
+ m_vscrollbar->setPageSize(s32(getTextDimension().Height));
m_vscrollbar->setVisible(false);
}
}
diff --git a/src/gui/intlGUIEditBox.h b/src/gui/intlGUIEditBox.h
index 3aa4f00b4..9d643495e 100644
--- a/src/gui/intlGUIEditBox.h
+++ b/src/gui/intlGUIEditBox.h
@@ -10,7 +10,7 @@
#include <IGUIEditBox.h>
#include "irrArray.h"
#include "IOSOperator.h"
-#include "IGUIScrollBar.h"
+#include "guiScrollBar.h"
namespace irr
{
@@ -198,7 +198,7 @@ namespace gui
core::rect<s32> CurrentTextRect = core::rect<s32>(0,0,1,1);
core::rect<s32> FrameRect; // temporary values
u32 m_scrollbar_width;
- IGUIScrollBar *m_vscrollbar;
+ GUIScrollBar *m_vscrollbar;
bool m_writable;
};
diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h
index ea9327813..102492255 100644
--- a/src/gui/mainmenumanager.h
+++ b/src/gui/mainmenumanager.h
@@ -62,21 +62,7 @@ public:
virtual void deletingMenu(gui::IGUIElement *menu)
{
// Remove all entries if there are duplicates
- bool removed_entry;
- do{
- removed_entry = false;
- for(std::list<gui::IGUIElement*>::iterator
- i = m_stack.begin();
- i != m_stack.end(); ++i)
- {
- if(*i == menu)
- {
- m_stack.erase(i);
- removed_entry = true;
- break;
- }
- }
- }while(removed_entry);
+ m_stack.remove(menu);
/*core::list<GUIModalMenu*>::Iterator i = m_stack.getLast();
assert(*i == menu);
diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp
index 4ffe88800..30417943d 100644
--- a/src/gui/modalMenu.cpp
+++ b/src/gui/modalMenu.cpp
@@ -260,7 +260,7 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
return retval;
}
}
- // clang-format on
+ // clang-format on
#endif
return false;
}
diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h
index 421f70963..1f9adda22 100644
--- a/src/gui/touchscreengui.h
+++ b/src/gui/touchscreengui.h
@@ -33,7 +33,8 @@ using namespace irr;
using namespace irr::core;
using namespace irr::gui;
-typedef enum {
+typedef enum
+{
jump_id = 0,
crunch_id,
zoom_id,
@@ -61,7 +62,8 @@ typedef enum {
joystick_center_id
} touch_gui_button_id;
-typedef enum {
+typedef enum
+{
j_forward = 0,
j_backward,
j_left,
@@ -69,7 +71,8 @@ typedef enum {
j_special1
} touch_gui_joystick_move_id;
-typedef enum {
+typedef enum
+{
AHBB_Dir_Top_Bottom,
AHBB_Dir_Bottom_Top,
AHBB_Dir_Left_Right,
diff --git a/src/inventory.cpp b/src/inventory.cpp
index 3bc67da1b..77ecf5876 100644
--- a/src/inventory.cpp
+++ b/src/inventory.cpp
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "inventory.h"
#include "serialization.h"
#include "debug.h"
+#include <algorithm>
#include <sstream>
#include "log.h"
#include "itemdef.h"
@@ -246,6 +247,14 @@ std::string ItemStack::getItemString() const
return os.str();
}
+std::string ItemStack::getDescription(IItemDefManager *itemdef) const
+{
+ std::string desc = metadata.getString("description");
+ if (desc.empty())
+ desc = getDefinition(itemdef).description;
+ return desc.empty() ? name : desc;
+}
+
ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef)
{
@@ -257,17 +266,8 @@ ItemStack ItemStack::addItem(ItemStack newitem, IItemDefManager *itemdef)
// If this is an empty item, it's an easy job.
else if(empty())
{
- const u16 stackMax = newitem.getStackMax(itemdef);
-
*this = newitem;
-
- // If the item fits fully, delete it
- if (count <= stackMax) {
- newitem.clear();
- } else { // Else the item does not fit fully. Return the rest.
- count = stackMax;
- newitem.remove(count);
- }
+ newitem.clear();
}
// If item name or metadata differs, bail out
else if (name != newitem.name
@@ -306,14 +306,7 @@ bool ItemStack::itemFits(ItemStack newitem,
// If this is an empty item, it's an easy job.
else if(empty())
{
- const u16 stackMax = newitem.getStackMax(itemdef);
-
- // If the item fits fully, delete it
- if (newitem.count <= stackMax) {
- newitem.clear();
- } else { // Else the item does not fit fully. Return the rest.
- newitem.remove(stackMax);
- }
+ newitem.clear();
}
// If item name or metadata differs, bail out
else if (name != newitem.name
@@ -335,6 +328,7 @@ bool ItemStack::itemFits(ItemStack newitem,
if(restitem)
*restitem = newitem;
+
return newitem.empty();
}
@@ -389,27 +383,32 @@ void InventoryList::clearItems()
m_items.emplace_back();
}
- //setDirty(true);
+ setModified();
}
void InventoryList::setSize(u32 newsize)
{
- if(newsize != m_items.size())
- m_items.resize(newsize);
+ if (newsize == m_items.size())
+ return;
+
+ m_items.resize(newsize);
m_size = newsize;
+ setModified();
}
void InventoryList::setWidth(u32 newwidth)
{
m_width = newwidth;
+ setModified();
}
void InventoryList::setName(const std::string &name)
{
m_name = name;
+ setModified();
}
-void InventoryList::serialize(std::ostream &os) const
+void InventoryList::serialize(std::ostream &os, bool incremental) const
{
//os.imbue(std::locale("C"));
@@ -422,6 +421,9 @@ void InventoryList::serialize(std::ostream &os) const
os<<"Item ";
item.serialize(os);
}
+ // TODO: Implement this:
+ // if (!incremental || item.checkModified())
+ // os << "Keep";
os<<"\n";
}
@@ -431,8 +433,8 @@ void InventoryList::serialize(std::ostream &os) const
void InventoryList::deSerialize(std::istream &is)
{
//is.imbue(std::locale("C"));
+ setModified();
- clearItems();
u32 item_i = 0;
m_width = 0;
@@ -446,12 +448,12 @@ void InventoryList::deSerialize(std::istream &is)
std::string name;
std::getline(iss, name, ' ');
- if (name == "EndInventoryList")
- return;
-
- // This is a temporary backwards compatibility fix
- if (name == "end")
+ if (name == "EndInventoryList" || name == "end") {
+ // If partial incremental: Clear leftover items (should not happen!)
+ for (size_t i = item_i; i < m_items.size(); ++i)
+ m_items[i].clear();
return;
+ }
if (name == "Width") {
iss >> m_width;
@@ -471,6 +473,8 @@ void InventoryList::deSerialize(std::istream &is)
if(item_i > getSize() - 1)
throw SerializationError("too many items");
m_items[item_i++].clear();
+ } else if (name == "Keep") {
+ ++item_i; // Unmodified item
}
}
@@ -508,14 +512,9 @@ bool InventoryList::operator == (const InventoryList &other) const
return false;
if(m_name != other.m_name)
return false;
- for(u32 i=0; i<m_items.size(); i++)
- {
- ItemStack s1 = m_items[i];
- ItemStack s2 = other.m_items[i];
- if(s1.name != s2.name || s1.wear!= s2.wear || s1.count != s2.count ||
- s1.metadata != s2.metadata)
+ for (u32 i = 0; i < m_items.size(); i++)
+ if (m_items[i] != other.m_items[i])
return false;
- }
return true;
}
@@ -569,7 +568,7 @@ ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem)
ItemStack olditem = m_items[i];
m_items[i] = newitem;
- //setDirty(true);
+ setModified();
return olditem;
}
@@ -577,6 +576,7 @@ void InventoryList::deleteItem(u32 i)
{
assert(i < m_items.size()); // Pre-condition
m_items[i].clear();
+ setModified();
}
ItemStack InventoryList::addItem(const ItemStack &newitem_)
@@ -624,8 +624,8 @@ ItemStack InventoryList::addItem(u32 i, const ItemStack &newitem)
return newitem;
ItemStack leftover = m_items[i].addItem(newitem, m_itemdef);
- //if(leftover != newitem)
- // setDirty(true);
+ if (leftover != newitem)
+ setModified();
return leftover;
}
@@ -680,11 +680,17 @@ ItemStack InventoryList::removeItem(const ItemStack &item)
for (auto i = m_items.rbegin(); i != m_items.rend(); ++i) {
if (i->name == item.name) {
u32 still_to_remove = item.count - removed.count;
- removed.addItem(i->takeItem(still_to_remove), m_itemdef);
+ ItemStack leftover = removed.addItem(i->takeItem(still_to_remove),
+ m_itemdef);
+ // Allow oversized stacks
+ removed.count += leftover.count;
+
if (removed.count == item.count)
break;
}
}
+ if (!removed.empty())
+ setModified();
return removed;
}
@@ -694,8 +700,8 @@ ItemStack InventoryList::takeItem(u32 i, u32 takecount)
return ItemStack();
ItemStack taken = m_items[i].takeItem(takecount);
- //if(!taken.empty())
- // setDirty(true);
+ if (!taken.empty())
+ setModified();
return taken;
}
@@ -711,27 +717,13 @@ void InventoryList::moveItemSomewhere(u32 i, InventoryList *dest, u32 count)
if (item1.empty())
return;
- // Try to add the item to destination list
- u32 dest_size = dest->getSize();
- // First try all the non-empty slots
- for (u32 dest_i = 0; dest_i < dest_size; dest_i++) {
- if (!m_items[dest_i].empty()) {
- item1 = dest->addItem(dest_i, item1);
- if (item1.empty()) return;
- }
- }
+ ItemStack leftover;
+ leftover = dest->addItem(item1);
- // Then try all the empty ones
- for (u32 dest_i = 0; dest_i < dest_size; dest_i++) {
- if (m_items[dest_i].empty()) {
- item1 = dest->addItem(dest_i, item1);
- if (item1.empty()) return;
- }
+ if (!leftover.empty()) {
+ // Add the remaining part back to the source item
+ addItem(i, leftover);
}
-
- // If we reach this, the item was not fully added
- // Add the remaining part back to the source item
- addItem(i, item1);
}
u32 InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i,
@@ -793,33 +785,22 @@ Inventory::~Inventory()
void Inventory::clear()
{
- m_dirty = true;
for (auto &m_list : m_lists) {
delete m_list;
}
m_lists.clear();
-}
-
-void Inventory::clearContents()
-{
- m_dirty = true;
- for (InventoryList *list : m_lists) {
- for (u32 j=0; j<list->getSize(); j++) {
- list->deleteItem(j);
- }
- }
+ setModified();
}
Inventory::Inventory(IItemDefManager *itemdef)
{
- m_dirty = false;
m_itemdef = itemdef;
+ setModified();
}
Inventory::Inventory(const Inventory &other)
{
*this = other;
- m_dirty = false;
}
Inventory & Inventory::operator = (const Inventory &other)
@@ -827,12 +808,12 @@ Inventory & Inventory::operator = (const Inventory &other)
// Gracefully handle self assignment
if(this != &other)
{
- m_dirty = true;
clear();
m_itemdef = other.m_itemdef;
for (InventoryList *list : other.m_lists) {
m_lists.push_back(new InventoryList(*list));
}
+ setModified();
}
return *this;
}
@@ -850,11 +831,16 @@ bool Inventory::operator == (const Inventory &other) const
return true;
}
-void Inventory::serialize(std::ostream &os) const
+void Inventory::serialize(std::ostream &os, bool incremental) const
{
- for (InventoryList *list : m_lists) {
- os<<"List "<<list->getName()<<" "<<list->getSize()<<"\n";
- list->serialize(os);
+ //std::cout << "Serialize " << (int)incremental << ", n=" << m_lists.size() << std::endl;
+ for (const InventoryList *list : m_lists) {
+ if (!incremental || list->checkModified()) {
+ os << "List " << list->getName() << " " << list->getSize() << "\n";
+ list->serialize(os, incremental);
+ } else {
+ os << "KeepList " << list->getName() << "\n";
+ }
}
os<<"EndInventory\n";
@@ -862,7 +848,8 @@ void Inventory::serialize(std::ostream &os) const
void Inventory::deSerialize(std::istream &is)
{
- clear();
+ std::vector<InventoryList *> new_lists;
+ new_lists.reserve(m_lists.size());
while (is.good()) {
std::string line;
@@ -873,12 +860,20 @@ void Inventory::deSerialize(std::istream &is)
std::string name;
std::getline(iss, name, ' ');
- if (name == "EndInventory")
- return;
+ if (name == "EndInventory" || name == "end") {
+ // Remove all lists that were not sent
+ for (auto &list : m_lists) {
+ if (std::find(new_lists.begin(), new_lists.end(), list) != new_lists.end())
+ continue;
- // This is a temporary backwards compatibility fix
- if (name == "end")
+ delete list;
+ list = nullptr;
+ setModified();
+ }
+ m_lists.erase(std::remove(m_lists.begin(), m_lists.end(),
+ nullptr), m_lists.end());
return;
+ }
if (name == "List") {
std::string listname;
@@ -887,15 +882,33 @@ void Inventory::deSerialize(std::istream &is)
std::getline(iss, listname, ' ');
iss>>listsize;
- InventoryList *list = new InventoryList(listname, listsize, m_itemdef);
+ InventoryList *list = getList(listname);
+ bool create_new = !list;
+ if (create_new)
+ list = new InventoryList(listname, listsize, m_itemdef);
+ else
+ list->setSize(listsize);
list->deSerialize(is);
- m_lists.push_back(list);
- }
- else
- {
- throw SerializationError("invalid inventory specifier: " + name);
+ new_lists.push_back(list);
+ if (create_new)
+ m_lists.push_back(list);
+
+ } else if (name == "KeepList") {
+ // Incrementally sent list
+ std::string listname;
+ std::getline(iss, listname, ' ');
+
+ InventoryList *list = getList(listname);
+ if (list) {
+ new_lists.push_back(list);
+ } else {
+ errorstream << "Inventory::deSerialize(): Tried to keep list '" <<
+ listname << "' which is non-existent." << std::endl;
+ }
}
+ // Any additional fields will throw errors when received by a client
+ // older than PROTOCOL_VERSION 38
}
// Contents given to deSerialize() were not terminated properly: throw error.
@@ -908,7 +921,7 @@ void Inventory::deSerialize(std::istream &is)
InventoryList * Inventory::addList(const std::string &name, u32 size)
{
- m_dirty = true;
+ setModified();
s32 i = getListIndex(name);
if(i != -1)
{
@@ -916,15 +929,18 @@ InventoryList * Inventory::addList(const std::string &name, u32 size)
{
delete m_lists[i];
m_lists[i] = new InventoryList(name, size, m_itemdef);
+ m_lists[i]->setModified();
}
return m_lists[i];
}
//don't create list with invalid name
- if (name.find(' ') != std::string::npos) return NULL;
+ if (name.find(' ') != std::string::npos)
+ return nullptr;
InventoryList *list = new InventoryList(name, size, m_itemdef);
+ list->setModified();
m_lists.push_back(list);
return list;
}
@@ -951,7 +967,8 @@ bool Inventory::deleteList(const std::string &name)
s32 i = getListIndex(name);
if(i == -1)
return false;
- m_dirty = true;
+
+ setModified();
delete m_lists[i];
m_lists.erase(m_lists.begin() + i);
return true;
diff --git a/src/inventory.h b/src/inventory.h
index 465aa66db..2828d3e5a 100644
--- a/src/inventory.h
+++ b/src/inventory.h
@@ -41,12 +41,14 @@ struct ItemStack
// Serialization
void serialize(std::ostream &os) const;
- // Deserialization. Pass itemdef unless you don't want aliases resolved.
+ // 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;
+ // Returns the tooltip
+ std::string getDescription(IItemDefManager *itemdef) const;
/*
Quantity methods
@@ -161,6 +163,19 @@ struct ItemStack
// Similar to takeItem, but keeps this ItemStack intact.
ItemStack peekItem(u32 peekcount) const;
+ bool operator ==(const ItemStack &s) const
+ {
+ return (this->name == s.name &&
+ this->count == s.count &&
+ this->wear == s.wear &&
+ this->metadata == s.metadata);
+ }
+
+ bool operator !=(const ItemStack &s) const
+ {
+ return !(*this == s);
+ }
+
/*
Properties
*/
@@ -179,7 +194,7 @@ public:
void setSize(u32 newsize);
void setWidth(u32 newWidth);
void setName(const std::string &name);
- void serialize(std::ostream &os) const;
+ void serialize(std::ostream &os, bool incremental) const;
void deSerialize(std::istream &is);
InventoryList(const InventoryList &other);
@@ -250,12 +265,16 @@ public:
// also with optional rollback recording
void moveItemSomewhere(u32 i, InventoryList *dest, u32 count);
+ inline bool checkModified() const { return m_dirty; }
+ inline void setModified(bool dirty = true) { m_dirty = dirty; }
+
private:
std::vector<ItemStack> m_items;
std::string m_name;
u32 m_size;
u32 m_width = 0;
IItemDefManager *m_itemdef;
+ bool m_dirty = true;
};
class Inventory
@@ -264,7 +283,6 @@ public:
~Inventory();
void clear();
- void clearContents();
Inventory(IItemDefManager *itemdef);
Inventory(const Inventory &other);
@@ -275,7 +293,8 @@ public:
return !(*this == other);
}
- void serialize(std::ostream &os) const;
+ // Never ever serialize to disk using "incremental"!
+ void serialize(std::ostream &os, bool incremental = false) const;
void deSerialize(std::istream &is);
InventoryList * addList(const std::string &name, u32 size);
@@ -286,28 +305,38 @@ public:
// A shorthand for adding items. Returns leftover item (possibly empty).
ItemStack addItem(const std::string &listname, const ItemStack &newitem)
{
- m_dirty = true;
InventoryList *list = getList(listname);
if(list == NULL)
return newitem;
return list->addItem(newitem);
}
- bool checkModified() const
+ inline bool checkModified() const
{
- return m_dirty;
+ if (m_dirty)
+ return true;
+
+ for (const auto &list : m_lists)
+ if (list->checkModified())
+ return true;
+
+ return false;
}
- void setModified(const bool x)
+ inline void setModified(bool dirty = true)
{
- m_dirty = x;
+ m_dirty = dirty;
+ // Set all as handled
+ if (!dirty) {
+ for (const auto &list : m_lists)
+ list->setModified(dirty);
+ }
}
-
private:
// -1 if not found
const s32 getListIndex(const std::string &name) const;
std::vector<InventoryList*> m_lists;
IItemDefManager *m_itemdef;
- bool m_dirty = false;
+ bool m_dirty = true;
};
diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp
index 51a472a56..57b561477 100644
--- a/src/inventorymanager.cpp
+++ b/src/inventorymanager.cpp
@@ -93,7 +93,7 @@ void InventoryLocation::deSerialize(std::istream &is)
}
}
-void InventoryLocation::deSerialize(std::string s)
+void InventoryLocation::deSerialize(const std::string &s)
{
std::istringstream is(s, std::ios::binary);
deSerialize(is);
@@ -348,6 +348,13 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
/* If no items will be moved, don't go further */
if (count == 0) {
+ // Undo client prediction. See 'clientApply'
+ if (from_inv.type == InventoryLocation::PLAYER)
+ list_from->setModified();
+
+ if (to_inv.type == InventoryLocation::PLAYER)
+ list_to->setModified();
+
infostream<<"IMoveAction::apply(): move was completely disallowed:"
<<" count="<<old_count
<<" from inv=\""<<from_inv.dump()<<"\""
@@ -524,9 +531,9 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
}
}
- mgr->setInventoryModified(from_inv, false);
+ mgr->setInventoryModified(from_inv);
if (inv_from != inv_to)
- mgr->setInventoryModified(to_inv, false);
+ mgr->setInventoryModified(to_inv);
}
void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
@@ -646,8 +653,6 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
if (src_can_take_count != -1 && src_can_take_count < take_count)
take_count = src_can_take_count;
- int actually_dropped_count = 0;
-
// Update item due executed callbacks
src_item = list_from->getItem(from_i);
@@ -656,10 +661,14 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
item1.count = take_count;
if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
player->getBasePosition())) {
- actually_dropped_count = take_count - item1.count;
+ int actually_dropped_count = take_count - item1.count;
if (actually_dropped_count == 0) {
infostream<<"Actually dropped no items"<<std::endl;
+
+ // Revert client prediction. See 'clientApply'
+ if (from_inv.type == InventoryLocation::PLAYER)
+ list_from->setModified();
return;
}
@@ -670,9 +679,10 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
if (item2.count != actually_dropped_count)
errorstream<<"Could not take dropped count of items"<<std::endl;
-
- mgr->setInventoryModified(from_inv, false);
}
+
+ src_item.count = actually_dropped_count;
+ mgr->setInventoryModified(from_inv);
}
infostream<<"IDropAction::apply(): dropped "
@@ -681,7 +691,6 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
<<" i="<<from_i
<<std::endl;
- src_item.count = actually_dropped_count;
/*
Report drop to endpoints
diff --git a/src/inventorymanager.h b/src/inventorymanager.h
index 30a82d4bf..69bf30169 100644
--- a/src/inventorymanager.h
+++ b/src/inventorymanager.h
@@ -97,7 +97,7 @@ struct InventoryLocation
std::string dump() const;
void serialize(std::ostream &os) const;
void deSerialize(std::istream &is);
- void deSerialize(std::string s);
+ void deSerialize(const std::string &s);
};
struct InventoryAction;
@@ -111,7 +111,7 @@ public:
// Get an inventory (server and client)
virtual Inventory* getInventory(const InventoryLocation &loc){return NULL;}
// Set modified (will be saved and sent over network; only on server)
- virtual void setInventoryModified(const InventoryLocation &loc, bool playerSend = true){}
+ virtual void setInventoryModified(const InventoryLocation &loc) {}
// Send inventory action to server (only on client)
virtual void inventoryAction(InventoryAction *a){}
};
diff --git a/src/irr_ptr.h b/src/irr_ptr.h
new file mode 100644
index 000000000..5022adb9d
--- /dev/null
+++ b/src/irr_ptr.h
@@ -0,0 +1,137 @@
+/*
+Minetest
+Copyright (C) 2018 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
+
+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 <type_traits>
+#include "irrlichttypes.h"
+#include "IReferenceCounted.h"
+
+/** Shared pointer for IrrLicht objects.
+ *
+ * It should only be used for user-managed objects, i.e. those created with
+ * the @c new operator or @c create* functions, like:
+ * `irr_ptr<scene::IMeshBuffer> buf{new scene::SMeshBuffer()};`
+ *
+ * It should *never* be used for engine-managed objects, including
+ * those created with @c addTexture and similar methods.
+ */
+template <class ReferenceCounted,
+ class = typename std::enable_if<std::is_base_of<IReferenceCounted,
+ ReferenceCounted>::value>::type>
+class irr_ptr
+{
+ ReferenceCounted *value = nullptr;
+
+ /** Drops stored pointer replacing it with the given one.
+ * @note Copy semantics: reference counter *is* increased.
+ */
+ void grab(ReferenceCounted *object)
+ {
+ if (object)
+ object->grab();
+ reset(object);
+ }
+
+public:
+ irr_ptr() {}
+
+ irr_ptr(std::nullptr_t) noexcept {}
+
+ irr_ptr(const irr_ptr &b) noexcept { grab(b.get()); }
+
+ irr_ptr(irr_ptr &&b) noexcept { reset(b.release()); }
+
+ template <typename B, class = typename std::enable_if<std::is_convertible<B *,
+ ReferenceCounted *>::value>::type>
+ irr_ptr(const irr_ptr<B> &b) noexcept
+ {
+ grab(b.get());
+ }
+
+ template <typename B, class = typename std::enable_if<std::is_convertible<B *,
+ ReferenceCounted *>::value>::type>
+ irr_ptr(irr_ptr<B> &&b) noexcept
+ {
+ reset(b.release());
+ }
+
+ /** Constructs a shared pointer out of a plain one
+ * @note Move semantics: reference counter is *not* increased.
+ */
+ explicit irr_ptr(ReferenceCounted *object) noexcept { reset(object); }
+
+ ~irr_ptr() { reset(); }
+
+ irr_ptr &operator=(const irr_ptr &b) noexcept
+ {
+ grab(b.get());
+ return *this;
+ }
+
+ irr_ptr &operator=(irr_ptr &&b) noexcept
+ {
+ reset(b.release());
+ return *this;
+ }
+
+ template <typename B, class = typename std::enable_if<std::is_convertible<B *,
+ ReferenceCounted *>::value>::type>
+ irr_ptr &operator=(const irr_ptr<B> &b) noexcept
+ {
+ grab(b.get());
+ return *this;
+ }
+
+ template <typename B, class = typename std::enable_if<std::is_convertible<B *,
+ ReferenceCounted *>::value>::type>
+ irr_ptr &operator=(irr_ptr<B> &&b) noexcept
+ {
+ reset(b.release());
+ return *this;
+ }
+
+ ReferenceCounted &operator*() const noexcept { return *value; }
+ ReferenceCounted *operator->() const noexcept { return value; }
+ explicit operator ReferenceCounted *() const noexcept { return value; }
+ explicit operator bool() const noexcept { return !!value; }
+
+ /** Returns the stored pointer.
+ */
+ ReferenceCounted *get() const noexcept { return value; }
+
+ /** Returns the stored pointer, erasing it from this class.
+ * @note Move semantics: reference counter is not changed.
+ */
+ ReferenceCounted *release() noexcept
+ {
+ ReferenceCounted *object = value;
+ value = nullptr;
+ return object;
+ }
+
+ /** Drops stored pointer replacing it with the given one.
+ * @note Move semantics: reference counter is *not* increased.
+ */
+ void reset(ReferenceCounted *object = nullptr) noexcept
+ {
+ if (value)
+ value->drop();
+ value = object;
+ }
+};
diff --git a/src/irrlicht_changes/static_text.cpp b/src/irrlicht_changes/static_text.cpp
index ed1655e0a..5a0f82673 100644
--- a/src/irrlicht_changes/static_text.cpp
+++ b/src/irrlicht_changes/static_text.cpp
@@ -109,10 +109,22 @@ void StaticText::draw()
font->getDimension(cText.c_str()).Width;
}
- irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
- tmp->draw(cText, frameRect,
- OverrideColorEnabled ? OverrideColor : skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT),
- HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER, (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+#if USE_FREETYPE
+ if (font->getType() == irr::gui::EGFT_CUSTOM) {
+ irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
+ tmp->draw(cText, frameRect,
+ OverrideColorEnabled ? OverrideColor :
+ skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT),
+ HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER,
+ (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+ } else
+#endif
+ {
+ font->draw(Text.c_str(), frameRect,
+ skin->getColor(EGDC_BUTTON_TEXT),
+ HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER,
+ (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+ }
}
else
{
@@ -140,18 +152,29 @@ void StaticText::draw()
font->getDimension(BrokenText[i].c_str()).Width;
}
- //std::vector<irr::video::SColor> colors;
- //std::wstring str;
EnrichedString str = BrokenText[i];
//str = colorizeText(BrokenText[i].c_str(), colors, previous_color);
//if (!colors.empty())
// previous_color = colors[colors.size() - 1];
- irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
- tmp->draw(str, r,
- previous_color, // FIXME
- HAlign == EGUIA_CENTER, false, (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+#if USE_FREETYPE
+ if (font->getType() == irr::gui::EGFT_CUSTOM) {
+ irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
+ tmp->draw(str,
+ r, previous_color, // FIXME
+ HAlign == EGUIA_CENTER, false,
+ (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+ } else
+#endif
+ {
+ // Draw non-colored text
+ font->draw(str.c_str(),
+ r, skin->getColor(EGDC_BUTTON_TEXT),
+ HAlign == EGUIA_CENTER, false,
+ (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+ }
+
r.LowerRightCorner.Y += height;
r.UpperLeftCorner.Y += height;
diff --git a/src/itemdef.cpp b/src/itemdef.cpp
index 9d6068bab..0d0afeb2b 100644
--- a/src/itemdef.cpp
+++ b/src/itemdef.cpp
@@ -38,10 +38,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <map>
#include <set>
-#ifdef __ANDROID__
-#include <GLES/gl.h>
-#endif
-
/*
ItemDefinition
*/
diff --git a/src/light.cpp b/src/light.cpp
index c60f8eaca..9b6f5c210 100644
--- a/src/light.cpp
+++ b/src/light.cpp
@@ -66,7 +66,7 @@ void set_light_table(float gamma)
params.center = g_settings->getFloat("lighting_boost_center");
params.sigma = g_settings->getFloat("lighting_boost_spread");
// Gamma correction
- params.gamma = rangelim(gamma, 0.5f, 3.0f);
+ params.gamma = rangelim(gamma, 0.5f, 10.0f);
// Boundary values should be fixed
light_LUT[0] = 0;
diff --git a/src/log.cpp b/src/log.cpp
index c84a847a5..30344b4df 100644
--- a/src/log.cpp
+++ b/src/log.cpp
@@ -310,16 +310,32 @@ void Logger::logToOutputs(LogLevel lev, const std::string &combined,
//// *LogOutput methods
////
-void FileLogOutput::open(const std::string &filename)
+void FileLogOutput::setFile(const std::string &filename, s64 file_size_max)
{
- m_stream.open(filename.c_str(), std::ios::app | std::ios::ate);
+ // Only move debug.txt if there is a valid maximum file size
+ bool is_too_large = false;
+ if (file_size_max > 0) {
+ std::ifstream ifile(filename, std::ios::binary | std::ios::ate);
+ is_too_large = ifile.tellg() > file_size_max;
+ ifile.close();
+ }
+
+ if (is_too_large) {
+ std::string filename_secondary = filename + ".1";
+ actionstream << "The log file grew too big; it is moved to " <<
+ filename_secondary << std::endl;
+ remove(filename_secondary.c_str());
+ rename(filename.c_str(), filename_secondary.c_str());
+ }
+ m_stream.open(filename, std::ios::app | std::ios::ate);
+
if (!m_stream.good())
throw FileNotGoodException("Failed to open log file " +
filename + ": " + strerror(errno));
m_stream << "\n\n"
- "-------------" << std::endl
- << " Separator" << std::endl
- << "-------------\n" << std::endl;
+ "-------------" << std::endl <<
+ " Separator" << std::endl <<
+ "-------------\n" << std::endl;
}
diff --git a/src/log.h b/src/log.h
index 952ebadb1..6350d8a86 100644
--- a/src/log.h
+++ b/src/log.h
@@ -118,16 +118,16 @@ public:
m_stream(stream)
{
#if !defined(_WIN32)
- colored = (Logger::color_mode == LOG_COLOR_ALWAYS) ||
- (Logger::color_mode == LOG_COLOR_AUTO && isatty(fileno(stdout)));
+ is_tty = isatty(fileno(stdout));
#else
- colored = Logger::color_mode == LOG_COLOR_ALWAYS;
+ is_tty = false;
#endif
}
void logRaw(LogLevel lev, const std::string &line)
{
- bool colored_message = colored;
+ 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:
@@ -160,12 +160,12 @@ public:
private:
std::ostream &m_stream;
- bool colored;
+ bool is_tty;
};
class FileLogOutput : public ICombinedLogOutput {
public:
- void open(const std::string &filename);
+ void setFile(const std::string &filename, s64 file_size_max);
void logRaw(LogLevel lev, const std::string &line)
{
diff --git a/src/main.cpp b/src/main.cpp
index 26ad978c6..4629f92ce 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "defaultsettings.h"
#include "gettext.h"
#include "log.h"
-#include "quicktune.h"
+#include "util/quicktune.h"
#include "httpfetch.h"
#include "gameparams.h"
#include "database/database.h"
@@ -79,7 +79,7 @@ static void print_modified_quicktune_values();
static void list_game_ids();
static void list_worlds(bool print_name, bool print_path);
-static void setup_log_params(const Settings &cmd_args);
+static bool setup_log_params(const Settings &cmd_args);
static bool create_userdata_path();
static bool init_common(const Settings &cmd_args, int argc, char *argv[]);
static void startup_message();
@@ -135,7 +135,8 @@ int main(int argc, char *argv[])
return 0;
}
- setup_log_params(cmd_args);
+ if (!setup_log_params(cmd_args))
+ return 1;
porting::signal_handler_init();
@@ -166,8 +167,12 @@ int main(int argc, char *argv[])
list_worlds(true, false);
} else if (cmd_args.get("worldlist") == "path") {
list_worlds(false, true);
- } else {
+ } else if (cmd_args.get("worldlist") == "both") {
list_worlds(true, true);
+ } else {
+ errorstream << "Invalid --worldlist value: "
+ << cmd_args.get("worldlist") << std::endl;
+ return 1;
}
return 0;
}
@@ -258,23 +263,17 @@ static void set_allowed_options(OptionList *allowed_options)
allowed_options->insert(std::make_pair("map-dir", ValueSpec(VALUETYPE_STRING,
_("Same as --world (deprecated)"))));
allowed_options->insert(std::make_pair("world", ValueSpec(VALUETYPE_STRING,
- _("Set world path (implies local game)"))));
+ _("Set world path (implies local game if used with option --go)"))));
allowed_options->insert(std::make_pair("worldname", ValueSpec(VALUETYPE_STRING,
- _("Set world by name (implies local game)"))));
+ _("Set world by name (implies local game if used with option --go)"))));
allowed_options->insert(std::make_pair("worldlist", ValueSpec(VALUETYPE_STRING,
- _("Get list of worlds (implies local game) ('path' lists paths, "
+ _("Get list of worlds ('path' lists paths, "
"'name' lists names, 'both' lists both)"))));
allowed_options->insert(std::make_pair("quiet", ValueSpec(VALUETYPE_FLAG,
_("Print to console errors only"))));
-#if !defined(_WIN32)
allowed_options->insert(std::make_pair("color", ValueSpec(VALUETYPE_STRING,
_("Coloured logs ('always', 'never' or 'auto'), defaults to 'auto'"
))));
-#else
- allowed_options->insert(std::make_pair("color", ValueSpec(VALUETYPE_STRING,
- _("Coloured logs ('always' or 'never'), defaults to 'never'"
- ))));
-#endif
allowed_options->insert(std::make_pair("info", ValueSpec(VALUETYPE_FLAG,
_("Print more information to console"))));
allowed_options->insert(std::make_pair("verbose", ValueSpec(VALUETYPE_FLAG,
@@ -398,7 +397,7 @@ static void print_modified_quicktune_values()
}
}
-static void setup_log_params(const Settings &cmd_args)
+static bool setup_log_params(const Settings &cmd_args)
{
// Quiet mode, print errors only
if (cmd_args.getFlag("quiet")) {
@@ -407,14 +406,27 @@ static void setup_log_params(const Settings &cmd_args)
}
// Coloured log messages (see log.h)
+ std::string color_mode;
if (cmd_args.exists("color")) {
- std::string mode = cmd_args.get("color");
- if (mode == "auto")
+ color_mode = cmd_args.get("color");
+#if !defined(_WIN32)
+ } else {
+ char *color_mode_env = getenv("MT_LOGCOLOR");
+ if (color_mode_env)
+ color_mode = color_mode_env;
+#endif
+ }
+ if (color_mode != "") {
+ if (color_mode == "auto") {
Logger::color_mode = LOG_COLOR_AUTO;
- else if (mode == "always")
+ } else if (color_mode == "always") {
Logger::color_mode = LOG_COLOR_ALWAYS;
- else
+ } else if (color_mode == "never") {
Logger::color_mode = LOG_COLOR_NEVER;
+ } else {
+ errorstream << "Invalid color mode: " << color_mode << std::endl;
+ return false;
+ }
}
// If trace is enabled, enable logging of certain things
@@ -433,6 +445,8 @@ static void setup_log_params(const Settings &cmd_args)
// In certain cases, output verbose level on stderr
if (cmd_args.getFlag("verbose") || cmd_args.getFlag("trace"))
g_logger.addOutput(&stderr_output, LL_VERBOSE);
+
+ return true;
}
static bool create_userdata_path()
@@ -566,9 +580,8 @@ static void init_log_streams(const Settings &cmd_args)
"using maximum." << std::endl;
}
- verbosestream << "log_filename = " << log_filename << std::endl;
-
- file_log_output.open(log_filename);
+ file_log_output.setFile(log_filename,
+ g_settings->getU64("debug_log_size_max") * 1000000);
g_logger.addOutputMaxLevel(&file_log_output, log_level);
}
diff --git a/src/map.cpp b/src/map.cpp
index 0114867e0..0a7099a06 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -89,14 +89,14 @@ void Map::removeEventReceiver(MapEventReceiver *event_receiver)
m_event_receivers.erase(event_receiver);
}
-void Map::dispatchEvent(MapEditEvent *event)
+void Map::dispatchEvent(const MapEditEvent &event)
{
for (MapEventReceiver *event_receiver : m_event_receivers) {
event_receiver->onMapEditEvent(event);
}
}
-MapSector * Map::getSectorNoGenerateNoExNoLock(v2s16 p)
+MapSector * Map::getSectorNoGenerateNoLock(v2s16 p)
{
if(m_sector_cache != NULL && p == m_sector_cache_p){
MapSector * sector = m_sector_cache;
@@ -117,24 +117,15 @@ MapSector * Map::getSectorNoGenerateNoExNoLock(v2s16 p)
return sector;
}
-MapSector * Map::getSectorNoGenerateNoEx(v2s16 p)
-{
- return getSectorNoGenerateNoExNoLock(p);
-}
-
MapSector * Map::getSectorNoGenerate(v2s16 p)
{
- MapSector *sector = getSectorNoGenerateNoEx(p);
- if(sector == NULL)
- throw InvalidPositionException();
-
- return sector;
+ return getSectorNoGenerateNoLock(p);
}
MapBlock * Map::getBlockNoCreateNoEx(v3s16 p3d)
{
v2s16 p2d(p3d.X, p3d.Z);
- MapSector * sector = getSectorNoGenerateNoEx(p2d);
+ MapSector * sector = getSectorNoGenerate(p2d);
if(sector == NULL)
return NULL;
MapBlock *block = sector->getBlockNoCreateNoEx(p3d.Y);
@@ -152,14 +143,8 @@ MapBlock * Map::getBlockNoCreate(v3s16 p3d)
bool Map::isNodeUnderground(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
- try{
- MapBlock * block = getBlockNoCreate(blockpos);
- return block->getIsUnderground();
- }
- catch(InvalidPositionException &e)
- {
- return false;
- }
+ MapBlock *block = getBlockNoCreateNoEx(blockpos);
+ return block && block->getIsUnderground();
}
bool Map::isValidPosition(v3s16 p)
@@ -170,7 +155,7 @@ bool Map::isValidPosition(v3s16 p)
}
// Returns a CONTENT_IGNORE node if not found
-MapNode Map::getNodeNoEx(v3s16 p, bool *is_valid_position)
+MapNode Map::getNode(v3s16 p, bool *is_valid_position)
{
v3s16 blockpos = getNodeBlockPos(p);
MapBlock *block = getBlockNoCreateNoEx(blockpos);
@@ -188,25 +173,6 @@ MapNode Map::getNodeNoEx(v3s16 p, bool *is_valid_position)
return node;
}
-#if 0
-// Deprecated
-// throws InvalidPositionException if not found
-// TODO: Now this is deprecated, getNodeNoEx should be renamed
-MapNode Map::getNode(v3s16 p)
-{
- v3s16 blockpos = getNodeBlockPos(p);
- MapBlock *block = getBlockNoCreateNoEx(blockpos);
- if (block == NULL)
- throw InvalidPositionException();
- v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
- bool is_valid_position;
- MapNode node = block->getNodeNoCheck(relpos, &is_valid_position);
- if (!is_valid_position)
- throw InvalidPositionException();
- return node;
-}
-#endif
-
// throws InvalidPositionException if not found
void Map::setNode(v3s16 p, MapNode & n)
{
@@ -233,7 +199,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
RollbackNode rollback_oldnode(this, p, m_gamedef);
// This is needed for updating the lighting
- MapNode oldnode = getNodeNoEx(p);
+ MapNode oldnode = getNode(p);
// Remove node metadata
if (remove_metadata) {
@@ -273,7 +239,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
v3s16 p2 = p + dir;
bool is_valid_position;
- MapNode n2 = getNodeNoEx(p2, &is_valid_position);
+ MapNode n2 = getNode(p2, &is_valid_position);
if(is_valid_position &&
(m_nodedef->get(n2).isLiquid() ||
n2.getContent() == CONTENT_AIR))
@@ -308,7 +274,7 @@ bool Map::addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata)
succeeded = false;
}
- dispatchEvent(&event);
+ dispatchEvent(event);
return succeeded;
}
@@ -333,7 +299,7 @@ bool Map::removeNodeWithEvent(v3s16 p)
succeeded = false;
}
- dispatchEvent(&event);
+ dispatchEvent(event);
return succeeded;
}
@@ -585,7 +551,7 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks,
v3s16 p0 = m_transforming_liquid.front();
m_transforming_liquid.pop_front();
- MapNode n0 = getNodeNoEx(p0);
+ MapNode n0 = getNode(p0);
/*
Collect information about current node
@@ -645,7 +611,7 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> &modified_blocks,
break;
}
v3s16 npos = p0 + dirs[i];
- NodeNeighbor nb(getNodeNoEx(npos), nt, npos);
+ NodeNeighbor nb(getNode(npos), nt, npos);
const ContentFeatures &cfnb = m_nodedef->get(nb.n);
switch (m_nodedef->get(nb.n.getContent()).liquid_type) {
case LIQUID_NONE:
@@ -1066,24 +1032,95 @@ void Map::removeNodeTimer(v3s16 p)
block->m_node_timers.remove(p_rel);
}
-bool Map::isOccluded(v3s16 p0, v3s16 p1, float step, float stepfac,
- float start_off, float end_off, u32 needed_count)
+bool Map::determineAdditionalOcclusionCheck(const v3s16 &pos_camera,
+ const core::aabbox3d<s16> &block_bounds, v3s16 &check)
+{
+ /*
+ This functions determines the node inside the target block that is
+ closest to the camera position. This increases the occlusion culling
+ accuracy in straight and diagonal corridors.
+ The returned position will be occlusion checked first in addition to the
+ others (8 corners + center).
+ No position is returned if
+ - the closest node is a corner, corners are checked anyway.
+ - the camera is inside the target block, it will never be occluded.
+ */
+#define CLOSEST_EDGE(pos, bounds, axis) \
+ ((pos).axis <= (bounds).MinEdge.axis) ? (bounds).MinEdge.axis : \
+ (bounds).MaxEdge.axis
+
+ bool x_inside = (block_bounds.MinEdge.X <= pos_camera.X) &&
+ (pos_camera.X <= block_bounds.MaxEdge.X);
+ bool y_inside = (block_bounds.MinEdge.Y <= pos_camera.Y) &&
+ (pos_camera.Y <= block_bounds.MaxEdge.Y);
+ bool z_inside = (block_bounds.MinEdge.Z <= pos_camera.Z) &&
+ (pos_camera.Z <= block_bounds.MaxEdge.Z);
+
+ if (x_inside && y_inside && z_inside)
+ return false; // Camera inside target mapblock
+
+ // straight
+ if (x_inside && y_inside) {
+ check = v3s16(pos_camera.X, pos_camera.Y, 0);
+ check.Z = CLOSEST_EDGE(pos_camera, block_bounds, Z);
+ return true;
+ } else if (y_inside && z_inside) {
+ check = v3s16(0, pos_camera.Y, pos_camera.Z);
+ check.X = CLOSEST_EDGE(pos_camera, block_bounds, X);
+ return true;
+ } else if (x_inside && z_inside) {
+ check = v3s16(pos_camera.X, 0, pos_camera.Z);
+ check.Y = CLOSEST_EDGE(pos_camera, block_bounds, Y);
+ return true;
+ }
+
+ // diagonal
+ if (x_inside) {
+ check = v3s16(pos_camera.X, 0, 0);
+ check.Y = CLOSEST_EDGE(pos_camera, block_bounds, Y);
+ check.Z = CLOSEST_EDGE(pos_camera, block_bounds, Z);
+ return true;
+ } else if (y_inside) {
+ check = v3s16(0, pos_camera.Y, 0);
+ check.X = CLOSEST_EDGE(pos_camera, block_bounds, X);
+ check.Z = CLOSEST_EDGE(pos_camera, block_bounds, Z);
+ return true;
+ } else if (z_inside) {
+ check = v3s16(0, 0, pos_camera.Z);
+ check.X = CLOSEST_EDGE(pos_camera, block_bounds, X);
+ check.Y = CLOSEST_EDGE(pos_camera, block_bounds, Y);
+ return true;
+ }
+
+ // Closest node would be a corner, none returned
+ return false;
+}
+
+bool Map::isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target,
+ float step, float stepfac, float offset, float end_offset, u32 needed_count)
{
- float d0 = (float)BS * p0.getDistanceFrom(p1);
- v3s16 u0 = p1 - p0;
- v3f uf = v3f(u0.X, u0.Y, u0.Z) * BS;
- uf.normalize();
- v3f p0f = v3f(p0.X, p0.Y, p0.Z) * BS;
+ v3f direction = intToFloat(pos_target - pos_camera, BS);
+ float distance = direction.getLength();
+
+ // Normalize direction vector
+ if (distance > 0.0f)
+ direction /= distance;
+
+ v3f pos_origin_f = intToFloat(pos_camera, BS);
u32 count = 0;
- for(float s=start_off; s<d0+end_off; s+=step){
- v3f pf = p0f + uf * s;
- v3s16 p = floatToInt(pf, BS);
- MapNode n = getNodeNoEx(p);
- const ContentFeatures &f = m_nodedef->get(n);
- if(f.drawtype == NDT_NORMAL){
- // not transparent, see ContentFeature::updateTextures
+ bool is_valid_position;
+
+ for (; offset < distance + end_offset; offset += step) {
+ v3f pos_node_f = pos_origin_f + direction * offset;
+ v3s16 pos_node = floatToInt(pos_node_f, BS);
+
+ MapNode node = getNode(pos_node, &is_valid_position);
+
+ if (is_valid_position &&
+ !m_nodedef->get(node).light_propagates) {
+ // Cannot see through light-blocking nodes --> occluded
count++;
- if(count >= needed_count)
+ if (count >= needed_count)
return true;
}
step *= stepfac;
@@ -1091,44 +1128,59 @@ bool Map::isOccluded(v3s16 p0, v3s16 p1, float step, float stepfac,
return false;
}
-bool Map::isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes) {
- v3s16 cpn = block->getPos() * MAP_BLOCKSIZE;
- cpn += v3s16(MAP_BLOCKSIZE / 2, MAP_BLOCKSIZE / 2, MAP_BLOCKSIZE / 2);
- float step = BS * 1;
- float stepfac = 1.1;
- float startoff = BS * 1;
+bool Map::isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes)
+{
+ // Check occlusion for center and all 8 corners of the mapblock
+ // Overshoot a little for less flickering
+ static const s16 bs2 = MAP_BLOCKSIZE / 2 + 1;
+ static const v3s16 dir9[9] = {
+ v3s16( 0, 0, 0),
+ v3s16( 1, 1, 1) * bs2,
+ v3s16( 1, 1, -1) * bs2,
+ v3s16( 1, -1, 1) * bs2,
+ v3s16( 1, -1, -1) * bs2,
+ v3s16(-1, 1, 1) * bs2,
+ v3s16(-1, 1, -1) * bs2,
+ v3s16(-1, -1, 1) * bs2,
+ v3s16(-1, -1, -1) * bs2,
+ };
+
+ v3s16 pos_blockcenter = block->getPosRelative() + (MAP_BLOCKSIZE / 2);
+
+ // Starting step size, value between 1m and sqrt(3)m
+ float step = BS * 1.2f;
+ // Multiply step by each iteraction by 'stepfac' to reduce checks in distance
+ float stepfac = 1.05f;
+
+ float start_offset = BS * 1.0f;
+
// The occlusion search of 'isOccluded()' must stop short of the target
- // point by distance 'endoff' (end offset) to not enter the target mapblock.
- // For the 8 mapblock corners 'endoff' must therefore be the maximum diagonal
- // of a mapblock, because we must consider all view angles.
+ // point by distance 'end_offset' to not enter the target mapblock.
+ // For the 8 mapblock corners 'end_offset' must therefore be the maximum
+ // diagonal of a mapblock, because we must consider all view angles.
// sqrt(1^2 + 1^2 + 1^2) = 1.732
- float endoff = -BS * MAP_BLOCKSIZE * 1.732050807569;
- s16 bs2 = MAP_BLOCKSIZE / 2 + 1;
+ float end_offset = -BS * MAP_BLOCKSIZE * 1.732f;
+
// to reduce the likelihood of falsely occluded blocks
// require at least two solid blocks
// this is a HACK, we should think of a more precise algorithm
u32 needed_count = 2;
- return (
- // For the central point of the mapblock 'endoff' can be halved
- isOccluded(cam_pos_nodes, cpn,
- step, stepfac, startoff, endoff / 2.0f, needed_count) &&
- isOccluded(cam_pos_nodes, cpn + v3s16(bs2,bs2,bs2),
- step, stepfac, startoff, endoff, needed_count) &&
- isOccluded(cam_pos_nodes, cpn + v3s16(bs2,bs2,-bs2),
- step, stepfac, startoff, endoff, needed_count) &&
- isOccluded(cam_pos_nodes, cpn + v3s16(bs2,-bs2,bs2),
- step, stepfac, startoff, endoff, needed_count) &&
- isOccluded(cam_pos_nodes, cpn + v3s16(bs2,-bs2,-bs2),
- step, stepfac, startoff, endoff, needed_count) &&
- isOccluded(cam_pos_nodes, cpn + v3s16(-bs2,bs2,bs2),
- step, stepfac, startoff, endoff, needed_count) &&
- isOccluded(cam_pos_nodes, cpn + v3s16(-bs2,bs2,-bs2),
- step, stepfac, startoff, endoff, needed_count) &&
- isOccluded(cam_pos_nodes, cpn + v3s16(-bs2,-bs2,bs2),
- step, stepfac, startoff, endoff, needed_count) &&
- isOccluded(cam_pos_nodes, cpn + v3s16(-bs2,-bs2,-bs2),
- step, stepfac, startoff, endoff, needed_count));
+ // Additional occlusion check, see comments in that function
+ v3s16 check;
+ if (determineAdditionalOcclusionCheck(cam_pos_nodes, block->getBox(), check)) {
+ // node is always on a side facing the camera, end_offset can be lower
+ if (!isOccluded(cam_pos_nodes, check, step, stepfac, start_offset,
+ -1.0f, needed_count))
+ return false;
+ }
+
+ for (const v3s16 &dir : dir9) {
+ if (!isOccluded(cam_pos_nodes, pos_blockcenter + dir, step, stepfac,
+ start_offset, end_offset, needed_count))
+ return false;
+ }
+ return true;
}
/*
@@ -1429,7 +1481,7 @@ MapSector *ServerMap::createSector(v2s16 p2d)
/*
Check if it exists already in memory
*/
- MapSector *sector = getSectorNoGenerateNoEx(p2d);
+ MapSector *sector = getSectorNoGenerate(p2d);
if (sector)
return sector;
@@ -1660,7 +1712,7 @@ void ServerMap::updateVManip(v3s16 pos)
return;
s32 idx = vm->m_area.index(pos);
- vm->m_data[idx] = getNodeNoEx(pos);
+ vm->m_data[idx] = getNode(pos);
vm->m_flags[idx] &= ~VOXELFLAG_NO_DATA;
vm->m_is_dirty = true;
@@ -1717,7 +1769,7 @@ bool ServerMap::loadFromFolders() {
return false;
}
-void ServerMap::createDirs(std::string path)
+void ServerMap::createDirs(const std::string &path)
{
if (!fs::CreateAllDirs(path)) {
m_dout<<"ServerMap: Failed to create directory "
@@ -2139,7 +2191,7 @@ MapBlock* ServerMap::loadBlock(v3s16 blockpos)
Make sure sector is loaded
*/
- MapSector *sector = getSectorNoGenerateNoEx(p2d);
+ MapSector *sector = getSectorNoGenerate(p2d);
/*
Make sure file exists
@@ -2168,7 +2220,7 @@ MapBlock* ServerMap::loadBlock(v3s16 blockpos)
for (it = modified_blocks.begin();
it != modified_blocks.end(); ++it)
event.modified_blocks.insert(it->first);
- dispatchEvent(&event);
+ dispatchEvent(event);
}
}
return block;
@@ -2182,7 +2234,7 @@ bool ServerMap::deleteBlock(v3s16 blockpos)
MapBlock *block = getBlockNoCreateNoEx(blockpos);
if (block) {
v2s16 p2d(blockpos.X, blockpos.Z);
- MapSector *sector = getSectorNoGenerateNoEx(p2d);
+ MapSector *sector = getSectorNoGenerate(p2d);
if (!sector)
return false;
sector->deleteBlock(block);
@@ -2248,20 +2300,15 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
continue;
bool block_data_inexistent = false;
- try
{
TimeTaker timer2("emerge load", &emerge_load_time);
- block = m_map->getBlockNoCreate(p);
- if(block->isDummy())
+ block = m_map->getBlockNoCreateNoEx(p);
+ if (!block || block->isDummy())
block_data_inexistent = true;
else
block->copyTo(*this);
}
- catch(InvalidPositionException &e)
- {
- block_data_inexistent = true;
- }
if(block_data_inexistent)
{
diff --git a/src/map.h b/src/map.h
index 7ef34b279..392ec3f25 100644
--- a/src/map.h
+++ b/src/map.h
@@ -79,18 +79,7 @@ struct MapEditEvent
MapEditEvent() = default;
- MapEditEvent * clone()
- {
- MapEditEvent *event = new MapEditEvent();
- event->type = type;
- event->p = p;
- event->n = n;
- event->modified_blocks = modified_blocks;
- event->is_private_change = is_private_change;
- return event;
- }
-
- VoxelArea getArea()
+ VoxelArea getArea() const
{
switch(type){
case MEET_ADDNODE:
@@ -125,7 +114,7 @@ class MapEventReceiver
{
public:
// event shall be deleted by caller after the call.
- virtual void onMapEditEvent(MapEditEvent *event) = 0;
+ virtual void onMapEditEvent(const MapEditEvent &event) = 0;
};
class Map /*: public NodeContainer*/
@@ -152,13 +141,11 @@ public:
void addEventReceiver(MapEventReceiver *event_receiver);
void removeEventReceiver(MapEventReceiver *event_receiver);
// event shall be deleted by caller after the call.
- void dispatchEvent(MapEditEvent *event);
+ void dispatchEvent(const MapEditEvent &event);
// On failure returns NULL
- MapSector * getSectorNoGenerateNoExNoLock(v2s16 p2d);
+ MapSector * getSectorNoGenerateNoLock(v2s16 p2d);
// Same as the above (there exists no lock anymore)
- MapSector * getSectorNoGenerateNoEx(v2s16 p2d);
- // On failure throws InvalidPositionException
MapSector * getSectorNoGenerate(v2s16 p2d);
// Gets an existing sector or creates an empty one
//MapSector * getSectorCreate(v2s16 p2d);
@@ -191,7 +178,7 @@ public:
// Returns a CONTENT_IGNORE node if not found
// If is_valid_position is not NULL then this will be set to true if the
// position is valid, otherwise false
- MapNode getNodeNoEx(v3s16 p, bool *is_valid_position = NULL);
+ MapNode getNode(v3s16 p, bool *is_valid_position = NULL);
/*
These handle lighting but not faces.
@@ -312,8 +299,11 @@ protected:
// This stores the properties of the nodes on the map.
const NodeDefManager *m_nodedef;
- bool isOccluded(v3s16 p0, v3s16 p1, float step, float stepfac,
- float start_off, float end_off, u32 needed_count);
+ bool determineAdditionalOcclusionCheck(const v3s16 &pos_camera,
+ const core::aabbox3d<s16> &block_bounds, v3s16 &check);
+ bool isOccluded(const v3s16 &pos_camera, const v3s16 &pos_target,
+ float step, float stepfac, float start_offset, float end_offset,
+ u32 needed_count);
private:
f32 m_transforming_liquid_loop_count_multiplier = 1.0f;
@@ -389,7 +379,7 @@ public:
Misc. helper functions for fiddling with directory and file
names when saving
*/
- void createDirs(std::string path);
+ void createDirs(const std::string &path);
// returns something like "map/sectors/xxxxxxxx"
std::string getSectorDir(v2s16 pos, int layout = 2);
// dirname: final directory name
diff --git a/src/map_settings_manager.cpp b/src/map_settings_manager.cpp
index 16e7d8230..a0ff1c754 100644
--- a/src/map_settings_manager.cpp
+++ b/src/map_settings_manager.cpp
@@ -174,8 +174,8 @@ MapgenParams *MapSettingsManager::makeMapgenParams()
// Create our MapgenParams
MapgenParams *params = Mapgen::createMapgenParams(mgtype);
- if (params == NULL)
- return NULL;
+ if (!params)
+ return nullptr;
params->mgtype = mgtype;
diff --git a/src/mapblock.cpp b/src/mapblock.cpp
index 0996dd168..5b755b7a6 100644
--- a/src/mapblock.cpp
+++ b/src/mapblock.cpp
@@ -100,7 +100,7 @@ bool MapBlock::isValidPositionParent(v3s16 p)
MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position)
{
if (!isValidPosition(p))
- return m_parent->getNodeNoEx(getPosRelative() + p, is_valid_position);
+ return m_parent->getNode(getPosRelative() + p, is_valid_position);
if (!data) {
if (is_valid_position)
diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp
index e54d76e08..fa34b7273 100644
--- a/src/mapgen/cavegen.cpp
+++ b/src/mapgen/cavegen.cpp
@@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mg_biome.h"
#include "cavegen.h"
+// TODO Remove this. Cave liquids are now defined and located using biome definitions
static NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0.6, 2.0);
@@ -321,9 +322,26 @@ void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax,
this->ystride = nmax.X - nmin.X + 1;
+ flooded = ps->range(1, 2) == 2;
+
+ // If flooded:
+ // Get biome at mapchunk midpoint. If cave liquid defined for biome, use it.
+ // If defined liquid is "air", disable 'flooded' to avoid placing "air".
+ use_biome_liquid = false;
+ if (flooded && bmgn) {
+ v3s16 midp = node_min + (node_max - node_min) / v3s16(2, 2, 2);
+ Biome *biome = (Biome *)bmgn->getBiomeAtPoint(midp);
+ if (biome->c_cave_liquid[0] != CONTENT_IGNORE) {
+ use_biome_liquid = true;
+ c_biome_liquid =
+ biome->c_cave_liquid[ps->range(0, biome->c_cave_liquid.size() - 1)];
+ if (c_biome_liquid == CONTENT_AIR)
+ flooded = false;
+ }
+ }
+
// Set initial parameters from randomness
int dswitchint = ps->range(1, 14);
- flooded = ps->range(1, 2) == 2;
if (large_cave) {
part_max_length_rs = ps->range(2, 4);
@@ -502,31 +520,21 @@ void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz)
fp.Z += 0.1f * ps->range(-10, 10);
v3s16 cp(fp.X, fp.Y, fp.Z);
- // Get biome at 'cp + of', the absolute centre point of this route
- v3s16 cpabs = cp + of;
+ // Choose cave liquid
MapNode liquidnode = CONTENT_IGNORE;
- if (bmgn) {
- Biome *biome = nullptr;
- if (cpabs.X < node_min.X || cpabs.X > node_max.X ||
- cpabs.Z < node_min.Z || cpabs.Z > node_max.Z)
- // Point is outside heat and humidity noise maps so use point noise
- // calculations.
- biome = (Biome *)bmgn->calcBiomeAtPoint(cpabs);
- else
- // Point is inside heat and humidity noise maps so use them
- biome = (Biome *)bmgn->getBiomeAtPoint(cpabs);
-
- if (biome->c_cave_liquid != CONTENT_IGNORE)
- liquidnode = biome->c_cave_liquid;
- }
-
- if (liquidnode == CONTENT_IGNORE) {
- // Fallback to classic behaviour using point 'startp'
- float nval = NoisePerlin3D(np_caveliquids, startp.X,
- startp.Y, startp.Z, seed);
- liquidnode = (nval < 0.40f && node_max.Y < lava_depth) ?
- lavanode : waternode;
+ if (flooded) {
+ if (use_biome_liquid) {
+ liquidnode = c_biome_liquid;
+ } else {
+ // TODO remove this. Cave liquids are now defined and located using biome
+ // definitions.
+ // If cave liquid not defined by biome, fallback to old hardcoded behaviour.
+ float nval = NoisePerlin3D(np_caveliquids, startp.X,
+ startp.Y, startp.Z, seed);
+ liquidnode = (nval < 0.40f && node_max.Y < lava_depth) ?
+ lavanode : waternode;
+ }
}
s16 d0 = -rs / 2;
diff --git a/src/mapgen/cavegen.h b/src/mapgen/cavegen.h
index 7b7be6219..3f1730ddb 100644
--- a/src/mapgen/cavegen.h
+++ b/src/mapgen/cavegen.h
@@ -119,6 +119,8 @@ public:
// configurable parameters
s32 seed;
int water_level;
+ // TODO 'lava_depth' and 'np_caveliquids' are deprecated and should be removed.
+ // Cave liquids are now defined and located using biome definitions.
int lava_depth;
NoiseParams *np_caveliquids;
@@ -133,6 +135,7 @@ public:
bool large_cave;
bool large_cave_is_flat;
bool flooded;
+ bool use_biome_liquid;
v3s16 node_min;
v3s16 node_max;
@@ -150,6 +153,7 @@ public:
content_t c_water_source;
content_t c_lava_source;
+ content_t c_biome_liquid;
// ndef is a mandatory parameter.
// If gennotify is NULL, generation events are not logged.
diff --git a/src/mapgen/dungeongen.cpp b/src/mapgen/dungeongen.cpp
index 77ac05770..acdb1a0f0 100644
--- a/src/mapgen/dungeongen.cpp
+++ b/src/mapgen/dungeongen.cpp
@@ -31,9 +31,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
//#define DGEN_USE_TORCHES
-NoiseParams nparams_dungeon_density(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0);
-NoiseParams nparams_dungeon_alt_wall(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
-
///////////////////////////////////////////////////////////////////////////////
@@ -69,28 +66,26 @@ DungeonGen::DungeonGen(const NodeDefManager *ndef,
dp.room_size_max = v3s16(8, 6, 8);
dp.room_size_large_min = v3s16(8, 8, 8);
dp.room_size_large_max = v3s16(16, 16, 16);
- dp.rooms_min = 2;
- dp.rooms_max = 16;
+ dp.large_room_chance = 1;
+ dp.num_rooms = 8;
+ dp.num_dungeons = 1;
dp.notifytype = GENNOTIFY_DUNGEON;
- dp.np_density = nparams_dungeon_density;
- dp.np_alt_wall = nparams_dungeon_alt_wall;
+ dp.np_alt_wall =
+ NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
}
}
void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax)
{
+ if (dp.num_dungeons == 0)
+ return;
+
assert(vm);
//TimeTaker t("gen dungeons");
- float nval_density = NoisePerlin3D(&dp.np_density, nmin.X, nmin.Y, nmin.Z, dp.seed);
- if (nval_density < 1.0f)
- return;
-
- static const bool preserve_ignore = !g_settings->getBool("projecting_dungeons");
-
this->vm = vm;
this->blockseed = bseed;
random.seed(bseed + 2);
@@ -99,9 +94,13 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax)
vm->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE);
if (dp.only_in_ground) {
- // Set all air and liquid drawtypes to be untouchable to make dungeons
- // open to air and liquids. Optionally set ignore to be untouchable to
- // prevent projecting dungeons.
+ // Set all air and liquid drawtypes to be untouchable to make dungeons generate
+ // in ground only.
+ // Set 'ignore' to be untouchable to prevent generation in ungenerated neighbor
+ // mapchunks, to avoid dungeon rooms generating outside ground.
+ // Like randomwalk caves, preserve nodes that have 'is_ground_content = false',
+ // to avoid dungeons that generate out beyond the edge of a mapchunk destroying
+ // nodes added by mods in 'register_on_generated()'.
for (s16 z = nmin.Z; z <= nmax.Z; z++) {
for (s16 y = nmin.Y; y <= nmax.Y; y++) {
u32 i = vm->m_area.index(nmin.X, y, z);
@@ -109,7 +108,7 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax)
content_t c = vm->m_data[i].getContent();
NodeDrawType dtype = ndef->get(c).drawtype;
if (dtype == NDT_AIRLIKE || dtype == NDT_LIQUID ||
- (preserve_ignore && c == CONTENT_IGNORE))
+ c == CONTENT_IGNORE || !ndef->get(c).is_ground_content)
vm->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE;
i++;
}
@@ -118,7 +117,7 @@ void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax)
}
// Add them
- for (u32 i = 0; i < std::floor(nval_density); i++)
+ for (u32 i = 0; i < dp.num_dungeons; i++)
makeDungeon(v3s16(1, 1, 1) * MAP_BLOCKSIZE);
// Optionally convert some structure to alternative structure
@@ -149,19 +148,13 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
/*
Find place for first room.
- There is a 1 in 4 chance of the first room being 'large',
- all other rooms are not 'large'.
*/
bool fits = false;
for (u32 i = 0; i < 100 && !fits; i++) {
- bool is_large_room = ((random.next() & 3) == 1);
- if (is_large_room) {
- roomsize.Z = random.range(
- dp.room_size_large_min.Z, dp.room_size_large_max.Z);
- roomsize.Y = random.range(
- dp.room_size_large_min.Y, dp.room_size_large_max.Y);
- roomsize.X = random.range(
- dp.room_size_large_min.X, dp.room_size_large_max.X);
+ if (dp.large_room_chance >= 1) {
+ roomsize.Z = random.range(dp.room_size_large_min.Z, dp.room_size_large_max.Z);
+ roomsize.Y = random.range(dp.room_size_large_min.Y, dp.room_size_large_max.Y);
+ roomsize.X = random.range(dp.room_size_large_min.X, dp.room_size_large_max.X);
} else {
roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z);
roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y);
@@ -203,8 +196,7 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
*/
v3s16 last_room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2);
- u32 room_count = random.range(dp.rooms_min, dp.rooms_max);
- for (u32 i = 0; i < room_count; i++) {
+ for (u32 i = 0; i < dp.num_rooms; i++) {
// Make a room to the determined place
makeRoom(roomsize, roomplace);
@@ -218,7 +210,7 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
#endif
// Quit if last room
- if (i == room_count - 1)
+ if (i + 1 == dp.num_rooms)
break;
// Determine walker start position
@@ -256,9 +248,16 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
makeCorridor(doorplace, doordir, corridor_end, corridor_end_dir);
// Find a place for a random sized room
- roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z);
- roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y);
- roomsize.X = random.range(dp.room_size_min.X, dp.room_size_max.X);
+ if (dp.large_room_chance > 1 && random.range(1, dp.large_room_chance) == 1) {
+ // Large room
+ roomsize.Z = random.range(dp.room_size_large_min.Z, dp.room_size_large_max.Z);
+ roomsize.Y = random.range(dp.room_size_large_min.Y, dp.room_size_large_max.Y);
+ roomsize.X = random.range(dp.room_size_large_min.X, dp.room_size_large_max.X);
+ } else {
+ roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z);
+ roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y);
+ roomsize.X = random.range(dp.room_size_min.X, dp.room_size_max.X);
+ }
m_pos = corridor_end;
m_dir = corridor_end_dir;
@@ -271,7 +270,6 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
else
// Don't actually make a door
roomplace -= doordir;
-
}
}
@@ -490,7 +488,7 @@ void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir,
if (partcount >= partlength) {
partcount = 0;
- dir = random_turn(random, dir);
+ random_turn(random, dir);
partlength = random.range(1, length);
@@ -651,20 +649,19 @@ v3s16 turn_xz(v3s16 olddir, int t)
}
-v3s16 random_turn(PseudoRandom &random, v3s16 olddir)
+void random_turn(PseudoRandom &random, v3s16 &dir)
{
int turn = random.range(0, 2);
- v3s16 dir;
- if (turn == 0)
- // Go straight
- dir = olddir;
- else if (turn == 1)
+ if (turn == 0) {
+ // Go straight: nothing to do
+ return;
+ } else if (turn == 1) {
// Turn right
- dir = turn_xz(olddir, 0);
- else
+ dir = turn_xz(dir, 0);
+ } else {
// Turn left
- dir = turn_xz(olddir, 1);
- return dir;
+ dir = turn_xz(dir, 1);
+ }
}
diff --git a/src/mapgen/dungeongen.h b/src/mapgen/dungeongen.h
index 2748524c5..35e6beef5 100644
--- a/src/mapgen/dungeongen.h
+++ b/src/mapgen/dungeongen.h
@@ -34,7 +34,7 @@ class NodeDefManager;
v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs);
v3s16 turn_xz(v3s16 olddir, int t);
-v3s16 random_turn(PseudoRandom &random, v3s16 olddir);
+void random_turn(PseudoRandom &random, v3s16 &dir);
int dir_to_facedir(v3s16 d);
@@ -42,24 +42,44 @@ struct DungeonParams {
s32 seed;
content_t c_wall;
+ // Randomly scattered alternative wall nodes
content_t c_alt_wall;
content_t c_stair;
- bool diagonal_dirs;
+ // 3D noise that determines which c_wall nodes are converted to c_alt_wall
+ NoiseParams np_alt_wall;
+
+ // Number of dungeons generated in mapchunk. All will use the same set of
+ // dungeonparams.
+ u16 num_dungeons;
+ // Dungeons only generate in ground
bool only_in_ground;
- v3s16 holesize;
- u16 corridor_len_min;
- u16 corridor_len_max;
+ // Number of rooms
+ u16 num_rooms;
+ // Room size random range. Includes walls / floor / ceilng
v3s16 room_size_min;
v3s16 room_size_max;
+ // Large room size random range. Includes walls / floor / ceilng
v3s16 room_size_large_min;
v3s16 room_size_large_max;
- u16 rooms_min;
- u16 rooms_max;
+ // Value 0 disables large rooms.
+ // Value 1 results in 1 large room, the first generated room.
+ // Value > 1 makes the first generated room large, all other rooms have a
+ // '1 in value' chance of being large.
+ u16 large_room_chance;
+ // Dimensions of 3D 'brush' that creates corridors.
+ // Dimensions are of the empty space, not including walls / floor / ceilng.
+ // Diagonal corridors must have hole width >=2 to be passable.
+ // Currently, hole width >= 3 causes stair corridor bugs.
+ v3s16 holesize;
+ // Corridor length random range
+ u16 corridor_len_min;
+ u16 corridor_len_max;
+ // Diagonal corridors are possible, 1 in 4 corridors will be diagonal
+ bool diagonal_dirs;
+ // Usually 'GENNOTIFY_DUNGEON', but mapgen v6 uses 'GENNOTIFY_TEMPLE' for
+ // desert dungeons.
GenNotifyType notifytype;
-
- NoiseParams np_density;
- NoiseParams np_alt_wall;
};
class DungeonGen {
@@ -82,8 +102,7 @@ public:
DungeonGen(const NodeDefManager *ndef,
GenerateNotifier *gennotify, DungeonParams *dparams);
- void generate(MMVManip *vm, u32 bseed,
- v3s16 full_node_min, v3s16 full_node_max);
+ void generate(MMVManip *vm, u32 bseed, v3s16 full_node_min, v3s16 full_node_max);
void makeDungeon(v3s16 start_padding);
void makeRoom(v3s16 roomsize, v3s16 roomplace);
diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp
index 7de367a27..6d5e721ce 100644
--- a/src/mapgen/mapgen.cpp
+++ b/src/mapgen/mapgen.cpp
@@ -19,6 +19,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 "mapgen.h"
#include "voxel.h"
#include "noise.h"
@@ -38,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "serialization.h"
#include "util/serialize.h"
#include "util/numeric.h"
+#include "util/directiontables.h"
#include "filesys.h"
#include "log.h"
#include "mapgen_carpathian.h"
@@ -80,15 +82,21 @@ struct MapgenDesc {
//// Built-in mapgens
////
+// Order used here defines the order of appearence in mainmenu.
+// v6 always last to discourage selection.
+// Special mapgens flat, fractal, singlenode, next to last. Of these, singlenode
+// last to discourage selection.
+// Of the remaining, v5 last due to age, v7 first due to being the default.
+// The order of 'enum MapgenType' in mapgen.h must match this order.
static MapgenDesc g_reg_mapgens[] = {
- {"v5", true},
- {"v6", true},
{"v7", true},
+ {"valleys", true},
+ {"carpathian", true},
+ {"v5", true},
{"flat", true},
{"fractal", true},
- {"valleys", true},
{"singlenode", true},
- {"carpathian", true},
+ {"v6", true},
};
STATIC_ASSERT(
@@ -148,28 +156,28 @@ const char *Mapgen::getMapgenName(MapgenType mgtype)
}
-Mapgen *Mapgen::createMapgen(MapgenType mgtype, int mgid,
- MapgenParams *params, EmergeManager *emerge)
+Mapgen *Mapgen::createMapgen(MapgenType mgtype, MapgenParams *params,
+ EmergeManager *emerge)
{
switch (mgtype) {
case MAPGEN_CARPATHIAN:
- return new MapgenCarpathian(mgid, (MapgenCarpathianParams *)params, emerge);
+ return new MapgenCarpathian((MapgenCarpathianParams *)params, emerge);
case MAPGEN_FLAT:
- return new MapgenFlat(mgid, (MapgenFlatParams *)params, emerge);
+ return new MapgenFlat((MapgenFlatParams *)params, emerge);
case MAPGEN_FRACTAL:
- return new MapgenFractal(mgid, (MapgenFractalParams *)params, emerge);
+ return new MapgenFractal((MapgenFractalParams *)params, emerge);
case MAPGEN_SINGLENODE:
- return new MapgenSinglenode(mgid, (MapgenSinglenodeParams *)params, emerge);
+ return new MapgenSinglenode((MapgenSinglenodeParams *)params, emerge);
case MAPGEN_V5:
- return new MapgenV5(mgid, (MapgenV5Params *)params, emerge);
+ return new MapgenV5((MapgenV5Params *)params, emerge);
case MAPGEN_V6:
- return new MapgenV6(mgid, (MapgenV6Params *)params, emerge);
+ return new MapgenV6((MapgenV6Params *)params, emerge);
case MAPGEN_V7:
- return new MapgenV7(mgid, (MapgenV7Params *)params, emerge);
+ return new MapgenV7((MapgenV7Params *)params, emerge);
case MAPGEN_VALLEYS:
- return new MapgenValleys(mgid, (MapgenValleysParams *)params, emerge);
+ return new MapgenValleys((MapgenValleysParams *)params, emerge);
default:
- return NULL;
+ return nullptr;
}
}
@@ -194,7 +202,7 @@ MapgenParams *Mapgen::createMapgenParams(MapgenType mgtype)
case MAPGEN_VALLEYS:
return new MapgenValleysParams;
default:
- return NULL;
+ return nullptr;
}
}
@@ -415,7 +423,7 @@ void Mapgen::updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nm
void Mapgen::setLighting(u8 light, v3s16 nmin, v3s16 nmax)
{
- ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG);
+ ScopeProfiler sp(g_profiler, "EmergeThread: update lighting", SPT_AVG);
VoxelArea a(nmin, nmax);
for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
@@ -428,7 +436,8 @@ void Mapgen::setLighting(u8 light, v3s16 nmin, v3s16 nmax)
}
-void Mapgen::lightSpread(VoxelArea &a, v3s16 p, u8 light)
+void Mapgen::lightSpread(VoxelArea &a, std::queue<std::pair<v3s16, u8>> &queue,
+ const v3s16 &p, u8 light)
{
if (light <= 1 || !a.contains(p))
return;
@@ -448,8 +457,8 @@ void Mapgen::lightSpread(VoxelArea &a, v3s16 p, u8 light)
// Bail out only if we have no more light from either bank to propogate, or
// we hit a solid block that light cannot pass through.
if ((light_day <= (n.param1 & 0x0F) &&
- light_night <= (n.param1 & 0xF0)) ||
- !ndef->get(n).light_propagates)
+ light_night <= (n.param1 & 0xF0)) ||
+ !ndef->get(n).light_propagates)
return;
// Since this recursive function only terminates when there is no light from
@@ -460,19 +469,15 @@ void Mapgen::lightSpread(VoxelArea &a, v3s16 p, u8 light)
n.param1 = light;
- lightSpread(a, p + v3s16(0, 0, 1), light);
- lightSpread(a, p + v3s16(0, 1, 0), light);
- lightSpread(a, p + v3s16(1, 0, 0), light);
- lightSpread(a, p - v3s16(0, 0, 1), light);
- lightSpread(a, p - v3s16(0, 1, 0), light);
- lightSpread(a, p - v3s16(1, 0, 0), light);
+ // add to queue
+ queue.emplace(p, light);
}
void Mapgen::calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax,
bool propagate_shadow)
{
- ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG);
+ ScopeProfiler sp(g_profiler, "EmergeThread: update lighting", SPT_AVG);
//TimeTaker t("updateLighting");
propagateSunlight(nmin, nmax, propagate_shadow);
@@ -518,9 +523,10 @@ void Mapgen::propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow)
}
-void Mapgen::spreadLight(v3s16 nmin, v3s16 nmax)
+void Mapgen::spreadLight(const v3s16 &nmin, const v3s16 &nmax)
{
//TimeTaker t("spreadLight");
+ std::queue<std::pair<v3s16, u8>> queue;
VoxelArea a(nmin, nmax);
for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) {
@@ -544,18 +550,24 @@ void Mapgen::spreadLight(v3s16 nmin, v3s16 nmax)
u8 light = n.param1;
if (light) {
- lightSpread(a, v3s16(x, y, z + 1), light);
- lightSpread(a, v3s16(x, y + 1, z ), light);
- lightSpread(a, v3s16(x + 1, y, z ), light);
- lightSpread(a, v3s16(x, y, z - 1), light);
- lightSpread(a, v3s16(x, y - 1, z ), light);
- lightSpread(a, v3s16(x - 1, y, z ), light);
+ const v3s16 p(x, y, z);
+ // spread to all 6 neighbor nodes
+ for (const auto &dir : g_6dirs)
+ lightSpread(a, queue, p + dir, light);
}
}
}
}
- //printf("spreadLight: %dms\n", t.stop());
+ while (!queue.empty()) {
+ const auto &i = queue.front();
+ // spread to all 6 neighbor nodes
+ for (const auto &dir : g_6dirs)
+ lightSpread(a, queue, i.first + dir, i.second);
+ queue.pop();
+ }
+
+ //printf("spreadLight: %lums\n", t.stop());
}
@@ -592,45 +604,20 @@ MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emer
this->heightmap = new s16[csize.X * csize.Z];
//// Initialize biome generator
- // TODO(hmmmm): should we have a way to disable biomemanager biomes?
biomegen = m_bmgr->createBiomeGen(BIOMEGEN_ORIGINAL, params->bparams, csize);
biomemap = biomegen->biomemap;
//// Look up some commonly used content
c_stone = ndef->getId("mapgen_stone");
- c_desert_stone = ndef->getId("mapgen_desert_stone");
- c_sandstone = ndef->getId("mapgen_sandstone");
c_water_source = ndef->getId("mapgen_water_source");
c_river_water_source = ndef->getId("mapgen_river_water_source");
c_lava_source = ndef->getId("mapgen_lava_source");
+ c_cobble = ndef->getId("mapgen_cobble");
- // Fall back to more basic content if not defined
- // river_water_source cannot fallback to water_source because river water
- // needs to be non-renewable and have a short flow range.
- if (c_desert_stone == CONTENT_IGNORE)
- c_desert_stone = c_stone;
- if (c_sandstone == CONTENT_IGNORE)
- c_sandstone = c_stone;
-
- //// Content used for dungeon generation
- c_cobble = ndef->getId("mapgen_cobble");
- c_mossycobble = ndef->getId("mapgen_mossycobble");
- c_stair_cobble = ndef->getId("mapgen_stair_cobble");
- c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone");
- c_sandstonebrick = ndef->getId("mapgen_sandstonebrick");
- c_stair_sandstone_block = ndef->getId("mapgen_stair_sandstone_block");
-
- // Fall back to more basic content if not defined
- if (c_mossycobble == CONTENT_IGNORE)
- c_mossycobble = c_cobble;
- if (c_stair_cobble == CONTENT_IGNORE)
- c_stair_cobble = c_cobble;
- if (c_stair_desert_stone == CONTENT_IGNORE)
- c_stair_desert_stone = c_desert_stone;
- if (c_sandstonebrick == CONTENT_IGNORE)
- c_sandstonebrick = c_sandstone;
- if (c_stair_sandstone_block == CONTENT_IGNORE)
- c_stair_sandstone_block = c_sandstonebrick;
+ // Fall back to more basic content if not defined.
+ // Lava falls back to water as both are suitable as cave liquids.
+ if (c_lava_source == CONTENT_IGNORE)
+ c_lava_source = c_water_source;
}
@@ -889,94 +876,59 @@ void MapgenBasic::generateDungeons(s16 max_stone_y)
if (max_stone_y < node_min.Y)
return;
- // Get biome at mapchunk midpoint
- v3s16 chunk_mid = node_min + (node_max - node_min) / v3s16(2, 2, 2);
- Biome *biome = (Biome *)biomegen->getBiomeAtPoint(chunk_mid);
+ u16 num_dungeons = std::fmax(std::floor(
+ NoisePerlin3D(&np_dungeons, node_min.X, node_min.Y, node_min.Z, seed)), 0.0f);
+ if (num_dungeons == 0)
+ return;
+
+ PseudoRandom ps(blockseed + 70033);
DungeonParams dp;
- dp.seed = seed;
- dp.only_in_ground = true;
- dp.corridor_len_min = 1;
- dp.corridor_len_max = 13;
- dp.rooms_min = 2;
- dp.rooms_max = 16;
+ dp.np_alt_wall =
+ NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
+
+ dp.seed = seed;
+ dp.only_in_ground = true;
+ dp.num_dungeons = num_dungeons;
+ dp.notifytype = GENNOTIFY_DUNGEON;
+ dp.num_rooms = ps.range(2, 16);
+ dp.room_size_min = v3s16(5, 5, 5);
+ dp.room_size_max = v3s16(12, 6, 12);
+ dp.room_size_large_min = v3s16(12, 6, 12);
+ dp.room_size_large_max = v3s16(16, 16, 16);
+ dp.large_room_chance = (ps.range(1, 4) == 1) ? 8 : 0;
+ dp.diagonal_dirs = ps.range(1, 8) == 1;
+ // Diagonal corridors must have 'hole' width >=2 to be passable
+ u8 holewidth = (dp.diagonal_dirs) ? 2 : ps.range(1, 2);
+ dp.holesize = v3s16(holewidth, 3, holewidth);
+ dp.corridor_len_min = 1;
+ dp.corridor_len_max = 13;
- dp.np_density = nparams_dungeon_density;
- dp.np_alt_wall = nparams_dungeon_alt_wall;
+ // Get biome at mapchunk midpoint
+ v3s16 chunk_mid = node_min + (node_max - node_min) / v3s16(2, 2, 2);
+ Biome *biome = (Biome *)biomegen->getBiomeAtPoint(chunk_mid);
- // Biome-defined dungeon nodes
+ // Use biome-defined dungeon nodes if defined
if (biome->c_dungeon != CONTENT_IGNORE) {
- dp.c_wall = biome->c_dungeon;
+ dp.c_wall = biome->c_dungeon;
// If 'node_dungeon_alt' is not defined by biome, it and dp.c_alt_wall
// become CONTENT_IGNORE which skips the alt wall node placement loop in
// dungeongen.cpp.
- dp.c_alt_wall = biome->c_dungeon_alt;
+ dp.c_alt_wall = biome->c_dungeon_alt;
// Stairs fall back to 'c_dungeon' if not defined by biome
dp.c_stair = (biome->c_dungeon_stair != CONTENT_IGNORE) ?
biome->c_dungeon_stair : biome->c_dungeon;
-
- dp.diagonal_dirs = false;
- dp.holesize = v3s16(2, 2, 2);
- dp.room_size_min = v3s16(6, 4, 6);
- dp.room_size_max = v3s16(10, 6, 10);
- dp.room_size_large_min = v3s16(10, 8, 10);
- dp.room_size_large_max = v3s16(18, 16, 18);
- dp.notifytype = GENNOTIFY_DUNGEON;
-
- // Otherwise classic behaviour
- } else if (biome->c_stone == c_stone) {
- dp.c_wall = c_cobble;
- dp.c_alt_wall = c_mossycobble;
- dp.c_stair = c_stair_cobble;
-
- dp.diagonal_dirs = false;
- dp.holesize = v3s16(1, 2, 1);
- dp.room_size_min = v3s16(4, 4, 4);
- dp.room_size_max = v3s16(8, 6, 8);
- dp.room_size_large_min = v3s16(8, 8, 8);
- dp.room_size_large_max = v3s16(16, 16, 16);
- dp.notifytype = GENNOTIFY_DUNGEON;
-
- } else if (biome->c_stone == c_desert_stone) {
- dp.c_wall = c_desert_stone;
- dp.c_alt_wall = CONTENT_IGNORE;
- dp.c_stair = c_stair_desert_stone;
-
- dp.diagonal_dirs = true;
- dp.holesize = v3s16(2, 3, 2);
- dp.room_size_min = v3s16(6, 9, 6);
- dp.room_size_max = v3s16(10, 11, 10);
- dp.room_size_large_min = v3s16(10, 13, 10);
- dp.room_size_large_max = v3s16(18, 21, 18);
- dp.notifytype = GENNOTIFY_TEMPLE;
-
- } else if (biome->c_stone == c_sandstone) {
- dp.c_wall = c_sandstonebrick;
- dp.c_alt_wall = CONTENT_IGNORE;
- dp.c_stair = c_stair_sandstone_block;
-
- dp.diagonal_dirs = false;
- dp.holesize = v3s16(2, 2, 2);
- dp.room_size_min = v3s16(6, 4, 6);
- dp.room_size_max = v3s16(10, 6, 10);
- dp.room_size_large_min = v3s16(10, 8, 10);
- dp.room_size_large_max = v3s16(18, 16, 18);
- dp.notifytype = GENNOTIFY_DUNGEON;
-
- // Fallback to using biome 'node_stone'
+ // Fallback to using cobble mapgen alias if defined
+ } else if (c_cobble != CONTENT_IGNORE) {
+ dp.c_wall = c_cobble;
+ dp.c_alt_wall = CONTENT_IGNORE;
+ dp.c_stair = c_cobble;
+ // Fallback to using biome-defined stone
} else {
- dp.c_wall = biome->c_stone;
- dp.c_alt_wall = CONTENT_IGNORE;
- dp.c_stair = biome->c_stone;
-
- dp.diagonal_dirs = false;
- dp.holesize = v3s16(2, 2, 2);
- dp.room_size_min = v3s16(6, 4, 6);
- dp.room_size_max = v3s16(10, 6, 10);
- dp.room_size_large_min = v3s16(10, 8, 10);
- dp.room_size_large_max = v3s16(18, 16, 18);
- dp.notifytype = GENNOTIFY_DUNGEON;
+ dp.c_wall = biome->c_stone;
+ dp.c_alt_wall = CONTENT_IGNORE;
+ dp.c_stair = biome->c_stone;
}
DungeonGen dgen(ndef, &gennotify, &dp);
diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h
index b60aea57e..0ac26d538 100644
--- a/src/mapgen/mapgen.h
+++ b/src/mapgen/mapgen.h
@@ -102,15 +102,16 @@ private:
std::list<GenNotifyEvent> m_notify_events;
};
+// Order must match the order of 'static MapgenDesc g_reg_mapgens[]' in mapgen.cpp
enum MapgenType {
- MAPGEN_V5,
- MAPGEN_V6,
MAPGEN_V7,
+ MAPGEN_VALLEYS,
+ MAPGEN_CARPATHIAN,
+ MAPGEN_V5,
MAPGEN_FLAT,
MAPGEN_FRACTAL,
- MAPGEN_VALLEYS,
MAPGEN_SINGLENODE,
- MAPGEN_CARPATHIAN,
+ MAPGEN_V6,
MAPGEN_INVALID,
};
@@ -189,11 +190,12 @@ public:
void updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax);
void setLighting(u8 light, v3s16 nmin, v3s16 nmax);
- void lightSpread(VoxelArea &a, v3s16 p, u8 light);
+ void lightSpread(VoxelArea &a, std::queue<std::pair<v3s16, u8>> &queue,
+ const v3s16 &p, u8 light);
void calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax,
bool propagate_shadow = true);
void propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow);
- void spreadLight(v3s16 nmin, v3s16 nmax);
+ void spreadLight(const v3s16 &nmin, const v3s16 &nmax);
virtual void makeChunk(BlockMakeData *data) {}
virtual int getGroundLevelAtPoint(v2s16 p) { return 0; }
@@ -208,8 +210,8 @@ public:
// Mapgen management functions
static MapgenType getMapgenType(const std::string &mgname);
static const char *getMapgenName(MapgenType mgtype);
- static Mapgen *createMapgen(MapgenType mgtype, int mgid,
- MapgenParams *params, EmergeManager *emerge);
+ static Mapgen *createMapgen(MapgenType mgtype, MapgenParams *params,
+ EmergeManager *emerge);
static MapgenParams *createMapgenParams(MapgenType mgtype);
static void getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden);
@@ -257,21 +259,11 @@ protected:
v3s16 full_node_min;
v3s16 full_node_max;
- // Content required for generateBiomes
content_t c_stone;
- content_t c_desert_stone;
- content_t c_sandstone;
content_t c_water_source;
content_t c_river_water_source;
content_t c_lava_source;
-
- // Content required for generateDungeons
content_t c_cobble;
- content_t c_stair_cobble;
- content_t c_mossycobble;
- content_t c_stair_desert_stone;
- content_t c_sandstonebrick;
- content_t c_stair_sandstone_block;
int ystride;
int zstride;
@@ -283,9 +275,12 @@ protected:
NoiseParams np_cave1;
NoiseParams np_cave2;
NoiseParams np_cavern;
+ NoiseParams np_dungeons;
float cave_width;
float cavern_limit;
float cavern_taper;
float cavern_threshold;
+ // TODO 'lava_depth' is deprecated and should be removed. Cave liquids are
+ // now defined and located using biome definitions.
int lava_depth;
};
diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp
index f7daef708..12ce07da4 100644
--- a/src/mapgen/mapgen_carpathian.cpp
+++ b/src/mapgen/mapgen_carpathian.cpp
@@ -1,8 +1,7 @@
/*
Minetest
-Copyright (C) 2017-2018 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
-Copyright (C) 2010-2018 paramat
-Copyright (C) 2010-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2017-2019 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
+Copyright (C) 2017-2019 paramat
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
@@ -43,6 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
FlagDesc flagdesc_mapgen_carpathian[] = {
{"caverns", MGCARPATHIAN_CAVERNS},
+ {"rivers", MGCARPATHIAN_RIVERS},
{NULL, 0}
};
@@ -50,11 +50,13 @@ FlagDesc flagdesc_mapgen_carpathian[] = {
///////////////////////////////////////////////////////////////////////////////
-MapgenCarpathian::MapgenCarpathian(
- int mapgenid, MapgenCarpathianParams *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
+MapgenCarpathian::MapgenCarpathian(MapgenCarpathianParams *params, EmergeManager *emerge)
+ : MapgenBasic(MAPGEN_CARPATHIAN, params, emerge)
{
base_level = params->base_level;
+ river_width = params->river_width;
+ river_depth = params->river_depth;
+ valley_width = params->valley_width;
spflags = params->spflags;
cave_width = params->cave_width;
@@ -80,6 +82,8 @@ MapgenCarpathian::MapgenCarpathian(
noise_hills = new Noise(&params->np_hills, seed, csize.X, csize.Z);
noise_ridge_mnt = new Noise(&params->np_ridge_mnt, seed, csize.X, csize.Z);
noise_step_mnt = new Noise(&params->np_step_mnt, seed, csize.X, csize.Z);
+ if (spflags & MGCARPATHIAN_RIVERS)
+ noise_rivers = new Noise(&params->np_rivers, seed, csize.X, csize.Z);
//// 3D terrain noise
// 1 up 1 down overgeneration
@@ -89,6 +93,7 @@ MapgenCarpathian::MapgenCarpathian(
MapgenBasic::np_cave1 = params->np_cave1;
MapgenBasic::np_cave2 = params->np_cave2;
MapgenBasic::np_cavern = params->np_cavern;
+ MapgenBasic::np_dungeons = params->np_dungeons;
}
@@ -105,26 +110,31 @@ MapgenCarpathian::~MapgenCarpathian()
delete noise_hills;
delete noise_ridge_mnt;
delete noise_step_mnt;
+ if (spflags & MGCARPATHIAN_RIVERS)
+ delete noise_rivers;
+
delete noise_mnt_var;
}
MapgenCarpathianParams::MapgenCarpathianParams():
- np_filler_depth (0, 1, v3f(128, 128, 128), 261, 3, 0.7, 2.0),
- np_height1 (0, 5, v3f(251, 251, 251), 9613, 5, 0.5, 2.0),
- np_height2 (0, 5, v3f(383, 383, 383), 1949, 5, 0.5, 2.0),
- np_height3 (0, 5, v3f(509, 509, 509), 3211, 5, 0.5, 2.0),
- np_height4 (0, 5, v3f(631, 631, 631), 1583, 5, 0.5, 2.0),
- np_hills_terrain (1, 1, v3f(1301, 1301, 1301), 1692, 5, 0.5, 2.0),
- np_ridge_terrain (1, 1, v3f(1889, 1889, 1889), 3568, 5, 0.5, 2.0),
- np_step_terrain (1, 1, v3f(1889, 1889, 1889), 4157, 5, 0.5, 2.0),
- np_hills (0, 3, v3f(257, 257, 257), 6604, 6, 0.5, 2.0),
- np_ridge_mnt (0, 12, v3f(743, 743, 743), 5520, 6, 0.7, 2.0),
- np_step_mnt (0, 8, v3f(509, 509, 509), 2590, 6, 0.6, 2.0),
- np_mnt_var (0, 1, v3f(499, 499, 499), 2490, 5, 0.55, 2.0),
- np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
- np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
- np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0)
+ np_filler_depth (0, 1, v3f(128, 128, 128), 261, 3, 0.7, 2.0),
+ np_height1 (0, 5, v3f(251, 251, 251), 9613, 5, 0.5, 2.0),
+ np_height2 (0, 5, v3f(383, 383, 383), 1949, 5, 0.5, 2.0),
+ np_height3 (0, 5, v3f(509, 509, 509), 3211, 5, 0.5, 2.0),
+ np_height4 (0, 5, v3f(631, 631, 631), 1583, 5, 0.5, 2.0),
+ np_hills_terrain (1, 1, v3f(1301, 1301, 1301), 1692, 5, 0.5, 2.0),
+ np_ridge_terrain (1, 1, v3f(1889, 1889, 1889), 3568, 5, 0.5, 2.0),
+ np_step_terrain (1, 1, v3f(1889, 1889, 1889), 4157, 5, 0.5, 2.0),
+ np_hills (0, 3, v3f(257, 257, 257), 6604, 6, 0.5, 2.0),
+ np_ridge_mnt (0, 12, v3f(743, 743, 743), 5520, 6, 0.7, 2.0),
+ np_step_mnt (0, 8, v3f(509, 509, 509), 2590, 6, 0.6, 2.0),
+ np_rivers (0, 1, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0),
+ np_mnt_var (0, 1, v3f(499, 499, 499), 2490, 5, 0.55, 2.0),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
+ np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0),
+ np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0)
{
}
@@ -132,7 +142,12 @@ MapgenCarpathianParams::MapgenCarpathianParams():
void MapgenCarpathianParams::readParams(const Settings *settings)
{
settings->getFlagStrNoEx("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian);
- settings->getFloatNoEx("mgcarpathian_base_level", base_level);
+
+ settings->getFloatNoEx("mgcarpathian_base_level", base_level);
+ settings->getFloatNoEx("mgcarpathian_river_width", river_width);
+ settings->getFloatNoEx("mgcarpathian_river_depth", river_depth);
+ settings->getFloatNoEx("mgcarpathian_valley_width", valley_width);
+
settings->getFloatNoEx("mgcarpathian_cave_width", cave_width);
settings->getS16NoEx("mgcarpathian_large_cave_depth", large_cave_depth);
settings->getS16NoEx("mgcarpathian_lava_depth", lava_depth);
@@ -153,17 +168,24 @@ void MapgenCarpathianParams::readParams(const Settings *settings)
settings->getNoiseParams("mgcarpathian_np_hills", np_hills);
settings->getNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt);
settings->getNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt);
+ settings->getNoiseParams("mgcarpathian_np_rivers", np_rivers);
settings->getNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var);
settings->getNoiseParams("mgcarpathian_np_cave1", np_cave1);
settings->getNoiseParams("mgcarpathian_np_cave2", np_cave2);
settings->getNoiseParams("mgcarpathian_np_cavern", np_cavern);
+ settings->getNoiseParams("mgcarpathian_np_dungeons", np_dungeons);
}
void MapgenCarpathianParams::writeParams(Settings *settings) const
{
settings->setFlagStr("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian, U32_MAX);
- settings->setFloat("mgcarpathian_base_level", base_level);
+
+ settings->setFloat("mgcarpathian_base_level", base_level);
+ settings->setFloat("mgcarpathian_river_width", river_width);
+ settings->setFloat("mgcarpathian_river_depth", river_depth);
+ settings->setFloat("mgcarpathian_valley_width", valley_width);
+
settings->setFloat("mgcarpathian_cave_width", cave_width);
settings->setS16("mgcarpathian_large_cave_depth", large_cave_depth);
settings->setS16("mgcarpathian_lava_depth", lava_depth);
@@ -184,10 +206,12 @@ void MapgenCarpathianParams::writeParams(Settings *settings) const
settings->setNoiseParams("mgcarpathian_np_hills", np_hills);
settings->setNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt);
settings->setNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt);
+ settings->setNoiseParams("mgcarpathian_np_rivers", np_rivers);
settings->setNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var);
settings->setNoiseParams("mgcarpathian_np_cave1", np_cave1);
settings->setNoiseParams("mgcarpathian_np_cave2", np_cave2);
settings->setNoiseParams("mgcarpathian_np_cavern", np_cavern);
+ settings->setNoiseParams("mgcarpathian_np_dungeons", np_dungeons);
}
@@ -307,64 +331,95 @@ void MapgenCarpathian::makeChunk(BlockMakeData *data)
int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p)
{
- s16 level_at_point = terrainLevelAtPoint(p.X, p.Y);
- if (level_at_point <= water_level || level_at_point > water_level + 32)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-
- return level_at_point;
-}
-
-
-float MapgenCarpathian::terrainLevelAtPoint(s16 x, s16 z)
-{
- float height1 = NoisePerlin2D(&noise_height1->np, x, z, seed);
- float height2 = NoisePerlin2D(&noise_height2->np, x, z, seed);
- float height3 = NoisePerlin2D(&noise_height3->np, x, z, seed);
- float height4 = NoisePerlin2D(&noise_height4->np, x, z, seed);
- float hter = NoisePerlin2D(&noise_hills_terrain->np, x, z, seed);
- float rter = NoisePerlin2D(&noise_ridge_terrain->np, x, z, seed);
- float ster = NoisePerlin2D(&noise_step_terrain->np, x, z, seed);
- float n_hills = NoisePerlin2D(&noise_hills->np, x, z, seed);
- float n_ridge_mnt = NoisePerlin2D(&noise_ridge_mnt->np, x, z, seed);
- float n_step_mnt = NoisePerlin2D(&noise_step_mnt->np, x, z, seed);
-
- int height = -MAX_MAP_GENERATION_LIMIT;
+ // If rivers are enabled, first check if in a river channel
+ if (spflags & MGCARPATHIAN_RIVERS) {
+ float river = std::fabs(NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed)) -
+ river_width;
+ if (river < 0.0f)
+ return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+ }
- for (s16 y = 1; y <= 30; y++) {
- float mnt_var = NoisePerlin3D(&noise_mnt_var->np, x, y, z, seed);
+ float height1 = NoisePerlin2D(&noise_height1->np, p.X, p.Y, seed);
+ float height2 = NoisePerlin2D(&noise_height2->np, p.X, p.Y, seed);
+ float height3 = NoisePerlin2D(&noise_height3->np, p.X, p.Y, seed);
+ float height4 = NoisePerlin2D(&noise_height4->np, p.X, p.Y, seed);
+
+ float hterabs = std::fabs(NoisePerlin2D(&noise_hills_terrain->np, p.X, p.Y, seed));
+ float n_hills = NoisePerlin2D(&noise_hills->np, p.X, p.Y, seed);
+ float hill_mnt = hterabs * hterabs * hterabs * n_hills * n_hills;
+
+ float rterabs = std::fabs(NoisePerlin2D(&noise_ridge_terrain->np, p.X, p.Y, seed));
+ float n_ridge_mnt = NoisePerlin2D(&noise_ridge_mnt->np, p.X, p.Y, seed);
+ float ridge_mnt = rterabs * rterabs * rterabs * (1.0f - std::fabs(n_ridge_mnt));
+
+ float sterabs = std::fabs(NoisePerlin2D(&noise_step_terrain->np, p.X, p.Y, seed));
+ float n_step_mnt = NoisePerlin2D(&noise_step_mnt->np, p.X, p.Y, seed);
+ float step_mnt = sterabs * sterabs * sterabs * getSteps(n_step_mnt);
+
+ float valley = 1.0f;
+ float river = 0.0f;
+
+ if ((spflags & MGCARPATHIAN_RIVERS) && node_max.Y >= water_level - 16) {
+ river = std::fabs(NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed)) - river_width;
+ if (river <= valley_width) {
+ // Within river valley
+ if (river < 0.0f) {
+ // River channel
+ valley = river;
+ } else {
+ // Valley slopes.
+ // 0 at river edge, 1 at valley edge.
+ float riversc = river / valley_width;
+ // Smoothstep
+ valley = riversc * riversc * (3.0f - 2.0f * riversc);
+ }
+ }
+ }
- // Gradient & shallow seabed
- s32 grad = (y < water_level) ? grad_wl + (water_level - y) * 3 : 1 - y;
+ bool solid_below = false;
+ u8 cons_non_solid = 0; // consecutive non-solid nodes
- // Hill/Mountain height (hilliness)
+ for (s16 y = water_level; y <= water_level + 32; y++) {
+ float mnt_var = NoisePerlin3D(&noise_mnt_var->np, p.X, y, p.Y, seed);
float hill1 = getLerp(height1, height2, mnt_var);
float hill2 = getLerp(height3, height4, mnt_var);
float hill3 = getLerp(height3, height2, mnt_var);
float hill4 = getLerp(height1, height4, mnt_var);
- float hilliness =
- std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4));
-
- // Rolling hills
- float hill_mnt = hilliness * std::pow(n_hills, 2.f);
- float hills = std::pow(std::fabs(hter), 3.f) * hill_mnt;
- // Ridged mountains
- float ridge_mnt = hilliness * (1.f - std::fabs(n_ridge_mnt));
- float ridged_mountains = std::pow(std::fabs(rter), 3.f) * ridge_mnt;
+ float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4));
+ float hills = hill_mnt * hilliness;
+ float ridged_mountains = ridge_mnt * hilliness;
+ float step_mountains = step_mnt * hilliness;
- // Step (terraced) mountains
- float step_mnt = hilliness * getSteps(n_step_mnt);
- float step_mountains = std::pow(std::fabs(ster), 3.f) * step_mnt;
+ s32 grad = 1 - y;
- // Final terrain level
float mountains = hills + ridged_mountains + step_mountains;
float surface_level = base_level + mountains + grad;
- if (y > surface_level && height < 0)
- height = y;
+ if ((spflags & MGCARPATHIAN_RIVERS) && river <= valley_width) {
+ if (valley < 0.0f) {
+ // River channel
+ surface_level = std::fmin(surface_level,
+ water_level - std::sqrt(-valley) * river_depth);
+ } else if (surface_level > water_level) {
+ // Valley slopes
+ surface_level = water_level + (surface_level - water_level) * valley;
+ }
+ }
+
+ if (y < surface_level) { //TODO '<=' fix from generateTerrain()
+ // solid node
+ solid_below = true;
+ cons_non_solid = 0;
+ } else {
+ // non-solid node
+ cons_non_solid++;
+ if (cons_non_solid == 3 && solid_below)
+ return y - 1;
+ }
}
- return height;
+ return MAX_MAP_GENERATION_LIMIT; // No suitable spawn point found
}
@@ -390,6 +445,9 @@ int MapgenCarpathian::generateTerrain()
noise_step_mnt->perlinMap2D(node_min.X, node_min.Z);
noise_mnt_var->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+ if (spflags & MGCARPATHIAN_RIVERS)
+ noise_rivers->perlinMap2D(node_min.X, node_min.Z);
+
//// Place nodes
const v3s16 &em = vm->m_area.getExtent();
s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
@@ -412,13 +470,34 @@ int MapgenCarpathian::generateTerrain()
float rterabs = std::fabs(noise_ridge_terrain->result[index2d]);
float n_ridge_mnt = noise_ridge_mnt->result[index2d];
float ridge_mnt = rterabs * rterabs * rterabs *
- (1.f - std::fabs(n_ridge_mnt));
+ (1.0f - std::fabs(n_ridge_mnt));
// Step (terraced) mountains
float sterabs = std::fabs(noise_step_terrain->result[index2d]);
float n_step_mnt = noise_step_mnt->result[index2d];
float step_mnt = sterabs * sterabs * sterabs * getSteps(n_step_mnt);
+ // Rivers
+ float valley = 1.0f;
+ float river = 0.0f;
+
+ if ((spflags & MGCARPATHIAN_RIVERS) && node_max.Y >= water_level - 16) {
+ river = std::fabs(noise_rivers->result[index2d]) - river_width;
+ if (river <= valley_width) {
+ // Within river valley
+ if (river < 0.0f) {
+ // River channel
+ valley = river;
+ } else {
+ // Valley slopes.
+ // 0 at river edge, 1 at valley edge.
+ float riversc = river / valley_width;
+ // Smoothstep
+ valley = riversc * riversc * (3.0f - 2.0f * riversc);
+ }
+ }
+ }
+
// Initialise 3D noise index and voxelmanip index to column base
u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
u32 vi = vm->m_area.index(x, node_min.Y - 1, z);
@@ -453,7 +532,20 @@ int MapgenCarpathian::generateTerrain()
float mountains = hills + ridged_mountains + step_mountains;
float surface_level = base_level + mountains + grad;
- if (y < surface_level) {
+ // Rivers
+ if ((spflags & MGCARPATHIAN_RIVERS) && node_max.Y >= water_level - 16 &&
+ river <= valley_width) {
+ if (valley < 0.0f) {
+ // River channel
+ surface_level = std::fmin(surface_level,
+ water_level - std::sqrt(-valley) * river_depth);
+ } else if (surface_level > water_level) {
+ // Valley slopes
+ surface_level = water_level + (surface_level - water_level) * valley;
+ }
+ }
+
+ if (y < surface_level) { //TODO '<='
vm->m_data[vi] = mn_stone; // Stone
if (y > stone_surface_max_y)
stone_surface_max_y = y;
diff --git a/src/mapgen/mapgen_carpathian.h b/src/mapgen/mapgen_carpathian.h
index c32212c9a..1fbac4bfd 100644
--- a/src/mapgen/mapgen_carpathian.h
+++ b/src/mapgen/mapgen_carpathian.h
@@ -1,8 +1,7 @@
/*
Minetest
-Copyright (C) 2017-2018 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
-Copyright (C) 2010-2018 paramat
-Copyright (C) 2010-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2017-2019 vlapsley, Vaughan Lapsley <vlapsley@gmail.com>
+Copyright (C) 2017-2019 paramat
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
@@ -23,8 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapgen.h"
-///////// Mapgen Carpathian flags
#define MGCARPATHIAN_CAVERNS 0x01
+#define MGCARPATHIAN_RIVERS 0x02
class BiomeManager;
@@ -34,6 +33,9 @@ extern FlagDesc flagdesc_mapgen_carpathian[];
struct MapgenCarpathianParams : public MapgenParams
{
float base_level = 12.0f;
+ float river_width = 0.05f;
+ float river_depth = 24.0f;
+ float valley_width = 0.25f;
u32 spflags = MGCARPATHIAN_CAVERNS;
float cave_width = 0.09f;
@@ -56,10 +58,12 @@ struct MapgenCarpathianParams : public MapgenParams
NoiseParams np_hills;
NoiseParams np_ridge_mnt;
NoiseParams np_step_mnt;
+ NoiseParams np_rivers;
NoiseParams np_mnt_var;
NoiseParams np_cave1;
NoiseParams np_cave2;
NoiseParams np_cavern;
+ NoiseParams np_dungeons;
MapgenCarpathianParams();
~MapgenCarpathianParams() = default;
@@ -71,21 +75,19 @@ struct MapgenCarpathianParams : public MapgenParams
class MapgenCarpathian : public MapgenBasic
{
public:
- MapgenCarpathian(int mapgenid, MapgenCarpathianParams *params,
- EmergeManager *emerge);
+ MapgenCarpathian(MapgenCarpathianParams *params, EmergeManager *emerge);
~MapgenCarpathian();
virtual MapgenType getType() const { return MAPGEN_CARPATHIAN; }
- float getSteps(float noise);
- inline float getLerp(float noise1, float noise2, float mod);
-
virtual void makeChunk(BlockMakeData *data);
int getSpawnLevelAtPoint(v2s16 p);
private:
float base_level;
- s32 grad_wl;
+ float river_width;
+ float river_depth;
+ float valley_width;
s16 large_cave_depth;
s16 dungeon_ymin;
@@ -101,8 +103,12 @@ private:
Noise *noise_hills;
Noise *noise_ridge_mnt;
Noise *noise_step_mnt;
+ Noise *noise_rivers = nullptr;
Noise *noise_mnt_var;
- float terrainLevelAtPoint(s16 x, s16 z);
+ s32 grad_wl;
+
+ float getSteps(float noise);
+ inline float getLerp(float noise1, float noise2, float mod);
int generateTerrain();
};
diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp
index 11b27f85c..773b7b10f 100644
--- a/src/mapgen/mapgen_flat.cpp
+++ b/src/mapgen/mapgen_flat.cpp
@@ -48,8 +48,8 @@ FlagDesc flagdesc_mapgen_flat[] = {
///////////////////////////////////////////////////////////////////////////////////////
-MapgenFlat::MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
+MapgenFlat::MapgenFlat(MapgenFlatParams *params, EmergeManager *emerge)
+ : MapgenBasic(MAPGEN_FLAT, params, emerge)
{
spflags = params->spflags;
ground_level = params->ground_level;
@@ -69,8 +69,9 @@ MapgenFlat::MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *em
if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
noise_terrain = new Noise(&params->np_terrain, seed, csize.X, csize.Z);
// 3D noise
- MapgenBasic::np_cave1 = params->np_cave1;
- MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_dungeons = params->np_dungeons;
}
@@ -84,10 +85,11 @@ MapgenFlat::~MapgenFlat()
MapgenFlatParams::MapgenFlatParams():
- np_terrain (0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0),
- np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
- np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
- np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
+ np_terrain (0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0),
+ np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
+ np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0)
{
}
@@ -110,6 +112,7 @@ void MapgenFlatParams::readParams(const Settings *settings)
settings->getNoiseParams("mgflat_np_filler_depth", np_filler_depth);
settings->getNoiseParams("mgflat_np_cave1", np_cave1);
settings->getNoiseParams("mgflat_np_cave2", np_cave2);
+ settings->getNoiseParams("mgflat_np_dungeons", np_dungeons);
}
@@ -131,6 +134,7 @@ void MapgenFlatParams::writeParams(Settings *settings) const
settings->setNoiseParams("mgflat_np_filler_depth", np_filler_depth);
settings->setNoiseParams("mgflat_np_cave1", np_cave1);
settings->setNoiseParams("mgflat_np_cave2", np_cave2);
+ settings->setNoiseParams("mgflat_np_dungeons", np_dungeons);
}
@@ -139,26 +143,31 @@ void MapgenFlatParams::writeParams(Settings *settings) const
int MapgenFlat::getSpawnLevelAtPoint(v2s16 p)
{
- s16 level_at_point = ground_level;
- float n_terrain = 0.0f;
- if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS))
- n_terrain = NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed);
+ s16 stone_level = ground_level;
+ float n_terrain =
+ ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) ?
+ NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed) :
+ 0.0f;
if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) {
- level_at_point = ground_level -
- (lake_threshold - n_terrain) * lake_steepness;
+ s16 depress = (lake_threshold - n_terrain) * lake_steepness;
+ stone_level = ground_level - depress;
} else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) {
- level_at_point = ground_level +
- (n_terrain - hill_threshold) * hill_steepness;
+ s16 rise = (n_terrain - hill_threshold) * hill_steepness;
+ stone_level = ground_level + rise;
}
- if (ground_level < water_level) // Ocean world, allow spawn in water
- return MYMAX(level_at_point, water_level);
+ if (ground_level < water_level)
+ // Ocean world, may not have islands so allow spawn in water
+ return MYMAX(stone_level + 2, water_level);
- if (level_at_point > water_level)
- return level_at_point; // Spawn on land
+ if (stone_level >= water_level)
+ // Spawn on land
+ // + 2 not + 1, to spawn above biome 'dust' nodes
+ return stone_level + 2;
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
+ // Unsuitable spawn point
+ return MAX_MAP_GENERATION_LIMIT;
}
diff --git a/src/mapgen/mapgen_flat.h b/src/mapgen/mapgen_flat.h
index d8ec9f126..d2598695f 100644
--- a/src/mapgen/mapgen_flat.h
+++ b/src/mapgen/mapgen_flat.h
@@ -48,6 +48,7 @@ struct MapgenFlatParams : public MapgenParams
NoiseParams np_filler_depth;
NoiseParams np_cave1;
NoiseParams np_cave2;
+ NoiseParams np_dungeons;
MapgenFlatParams();
~MapgenFlatParams() = default;
@@ -59,7 +60,7 @@ struct MapgenFlatParams : public MapgenParams
class MapgenFlat : public MapgenBasic
{
public:
- MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge);
+ MapgenFlat(MapgenFlatParams *params, EmergeManager *emerge);
~MapgenFlat();
virtual MapgenType getType() const { return MAPGEN_FLAT; }
diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp
index 68a66bba9..091dbacfa 100644
--- a/src/mapgen/mapgen_fractal.cpp
+++ b/src/mapgen/mapgen_fractal.cpp
@@ -1,7 +1,7 @@
/*
Minetest
-Copyright (C) 2015-2018 paramat
-Copyright (C) 2015-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2019 paramat
+Copyright (C) 2015-2016 kwolekr, Ryan Kwolek
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
@@ -41,14 +41,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
FlagDesc flagdesc_mapgen_fractal[] = {
- {NULL, 0}
+ {"terrain", MGFRACTAL_TERRAIN},
+ {NULL, 0}
};
///////////////////////////////////////////////////////////////////////////////////////
-MapgenFractal::MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
+MapgenFractal::MapgenFractal(MapgenFractalParams *params, EmergeManager *emerge)
+ : MapgenBasic(MAPGEN_FRACTAL, params, emerge)
{
spflags = params->spflags;
cave_width = params->cave_width;
@@ -66,12 +67,17 @@ MapgenFractal::MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeMa
julia_z = params->julia_z;
julia_w = params->julia_w;
- //// 2D terrain noise
- noise_seabed = new Noise(&params->np_seabed, seed, csize.X, csize.Z);
+ //// 2D noise
+ if (spflags & MGFRACTAL_TERRAIN)
+ noise_seabed = new Noise(&params->np_seabed, seed, csize.X, csize.Z);
+
noise_filler_depth = new Noise(&params->np_filler_depth, seed, csize.X, csize.Z);
- MapgenBasic::np_cave1 = params->np_cave1;
- MapgenBasic::np_cave2 = params->np_cave2;
+ //// 3D noise
+ MapgenBasic::np_dungeons = params->np_dungeons;
+ // Overgeneration to node_min.Y - 1
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
formula = fractal / 2 + fractal % 2;
julia = fractal % 2 == 0;
@@ -80,7 +86,9 @@ MapgenFractal::MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeMa
MapgenFractal::~MapgenFractal()
{
- delete noise_seabed;
+ if (noise_seabed)
+ delete noise_seabed;
+
delete noise_filler_depth;
}
@@ -89,7 +97,8 @@ MapgenFractalParams::MapgenFractalParams():
np_seabed (-14, 9, v3f(600, 600, 600), 41900, 5, 0.6, 2.0),
np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0),
np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
- np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0)
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
+ np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0)
{
}
@@ -116,6 +125,7 @@ void MapgenFractalParams::readParams(const Settings *settings)
settings->getNoiseParams("mgfractal_np_filler_depth", np_filler_depth);
settings->getNoiseParams("mgfractal_np_cave1", np_cave1);
settings->getNoiseParams("mgfractal_np_cave2", np_cave2);
+ settings->getNoiseParams("mgfractal_np_dungeons", np_dungeons);
}
@@ -141,6 +151,7 @@ void MapgenFractalParams::writeParams(Settings *settings) const
settings->setNoiseParams("mgfractal_np_filler_depth", np_filler_depth);
settings->setNoiseParams("mgfractal_np_cave1", np_cave1);
settings->setNoiseParams("mgfractal_np_cave2", np_cave2);
+ settings->setNoiseParams("mgfractal_np_dungeons", np_dungeons);
}
@@ -149,21 +160,25 @@ void MapgenFractalParams::writeParams(Settings *settings) const
int MapgenFractal::getSpawnLevelAtPoint(v2s16 p)
{
- bool solid_below = false; // Dry solid node is present below to spawn on
- u8 air_count = 0; // Consecutive air nodes above the dry solid node
- s16 seabed_level = NoisePerlin2D(&noise_seabed->np, p.X, p.Y, seed);
- // Seabed can rise above water_level or might be raised to create dry land
- s16 search_start = MYMAX(seabed_level, water_level + 1);
- if (seabed_level > water_level)
- solid_below = true;
-
- for (s16 y = search_start; y <= search_start + 128; y++) {
- if (getFractalAtPoint(p.X, y, p.Y)) { // Fractal node
+ bool solid_below = false; // Fractal node is present below to spawn on
+ u8 air_count = 0; // Consecutive air nodes above a fractal node
+ s16 search_start = 0; // No terrain search start
+
+ // If terrain present, don't start search below terrain or water level
+ if (noise_seabed) {
+ s16 seabed_level = NoisePerlin2D(&noise_seabed->np, p.X, p.Y, seed);
+ search_start = MYMAX(search_start, MYMAX(seabed_level, water_level));
+ }
+
+ for (s16 y = search_start; y <= search_start + 4096; y++) {
+ if (getFractalAtPoint(p.X, y, p.Y)) {
+ // Fractal node
solid_below = true;
air_count = 0;
- } else if (solid_below) { // Air above solid node
+ } else if (solid_below) {
+ // Air above fractal node
air_count++;
- // 3 to account for snowblock dust
+ // 3 and -2 to account for biome dust nodes
if (air_count == 3)
return y - 2;
}
@@ -185,10 +200,11 @@ void MapgenFractal::makeChunk(BlockMakeData *data)
data->blockpos_requested.Y <= data->blockpos_max.Y &&
data->blockpos_requested.Z <= data->blockpos_max.Z);
+ //TimeTaker t("makeChunk");
+
this->generating = true;
- this->vm = data->vmanip;
+ this->vm = data->vmanip;
this->ndef = data->nodedef;
- //TimeTaker t("makeChunk");
v3s16 blockpos_min = data->blockpos_min;
v3s16 blockpos_max = data->blockpos_max;
@@ -199,7 +215,7 @@ void MapgenFractal::makeChunk(BlockMakeData *data)
blockseed = getBlockSeed2(full_node_min, seed);
- // Generate base terrain, mountains, and ridges with initial heightmaps
+ // Generate fractal and optional terrain
s16 stone_surface_max_y = generateTerrain();
// Create heightmap
@@ -211,16 +227,16 @@ void MapgenFractal::makeChunk(BlockMakeData *data)
generateBiomes();
}
+ // Generate tunnels and randomwalk caves
if (flags & MG_CAVES) {
- // Generate tunnels
generateCavesNoiseIntersection(stone_surface_max_y);
- // Generate large randomwalk caves
generateCavesRandomWalk(stone_surface_max_y, large_cave_depth);
}
// Generate the registered ores
m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
+ // Generate dungeons
if ((flags & MG_DUNGEONS) && full_node_min.Y >= dungeon_ymin &&
full_node_max.Y <= dungeon_ymax)
generateDungeons(stone_surface_max_y);
@@ -233,18 +249,18 @@ void MapgenFractal::makeChunk(BlockMakeData *data)
if (flags & MG_BIOMES)
dustTopNodes();
- //printf("makeChunk: %dms\n", t.stop());
-
- updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+ // Update liquids
+ if (spflags & MGFRACTAL_TERRAIN)
+ updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
+ // Calculate lighting
if (flags & MG_LIGHT)
calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
full_node_min, full_node_max);
- //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE,
- // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF);
-
this->generating = false;
+
+ //printf("makeChunk: %lums\n", t.stop());
}
@@ -387,24 +403,29 @@ s16 MapgenFractal::generateTerrain()
s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
u32 index2d = 0;
- noise_seabed->perlinMap2D(node_min.X, node_min.Z);
+ if (noise_seabed)
+ noise_seabed->perlinMap2D(node_min.X, node_min.Z);
for (s16 z = node_min.Z; z <= node_max.Z; z++) {
for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
u32 vi = vm->m_area.index(node_min.X, y, z);
for (s16 x = node_min.X; x <= node_max.X; x++, vi++, index2d++) {
- if (vm->m_data[vi].getContent() == CONTENT_IGNORE) {
- s16 seabed_height = noise_seabed->result[index2d];
-
- if (y <= seabed_height || getFractalAtPoint(x, y, z)) {
- vm->m_data[vi] = n_stone;
- if (y > stone_surface_max_y)
- stone_surface_max_y = y;
- } else if (y <= water_level) {
- vm->m_data[vi] = n_water;
- } else {
- vm->m_data[vi] = n_air;
- }
+ if (vm->m_data[vi].getContent() != CONTENT_IGNORE)
+ continue;
+
+ s16 seabed_height = -MAX_MAP_GENERATION_LIMIT;
+ if (noise_seabed)
+ seabed_height = noise_seabed->result[index2d];
+
+ if (((spflags & MGFRACTAL_TERRAIN) && y <= seabed_height) ||
+ getFractalAtPoint(x, y, z)) {
+ vm->m_data[vi] = n_stone;
+ if (y > stone_surface_max_y)
+ stone_surface_max_y = y;
+ } else if ((spflags & MGFRACTAL_TERRAIN) && y <= water_level) {
+ vm->m_data[vi] = n_water;
+ } else {
+ vm->m_data[vi] = n_air;
}
}
index2d -= ystride;
diff --git a/src/mapgen/mapgen_fractal.h b/src/mapgen/mapgen_fractal.h
index 5a1948b9a..82622d4d9 100644
--- a/src/mapgen/mapgen_fractal.h
+++ b/src/mapgen/mapgen_fractal.h
@@ -1,7 +1,7 @@
/*
Minetest
-Copyright (C) 2015-2018 paramat
-Copyright (C) 2015-2018 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
+Copyright (C) 2015-2019 paramat
+Copyright (C) 2015-2016 kwolekr, Ryan Kwolek
Fractal formulas from http://www.bugman123.com/Hypercomplex/index.html
by Paul Nylander, and from http://www.fractalforums.com, thank you.
@@ -25,13 +25,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapgen.h"
+///////////// Mapgen Fractal flags
+#define MGFRACTAL_TERRAIN 0x01
+
class BiomeManager;
extern FlagDesc flagdesc_mapgen_fractal[];
+
struct MapgenFractalParams : public MapgenParams
{
- u32 spflags = 0;
+ u32 spflags = MGFRACTAL_TERRAIN;
float cave_width = 0.09f;
s16 large_cave_depth = -33;
s16 lava_depth = -256;
@@ -51,6 +55,7 @@ struct MapgenFractalParams : public MapgenParams
NoiseParams np_filler_depth;
NoiseParams np_cave1;
NoiseParams np_cave2;
+ NoiseParams np_dungeons;
MapgenFractalParams();
~MapgenFractalParams() = default;
@@ -59,10 +64,11 @@ struct MapgenFractalParams : public MapgenParams
void writeParams(Settings *settings) const;
};
+
class MapgenFractal : public MapgenBasic
{
public:
- MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge);
+ MapgenFractal(MapgenFractalParams *params, EmergeManager *emerge);
~MapgenFractal();
virtual MapgenType getType() const { return MAPGEN_FRACTAL; }
@@ -88,5 +94,5 @@ private:
float julia_y;
float julia_z;
float julia_w;
- Noise *noise_seabed;
+ Noise *noise_seabed = nullptr;
};
diff --git a/src/mapgen/mapgen_singlenode.cpp b/src/mapgen/mapgen_singlenode.cpp
index 76f7e6e8e..b64524e1c 100644
--- a/src/mapgen/mapgen_singlenode.cpp
+++ b/src/mapgen/mapgen_singlenode.cpp
@@ -29,9 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "emerge.h"
-MapgenSinglenode::MapgenSinglenode(int mapgenid,
- MapgenParams *params, EmergeManager *emerge)
- : Mapgen(mapgenid, params, emerge)
+MapgenSinglenode::MapgenSinglenode(MapgenParams *params, EmergeManager *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 ebfb3c729..c21089eda 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(int mapgenid, MapgenParams *params, EmergeManager *emerge);
+ MapgenSinglenode(MapgenParams *params, EmergeManager *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 c5be727b9..bf99fd335 100644
--- a/src/mapgen/mapgen_v5.cpp
+++ b/src/mapgen/mapgen_v5.cpp
@@ -45,8 +45,8 @@ FlagDesc flagdesc_mapgen_v5[] = {
};
-MapgenV5::MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
+MapgenV5::MapgenV5(MapgenV5Params *params, EmergeManager *emerge)
+ : MapgenBasic(MAPGEN_V5, params, emerge)
{
spflags = params->spflags;
cave_width = params->cave_width;
@@ -67,9 +67,10 @@ MapgenV5::MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge)
// 1-up 1-down overgeneration
noise_ground = new Noise(&params->np_ground, seed, csize.X, csize.Y + 2, csize.Z);
// 1 down overgeneration
- MapgenBasic::np_cave1 = params->np_cave1;
- MapgenBasic::np_cave2 = params->np_cave2;
- MapgenBasic::np_cavern = params->np_cavern;
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_cavern = params->np_cavern;
+ MapgenBasic::np_dungeons = params->np_dungeons;
}
@@ -83,13 +84,14 @@ MapgenV5::~MapgenV5()
MapgenV5Params::MapgenV5Params():
- np_filler_depth (0, 1, v3f(150, 150, 150), 261, 4, 0.7, 2.0),
- np_factor (0, 1, v3f(250, 250, 250), 920381, 3, 0.45, 2.0),
- np_height (0, 10, v3f(250, 250, 250), 84174, 4, 0.5, 2.0),
- np_ground (0, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED),
- np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
- np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
- np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0)
+ np_filler_depth (0, 1, v3f(150, 150, 150), 261, 4, 0.7, 2.0),
+ np_factor (0, 1, v3f(250, 250, 250), 920381, 3, 0.45, 2.0),
+ np_height (0, 10, v3f(250, 250, 250), 84174, 4, 0.5, 2.0),
+ np_ground (0, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED),
+ np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0),
+ np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
+ np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0),
+ np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0)
{
}
@@ -113,6 +115,7 @@ void MapgenV5Params::readParams(const Settings *settings)
settings->getNoiseParams("mgv5_np_cave1", np_cave1);
settings->getNoiseParams("mgv5_np_cave2", np_cave2);
settings->getNoiseParams("mgv5_np_cavern", np_cavern);
+ settings->getNoiseParams("mgv5_np_dungeons", np_dungeons);
}
@@ -135,6 +138,7 @@ void MapgenV5Params::writeParams(Settings *settings) const
settings->setNoiseParams("mgv5_np_cave1", np_cave1);
settings->setNoiseParams("mgv5_np_cave2", np_cave2);
settings->setNoiseParams("mgv5_np_cavern", np_cavern);
+ settings->setNoiseParams("mgv5_np_dungeons", np_dungeons);
}
diff --git a/src/mapgen/mapgen_v5.h b/src/mapgen/mapgen_v5.h
index a1b56a070..1a3b6d3c3 100644
--- a/src/mapgen/mapgen_v5.h
+++ b/src/mapgen/mapgen_v5.h
@@ -48,6 +48,7 @@ struct MapgenV5Params : public MapgenParams
NoiseParams np_cave1;
NoiseParams np_cave2;
NoiseParams np_cavern;
+ NoiseParams np_dungeons;
MapgenV5Params();
~MapgenV5Params() = default;
@@ -59,7 +60,7 @@ struct MapgenV5Params : public MapgenParams
class MapgenV5 : public MapgenBasic
{
public:
- MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge);
+ MapgenV5(MapgenV5Params *params, EmergeManager *emerge);
~MapgenV5();
virtual MapgenType getType() const { return MAPGEN_V5; }
diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp
index fdfebe575..4e876fc53 100644
--- a/src/mapgen/mapgen_v6.cpp
+++ b/src/mapgen/mapgen_v6.cpp
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
+#include <cmath>
#include "mapgen.h"
#include "voxel.h"
#include "noise.h"
@@ -55,11 +56,11 @@ FlagDesc flagdesc_mapgen_v6[] = {
/////////////////////////////////////////////////////////////////////////////
-MapgenV6::MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge)
- : Mapgen(mapgenid, params, emerge)
+MapgenV6::MapgenV6(MapgenV6Params *params, EmergeManager *emerge)
+ : Mapgen(MAPGEN_V6, params, emerge)
{
m_emerge = emerge;
- ystride = csize.X; //////fix this
+ ystride = csize.X;
heightmap = new s16[csize.X * csize.Z];
@@ -74,6 +75,8 @@ MapgenV6::MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge)
np_trees = &params->np_trees;
np_apple_trees = &params->np_apple_trees;
+ np_dungeons = NoiseParams(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0);
+
//// Create noise objects
noise_terrain_base = new Noise(&params->np_terrain_base, seed, csize.X, csize.Y);
noise_terrain_higher = new Noise(&params->np_terrain_higher, seed, csize.X, csize.Y);
@@ -535,7 +538,7 @@ void MapgenV6::makeChunk(BlockMakeData *data)
updateHeightmap(node_min, node_max);
const s16 max_spread_amount = MAP_BLOCKSIZE;
- // Limit dirt flow area by 1 because mud is flown into neighbors.
+ // Limit dirt flow area by 1 because mud is flowed into neighbors
s16 mudflow_minpos = -max_spread_amount + 1;
s16 mudflow_maxpos = central_area_size.X + max_spread_amount - 2;
@@ -561,48 +564,54 @@ void MapgenV6::makeChunk(BlockMakeData *data)
// Add dungeons
if ((flags & MG_DUNGEONS) && stone_surface_max_y >= node_min.Y &&
full_node_min.Y >= dungeon_ymin && full_node_max.Y <= dungeon_ymax) {
- DungeonParams dp;
-
- dp.seed = seed;
- dp.only_in_ground = true;
- dp.corridor_len_min = 1;
- dp.corridor_len_max = 13;
- dp.rooms_min = 2;
- dp.rooms_max = 16;
-
- dp.np_density
- = NoiseParams(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0);
- dp.np_alt_wall
- = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
-
- if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) {
- dp.c_wall = c_desert_stone;
- dp.c_alt_wall = CONTENT_IGNORE;
- dp.c_stair = c_stair_desert_stone;
-
- dp.diagonal_dirs = true;
- dp.holesize = v3s16(2, 3, 2);
- dp.room_size_min = v3s16(6, 9, 6);
- dp.room_size_max = v3s16(10, 11, 10);
- dp.room_size_large_min = v3s16(10, 13, 10);
- dp.room_size_large_max = v3s16(18, 21, 18);
- dp.notifytype = GENNOTIFY_TEMPLE;
- } else {
- dp.c_wall = c_cobble;
- dp.c_alt_wall = c_mossycobble;
- dp.c_stair = c_stair_cobble;
-
- dp.diagonal_dirs = false;
- dp.holesize = v3s16(1, 2, 1);
- dp.room_size_min = v3s16(4, 4, 4);
- dp.room_size_max = v3s16(8, 6, 8);
- dp.room_size_large_min = v3s16(8, 8, 8);
- dp.room_size_large_max = v3s16(16, 16, 16);
- dp.notifytype = GENNOTIFY_DUNGEON;
- }
+ u16 num_dungeons = std::fmax(std::floor(
+ NoisePerlin3D(&np_dungeons, node_min.X, node_min.Y, node_min.Z, seed)), 0.0f);
+
+ if (num_dungeons >= 1) {
+ PseudoRandom ps(blockseed + 4713);
+
+ DungeonParams dp;
+
+ dp.seed = seed;
+ dp.num_dungeons = num_dungeons;
+ dp.only_in_ground = true;
+ dp.corridor_len_min = 1;
+ dp.corridor_len_max = 13;
+ dp.num_rooms = ps.range(2, 16);
+ dp.large_room_chance = (ps.range(1, 4) == 1) ? 1 : 0;
+
+ dp.np_alt_wall
+ = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0);
+
+ if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) {
+ dp.c_wall = c_desert_stone;
+ dp.c_alt_wall = CONTENT_IGNORE;
+ dp.c_stair = c_stair_desert_stone;
+
+ dp.diagonal_dirs = true;
+ dp.holesize = v3s16(2, 3, 2);
+ dp.room_size_min = v3s16(6, 9, 6);
+ dp.room_size_max = v3s16(10, 11, 10);
+ dp.room_size_large_min = v3s16(10, 13, 10);
+ dp.room_size_large_max = v3s16(18, 21, 18);
+ dp.notifytype = GENNOTIFY_TEMPLE;
+ } else {
+ dp.c_wall = c_cobble;
+ dp.c_alt_wall = c_mossycobble;
+ dp.c_stair = c_stair_cobble;
+
+ dp.diagonal_dirs = false;
+ dp.holesize = v3s16(1, 2, 1);
+ dp.room_size_min = v3s16(4, 4, 4);
+ dp.room_size_max = v3s16(8, 6, 8);
+ dp.room_size_large_min = v3s16(8, 8, 8);
+ dp.room_size_large_max = v3s16(16, 16, 16);
+ dp.notifytype = GENNOTIFY_DUNGEON;
+ }
- DungeonGen dgen(ndef, &gennotify, &dp);
- dgen.generate(vm, blockseed, full_node_min, full_node_max);
+ DungeonGen dgen(ndef, &gennotify, &dp);
+ dgen.generate(vm, blockseed, full_node_min, full_node_max);
+ }
}
// Add top and bottom side of water to transforming_liquid queue
@@ -764,128 +773,113 @@ void MapgenV6::addMud()
void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos)
{
- // 340ms @cs=8
- //TimeTaker timer1("flow mud");
-
- // Iterate a few times
- for (s16 k = 0; k < 3; k++) {
+ const v3s16 &em = vm->m_area.getExtent();
+ static const v3s16 dirs4[4] = {
+ v3s16(0, 0, 1), // Back
+ v3s16(1, 0, 0), // Right
+ v3s16(0, 0, -1), // Front
+ v3s16(-1, 0, 0), // Left
+ };
+
+ // Iterate twice
+ for (s16 k = 0; k < 2; k++) {
for (s16 z = mudflow_minpos; z <= mudflow_maxpos; z++)
for (s16 x = mudflow_minpos; x <= mudflow_maxpos; x++) {
- // Invert coordinates every 2nd iteration
- if (k % 2 == 0) {
- x = mudflow_maxpos - (x - mudflow_minpos);
- z = mudflow_maxpos - (z - mudflow_minpos);
- }
+ // Node column position
+ v2s16 p2d;
+ // Invert coordinates on second iteration to process columns in
+ // opposite order, to avoid a directional bias.
+ if (k == 1)
+ p2d = v2s16(node_max.X, node_max.Z) - v2s16(x, z);
+ else
+ p2d = v2s16(node_min.X, node_min.Z) + v2s16(x, z);
- // Node position in 2d
- v2s16 p2d = v2s16(node_min.X, node_min.Z) + v2s16(x, z);
-
- const v3s16 &em = vm->m_area.getExtent();
- u32 i = vm->m_area.index(p2d.X, node_max.Y, p2d.Y);
s16 y = node_max.Y;
while (y >= node_min.Y) {
+ for (;; y--) {
+ u32 i = vm->m_area.index(p2d.X, y, p2d.Y);
+ MapNode *n = nullptr;
+
+ // Find next mud node in mapchunk column
+ for (; y >= node_min.Y; y--) {
+ n = &vm->m_data[i];
+ if (n->getContent() == c_dirt ||
+ n->getContent() == c_dirt_with_grass ||
+ n->getContent() == c_gravel)
+ break;
- for (;; y--) {
- MapNode *n = NULL;
- // Find mud
- for (; y >= node_min.Y; y--) {
- n = &vm->m_data[i];
- if (n->getContent() == c_dirt ||
- n->getContent() == c_dirt_with_grass ||
- n->getContent() == c_gravel)
+ VoxelArea::add_y(em, i, -1);
+ }
+ if (y < node_min.Y)
+ // No mud found in mapchunk column, process the next column
break;
- VoxelArea::add_y(em, i, -1);
- }
-
- // Stop if out of area
- //if(vmanip.m_area.contains(i) == false)
- if (y < node_min.Y)
- break;
-
- if (n->getContent() == c_dirt ||
- n->getContent() == c_dirt_with_grass) {
- // Make it exactly mud
- n->setContent(c_dirt);
-
- // Don't flow it if the stuff under it is not mud
- {
+ if (n->getContent() == c_dirt || n->getContent() == c_dirt_with_grass) {
+ // Convert dirt_with_grass to dirt
+ n->setContent(c_dirt);
+ // Don't flow mud if the stuff under it is not mud,
+ // to leave at least 1 node of mud.
u32 i2 = i;
VoxelArea::add_y(em, i2, -1);
- // Cancel if out of area
- if (!vm->m_area.contains(i2))
- continue;
MapNode *n2 = &vm->m_data[i2];
if (n2->getContent() != c_dirt &&
n2->getContent() != c_dirt_with_grass)
+ // Find next mud node in column
continue;
}
- }
- static const v3s16 dirs4[4] = {
- v3s16(0, 0, 1), // back
- v3s16(1, 0, 0), // right
- v3s16(0, 0, -1), // front
- v3s16(-1, 0, 0), // left
- };
-
- // Check that upper is walkable. Cancel
- // dropping if upper keeps it in place.
- u32 i3 = i;
- VoxelArea::add_y(em, i3, 1);
- MapNode *n3 = NULL;
-
- if (vm->m_area.contains(i3)) {
- n3 = &vm->m_data[i3];
+ // Check if node above is walkable. If so, cancel
+ // flowing as if node above keeps it in place.
+ u32 i3 = i;
+ VoxelArea::add_y(em, i3, 1);
+ MapNode *n3 = &vm->m_data[i3];
if (ndef->get(*n3).walkable)
+ // Find next mud node in column
continue;
- }
- // Drop mud on side
- for (const v3s16 &dirp : dirs4) {
- u32 i2 = i;
- // Move to side
- VoxelArea::add_p(em, i2, dirp);
- // Fail if out of area
- if (!vm->m_area.contains(i2))
- continue;
- // Check that side is air
- MapNode *n2 = &vm->m_data[i2];
- if (ndef->get(*n2).walkable)
- continue;
- // Check that under side is air
- VoxelArea::add_y(em, i2, -1);
- if (!vm->m_area.contains(i2))
- continue;
- n2 = &vm->m_data[i2];
- if (ndef->get(*n2).walkable)
- continue;
- // Loop further down until not air
- bool dropped_to_unknown = false;
- do {
+ // Drop mud on one side
+ for (const v3s16 &dirp : dirs4) {
+ u32 i2 = i;
+ // Move to side
+ VoxelArea::add_p(em, i2, dirp);
+ // Check that side is air
+ MapNode *n2 = &vm->m_data[i2];
+ if (ndef->get(*n2).walkable)
+ continue;
+
+ // Check that under side is air
VoxelArea::add_y(em, i2, -1);
n2 = &vm->m_data[i2];
- // if out of known area
- if (!vm->m_area.contains(i2) ||
- n2->getContent() == CONTENT_IGNORE) {
- dropped_to_unknown = true;
- break;
- }
- } while (!ndef->get(*n2).walkable);
- // Loop one up so that we're in air
- VoxelArea::add_y(em, i2, 1);
-
- // Move mud to new place. Outside mapchunk remove
- // any decorations above removed or placed mud.
- if (!dropped_to_unknown)
- moveMud(i, i2, i3, p2d, em);
+ if (ndef->get(*n2).walkable)
+ continue;
- // Done
- break;
+ // Loop further down until not air
+ s16 y2 = y - 1; // y of i2
+ bool dropped_to_unknown = false;
+ do {
+ y2--;
+ VoxelArea::add_y(em, i2, -1);
+ n2 = &vm->m_data[i2];
+ // If out of area or in ungenerated world
+ if (y2 < full_node_min.Y || n2->getContent() == CONTENT_IGNORE) {
+ dropped_to_unknown = true;
+ break;
+ }
+ } while (!ndef->get(*n2).walkable);
+
+ if (!dropped_to_unknown) {
+ // Move up one so that we're in air
+ VoxelArea::add_y(em, i2, 1);
+ // Move mud to new place, and if outside mapchunk remove
+ // any decorations above removed or placed mud.
+ moveMud(i, i2, i3, p2d, em);
+ }
+ // Done, find next mud node in column
+ break;
+ }
}
}
- }
}
}
}
diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h
index 056281f2f..7d5229559 100644
--- a/src/mapgen/mapgen_v6.h
+++ b/src/mapgen/mapgen_v6.h
@@ -108,6 +108,8 @@ public:
NoiseParams *np_trees;
NoiseParams *np_apple_trees;
+ NoiseParams np_dungeons;
+
float freq_desert;
float freq_beach;
s16 dungeon_ymin;
@@ -132,7 +134,7 @@ public:
content_t c_stair_cobble;
content_t c_stair_desert_stone;
- MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge);
+ MapgenV6(MapgenV6Params *params, EmergeManager *emerge);
~MapgenV6();
virtual MapgenType getType() const { return MAPGEN_V6; }
diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp
index 1354bf256..c9568760f 100644
--- a/src/mapgen/mapgen_v7.cpp
+++ b/src/mapgen/mapgen_v7.cpp
@@ -52,8 +52,8 @@ FlagDesc flagdesc_mapgen_v7[] = {
////////////////////////////////////////////////////////////////////////////////
-MapgenV7::MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
+MapgenV7::MapgenV7(MapgenV7Params *params, EmergeManager *emerge)
+ : MapgenBasic(MAPGEN_V7, params, emerge)
{
spflags = params->spflags;
mount_zero_level = params->mount_zero_level;
@@ -113,9 +113,10 @@ MapgenV7::MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge)
new Noise(&params->np_mountain, 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;
- MapgenBasic::np_cavern = params->np_cavern;
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_cavern = params->np_cavern;
+ MapgenBasic::np_dungeons = params->np_dungeons;
}
@@ -159,7 +160,8 @@ MapgenV7Params::MapgenV7Params():
np_ridge (0.0, 1.0, v3f(100, 100, 100), 6467, 4, 0.75, 2.0),
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)
+ np_cave2 (0.0, 12.0, v3f(67, 67, 67), 10325, 3, 0.5, 2.0),
+ np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0)
{
}
@@ -196,6 +198,7 @@ void MapgenV7Params::readParams(const Settings *settings)
settings->getNoiseParams("mgv7_np_cavern", np_cavern);
settings->getNoiseParams("mgv7_np_cave1", np_cave1);
settings->getNoiseParams("mgv7_np_cave2", np_cave2);
+ settings->getNoiseParams("mgv7_np_dungeons", np_dungeons);
}
@@ -231,6 +234,7 @@ void MapgenV7Params::writeParams(Settings *settings) const
settings->setNoiseParams("mgv7_np_cavern", np_cavern);
settings->setNoiseParams("mgv7_np_cave1", np_cave1);
settings->setNoiseParams("mgv7_np_cave2", np_cave2);
+ settings->setNoiseParams("mgv7_np_dungeons", np_dungeons);
}
diff --git a/src/mapgen/mapgen_v7.h b/src/mapgen/mapgen_v7.h
index b55c80d3a..50039b16a 100644
--- a/src/mapgen/mapgen_v7.h
+++ b/src/mapgen/mapgen_v7.h
@@ -66,6 +66,7 @@ struct MapgenV7Params : public MapgenParams {
NoiseParams np_cavern;
NoiseParams np_cave1;
NoiseParams np_cave2;
+ NoiseParams np_dungeons;
MapgenV7Params();
~MapgenV7Params() = default;
@@ -77,7 +78,7 @@ struct MapgenV7Params : public MapgenParams {
class MapgenV7 : public MapgenBasic {
public:
- MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge);
+ MapgenV7(MapgenV7Params *params, EmergeManager *emerge);
~MapgenV7();
virtual MapgenType getType() const { return MAPGEN_V7; }
diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp
index 5f9267875..d0b36f29b 100644
--- a/src/mapgen/mapgen_valleys.cpp
+++ b/src/mapgen/mapgen_valleys.cpp
@@ -1,7 +1,7 @@
/*
Minetest
-Copyright (C) 2016-2018 Duane Robertson <duane@duanerobertson.com>
-Copyright (C) 2016-2018 paramat
+Copyright (C) 2016-2019 Duane Robertson <duane@duanerobertson.com>
+Copyright (C) 2016-2019 paramat
Based on Valleys Mapgen by Gael de Sailly
(https://forum.minetest.net/viewtopic.php?f=9&t=11430)
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+
#include "mapgen.h"
#include "voxel.h"
#include "noise.h"
@@ -53,18 +54,12 @@ FlagDesc flagdesc_mapgen_valleys[] = {
};
-////////////////////////////////////////////////////////////////////////////////
-
-
-MapgenValleys::MapgenValleys(int mapgenid, MapgenValleysParams *params,
- EmergeManager *emerge)
- : MapgenBasic(mapgenid, params, emerge)
+MapgenValleys::MapgenValleys(MapgenValleysParams *params, EmergeManager *emerge)
+ : MapgenBasic(MAPGEN_VALLEYS, params, emerge)
{
// NOTE: MapgenValleys has a hard dependency on BiomeGenOriginal
m_bgen = (BiomeGenOriginal *)biomegen;
- BiomeParamsOriginal *bp = (BiomeParamsOriginal *)params->bparams;
-
spflags = params->spflags;
altitude_chill = params->altitude_chill;
river_depth_bed = params->river_depth + 1.0f;
@@ -92,11 +87,10 @@ MapgenValleys::MapgenValleys(int mapgenid, MapgenValleysParams *params,
noise_inter_valley_fill = new Noise(&params->np_inter_valley_fill,
seed, csize.X, csize.Y + 2, csize.Z);
// 1-down overgeneraion
- MapgenBasic::np_cave1 = params->np_cave1;
- MapgenBasic::np_cave2 = params->np_cave2;
- MapgenBasic::np_cavern = params->np_cavern;
-
- humidity_adjust = bp->np_humidity.offset - 50.0f;
+ MapgenBasic::np_cave1 = params->np_cave1;
+ MapgenBasic::np_cave2 = params->np_cave2;
+ MapgenBasic::np_cavern = params->np_cavern;
+ MapgenBasic::np_dungeons = params->np_dungeons;
}
@@ -122,7 +116,8 @@ MapgenValleysParams::MapgenValleysParams():
np_valley_profile (0.6, 0.50, v3f(512, 512, 512), 777, 1, 1.0, 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),
- np_cavern (0.0, 1.0, v3f(768, 256, 768), 59033, 6, 0.63, 2.0)
+ np_cavern (0.0, 1.0, v3f(768, 256, 768), 59033, 6, 0.63, 2.0),
+ np_dungeons (0.9, 0.5, v3f(500, 500, 500), 0, 2, 0.8, 2.0)
{
}
@@ -153,6 +148,7 @@ void MapgenValleysParams::readParams(const Settings *settings)
settings->getNoiseParams("mgvalleys_np_cave1", np_cave1);
settings->getNoiseParams("mgvalleys_np_cave2", np_cave2);
settings->getNoiseParams("mgvalleys_np_cavern", np_cavern);
+ settings->getNoiseParams("mgvalleys_np_dungeons", np_dungeons);
}
@@ -182,12 +178,10 @@ void MapgenValleysParams::writeParams(Settings *settings) const
settings->setNoiseParams("mgvalleys_np_cave1", np_cave1);
settings->setNoiseParams("mgvalleys_np_cave2", np_cave2);
settings->setNoiseParams("mgvalleys_np_cavern", np_cavern);
+ settings->setNoiseParams("mgvalleys_np_dungeons", np_dungeons);
}
-////////////////////////////////////////////////////////////////////////////////
-
-
void MapgenValleys::makeChunk(BlockMakeData *data)
{
// Pre-conditions
@@ -220,19 +214,16 @@ void MapgenValleys::makeChunk(BlockMakeData *data)
// biome-related noises.
m_bgen->calcBiomeNoise(node_min);
- // Generate noise maps and base terrain height.
- // Modify heat and humidity maps.
- calculateNoise();
-
- // Generate base terrain with initial heightmaps
+ // Generate terrain
s16 stone_surface_max_y = generateTerrain();
- // Recalculate heightmap
+ // Create heightmap
updateHeightmap(node_min, node_max);
// Place biome-specific nodes and build biomemap
- if (flags & MG_BIOMES)
+ if (flags & MG_BIOMES) {
generateBiomes();
+ }
// Generate tunnels, caverns and large randomwalk caves
if (flags & MG_CAVES) {
@@ -281,244 +272,151 @@ void MapgenValleys::makeChunk(BlockMakeData *data)
}
-void MapgenValleys::calculateNoise()
-{
- int x = node_min.X;
- int y = node_min.Y - 1;
- int z = node_min.Z;
-
- noise_inter_valley_slope->perlinMap2D(x, z);
- noise_rivers->perlinMap2D(x, z);
- noise_terrain_height->perlinMap2D(x, z);
- noise_valley_depth->perlinMap2D(x, z);
- noise_valley_profile->perlinMap2D(x, z);
-
- noise_inter_valley_fill->perlinMap3D(x, y, z);
-
- float heat_offset = 0.0f;
- float humidity_scale = 1.0f;
- // Altitude chill tends to reduce the average heat.
- if (spflags & MGVALLEYS_ALT_CHILL)
- heat_offset = 5.0f;
- // River humidity tends to increase the humidity range.
- if (spflags & MGVALLEYS_HUMID_RIVERS)
- humidity_scale = 0.8f;
-
- for (s32 index = 0; index < csize.X * csize.Z; index++) {
- m_bgen->heatmap[index] += heat_offset;
- m_bgen->humidmap[index] *= humidity_scale;
- }
-
- TerrainNoise tn;
-
- u32 index = 0;
- for (tn.z = node_min.Z; tn.z <= node_max.Z; tn.z++)
- for (tn.x = node_min.X; tn.x <= node_max.X; tn.x++, index++) {
- // The parameters that we actually need to generate terrain are passed
- // by address (and the return value).
- tn.terrain_height = noise_terrain_height->result[index];
- // River noise is replaced with base terrain, which is basically the
- // height of the water table.
- tn.rivers = &noise_rivers->result[index];
- // Valley depth noise is replaced with the valley number that represents
- // the height of terrain over rivers and is used to determine how close
- // a river is for humidity calculation.
- tn.valley = &noise_valley_depth->result[index];
- tn.valley_profile = noise_valley_profile->result[index];
- // Slope noise is replaced by the calculated slope which is used to get
- // terrain height in the slow method, to create sharper mountains.
- tn.slope = &noise_inter_valley_slope->result[index];
- tn.inter_valley_fill = noise_inter_valley_fill->result[index];
-
- // This is the actual terrain height.
- float mount = terrainLevelFromNoise(&tn);
- noise_terrain_height->result[index] = mount;
- }
-}
-
-
-float MapgenValleys::terrainLevelFromNoise(TerrainNoise *tn)
-{
- // The square function changes the behaviour of this noise: very often
- // small, and sometimes very high.
- float valley_d = MYSQUARE(*tn->valley);
-
- // valley_d is here because terrain is generally higher where valleys are
- // deep (mountains). base represents the height of the rivers, most of the
- // surface is above.
- float base = tn->terrain_height + valley_d;
-
- // "river" represents the distance from the river
- float river = std::fabs(*tn->rivers) - river_size_factor;
-
- // Use the curve of the function 1-exp(-(x/a)^2) to model valleys.
- // "valley" represents the height of the terrain, from the rivers.
- float tv = std::fmax(river / tn->valley_profile, 0.0f);
- *tn->valley = valley_d * (1.0f - std::exp(-MYSQUARE(tv)));
-
- // Approximate height of the terrain at this point
- float mount = base + *tn->valley;
-
- *tn->slope *= *tn->valley;
-
- // Base ground is returned as rivers since it's basically the water table.
- *tn->rivers = base;
-
- // Rivers are placed where "river" is negative, so where the original noise
- // value is close to zero.
- if (river < 0.0f) {
- // Use the the function -sqrt(1-x^2) which models a circle
- float tr = river / river_size_factor + 1.0f;
- float depth = (river_depth_bed *
- std::sqrt(std::fmax(0.0f, 1.0f - MYSQUARE(tr))));
-
- // base - depth : height of the bottom of the river
- // water_level - 3 : don't make rivers below 3 nodes under the surface.
- // We use three because that's as low as the swamp biomes go.
- // There is no logical equivalent to this using rangelim.
- mount =
- std::fmin(std::fmax(base - depth, (float)(water_level - 3)), mount);
-
- // Slope has no influence on rivers
- *tn->slope = 0.0f;
- }
-
- return mount;
-}
-
-
-// This avoids duplicating the code in terrainLevelFromNoise, adding only the
-// final step of terrain generation without a noise map.
-
-float MapgenValleys::adjustedTerrainLevelFromNoise(TerrainNoise *tn)
-{
- float mount = terrainLevelFromNoise(tn);
- float result = mount;
- s16 y_start = myround(mount);
- float fill =
- NoisePerlin3D(&noise_inter_valley_fill->np, tn->x, y_start, tn->z, seed);
- bool is_ground = fill * *tn->slope >= y_start - mount;
- s16 search_direction = is_ground ? 1 : -1;
-
- for (s16 i = 1; i <= 1000; i++) {
- s16 y = y_start + i * search_direction;
- fill =
- NoisePerlin3D(&noise_inter_valley_fill->np, tn->x, y, tn->z, seed);
-
- bool was_ground = is_ground;
- is_ground = fill * *tn->slope >= y - mount;
- if (is_ground)
- result = y;
- if (is_ground != was_ground)
- break;
- }
-
- return result;
-}
-
-
int MapgenValleys::getSpawnLevelAtPoint(v2s16 p)
{
- // Check if in a river
- float rivers = NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed);
- if (std::fabs(rivers) < river_size_factor)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-
- s16 level_at_point = terrainLevelAtPoint(p.X, p.Y);
- if (level_at_point <= water_level ||
- level_at_point > water_level + 16)
- return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point
-
- // +1 to account for biome dust that can be 1 node deep
- return level_at_point + 1;
-}
-
-
-float MapgenValleys::terrainLevelAtPoint(s16 x, s16 z)
-{
- TerrainNoise tn;
-
- float rivers = NoisePerlin2D(&noise_rivers->np, x, z, seed);
- float valley = NoisePerlin2D(&noise_valley_depth->np, x, z, seed);
- float inter_valley_slope =
- NoisePerlin2D(&noise_inter_valley_slope->np, x, z, seed);
-
- tn.x = x;
- tn.z = z;
- tn.terrain_height = NoisePerlin2D(&noise_terrain_height->np, x, z, seed);
- tn.rivers = &rivers;
- tn.valley = &valley;
- tn.valley_profile = NoisePerlin2D(&noise_valley_profile->np, x, z, seed);
- tn.slope = &inter_valley_slope;
- tn.inter_valley_fill = 0.0f;
-
- return adjustedTerrainLevelFromNoise(&tn);
+ // Check if in a river channel
+ float n_rivers = NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed);
+ if (std::fabs(n_rivers) <= river_size_factor)
+ // Unsuitable spawn point
+ return MAX_MAP_GENERATION_LIMIT;
+
+ float n_slope = NoisePerlin2D(&noise_inter_valley_slope->np, p.X, p.Y, seed);
+ float n_terrain_height = NoisePerlin2D(&noise_terrain_height->np, p.X, p.Y, seed);
+ float n_valley = NoisePerlin2D(&noise_valley_depth->np, p.X, p.Y, seed);
+ float n_valley_profile = NoisePerlin2D(&noise_valley_profile->np, p.X, p.Y, seed);
+
+ float valley_d = n_valley * n_valley;
+ float base = n_terrain_height + valley_d;
+ float river = std::fabs(n_rivers) - river_size_factor;
+ float tv = std::fmax(river / n_valley_profile, 0.0f);
+ float valley_h = valley_d * (1.0f - std::exp(-tv * tv));
+ float surface_y = base + valley_h;
+ float slope = n_slope * valley_h;
+ float river_y = base - 1.0f;
+
+ // Raising the maximum spawn level above 'water_level + 16' is necessary for custom
+ // parameters that set average terrain level much higher than water_level.
+ s16 max_spawn_y = std::fmax(
+ noise_terrain_height->np.offset +
+ noise_valley_depth->np.offset * noise_valley_depth->np.offset,
+ water_level + 16);
+
+ // Starting spawn search at max_spawn_y + 128 ensures 128 nodes of open
+ // space above spawn position. Avoids spawning in possibly sealed voids.
+ for (s16 y = max_spawn_y + 128; y >= water_level; y--) {
+ float n_fill = NoisePerlin3D(&noise_inter_valley_fill->np, p.X, y, p.Y, seed);
+ float surface_delta = (float)y - surface_y;
+ float density = slope * n_fill - surface_delta;
+
+ if (density > 0.0f) { // If solid
+ // Sometimes surface level is below river water level in places that are not
+ // river channels.
+ if (y < water_level || y > max_spawn_y || y < (s16)river_y)
+ // Unsuitable spawn point
+ return MAX_MAP_GENERATION_LIMIT;
+
+ // y + 2 because y is surface and due to biome 'dust' nodes.
+ return y + 2;
+ }
+ }
+ // Unsuitable spawn position, no ground found
+ return MAX_MAP_GENERATION_LIMIT;
}
int MapgenValleys::generateTerrain()
{
- // Raising this reduces the rate of evaporation
- static const float evaporation = 300.0f;
- static const float humidity_dropoff = 4.0f;
- // Constant to convert altitude chill to heat
- static const float alt_to_heat = 20.0f;
- // Humidity reduction by altitude
- static const float alt_to_humid = 10.0f;
-
MapNode n_air(CONTENT_AIR);
MapNode n_river_water(c_river_water_source);
MapNode n_stone(c_stone);
MapNode n_water(c_water_source);
+ noise_inter_valley_slope->perlinMap2D(node_min.X, node_min.Z);
+ noise_rivers->perlinMap2D(node_min.X, node_min.Z);
+ noise_terrain_height->perlinMap2D(node_min.X, node_min.Z);
+ noise_valley_depth->perlinMap2D(node_min.X, node_min.Z);
+ noise_valley_profile->perlinMap2D(node_min.X, node_min.Z);
+
+ noise_inter_valley_fill->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
+
const v3s16 &em = vm->m_area.getExtent();
s16 surface_max_y = -MAX_MAP_GENERATION_LIMIT;
u32 index_2d = 0;
for (s16 z = node_min.Z; z <= node_max.Z; z++)
for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) {
- float river_y = noise_rivers->result[index_2d];
- float surface_y = noise_terrain_height->result[index_2d];
- float slope = noise_inter_valley_slope->result[index_2d];
- float t_heat = m_bgen->heatmap[index_2d];
-
- heightmap[index_2d] = -MAX_MAP_GENERATION_LIMIT;
-
- if (surface_y > surface_max_y)
- surface_max_y = std::ceil(surface_y);
+ float n_slope = noise_inter_valley_slope->result[index_2d];
+ float n_rivers = noise_rivers->result[index_2d];
+ float n_terrain_height = noise_terrain_height->result[index_2d];
+ float n_valley = noise_valley_depth->result[index_2d];
+ float n_valley_profile = noise_valley_profile->result[index_2d];
+
+ float valley_d = n_valley * n_valley;
+ // 'base' represents the level of the river banks
+ float base = n_terrain_height + valley_d;
+ // 'river' represents the distance from the river edge
+ float river = std::fabs(n_rivers) - river_size_factor;
+ // Use the curve of the function 1-exp(-(x/a)^2) to model valleys.
+ // 'valley_h' represents the height of the terrain, from the rivers.
+ float tv = std::fmax(river / n_valley_profile, 0.0f);
+ float valley_h = valley_d * (1.0f - std::exp(-tv * tv));
+ // Approximate height of the terrain
+ float surface_y = base + valley_h;
+ float slope = n_slope * valley_h;
+ // River water surface is 1 node below river banks
+ float river_y = base - 1.0f;
+
+ // Rivers are placed where 'river' is negative
+ if (river < 0.0f) {
+ // Use the the function -sqrt(1-x^2) which models a circle
+ float tr = river / river_size_factor + 1.0f;
+ float depth = (river_depth_bed *
+ std::sqrt(std::fmax(0.0f, 1.0f - tr * tr)));
+ // There is no logical equivalent to this using rangelim
+ surface_y = std::fmin(
+ std::fmax(base - depth, (float)(water_level - 3)),
+ surface_y);
+ slope = 0.0f;
+ }
// Optionally vary river depth according to heat and humidity
if (spflags & MGVALLEYS_VARY_RIVER_DEPTH) {
- float heat = ((spflags & MGVALLEYS_ALT_CHILL) &&
- (surface_y > 0.0f || river_y > 0.0f)) ?
- t_heat - alt_to_heat *
- std::fmax(surface_y, river_y) / altitude_chill :
+ float t_heat = m_bgen->heatmap[index_2d];
+ float heat = (spflags & MGVALLEYS_ALT_CHILL) ?
+ // Match heat value calculated below in
+ // 'Optionally decrease heat with altitude'.
+ // In rivers, 'ground height ignoring riverbeds' is 'base'.
+ // As this only affects river water we can assume y > water_level.
+ t_heat + 5.0f - (base - water_level) * 20.0f / altitude_chill :
t_heat;
float delta = m_bgen->humidmap[index_2d] - 50.0f;
if (delta < 0.0f) {
- float t_evap = (heat - 32.0f) / evaporation;
+ float t_evap = (heat - 32.0f) / 300.0f;
river_y += delta * std::fmax(t_evap, 0.08f);
}
}
+ // Highest solid node in column
+ s16 column_max_y = surface_y;
u32 index_3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
u32 index_data = vm->m_area.index(x, node_min.Y - 1, z);
for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) {
if (vm->m_data[index_data].getContent() == CONTENT_IGNORE) {
- float fill = noise_inter_valley_fill->result[index_3d];
+ float n_fill = noise_inter_valley_fill->result[index_3d];
float surface_delta = (float)y - surface_y;
- bool river = y < river_y - 1;
+ // Density = density noise + density gradient
+ float density = slope * n_fill - surface_delta;
- if (slope * fill > surface_delta) {
+ if (density > 0.0f) {
vm->m_data[index_data] = n_stone; // Stone
- if (y > heightmap[index_2d])
- heightmap[index_2d] = y;
if (y > surface_max_y)
surface_max_y = y;
+ if (y > column_max_y)
+ column_max_y = y;
} else if (y <= water_level) {
vm->m_data[index_data] = n_water; // Water
- } else if (river) {
+ } else if (y <= (s16)river_y) {
vm->m_data[index_data] = n_river_water; // River water
} else {
vm->m_data[index_data] = n_air; // Air
@@ -529,26 +427,13 @@ int MapgenValleys::generateTerrain()
index_3d += ystride;
}
- if (heightmap[index_2d] == -MAX_MAP_GENERATION_LIMIT) {
- s16 surface_y_int = myround(surface_y);
-
- if (surface_y_int > node_max.Y + 1 ||
- surface_y_int < node_min.Y - 1) {
- // If surface_y is outside the chunk, it's good enough
- heightmap[index_2d] = surface_y_int;
- } else {
- // If the ground is outside of this chunk, but surface_y is
- // within the chunk, give a value outside.
- heightmap[index_2d] = node_min.Y - 2;
- }
- }
-
// Optionally increase humidity around rivers
if (spflags & MGVALLEYS_HUMID_RIVERS) {
+ // Compensate to avoid increasing average humidity
+ m_bgen->humidmap[index_2d] *= 0.8f;
// Ground height ignoring riverbeds
- float t_alt = std::fmax(noise_rivers->result[index_2d],
- (float)heightmap[index_2d]);
- float water_depth = (t_alt - river_y) / humidity_dropoff;
+ float t_alt = std::fmax(base, (float)column_max_y);
+ float water_depth = (t_alt - base) / 4.0f;
m_bgen->humidmap[index_2d] *=
1.0f + std::pow(0.5f, std::fmax(water_depth, 1.0f));
}
@@ -556,21 +441,23 @@ int MapgenValleys::generateTerrain()
// Optionally decrease humidity with altitude
if (spflags & MGVALLEYS_ALT_DRY) {
// Ground height ignoring riverbeds
- float t_alt = std::fmax(noise_rivers->result[index_2d],
- (float)heightmap[index_2d]);
- if (t_alt > 0.0f)
+ float t_alt = std::fmax(base, (float)column_max_y);
+ // Only decrease above water_level
+ if (t_alt > water_level)
m_bgen->humidmap[index_2d] -=
- alt_to_humid * t_alt / altitude_chill;
+ (t_alt - water_level) * 10.0f / altitude_chill;
}
// Optionally decrease heat with altitude
if (spflags & MGVALLEYS_ALT_CHILL) {
+ // Compensate to avoid reducing the average heat
+ m_bgen->heatmap[index_2d] += 5.0f;
// Ground height ignoring riverbeds
- float t_alt = std::fmax(noise_rivers->result[index_2d],
- (float)heightmap[index_2d]);
- if (t_alt > 0.0f)
+ float t_alt = std::fmax(base, (float)column_max_y);
+ // Only decrease above water_level
+ if (t_alt > water_level)
m_bgen->heatmap[index_2d] -=
- alt_to_heat * t_alt / altitude_chill;
+ (t_alt - water_level) * 20.0f / altitude_chill;
}
}
diff --git a/src/mapgen/mapgen_valleys.h b/src/mapgen/mapgen_valleys.h
index 8bde7a622..ab80dc5c9 100644
--- a/src/mapgen/mapgen_valleys.h
+++ b/src/mapgen/mapgen_valleys.h
@@ -1,11 +1,11 @@
/*
Minetest
-Copyright (C) 2016-2018 Duane Robertson <duane@duanerobertson.com>
-Copyright (C) 2016-2018 paramat
+Copyright (C) 2016-2019 Duane Robertson <duane@duanerobertson.com>
+Copyright (C) 2016-2019 paramat
Based on Valleys Mapgen by Gael de Sailly
(https://forum.minetest.net/viewtopic.php?f=9&t=11430)
-and mapgen_v7 by kwolekr and paramat.
+and mapgen_v7, mapgen_flat by kwolekr and paramat.
Licensing changed by permission of Gael de Sailly.
@@ -24,20 +24,16 @@ 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 "mapgen.h"
-/////////////////// Mapgen Valleys flags
#define MGVALLEYS_ALT_CHILL 0x01
#define MGVALLEYS_HUMID_RIVERS 0x02
#define MGVALLEYS_VARY_RIVER_DEPTH 0x04
#define MGVALLEYS_ALT_DRY 0x08
-// Feed only one variable into these
-#define MYSQUARE(x) (x) * (x)
-#define MYCUBE(x) (x) * (x) * (x)
-
class BiomeManager;
class BiomeGenOriginal;
@@ -71,6 +67,7 @@ struct MapgenValleysParams : public MapgenParams {
NoiseParams np_cave1;
NoiseParams np_cave2;
NoiseParams np_cavern;
+ NoiseParams np_dungeons;
MapgenValleysParams();
~MapgenValleysParams() = default;
@@ -79,21 +76,11 @@ struct MapgenValleysParams : public MapgenParams {
void writeParams(Settings *settings) const;
};
-struct TerrainNoise {
- s16 x;
- s16 z;
- float terrain_height;
- float *rivers;
- float *valley;
- float valley_profile;
- float *slope;
- float inter_valley_fill;
-};
class MapgenValleys : public MapgenBasic {
public:
- MapgenValleys(int mapgenid, MapgenValleysParams *params,
+ MapgenValleys(MapgenValleysParams *params,
EmergeManager *emerge);
~MapgenValleys();
@@ -106,7 +93,6 @@ private:
BiomeGenOriginal *m_bgen;
float altitude_chill;
- float humidity_adjust;
float river_depth_bed;
float river_size_factor;
@@ -121,9 +107,5 @@ private:
Noise *noise_valley_depth;
Noise *noise_valley_profile;
- float terrainLevelAtPoint(s16 x, s16 z);
- void calculateNoise();
virtual int generateTerrain();
- float terrainLevelFromNoise(TerrainNoise *tn);
- float adjustedTerrainLevelFromNoise(TerrainNoise *tn);
};
diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp
index 7f717011c..345bc8c6a 100644
--- a/src/mapgen/mg_biome.cpp
+++ b/src/mapgen/mg_biome.cpp
@@ -63,6 +63,7 @@ BiomeManager::BiomeManager(Server *server) :
b->m_nodenames.emplace_back("mapgen_stone");
b->m_nodenames.emplace_back("ignore");
b->m_nodenames.emplace_back("ignore");
+ b->m_nnlistsizes.push_back(1);
b->m_nodenames.emplace_back("ignore");
b->m_nodenames.emplace_back("ignore");
b->m_nodenames.emplace_back("ignore");
@@ -330,7 +331,7 @@ void Biome::resolveNodeNames()
getIdFromNrBacklog(&c_river_water, "mapgen_river_water_source", CONTENT_AIR, false);
getIdFromNrBacklog(&c_riverbed, "mapgen_stone", CONTENT_AIR, false);
getIdFromNrBacklog(&c_dust, "ignore", CONTENT_IGNORE, false);
- getIdFromNrBacklog(&c_cave_liquid, "ignore", CONTENT_IGNORE, false);
+ getIdsFromNrBacklog(&c_cave_liquid);
getIdFromNrBacklog(&c_dungeon, "ignore", CONTENT_IGNORE, false);
getIdFromNrBacklog(&c_dungeon_alt, "ignore", CONTENT_IGNORE, false);
getIdFromNrBacklog(&c_dungeon_stair, "ignore", CONTENT_IGNORE, false);
diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h
index 1f60f7bac..ee148adbc 100644
--- a/src/mapgen/mg_biome.h
+++ b/src/mapgen/mg_biome.h
@@ -52,7 +52,7 @@ public:
content_t c_river_water;
content_t c_riverbed;
content_t c_dust;
- content_t c_cave_liquid;
+ std::vector<content_t> c_cave_liquid;
content_t c_dungeon;
content_t c_dungeon_alt;
content_t c_dungeon_stair;
diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp
index 36f1dd76b..c1acbfd9d 100644
--- a/src/mapgen/mg_schematic.cpp
+++ b/src/mapgen/mg_schematic.cpp
@@ -246,7 +246,7 @@ void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags,
for (it = modified_blocks.begin(); it != modified_blocks.end(); ++it)
event.modified_blocks.insert(it->first);
- map->dispatchEvent(&event);
+ map->dispatchEvent(event);
}
diff --git a/src/mapgen/treegen.cpp b/src/mapgen/treegen.cpp
index 4c351fcef..0d8af2851 100644
--- a/src/mapgen/treegen.cpp
+++ b/src/mapgen/treegen.cpp
@@ -135,7 +135,7 @@ treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0,
event.type = MEET_OTHER;
for (auto &modified_block : modified_blocks)
event.modified_blocks.insert(modified_block.first);
- map->dispatchEvent(&event);
+ map->dispatchEvent(event);
return SUCCESS;
}
diff --git a/src/mapnode.cpp b/src/mapnode.cpp
index 3b19160e1..557b376c3 100644
--- a/src/mapnode.cpp
+++ b/src/mapnode.cpp
@@ -44,18 +44,6 @@ static const u8 rot_to_wallmounted[] = {
MapNode
*/
-// Create directly from a nodename
-// If name is unknown, sets CONTENT_IGNORE
-MapNode::MapNode(const NodeDefManager *ndef, const std::string &name,
- u8 a_param1, u8 a_param2)
-{
- content_t id = CONTENT_IGNORE;
- ndef->getId(name, id);
- param0 = id;
- param1 = a_param1;
- param2 = a_param2;
-}
-
void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const
{
if (f.palette) {
@@ -524,7 +512,7 @@ static inline void getNeighborConnectingFace(
const v3s16 &p, const NodeDefManager *nodedef,
Map *map, MapNode n, u8 bitmask, u8 *neighbors)
{
- MapNode n2 = map->getNodeNoEx(p);
+ MapNode n2 = map->getNode(p);
if (nodedef->nodeboxConnects(n, n2, bitmask))
*neighbors |= bitmask;
}
diff --git a/src/mapnode.h b/src/mapnode.h
index 6e48bce8c..7a3d30ddc 100644
--- a/src/mapnode.h
+++ b/src/mapnode.h
@@ -145,11 +145,6 @@ struct MapNode
param2(a_param2)
{ }
- // Create directly from a nodename
- // If name is unknown, sets CONTENT_IGNORE
- MapNode(const NodeDefManager *ndef, const std::string &name,
- u8 a_param1=0, u8 a_param2=0);
-
bool operator==(const MapNode &other) const noexcept
{
return (param0 == other.param0
diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp
index 7f3ab50ed..498583df9 100644
--- a/src/network/clientopcodes.cpp
+++ b/src/network/clientopcodes.cpp
@@ -67,7 +67,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] =
null_command_handler,
{ "TOCLIENT_TIME_OF_DAY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_TimeOfDay }, // 0x29
{ "TOCLIENT_CSM_RESTRICTION_FLAGS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CSMRestrictionFlags }, // 0x2A
- null_command_handler,
+ { "TOCLIENT_PLAYER_SPEED", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_PlayerSpeed }, // 0x2B
null_command_handler,
null_command_handler,
null_command_handler,
@@ -78,7 +78,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] =
{ "TOCLIENT_HP", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HP }, // 0x33
{ "TOCLIENT_MOVE_PLAYER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MovePlayer }, // 0x34
{ "TOCLIENT_ACCESS_DENIED_LEGACY", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_AccessDenied }, // 0x35
- null_command_handler,
+ { "TOCLIENT_FOV", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Fov }, // 0x36
{ "TOCLIENT_DEATHSCREEN", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeathScreen }, // 0x37
{ "TOCLIENT_MEDIA", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Media }, // 0x38
null_command_handler,
@@ -151,9 +151,9 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] =
null_command_factory, // 0x14
null_command_factory, // 0x15
null_command_factory, // 0x16
- { "TOSERVER_MODCHANNEL_JOIN", 0, true }, // 0x17
- { "TOSERVER_MODCHANNEL_LEAVE", 0, true }, // 0x18
- { "TOSERVER_MODCHANNEL_MSG", 0, true }, // 0x19
+ { "TOSERVER_MODCHANNEL_JOIN", 0, true }, // 0x17
+ { "TOSERVER_MODCHANNEL_LEAVE", 0, true }, // 0x18
+ { "TOSERVER_MODCHANNEL_MSG", 0, true }, // 0x19
null_command_factory, // 0x1a
null_command_factory, // 0x1b
null_command_factory, // 0x1c
diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp
index 2f5deae2a..b6e9defb0 100644
--- a/src/network/clientpackethandler.cpp
+++ b/src/network/clientpackethandler.cpp
@@ -333,7 +333,7 @@ void Client::handleCommand_Inventory(NetworkPacket* pkt)
player->inventory.deSerialize(is);
- m_inventory_updated = true;
+ m_update_wielded_item = true;
delete m_inventory_from_server;
m_inventory_from_server = new Inventory(player->inventory);
@@ -415,7 +415,7 @@ void Client::handleCommand_ChatMessage(NetworkPacket *pkt)
chatMessage->type = (ChatMessageType) message_type;
// @TODO send this to CSM using ChatMessage object
- if (moddingEnabled() && m_script->on_receiving_message(
+ if (modsLoaded() && m_script->on_receiving_message(
wide_to_utf8(chatMessage->message))) {
// Message was consumed by CSM and should not be handled by client
delete chatMessage;
@@ -463,6 +463,10 @@ void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt)
infostream << "handleCommand_ActiveObjectRemoveAdd: " << e.what()
<< ". The packet is unreliable, ignoring" << std::endl;
}
+
+ // m_activeobjects_received is false before the first
+ // TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD packet is received
+ m_activeobjects_received = true;
}
void Client::handleCommand_ActiveObjectMessages(NetworkPacket* pkt)
@@ -519,22 +523,30 @@ void Client::handleCommand_Movement(NetworkPacket* pkt)
player->movement_gravity = g * BS;
}
-void Client::handleCommand_HP(NetworkPacket* pkt)
+void Client::handleCommand_Fov(NetworkPacket *pkt)
{
+ f32 fov;
+ bool is_multiplier;
+ *pkt >> fov >> is_multiplier;
LocalPlayer *player = m_env.getLocalPlayer();
+ player->setFov({ fov, is_multiplier });
+}
+
+void Client::handleCommand_HP(NetworkPacket *pkt)
+{
+ LocalPlayer *player = m_env.getLocalPlayer();
assert(player != NULL);
- u16 oldhp = player->hp;
+ u16 oldhp = player->hp;
u16 hp;
*pkt >> hp;
player->hp = hp;
- if (moddingEnabled()) {
+ if (modsLoaded())
m_script->on_hp_modification(hp);
- }
if (hp < oldhp) {
// Add to ClientEvent queue
@@ -900,7 +912,7 @@ void Client::handleCommand_DetachedInventory(NetworkPacket* pkt)
u16 ignore;
*pkt >> ignore; // this used to be the length of the following string, ignore it
- std::string contents = pkt->getRemainingString();
+ std::string contents(pkt->getRemainingString(), pkt->getRemainingBytes());
std::istringstream is(contents, std::ios::binary);
inv->deSerialize(is);
}
@@ -1383,6 +1395,17 @@ void Client::handleCommand_CSMRestrictionFlags(NetworkPacket *pkt)
loadMods();
}
+void Client::handleCommand_PlayerSpeed(NetworkPacket *pkt)
+{
+ v3f added_vel;
+
+ *pkt >> added_vel;
+
+ LocalPlayer *player = m_env.getLocalPlayer();
+ assert(player != NULL);
+ player->addVelocity(added_vel);
+}
+
/*
* Mod channels
*/
diff --git a/src/network/connection.cpp b/src/network/connection.cpp
index 28084c921..0bc13a2f0 100644
--- a/src/network/connection.cpp
+++ b/src/network/connection.cpp
@@ -56,7 +56,7 @@ std::mutex log_message_mutex;
#define PING_TIMEOUT 5.0
-BufferedPacket makePacket(Address &address, SharedBuffer<u8> data,
+BufferedPacket makePacket(Address &address, const SharedBuffer<u8> &data,
u32 protocol_id, session_t sender_peer_id, u8 channel)
{
u32 packet_size = data.getSize() + BASE_HEADER_SIZE;
@@ -126,7 +126,7 @@ void makeSplitPacket(const SharedBuffer<u8> &data, u32 chunksize_max, u16 seqnum
}
}
-void makeAutoSplitPacket(SharedBuffer<u8> data, u32 chunksize_max,
+void makeAutoSplitPacket(const SharedBuffer<u8> &data, u32 chunksize_max,
u16 &split_seqnum, std::list<SharedBuffer<u8>> *list)
{
u32 original_header_size = 1;
@@ -140,7 +140,7 @@ void makeAutoSplitPacket(SharedBuffer<u8> data, u32 chunksize_max,
list->push_back(makeOriginalPacket(data));
}
-SharedBuffer<u8> makeReliablePacket(SharedBuffer<u8> data, u16 seqnum)
+SharedBuffer<u8> makeReliablePacket(const SharedBuffer<u8> &data, u16 seqnum)
{
u32 header_size = 3;
u32 packet_size = data.getSize() + header_size;
@@ -169,6 +169,7 @@ void ReliablePacketBuffer::print()
index++;
}
}
+
bool ReliablePacketBuffer::empty()
{
MutexAutoLock listlock(m_list_mutex);
@@ -177,12 +178,8 @@ bool ReliablePacketBuffer::empty()
u32 ReliablePacketBuffer::size()
{
- return m_list_size;
-}
-
-bool ReliablePacketBuffer::containsPacket(u16 seqnum)
-{
- return !(findPacket(seqnum) == m_list.end());
+ MutexAutoLock listlock(m_list_mutex);
+ return m_list.size();
}
RPBSearchResult ReliablePacketBuffer::findPacket(u16 seqnum)
@@ -191,24 +188,24 @@ RPBSearchResult ReliablePacketBuffer::findPacket(u16 seqnum)
for(; i != m_list.end(); ++i)
{
u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1]));
- /*dout_con<<"findPacket(): finding seqnum="<<seqnum
- <<", comparing to s="<<s<<std::endl;*/
if (s == seqnum)
break;
}
return i;
}
+
RPBSearchResult ReliablePacketBuffer::notFound()
{
return m_list.end();
}
+
bool ReliablePacketBuffer::getFirstSeqnum(u16& result)
{
MutexAutoLock listlock(m_list_mutex);
if (m_list.empty())
return false;
- BufferedPacket p = *m_list.begin();
- result = readU16(&p.data[BASE_HEADER_SIZE+1]);
+ const BufferedPacket &p = *m_list.begin();
+ result = readU16(&p.data[BASE_HEADER_SIZE + 1]);
return true;
}
@@ -219,16 +216,16 @@ BufferedPacket ReliablePacketBuffer::popFirst()
throw NotFoundException("Buffer is empty");
BufferedPacket p = *m_list.begin();
m_list.erase(m_list.begin());
- --m_list_size;
- if (m_list_size == 0) {
+ if (m_list.empty()) {
m_oldest_non_answered_ack = 0;
} else {
m_oldest_non_answered_ack =
- readU16(&(*m_list.begin()).data[BASE_HEADER_SIZE+1]);
+ readU16(&m_list.begin()->data[BASE_HEADER_SIZE + 1]);
}
return p;
}
+
BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum)
{
MutexAutoLock listlock(m_list_mutex);
@@ -249,15 +246,17 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum)
}
m_list.erase(r);
- --m_list_size;
- if (m_list_size == 0)
- { m_oldest_non_answered_ack = 0; }
- else
- { m_oldest_non_answered_ack = readU16(&(*m_list.begin()).data[BASE_HEADER_SIZE+1]); }
+ if (m_list.empty()) {
+ m_oldest_non_answered_ack = 0;
+ } else {
+ m_oldest_non_answered_ack =
+ readU16(&m_list.begin()->data[BASE_HEADER_SIZE + 1]);
+ }
return p;
}
-void ReliablePacketBuffer::insert(BufferedPacket &p,u16 next_expected)
+
+void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected)
{
MutexAutoLock listlock(m_list_mutex);
if (p.data.getSize() < BASE_HEADER_SIZE + 3) {
@@ -284,8 +283,7 @@ void ReliablePacketBuffer::insert(BufferedPacket &p,u16 next_expected)
return;
}
- ++m_list_size;
- sanity_check(m_list_size <= SEQNUM_MAX+1); // FIXME: Handle the error?
+ sanity_check(m_list.size() <= SEQNUM_MAX); // FIXME: Handle the error?
// Find the right place for the packet and insert it there
// If list is empty, just add it
@@ -322,6 +320,8 @@ void ReliablePacketBuffer::insert(BufferedPacket &p,u16 next_expected)
}
if (s == seqnum) {
+ /* nothing to do this seems to be a resent packet */
+ /* for paranoia reason data should be compared */
if (
(readU16(&(i->data[BASE_HEADER_SIZE+1])) != seqnum) ||
(i->data.getSize() != p.data.getSize()) ||
@@ -340,16 +340,11 @@ void ReliablePacketBuffer::insert(BufferedPacket &p,u16 next_expected)
p.address.serializeString().c_str());
throw IncomingDataCorruption("duplicated packet isn't same as original one");
}
-
- /* nothing to do this seems to be a resent packet */
- /* for paranoia reason data should be compared */
- --m_list_size;
}
/* insert or push back */
else if (i != m_list.end()) {
m_list.insert(i, p);
- }
- else {
+ } else {
m_list.push_back(p);
}
@@ -385,6 +380,48 @@ std::list<BufferedPacket> ReliablePacketBuffer::getTimedOuts(float timeout,
}
/*
+ IncomingSplitPacket
+*/
+
+bool IncomingSplitPacket::insert(u32 chunk_num, SharedBuffer<u8> &chunkdata)
+{
+ sanity_check(chunk_num < chunk_count);
+
+ // If chunk already exists, ignore it.
+ // Sometimes two identical packets may arrive when there is network
+ // lag and the server re-sends stuff.
+ if (chunks.find(chunk_num) != chunks.end())
+ return false;
+
+ // Set chunk data in buffer
+ chunks[chunk_num] = chunkdata;
+
+ return true;
+}
+
+SharedBuffer<u8> IncomingSplitPacket::reassemble()
+{
+ sanity_check(allReceived());
+
+ // Calculate total size
+ u32 totalsize = 0;
+ for (const auto &chunk : chunks)
+ totalsize += chunk.second.getSize();
+
+ SharedBuffer<u8> fulldata(totalsize);
+
+ // Copy chunks to data buffer
+ u32 start = 0;
+ for (u32 chunk_i = 0; chunk_i < chunk_count; chunk_i++) {
+ const SharedBuffer<u8> &buf = chunks[chunk_i];
+ memcpy(&fulldata[start], *buf, buf.getSize());
+ start += buf.getSize();
+ }
+
+ return fulldata;
+}
+
+/*
IncomingSplitBuffer
*/
@@ -395,10 +432,7 @@ IncomingSplitBuffer::~IncomingSplitBuffer()
delete i.second;
}
}
-/*
- This will throw a GotSplitPacketException when a full
- split packet is constructed.
-*/
+
SharedBuffer<u8> IncomingSplitBuffer::insert(const BufferedPacket &p, bool reliable)
{
MutexAutoLock listlock(m_map_mutex);
@@ -417,57 +451,45 @@ SharedBuffer<u8> IncomingSplitBuffer::insert(const BufferedPacket &p, bool relia
<< std::endl;
return SharedBuffer<u8>();
}
+ if (chunk_num >= chunk_count) {
+ errorstream << "IncomingSplitBuffer::insert(): chunk_num=" << chunk_num
+ << " >= chunk_count=" << chunk_count << std::endl;
+ return SharedBuffer<u8>();
+ }
// Add if doesn't exist
+ IncomingSplitPacket *sp;
if (m_buf.find(seqnum) == m_buf.end()) {
- m_buf[seqnum] = new IncomingSplitPacket(chunk_count, reliable);
+ sp = new IncomingSplitPacket(chunk_count, reliable);
+ m_buf[seqnum] = sp;
+ } else {
+ sp = m_buf[seqnum];
}
- IncomingSplitPacket *sp = m_buf[seqnum];
-
- if (chunk_count != sp->chunk_count)
- LOG(derr_con<<"Connection: WARNING: chunk_count="<<chunk_count
- <<" != sp->chunk_count="<<sp->chunk_count
- <<std::endl);
+ if (chunk_count != sp->chunk_count) {
+ errorstream << "IncomingSplitBuffer::insert(): chunk_count="
+ << chunk_count << " != sp->chunk_count=" << sp->chunk_count
+ << std::endl;
+ return SharedBuffer<u8>();
+ }
if (reliable != sp->reliable)
LOG(derr_con<<"Connection: WARNING: reliable="<<reliable
<<" != sp->reliable="<<sp->reliable
<<std::endl);
- // If chunk already exists, ignore it.
- // Sometimes two identical packets may arrive when there is network
- // lag and the server re-sends stuff.
- if (sp->chunks.find(chunk_num) != sp->chunks.end())
- return SharedBuffer<u8>();
-
// Cut chunk data out of packet
u32 chunkdatasize = p.data.getSize() - headersize;
SharedBuffer<u8> chunkdata(chunkdatasize);
memcpy(*chunkdata, &(p.data[headersize]), chunkdatasize);
- // Set chunk data in buffer
- sp->chunks[chunk_num] = chunkdata;
+ if (!sp->insert(chunk_num, chunkdata))
+ return SharedBuffer<u8>();
// If not all chunks are received, return empty buffer
if (!sp->allReceived())
return SharedBuffer<u8>();
- // Calculate total size
- u32 totalsize = 0;
- for (const auto &chunk : sp->chunks) {
- totalsize += chunk.second.getSize();
- }
-
- SharedBuffer<u8> fulldata(totalsize);
-
- // Copy chunks to data buffer
- u32 start = 0;
- for (u32 chunk_i=0; chunk_i<sp->chunk_count; chunk_i++) {
- const SharedBuffer<u8> &buf = sp->chunks[chunk_i];
- u16 buf_chunkdatasize = buf.getSize();
- memcpy(&fulldata[start], *buf, buf_chunkdatasize);
- start += buf_chunkdatasize;
- }
+ SharedBuffer<u8> fulldata = sp->reassemble();
// Remove sp from buffer
m_buf.erase(seqnum);
@@ -475,6 +497,7 @@ SharedBuffer<u8> IncomingSplitBuffer::insert(const BufferedPacket &p, bool relia
return fulldata;
}
+
void IncomingSplitBuffer::removeUnreliableTimedOuts(float dtime, float timeout)
{
std::deque<u16> remove_queue;
@@ -849,8 +872,8 @@ void Peer::RTTStatistics(float rtt, const std::string &profiler_id,
jitter * (1/num_samples);
if (!profiler_id.empty()) {
- g_profiler->graphAdd(profiler_id + "_rtt", rtt);
- g_profiler->graphAdd(profiler_id + "_jitter", jitter);
+ g_profiler->graphAdd(profiler_id + " RTT [ms]", rtt * 1000.f);
+ g_profiler->graphAdd(profiler_id + " jitter [ms]", jitter * 1000.f);
}
}
/* save values required for next loop */
diff --git a/src/network/connection.h b/src/network/connection.h
index 346c7f886..057bd39f6 100644
--- a/src/network/connection.h
+++ b/src/network/connection.h
@@ -103,16 +103,16 @@ struct BufferedPacket
};
// This adds the base headers to the data and makes a packet out of it
-BufferedPacket makePacket(Address &address, SharedBuffer<u8> data,
+BufferedPacket makePacket(Address &address, const SharedBuffer<u8> &data,
u32 protocol_id, session_t sender_peer_id, u8 channel);
// Depending on size, make a TYPE_ORIGINAL or TYPE_SPLIT packet
// Increments split_seqnum if a split packet is made
-void makeAutoSplitPacket(SharedBuffer<u8> data, u32 chunksize_max,
+void makeAutoSplitPacket(const SharedBuffer<u8> &data, u32 chunksize_max,
u16 &split_seqnum, std::list<SharedBuffer<u8>> *list);
// Add the TYPE_RELIABLE header to the data
-SharedBuffer<u8> makeReliablePacket(SharedBuffer<u8> data, u16 seqnum);
+SharedBuffer<u8> makeReliablePacket(const SharedBuffer<u8> &data, u16 seqnum);
struct IncomingSplitPacket
{
@@ -121,16 +121,20 @@ struct IncomingSplitPacket
IncomingSplitPacket() = delete;
- // Key is chunk number, value is data without headers
- std::map<u16, SharedBuffer<u8>> chunks;
- u32 chunk_count;
float time = 0.0f; // Seconds from adding
- bool reliable = false; // If true, isn't deleted on timeout
+ u32 chunk_count;
+ bool reliable; // If true, isn't deleted on timeout
bool allReceived() const
{
return (chunks.size() == chunk_count);
}
+ bool insert(u32 chunk_num, SharedBuffer<u8> &chunkdata);
+ SharedBuffer<u8> reassemble();
+
+private:
+ // Key is chunk number, value is data without headers
+ std::map<u16, SharedBuffer<u8>> chunks;
};
/*
@@ -240,7 +244,7 @@ public:
BufferedPacket popFirst();
BufferedPacket popSeqnum(u16 seqnum);
- void insert(BufferedPacket &p,u16 next_expected);
+ void insert(BufferedPacket &p, u16 next_expected);
void incrementTimeouts(float dtime);
std::list<BufferedPacket> getTimedOuts(float timeout,
@@ -248,16 +252,14 @@ public:
void print();
bool empty();
- bool containsPacket(u16 seqnum);
RPBSearchResult notFound();
u32 size();
private:
- RPBSearchResult findPacket(u16 seqnum);
+ RPBSearchResult findPacket(u16 seqnum); // does not perform locking
std::list<BufferedPacket> m_list;
- u32 m_list_size = 0;
u16 m_oldest_non_answered_ack;
diff --git a/src/network/connectionthreads.cpp b/src/network/connectionthreads.cpp
index 63a9064e7..f8b58c025 100644
--- a/src/network/connectionthreads.cpp
+++ b/src/network/connectionthreads.cpp
@@ -327,7 +327,7 @@ void ConnectionSendThread::sendAsPacketReliable(BufferedPacket &p, Channel *chan
}
bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum,
- SharedBuffer<u8> data, bool reliable)
+ const SharedBuffer<u8> &data, bool reliable)
{
PeerHelper peer = m_connection->getPeerNoEx(peer_id);
if (!peer) {
@@ -575,7 +575,7 @@ void ConnectionSendThread::disconnect_peer(session_t peer_id)
}
void ConnectionSendThread::send(session_t peer_id, u8 channelnum,
- SharedBuffer<u8> data)
+ const SharedBuffer<u8> &data)
{
assert(channelnum < CHANNEL_COUNT); // Pre-condition
@@ -615,7 +615,7 @@ void ConnectionSendThread::sendReliable(ConnectionCommand &c)
peer->PutReliableSendCommand(c, m_max_packet_size);
}
-void ConnectionSendThread::sendToAll(u8 channelnum, SharedBuffer<u8> data)
+void ConnectionSendThread::sendToAll(u8 channelnum, const SharedBuffer<u8> &data)
{
std::list<session_t> peerids = m_connection->getPeerIDs();
@@ -776,7 +776,7 @@ void ConnectionSendThread::sendPackets(float dtime)
}
void ConnectionSendThread::sendAsPacket(session_t peer_id, u8 channelnum,
- SharedBuffer<u8> data, bool ack)
+ const SharedBuffer<u8> &data, bool ack)
{
OutgoingPacket packet(peer_id, channelnum, data, false, ack);
m_outgoing_queue.push(packet);
@@ -1086,7 +1086,7 @@ bool ConnectionReceiveThread::checkIncomingBuffers(Channel *channel,
}
SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel,
- SharedBuffer<u8> packetdata, session_t peer_id, u8 channelnum, bool reliable)
+ const SharedBuffer<u8> &packetdata, session_t peer_id, u8 channelnum, bool reliable)
{
PeerHelper peer = m_connection->getPeerNoEx(peer_id);
@@ -1125,7 +1125,7 @@ const ConnectionReceiveThread::PacketTypeHandler
};
SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Control(Channel *channel,
- SharedBuffer<u8> packetdata, Peer *peer, u8 channelnum, bool reliable)
+ const SharedBuffer<u8> &packetdata, Peer *peer, u8 channelnum, bool reliable)
{
if (packetdata.getSize() < 2)
throw InvalidIncomingDataException("packetdata.getSize() < 2");
@@ -1224,7 +1224,7 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Control(Channel *chan
}
SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Original(Channel *channel,
- SharedBuffer<u8> packetdata, Peer *peer, u8 channelnum, bool reliable)
+ const SharedBuffer<u8> &packetdata, Peer *peer, u8 channelnum, bool reliable)
{
if (packetdata.getSize() <= ORIGINAL_HEADER_SIZE)
throw InvalidIncomingDataException
@@ -1238,7 +1238,7 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Original(Channel *cha
}
SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Split(Channel *channel,
- SharedBuffer<u8> packetdata, Peer *peer, u8 channelnum, bool reliable)
+ const SharedBuffer<u8> &packetdata, Peer *peer, u8 channelnum, bool reliable)
{
Address peer_address;
@@ -1269,7 +1269,7 @@ SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Split(Channel *channe
}
SharedBuffer<u8> ConnectionReceiveThread::handlePacketType_Reliable(Channel *channel,
- SharedBuffer<u8> packetdata, Peer *peer, u8 channelnum, bool reliable)
+ const SharedBuffer<u8> &packetdata, Peer *peer, u8 channelnum, bool reliable)
{
assert(channel != NULL);
diff --git a/src/network/connectionthreads.h b/src/network/connectionthreads.h
index ddac4ae20..da4ea92f5 100644
--- a/src/network/connectionthreads.h
+++ b/src/network/connectionthreads.h
@@ -52,8 +52,8 @@ public:
private:
void runTimeouts(float dtime);
void rawSend(const BufferedPacket &packet);
- bool rawSendAsPacket(session_t peer_id, u8 channelnum, SharedBuffer<u8> data,
- bool reliable);
+ bool rawSendAsPacket(session_t peer_id, u8 channelnum,
+ const SharedBuffer<u8> &data, bool reliable);
void processReliableCommand(ConnectionCommand &c);
void processNonReliableCommand(ConnectionCommand &c);
@@ -61,14 +61,14 @@ private:
void connect(Address address);
void disconnect();
void disconnect_peer(session_t peer_id);
- void send(session_t peer_id, u8 channelnum, SharedBuffer<u8> data);
+ void send(session_t peer_id, u8 channelnum, const SharedBuffer<u8> &data);
void sendReliable(ConnectionCommand &c);
- void sendToAll(u8 channelnum, SharedBuffer<u8> data);
+ void sendToAll(u8 channelnum, const SharedBuffer<u8> &data);
void sendToAllReliable(ConnectionCommand &c);
void sendPackets(float dtime);
- void sendAsPacket(session_t peer_id, u8 channelnum, SharedBuffer<u8> data,
+ void sendAsPacket(session_t peer_id, u8 channelnum, const SharedBuffer<u8> &data,
bool ack = false);
void sendAsPacketReliable(BufferedPacket &p, Channel *channel);
@@ -119,26 +119,27 @@ private:
channelnum: channel on which the packet was sent
reliable: true if recursing into a reliable packet
*/
- SharedBuffer<u8> processPacket(Channel *channel, SharedBuffer<u8> packetdata,
- session_t peer_id, u8 channelnum, bool reliable);
+ SharedBuffer<u8> processPacket(Channel *channel,
+ const SharedBuffer<u8> &packetdata, session_t peer_id,
+ u8 channelnum, bool reliable);
SharedBuffer<u8> handlePacketType_Control(Channel *channel,
- SharedBuffer<u8> packetdata, Peer *peer, u8 channelnum,
+ const SharedBuffer<u8> &packetdata, Peer *peer, u8 channelnum,
bool reliable);
SharedBuffer<u8> handlePacketType_Original(Channel *channel,
- SharedBuffer<u8> packetdata, Peer *peer, u8 channelnum,
+ const SharedBuffer<u8> &packetdata, Peer *peer, u8 channelnum,
bool reliable);
SharedBuffer<u8> handlePacketType_Split(Channel *channel,
- SharedBuffer<u8> packetdata, Peer *peer, u8 channelnum,
+ const SharedBuffer<u8> &packetdata, Peer *peer, u8 channelnum,
bool reliable);
SharedBuffer<u8> handlePacketType_Reliable(Channel *channel,
- SharedBuffer<u8> packetdata, Peer *peer, u8 channelnum,
+ const SharedBuffer<u8> &packetdata, Peer *peer, u8 channelnum,
bool reliable);
struct PacketTypeHandler
{
SharedBuffer<u8> (ConnectionReceiveThread::*handler)(Channel *channel,
- SharedBuffer<u8> packet, Peer *peer, u8 channelnum,
+ const SharedBuffer<u8> &packet, Peer *peer, u8 channelnum,
bool reliable);
};
diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h
index 85436068f..5a13c1353 100644
--- a/src/network/networkprotocol.h
+++ b/src/network/networkprotocol.h
@@ -194,9 +194,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
New network float format
ContentFeatures version 13
Add full Euler rotations instead of just yaw
+ Add TOCLIENT_PLAYER_SPEED
+ PROTOCOL VERSION 38:
+ Incremental inventory sending mode
+ Unknown inventory serialization fields no longer throw an error
+ Mod-specific formspec version
+ Player FOV override API
*/
-#define LATEST_PROTOCOL_VERSION 37
+#define LATEST_PROTOCOL_VERSION 38
#define LATEST_PROTOCOL_VERSION_STRING TOSTRING(LATEST_PROTOCOL_VERSION)
// Server's supported network protocol range
@@ -215,8 +221,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define PASSWORD_SIZE 28 // Maximum password length. Allows for
// base64-encoded SHA-1 (27+\0).
-#define FORMSPEC_API_VERSION 1
-#define FORMSPEC_VERSION_STRING "formspec_version[" TOSTRING(FORMSPEC_API_VERSION) "]"
+/*
+ Changes by FORMSPEC_API_VERSION:
+
+ FORMSPEC VERSION 1:
+ (too much)
+ FORMSPEC VERSION 2:
+ Forced real coordinates
+ background[]: 9-slice scaling parameters
+*/
+#define FORMSPEC_API_VERSION 2
#define TEXTURENAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-"
@@ -295,6 +309,11 @@ enum ToClientCommand
u32 CSMRestrictionFlags byteflag
*/
+ TOCLIENT_PLAYER_SPEED = 0x2B,
+ /*
+ v3f added_vel
+ */
+
// (oops, there is some gap here)
TOCLIENT_CHAT_MESSAGE = 0x2F,
@@ -352,7 +371,13 @@ enum ToClientCommand
wstring reason
*/
- TOCLIENT_PLAYERITEM = 0x36, // Obsolete
+ TOCLIENT_FOV = 0x36,
+ /*
+ Sends an FOV override/multiplier to client.
+
+ float fov
+ bool is_multiplier
+ */
TOCLIENT_DEATHSCREEN = 0x37,
/*
@@ -954,10 +979,20 @@ enum CSMRestrictionFlags : u64 {
// When those are complete, this should return to only being a restriction on the
// loading of client mods.
CSM_RF_LOAD_CLIENT_MODS = 0x00000001, // Don't load client-provided mods or 'builtin'
- CSM_RF_CHAT_MESSAGES = 0x00000002, // Disable chat message sending from CSM
- CSM_RF_READ_ITEMDEFS = 0x00000004, // Disable itemdef lookups
- CSM_RF_READ_NODEDEFS = 0x00000008, // Disable nodedef lookups
- CSM_RF_LOOKUP_NODES = 0x00000010, // Limit node lookups
- CSM_RF_READ_PLAYERINFO = 0x00000020, // Disable player info lookups
+ CSM_RF_CHAT_MESSAGES = 0x00000002, // Disable chat message sending from CSM
+ CSM_RF_READ_ITEMDEFS = 0x00000004, // Disable itemdef lookups
+ CSM_RF_READ_NODEDEFS = 0x00000008, // Disable nodedef lookups
+ CSM_RF_LOOKUP_NODES = 0x00000010, // Limit node lookups
+ CSM_RF_READ_PLAYERINFO = 0x00000020, // Disable player info lookups
CSM_RF_ALL = 0xFFFFFFFF,
};
+
+enum InteractAction : u8
+{
+ INTERACT_START_DIGGING, // 0: start digging (from undersurface) or use
+ INTERACT_STOP_DIGGING, // 1: stop digging (all parameters ignored)
+ INTERACT_DIGGING_COMPLETED, // 2: digging completed
+ INTERACT_PLACE, // 3: place block or item (to abovesurface)
+ INTERACT_USE, // 4: use item
+ INTERACT_ACTIVATE // 5: rightclick air ("activate")
+};
diff --git a/src/network/peerhandler.h b/src/network/peerhandler.h
index 208ab801e..da65483ef 100644
--- a/src/network/peerhandler.h
+++ b/src/network/peerhandler.h
@@ -24,7 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
namespace con
{
-typedef enum {
+typedef enum
+{
MIN_RTT,
MAX_RTT,
AVG_RTT,
diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp
index 013b549c6..8c8d49955 100644
--- a/src/network/serveropcodes.cpp
+++ b/src/network/serveropcodes.cpp
@@ -104,9 +104,9 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] =
null_command_handler, // 0x4d
null_command_handler, // 0x4e
null_command_handler, // 0x4f
- { "TOSERVER_FIRST_SRP", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_FirstSrp }, // 0x50
- { "TOSERVER_SRP_BYTES_A", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_SrpBytesA }, // 0x51
- { "TOSERVER_SRP_BYTES_M", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_SrpBytesM }, // 0x52
+ { "TOSERVER_FIRST_SRP", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_FirstSrp }, // 0x50
+ { "TOSERVER_SRP_BYTES_A", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_SrpBytesA }, // 0x51
+ { "TOSERVER_SRP_BYTES_M", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_SrpBytesM }, // 0x52
};
const static ClientCommandFactory null_command_factory = { "TOCLIENT_NULL", 0, false };
@@ -115,51 +115,51 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
{
null_command_factory, // 0x00
null_command_factory, // 0x01
- { "TOCLIENT_HELLO", 0, true }, // 0x02
- { "TOCLIENT_AUTH_ACCEPT", 0, true }, // 0x03
- { "TOCLIENT_ACCEPT_SUDO_MODE", 0, true }, // 0x04
- { "TOCLIENT_DENY_SUDO_MODE", 0, true }, // 0x05
+ { "TOCLIENT_HELLO", 0, true }, // 0x02
+ { "TOCLIENT_AUTH_ACCEPT", 0, true }, // 0x03
+ { "TOCLIENT_ACCEPT_SUDO_MODE", 0, true }, // 0x04
+ { "TOCLIENT_DENY_SUDO_MODE", 0, true }, // 0x05
null_command_factory, // 0x06
null_command_factory, // 0x07
null_command_factory, // 0x08
null_command_factory, // 0x09
- { "TOCLIENT_ACCESS_DENIED", 0, true }, // 0x0A
+ { "TOCLIENT_ACCESS_DENIED", 0, true }, // 0x0A
null_command_factory, // 0x0B
null_command_factory, // 0x0C
null_command_factory, // 0x0D
null_command_factory, // 0x0E
null_command_factory, // 0x0F
- { "TOCLIENT_INIT", 0, true }, // 0x10
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
+ { "TOCLIENT_INIT", 0, true }, // 0x10
+ null_command_factory, // 0x11
+ null_command_factory, // 0x12
+ null_command_factory, // 0x13
+ null_command_factory, // 0x14
+ null_command_factory, // 0x15
+ null_command_factory, // 0x16
+ null_command_factory, // 0x17
+ null_command_factory, // 0x18
+ null_command_factory, // 0x19
+ null_command_factory, // 0x1A
+ null_command_factory, // 0x1B
+ null_command_factory, // 0x1C
+ null_command_factory, // 0x1D
+ null_command_factory, // 0x1E
+ null_command_factory, // 0x1F
{ "TOCLIENT_BLOCKDATA", 2, true }, // 0x20
{ "TOCLIENT_ADDNODE", 0, true }, // 0x21
{ "TOCLIENT_REMOVENODE", 0, true }, // 0x22
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
+ null_command_factory, // 0x23
+ null_command_factory, // 0x24
+ null_command_factory, // 0x25
+ null_command_factory, // 0x26
{ "TOCLIENT_INVENTORY", 0, true }, // 0x27
- null_command_factory,
+ null_command_factory, // 0x28
{ "TOCLIENT_TIME_OF_DAY", 0, true }, // 0x29
{ "TOCLIENT_CSM_RESTRICTION_FLAGS", 0, true }, // 0x2A
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
+ { "TOCLIENT_PLAYER_SPEED", 0, true }, // 0x2B
+ null_command_factory, // 0x2C
+ 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
@@ -167,15 +167,15 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
{ "TOCLIENT_HP", 0, true }, // 0x33
{ "TOCLIENT_MOVE_PLAYER", 0, true }, // 0x34
{ "TOCLIENT_ACCESS_DENIED_LEGACY", 0, true }, // 0x35
- null_command_factory, // 0x36
+ { "TOCLIENT_FOV", 0, true }, // 0x36
{ "TOCLIENT_DEATHSCREEN", 0, true }, // 0x37
{ "TOCLIENT_MEDIA", 2, true }, // 0x38
null_command_factory, // 0x39
- { "TOCLIENT_NODEDEF", 0, true }, // 0x3a
- null_command_factory, // 0x3b
- { "TOCLIENT_ANNOUNCE_MEDIA", 0, true }, // 0x3c
- { "TOCLIENT_ITEMDEF", 0, true }, // 0x3d
- null_command_factory,
+ { "TOCLIENT_NODEDEF", 0, true }, // 0x3A
+ null_command_factory, // 0x3B
+ { "TOCLIENT_ANNOUNCE_MEDIA", 0, true }, // 0x3C
+ { "TOCLIENT_ITEMDEF", 0, true }, // 0x3D
+ null_command_factory, // 0x3E
{ "TOCLIENT_PLAY_SOUND", 0, true }, // 0x3f
{ "TOCLIENT_STOP_SOUND", 0, true }, // 0x40
{ "TOCLIENT_PRIVILEGES", 0, true }, // 0x41
@@ -203,12 +203,12 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
{ "TOCLIENT_MODCHANNEL_MSG", 0, true }, // 0x57
{ "TOCLIENT_MODCHANNEL_SIGNAL", 0, true }, // 0x58
{ "TOCLIENT_NODEMETA_CHANGED", 0, true }, // 0x59
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
- null_command_factory,
+ null_command_factory, // 0x5A
+ null_command_factory, // 0x5B
+ null_command_factory, // 0x5C
+ null_command_factory, // 0x5D
+ null_command_factory, // 0x5E
+ null_command_factory, // 0x5F
{ "TOSERVER_SRP_BYTES_S_B", 0, true }, // 0x60
{ "TOCLIENT_FORMSPEC_PREPEND", 0, true }, // 0x61
};
diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp
index 6f17d666a..d8fbeebd5 100644
--- a/src/network/serverpackethandler.cpp
+++ b/src/network/serverpackethandler.cpp
@@ -298,9 +298,6 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
infostream << "Server: Sending content to "
<< getPlayerName(pkt->getPeerId()) << std::endl;
- // Send player movement settings
- SendMovement(pkt->getPeerId());
-
// Send item definitions
SendItemDef(pkt->getPeerId(), m_itemdef, protocol_version);
@@ -312,8 +309,20 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
// Send media announcement
sendMediaAnnouncement(pkt->getPeerId(), lang);
+ RemoteClient *client = getClient(pkt->getPeerId(), CS_InitDone);
+
+ // Send active objects
+ {
+ PlayerSAO *sao = getPlayerSAO(pkt->getPeerId());
+ if (client && sao)
+ SendActiveObjectRemoveAdd(client, sao);
+ }
+
// Send detached inventories
- sendDetachedInventories(pkt->getPeerId());
+ sendDetachedInventories(pkt->getPeerId(), false);
+
+ // Send player movement settings
+ SendMovement(pkt->getPeerId());
// Send time of day
u16 time = m_env->getTimeOfDay();
@@ -323,11 +332,10 @@ void Server::handleCommand_Init2(NetworkPacket* pkt)
SendCSMRestrictionFlags(pkt->getPeerId());
// Warnings about protocol version can be issued here
- if (getClient(pkt->getPeerId())->net_proto_version < LATEST_PROTOCOL_VERSION) {
+ if (client->net_proto_version < LATEST_PROTOCOL_VERSION) {
SendChatMessage(pkt->getPeerId(), ChatMessage(CHATMESSAGE_TYPE_SYSTEM,
- L"# Server: WARNING: YOUR CLIENT'S VERSION MAY NOT BE FULLY COMPATIBLE "
- L"WITH THIS SERVER!"));
-
+ L"# Server: WARNING: YOUR CLIENT'S VERSION MAY NOT BE FULLY COMPATIBLE "
+ L"WITH THIS SERVER!"));
}
}
@@ -386,6 +394,9 @@ void Server::handleCommand_ClientReady(NetworkPacket* pkt)
peer_id, major_ver, minor_ver, patch_ver,
full_ver);
+ if (pkt->getRemainingBytes() >= 2)
+ *pkt >> playersao->getPlayer()->formspec_version;
+
const std::vector<std::string> &players = m_clients.getPlayerNames();
NetworkPacket list_pkt(TOCLIENT_UPDATE_PLAYER_LIST, 0, peer_id);
list_pkt << (u8) PLAYER_LIST_INIT << (u16) players.size();
@@ -608,10 +619,9 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
ma->from_inv.applyCurrentPlayer(player->getName());
ma->to_inv.applyCurrentPlayer(player->getName());
- setInventoryModified(ma->from_inv, false);
- if (ma->from_inv != ma->to_inv) {
- setInventoryModified(ma->to_inv, false);
- }
+ setInventoryModified(ma->from_inv);
+ if (ma->from_inv != ma->to_inv)
+ setInventoryModified(ma->to_inv);
bool from_inv_is_current_player =
(ma->from_inv.type == InventoryLocation::PLAYER) &&
@@ -676,7 +686,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
da->from_inv.applyCurrentPlayer(player->getName());
- setInventoryModified(da->from_inv, false);
+ setInventoryModified(da->from_inv);
/*
Disable dropping items out of craftpreview
@@ -712,7 +722,7 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
ca->craft_inv.applyCurrentPlayer(player->getName());
- setInventoryModified(ca->craft_inv, false);
+ setInventoryModified(ca->craft_inv);
//bool craft_inv_is_current_player =
// (ca->craft_inv.type == InventoryLocation::PLAYER) &&
@@ -731,8 +741,6 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)
a->apply(this, playersao, this);
// Eat the action
delete a;
-
- SendInventory(playersao);
}
void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
@@ -799,7 +807,7 @@ void Server::handleCommand_Damage(NetworkPacket* pkt)
return;
}
- if (g_settings->getBool("enable_damage")) {
+ if (!playersao->isImmortal()) {
if (playersao->isDead()) {
verbosestream << "Server::ProcessData(): Info: "
"Ignoring damage as player " << player->getName()
@@ -923,7 +931,7 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt)
*pkt >> item;
- playersao->setWieldIndex(item);
+ playersao->getPlayer()->setWieldIndex(item);
}
void Server::handleCommand_Respawn(NetworkPacket* pkt)
@@ -952,22 +960,12 @@ void Server::handleCommand_Respawn(NetworkPacket* pkt)
// the previous addition has been successfully removed
}
-bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std::string what)
+bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what)
{
- PlayerSAO *playersao = player->getPlayerSAO();
- const InventoryList *hlist = playersao->getInventory()->getList("hand");
- const ItemDefinition &playeritem_def =
- playersao->getWieldedItem().getDefinition(m_itemdef);
- const ItemDefinition &hand_def =
- hlist ? hlist->getItem(0).getDefinition(m_itemdef) : m_itemdef->get("");
-
- float max_d = BS * playeritem_def.range;
- float max_d_hand = BS * hand_def.range;
-
- if (max_d < 0 && max_d_hand >= 0)
- max_d = max_d_hand;
- else if (max_d < 0)
- max_d = BS * 4.0f;
+ ItemStack selected_item, hand_item;
+ player->getWieldedItem(&selected_item, &hand_item);
+ f32 max_d = BS * getToolRange(selected_item.getDefinition(m_itemdef),
+ hand_item.getDefinition(m_itemdef));
// Cube diagonal * 1.5 for maximal supported node extents:
// sqrt(3) * 1.5 ≅ 2.6
@@ -978,13 +976,13 @@ bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std:
<< "d=" << d <<", max_d=" << max_d
<< ". ignoring." << std::endl;
// Call callbacks
- m_script->on_cheat(playersao, "interacted_too_far");
+ m_script->on_cheat(player->getPlayerSAO(), "interacted_too_far");
return false;
}
return true;
}
-void Server::handleCommand_Interact(NetworkPacket* pkt)
+void Server::handleCommand_Interact(NetworkPacket *pkt)
{
/*
[0] u16 command
@@ -993,18 +991,14 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
[5] u32 length of the next item (plen)
[9] serialized PointedThing
[9 + plen] player position information
- actions:
- 0: start digging (from undersurface) or use
- 1: stop digging (all parameters ignored)
- 2: digging completed
- 3: place block or item (to abovesurface)
- 4: use item
- 5: rightclick air ("activate")
*/
- u8 action;
+
+ InteractAction action;
u16 item_i;
- *pkt >> action;
+
+ *pkt >> (u8 &)action;
*pkt >> item_i;
+
std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);
PointedThing pointed;
pointed.deSerialize(tmp_is);
@@ -1050,7 +1044,7 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
v3f player_pos = playersao->getLastGoodPosition();
// Update wielded item
- playersao->setWieldIndex(item_i);
+ playersao->getPlayer()->setWieldIndex(item_i);
// Get pointed to node (undefined if not POINTEDTYPE_NODE)
v3s16 p_under = pointed.node_undersurface;
@@ -1083,18 +1077,18 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
Make sure the player is allowed to do it
*/
if (!checkPriv(player->getName(), "interact")) {
- actionstream<<player->getName()<<" attempted to interact with "
- <<pointed.dump()<<" without 'interact' privilege"
- <<std::endl;
+ actionstream << player->getName() << " attempted to interact with " <<
+ pointed.dump() << " without 'interact' privilege" << std::endl;
+
// Re-send block to revert change on client-side
RemoteClient *client = getClient(pkt->getPeerId());
// Digging completed -> under
- if (action == 2) {
+ if (action == INTERACT_DIGGING_COMPLETED) {
v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
client->SetBlockNotSent(blockpos);
}
// Placement -> above
- else if (action == 3) {
+ else if (action == INTERACT_PLACE) {
v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
client->SetBlockNotSent(blockpos);
}
@@ -1108,10 +1102,10 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
static thread_local const bool enable_anticheat =
!g_settings->getBool("disable_anticheat");
- if ((action == 0 || action == 2 || action == 3 || action == 4) &&
+ if ((action == INTERACT_START_DIGGING || action == INTERACT_DIGGING_COMPLETED ||
+ action == INTERACT_PLACE || action == INTERACT_USE) &&
enable_anticheat && !isSingleplayer()) {
- float d = playersao->getEyePosition()
- .getDistanceFrom(pointed_pos_under);
+ float d = playersao->getEyePosition().getDistanceFrom(pointed_pos_under);
if (!checkInteractDistance(player, d, pointed.dump())) {
// Re-send block to revert change on client-side
@@ -1131,12 +1125,12 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
/*
0: start digging or punch object
*/
- if (action == 0) {
+ if (action == INTERACT_START_DIGGING) {
if (pointed.type == POINTEDTHING_NODE) {
MapNode n(CONTENT_IGNORE);
bool pos_ok;
- n = m_env->getMap().getNodeNoEx(p_under, &pos_ok);
+ n = m_env->getMap().getNode(p_under, &pos_ok);
if (!pos_ok) {
infostream << "Server: Not punching: Node not found."
<< " Adding block to emerge queue."
@@ -1156,13 +1150,10 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
if (pointed_object->isGone())
return;
- actionstream<<player->getName()<<" punches object "
- <<pointed.object_id<<": "
- <<pointed_object->getDescription()<<std::endl;
-
- ItemStack punchitem = playersao->getWieldedItemOrHand();
+ ItemStack selected_item, hand_item;
+ ItemStack tool_item = playersao->getWieldedItem(&selected_item, &hand_item);
ToolCapabilities toolcap =
- punchitem.getToolCapabilities(m_itemdef);
+ tool_item.getToolCapabilities(m_itemdef);
v3f dir = (pointed_object->getBasePosition() -
(playersao->getBasePosition() + playersao->getEyeOffset())
).normalize();
@@ -1172,9 +1163,13 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
u16 src_original_hp = pointed_object->getHP();
u16 dst_origin_hp = playersao->getHP();
- pointed_object->punch(dir, &toolcap, playersao,
+ u16 wear = pointed_object->punch(dir, &toolcap, playersao,
time_from_last_punch);
+ bool changed = selected_item.addWear(wear, m_itemdef);
+ if (changed)
+ playersao->setWieldedItem(selected_item);
+
// If the object is a player and its HP changed
if (src_original_hp != pointed_object->getHP() &&
pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
@@ -1188,22 +1183,22 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object));
}
- } // action == 0
+ } // action == INTERACT_START_DIGGING
/*
1: stop digging
*/
- else if (action == 1) {
- } // action == 1
+ else if (action == INTERACT_STOP_DIGGING) {
+ } // action == INTERACT_STOP_DIGGING
/*
2: Digging completed
*/
- else if (action == 2) {
+ else if (action == INTERACT_DIGGING_COMPLETED) {
// Only digging of nodes
if (pointed.type == POINTEDTHING_NODE) {
bool pos_ok;
- MapNode n = m_env->getMap().getNodeNoEx(p_under, &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."
@@ -1228,22 +1223,19 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
// Call callbacks
m_script->on_cheat(playersao, "finished_unknown_dig");
}
+
// Get player's wielded item
- ItemStack playeritem = playersao->getWieldedItemOrHand();
- ToolCapabilities playeritem_toolcap =
- playeritem.getToolCapabilities(m_itemdef);
+ // See also: Game::handleDigging
+ ItemStack selected_item, hand_item;
+ playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item);
+
// Get diggability and expected digging time
DigParams params = getDigParams(m_nodedef->get(n).groups,
- &playeritem_toolcap);
+ &selected_item.getToolCapabilities(m_itemdef));
// If can't dig, try hand
if (!params.diggable) {
- InventoryList *hlist = playersao->getInventory()->getList("hand");
- const ToolCapabilities *tp = hlist
- ? &hlist->getItem(0).getToolCapabilities(m_itemdef)
- : m_itemdef->get("").tool_capabilities;
-
- if (tp)
- params = getDigParams(m_nodedef->get(n).groups, tp);
+ params = getDigParams(m_nodedef->get(n).groups,
+ &hand_item.getToolCapabilities(m_itemdef));
}
// If can't dig, ignore dig
if (!params.diggable) {
@@ -1290,7 +1282,7 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
RemoteClient *client = getClient(pkt->getPeerId());
// Send unusual result (that is, node not being removed)
- if (m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR) {
+ if (m_env->getMap().getNode(p_under).getContent() != CONTENT_AIR) {
// Re-send block to revert change on client-side
client->SetBlockNotSent(blockpos);
}
@@ -1298,17 +1290,18 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
client->ResendBlockIfOnWire(blockpos);
}
}
- } // action == 2
+ } // action == INTERACT_DIGGING_COMPLETED
/*
3: place block or right-click object
*/
- else if (action == 3) {
- ItemStack item = playersao->getWieldedItem();
+ else if (action == INTERACT_PLACE) {
+ ItemStack selected_item;
+ playersao->getWieldedItem(&selected_item, nullptr);
// Reset build time counter
if (pointed.type == POINTEDTHING_NODE &&
- item.getDefinition(m_itemdef).type == ITEM_NODE)
+ selected_item.getDefinition(m_itemdef).type == ITEM_NODE)
getClient(pkt->getPeerId())->m_time_from_building = 0.0;
if (pointed.type == POINTEDTHING_OBJECT) {
@@ -1324,14 +1317,13 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
// Do stuff
pointed_object->rightClick(playersao);
- }
- else if (m_script->item_OnPlace(
- item, playersao, pointed)) {
+ } else if (m_script->item_OnPlace(
+ selected_item, playersao, pointed)) {
// Placement was handled in lua
// Apply returned ItemStack
- if (playersao->setWieldedItem(item)) {
- SendInventory(playersao);
+ if (playersao->setWieldedItem(selected_item)) {
+ SendInventory(playersao, true);
}
}
@@ -1340,7 +1332,7 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
RemoteClient *client = getClient(pkt->getPeerId());
v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS));
v3s16 blockpos2 = getNodeBlockPos(floatToInt(pointed_pos_under, BS));
- if (!item.getDefinition(m_itemdef).node_placement_prediction.empty()) {
+ if (!selected_item.getDefinition(m_itemdef).node_placement_prediction.empty()) {
client->SetBlockNotSent(blockpos);
if (blockpos2 != blockpos) {
client->SetBlockNotSent(blockpos2);
@@ -1352,43 +1344,45 @@ void Server::handleCommand_Interact(NetworkPacket* pkt)
client->ResendBlockIfOnWire(blockpos2);
}
}
- } // action == 3
+ } // action == INTERACT_PLACE
/*
4: use
*/
- else if (action == 4) {
- ItemStack item = playersao->getWieldedItem();
+ else if (action == INTERACT_USE) {
+ ItemStack selected_item;
+ playersao->getWieldedItem(&selected_item, nullptr);
- actionstream << player->getName() << " uses " << item.name
+ actionstream << player->getName() << " uses " << selected_item.name
<< ", pointing at " << pointed.dump() << std::endl;
if (m_script->item_OnUse(
- item, playersao, pointed)) {
+ selected_item, playersao, pointed)) {
// Apply returned ItemStack
- if (playersao->setWieldedItem(item)) {
- SendInventory(playersao);
+ if (playersao->setWieldedItem(selected_item)) {
+ SendInventory(playersao, true);
}
}
- } // action == 4
+ } // action == INTERACT_USE
/*
5: rightclick air
*/
- else if (action == 5) {
- ItemStack item = playersao->getWieldedItem();
+ else if (action == INTERACT_ACTIVATE) {
+ ItemStack selected_item;
+ playersao->getWieldedItem(&selected_item, nullptr);
actionstream << player->getName() << " activates "
- << item.name << std::endl;
+ << selected_item.name << std::endl;
if (m_script->item_OnSecondaryUse(
- item, playersao)) {
- if( playersao->setWieldedItem(item)) {
- SendInventory(playersao);
+ selected_item, playersao)) {
+ if (playersao->setWieldedItem(selected_item)) {
+ SendInventory(playersao, true);
}
}
- }
+ } // action == INTERACT_ACTIVATE
/*
diff --git a/src/nodedef.cpp b/src/nodedef.cpp
index 03a163bd3..977a4533d 100644
--- a/src/nodedef.cpp
+++ b/src/nodedef.cpp
@@ -798,6 +798,8 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
material_type = TILE_MATERIAL_WAVING_PLANTS;
else if (waving == 2)
material_type = TILE_MATERIAL_WAVING_LEAVES;
+ else if (waving == 3)
+ material_type = TILE_MATERIAL_WAVING_LIQUID_BASIC;
break;
case NDT_TORCHLIKE:
case NDT_SIGNLIKE:
@@ -815,8 +817,14 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
correctAlpha(tdef, 6);
correctAlpha(tdef_overlay, 6);
correctAlpha(tdef_spec, CF_SPECIAL_COUNT);
- material_type = (alpha == 255) ?
- TILE_MATERIAL_LIQUID_OPAQUE : TILE_MATERIAL_LIQUID_TRANSPARENT;
+
+ if (waving == 3) {
+ material_type = (alpha == 255) ? TILE_MATERIAL_WAVING_LIQUID_OPAQUE :
+ TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT;
+ } else {
+ material_type = (alpha == 255) ? TILE_MATERIAL_LIQUID_OPAQUE :
+ TILE_MATERIAL_LIQUID_TRANSPARENT;
+ }
}
u32 tile_shader = shdsrc->getShader("nodes_shader", material_type, drawtype);
@@ -1194,6 +1202,26 @@ inline void NodeDefManager::fixSelectionBoxIntUnion()
}
+void NodeDefManager::eraseIdFromGroups(content_t id)
+{
+ // For all groups in m_group_to_items...
+ for (auto iter_groups = m_group_to_items.begin();
+ iter_groups != m_group_to_items.end();) {
+ // Get the group items vector.
+ std::vector<content_t> &items = iter_groups->second;
+
+ // Remove any occurence of the id in the group items vector.
+ items.erase(std::remove(items.begin(), items.end(), id), items.end());
+
+ // If group is empty, erase its vector from the map.
+ if (items.empty())
+ iter_groups = m_group_to_items.erase(iter_groups);
+ else
+ ++iter_groups;
+ }
+}
+
+
// IWritableNodeDefManager
content_t NodeDefManager::set(const std::string &name, const ContentFeatures &def)
{
@@ -1214,19 +1242,24 @@ content_t NodeDefManager::set(const std::string &name, const ContentFeatures &de
assert(id != CONTENT_IGNORE);
addNameIdMapping(id, name);
}
+
+ // If there is already ContentFeatures registered for this id, clear old groups
+ if (id < m_content_features.size())
+ eraseIdFromGroups(id);
+
m_content_features[id] = def;
verbosestream << "NodeDefManager: registering content id \"" << id
<< "\": name=\"" << def.name << "\""<<std::endl;
getNodeBoxUnion(def.selection_box, def, &m_selection_box_union);
fixSelectionBoxIntUnion();
+
// Add this content to the list of all groups it belongs to
- // FIXME: This should remove a node from groups it no longer
- // belongs to when a node is re-registered
for (const auto &group : def.groups) {
const std::string &group_name = group.first;
m_group_to_items[group_name].push_back(id);
}
+
return id;
}
@@ -1252,18 +1285,7 @@ void NodeDefManager::removeNode(const std::string &name)
m_name_id_mapping_with_aliases.erase(name);
}
- // Erase node content from all groups it belongs to
- for (std::unordered_map<std::string, std::vector<content_t>>::iterator iter_groups =
- m_group_to_items.begin(); iter_groups != m_group_to_items.end();) {
- std::vector<content_t> &items = iter_groups->second;
- items.erase(std::remove(items.begin(), items.end(), id), items.end());
-
- // Check if group is empty
- if (items.empty())
- m_group_to_items.erase(iter_groups++);
- else
- ++iter_groups;
- }
+ eraseIdFromGroups(id);
}
@@ -1292,8 +1314,11 @@ void NodeDefManager::applyTextureOverrides(const std::string &override_filepath)
int line_c = 0;
while (std::getline(infile, line)) {
line_c++;
- if (trim(line).empty())
+ // 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
diff --git a/src/nodedef.h b/src/nodedef.h
index 60d91f8d9..1a12aae93 100644
--- a/src/nodedef.h
+++ b/src/nodedef.h
@@ -669,6 +669,14 @@ private:
void addNameIdMapping(content_t i, std::string name);
/*!
+ * Removes a content ID from all groups.
+ * Erases content IDs from vectors in \ref m_group_to_items and
+ * removes empty vectors.
+ * @param id Content ID
+ */
+ void eraseIdFromGroups(content_t id);
+
+ /*!
* Recalculates m_selection_box_int_union based on
* m_selection_box_union.
*/
diff --git a/src/pathfinder.cpp b/src/pathfinder.cpp
index 8f6033813..48e951477 100644
--- a/src/pathfinder.cpp
+++ b/src/pathfinder.cpp
@@ -498,8 +498,8 @@ void GridNodeContainer::initNode(v3s16 ipos, PathGridnode *p_node)
v3s16 realpos = m_pathf->getRealPos(ipos);
- MapNode current = m_pathf->m_env->getMap().getNodeNoEx(realpos);
- MapNode below = m_pathf->m_env->getMap().getNodeNoEx(realpos + v3s16(0, -1, 0));
+ MapNode current = m_pathf->m_env->getMap().getNode(realpos);
+ MapNode below = m_pathf->m_env->getMap().getNode(realpos + v3s16(0, -1, 0));
if ((current.param0 == CONTENT_IGNORE) ||
@@ -769,7 +769,7 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir)
return retval;
}
- MapNode node_at_pos2 = m_env->getMap().getNodeNoEx(pos2);
+ MapNode node_at_pos2 = m_env->getMap().getNode(pos2);
//did we get information about node?
if (node_at_pos2.param0 == CONTENT_IGNORE ) {
@@ -780,7 +780,7 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir)
if (!ndef->get(node_at_pos2).walkable) {
MapNode node_below_pos2 =
- m_env->getMap().getNodeNoEx(pos2 + v3s16(0, -1, 0));
+ m_env->getMap().getNode(pos2 + v3s16(0, -1, 0));
//did we get information about node?
if (node_below_pos2.param0 == CONTENT_IGNORE ) {
@@ -798,13 +798,13 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir)
}
else {
v3s16 testpos = pos2 - v3s16(0, -1, 0);
- MapNode node_at_pos = m_env->getMap().getNodeNoEx(testpos);
+ MapNode node_at_pos = m_env->getMap().getNode(testpos);
while ((node_at_pos.param0 != CONTENT_IGNORE) &&
(!ndef->get(node_at_pos).walkable) &&
(testpos.Y > m_limits.MinEdge.Y)) {
testpos += v3s16(0, -1, 0);
- node_at_pos = m_env->getMap().getNodeNoEx(testpos);
+ node_at_pos = m_env->getMap().getNode(testpos);
}
//did we find surface?
@@ -832,13 +832,13 @@ PathCost Pathfinder::calcCost(v3s16 pos, v3s16 dir)
}
else {
v3s16 testpos = pos2;
- MapNode node_at_pos = m_env->getMap().getNodeNoEx(testpos);
+ MapNode node_at_pos = m_env->getMap().getNode(testpos);
while ((node_at_pos.param0 != CONTENT_IGNORE) &&
(ndef->get(node_at_pos).walkable) &&
(testpos.Y < m_limits.MaxEdge.Y)) {
testpos += v3s16(0, 1, 0);
- node_at_pos = m_env->getMap().getNodeNoEx(testpos);
+ node_at_pos = m_env->getMap().getNode(testpos);
}
//did we find surface?
diff --git a/src/player.cpp b/src/player.cpp
index 9beeab74e..d3ba5c2c2 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -90,6 +90,29 @@ Player::~Player()
clearHud();
}
+void Player::setWieldIndex(u16 index)
+{
+ const InventoryList *mlist = inventory.getList("main");
+ m_wield_index = MYMIN(index, mlist ? mlist->getSize() : 0);
+}
+
+ItemStack &Player::getWieldedItem(ItemStack *selected, ItemStack *hand) const
+{
+ assert(selected);
+
+ const InventoryList *mlist = inventory.getList("main"); // TODO: Make this generic
+ const InventoryList *hlist = inventory.getList("hand");
+
+ if (mlist && m_wield_index < mlist->getSize())
+ *selected = mlist->getItem(m_wield_index);
+
+ if (hand && hlist)
+ *hand = hlist->getItem(0);
+
+ // Return effective tool item
+ return (hand && selected->name.empty()) ? *hand : *selected;
+}
+
u32 Player::addHud(HudElement *toadd)
{
MutexAutoLock lock(m_mutex);
diff --git a/src/player.h b/src/player.h
index 05b6fcbb5..de7f427e9 100644
--- a/src/player.h
+++ b/src/player.h
@@ -32,6 +32,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define PLAYERNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
#define PLAYERNAME_ALLOWED_CHARS_USER_EXPL "'a' to 'z', 'A' to 'Z', '0' to '9', '-', '_'"
+struct PlayerFovSpec
+{
+ f32 fov;
+ bool is_multiplier;
+};
+
struct PlayerControl
{
PlayerControl() = default;
@@ -173,6 +179,21 @@ public:
PlayerSettings &getPlayerSettings() { return m_player_settings; }
static void settingsChangedCallback(const std::string &name, void *data);
+ // Returns non-empty `selected` ItemStack. `hand` is a fallback, if specified
+ ItemStack &getWieldedItem(ItemStack *selected, ItemStack *hand) const;
+ void setWieldIndex(u16 index);
+ u16 getWieldIndex() const { return m_wield_index; }
+
+ void setFov(const PlayerFovSpec &spec)
+ {
+ m_fov_spec = spec;
+ }
+
+ const PlayerFovSpec &getFov() const
+ {
+ return m_fov_spec;
+ }
+
u32 keyPressed = 0;
HudElement* getHud(u32 id);
@@ -182,9 +203,12 @@ public:
u32 hud_flags;
s32 hud_hotbar_itemcount;
+
protected:
char m_name[PLAYERNAME_SIZE];
v3f m_speed;
+ u16 m_wield_index = 0;
+ PlayerFovSpec m_fov_spec = { 0.0f, false };
std::vector<HudElement *> hud;
private:
diff --git a/src/profiler.cpp b/src/profiler.cpp
index f8d4fc181..be8be591e 100644
--- a/src/profiler.cpp
+++ b/src/profiler.cpp
@@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include "profiler.h"
+#include "porting.h"
static Profiler main_profiler;
Profiler *g_profiler = &main_profiler;
@@ -26,8 +27,9 @@ ScopeProfiler::ScopeProfiler(
m_profiler(profiler),
m_name(name), m_type(type)
{
+ m_name.append(" [ms]");
if (m_profiler)
- m_timer = new TimeTaker(m_name);
+ m_timer = new TimeTaker(m_name, nullptr, PRECISION_MILLI);
}
ScopeProfiler::~ScopeProfiler()
@@ -52,3 +54,129 @@ ScopeProfiler::~ScopeProfiler()
}
delete m_timer;
}
+
+Profiler::Profiler()
+{
+ m_start_time = porting::getTimeMs();
+}
+
+void Profiler::add(const std::string &name, float value)
+{
+ MutexAutoLock lock(m_mutex);
+ {
+ /* No average shall have been used; mark add used as -2 */
+ std::map<std::string, int>::iterator n = m_avgcounts.find(name);
+ if (n == m_avgcounts.end()) {
+ m_avgcounts[name] = -2;
+ } else {
+ if (n->second == -1)
+ n->second = -2;
+ assert(n->second == -2);
+ }
+ }
+ {
+ std::map<std::string, float>::iterator n = m_data.find(name);
+ if (n == m_data.end())
+ m_data[name] = value;
+ else
+ n->second += value;
+ }
+}
+
+void Profiler::avg(const std::string &name, float value)
+{
+ MutexAutoLock lock(m_mutex);
+ int &count = m_avgcounts[name];
+
+ assert(count != -2);
+ count = MYMAX(count, 0) + 1;
+ m_data[name] += value;
+}
+
+void Profiler::clear()
+{
+ MutexAutoLock lock(m_mutex);
+ for (auto &it : m_data) {
+ it.second = 0;
+ }
+ m_avgcounts.clear();
+ m_start_time = porting::getTimeMs();
+}
+
+float Profiler::getValue(const std::string &name) const
+{
+ auto numerator = m_data.find(name);
+ if (numerator == m_data.end())
+ return 0.f;
+
+ auto denominator = m_avgcounts.find(name);
+ if (denominator != m_avgcounts.end()) {
+ if (denominator->second >= 1)
+ return numerator->second / denominator->second;
+ }
+
+ return numerator->second;
+}
+
+int Profiler::getAvgCount(const std::string &name) const
+{
+ auto n = m_avgcounts.find(name);
+
+ if (n != m_avgcounts.end() && n->second >= 1)
+ return n->second;
+
+ return 1;
+}
+
+u64 Profiler::getElapsedMs() const
+{
+ return porting::getTimeMs() - m_start_time;
+}
+
+int Profiler::print(std::ostream &o, u32 page, u32 pagecount)
+{
+ GraphValues values;
+ getPage(values, page, pagecount);
+ char num_buf[50];
+
+ for (const auto &i : values) {
+ o << " " << i.first << " ";
+ if (i.second == 0) {
+ o << std::endl;
+ continue;
+ }
+
+ s32 space = 44 - i.first.size();
+ for (s32 j = 0; j < space; j++) {
+ if ((j & 1) && j < space - 1)
+ o << ".";
+ else
+ o << " ";
+ }
+ porting::mt_snprintf(num_buf, sizeof(num_buf), "% 4ix % 3g",
+ getAvgCount(i.first), i.second);
+ o << num_buf << std::endl;
+ }
+ return values.size();
+}
+
+void Profiler::getPage(GraphValues &o, u32 page, u32 pagecount)
+{
+ MutexAutoLock lock(m_mutex);
+
+ u32 minindex, maxindex;
+ paging(m_data.size(), page, pagecount, minindex, maxindex);
+
+ for (const auto &i : m_data) {
+ if (maxindex == 0)
+ break;
+ maxindex--;
+
+ if (minindex != 0) {
+ minindex--;
+ continue;
+ }
+
+ o[i.first] = i.second / getAvgCount(i.first);
+ }
+}
diff --git a/src/profiler.h b/src/profiler.h
index 6704afd51..b4a0657f9 100644
--- a/src/profiler.h
+++ b/src/profiler.h
@@ -29,8 +29,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/timetaker.h"
#include "util/numeric.h" // paging()
-#define MAX_PROFILER_TEXT_ROWS 20
-
// Global profiler
class Profiler;
extern Profiler *g_profiler;
@@ -42,109 +40,22 @@ extern Profiler *g_profiler;
class Profiler
{
public:
- Profiler() = default;
+ Profiler();
- void add(const std::string &name, float value)
- {
- MutexAutoLock lock(m_mutex);
- {
- /* No average shall have been used; mark add used as -2 */
- std::map<std::string, int>::iterator n = m_avgcounts.find(name);
- if(n == m_avgcounts.end())
- m_avgcounts[name] = -2;
- else{
- if(n->second == -1)
- n->second = -2;
- assert(n->second == -2);
- }
- }
- {
- std::map<std::string, float>::iterator n = m_data.find(name);
- if(n == m_data.end())
- m_data[name] = value;
- else
- n->second += value;
- }
- }
+ void add(const std::string &name, float value);
+ void avg(const std::string &name, float value);
+ void clear();
- void avg(const std::string &name, float value)
- {
- MutexAutoLock lock(m_mutex);
- int &count = m_avgcounts[name];
+ float getValue(const std::string &name) const;
+ int getAvgCount(const std::string &name) const;
+ u64 getElapsedMs() const;
- assert(count != -2);
- count = MYMAX(count, 0) + 1;
- m_data[name] += value;
- }
-
- void clear()
- {
- MutexAutoLock lock(m_mutex);
- for (auto &it : m_data) {
- it.second = 0;
- }
- m_avgcounts.clear();
- }
-
- void print(std::ostream &o)
- {
- printPage(o, 1, 1);
- }
-
- float getValue(const std::string &name) const
- {
- std::map<std::string, float>::const_iterator numerator = m_data.find(name);
- if (numerator == m_data.end())
- return 0.f;
-
- std::map<std::string, int>::const_iterator denominator = m_avgcounts.find(name);
- if (denominator != m_avgcounts.end()){
- if (denominator->second >= 1)
- return numerator->second / denominator->second;
- }
-
- return numerator->second;
- }
-
- void printPage(std::ostream &o, u32 page, u32 pagecount)
- {
- MutexAutoLock lock(m_mutex);
+ typedef std::map<std::string, float> GraphValues;
- u32 minindex, maxindex;
- paging(m_data.size(), page, pagecount, minindex, maxindex);
-
- for (std::map<std::string, float>::const_iterator i = m_data.begin();
- i != m_data.end(); ++i) {
- if (maxindex == 0)
- break;
- maxindex--;
-
- if (minindex != 0) {
- minindex--;
- continue;
- }
-
- int avgcount = 1;
- std::map<std::string, int>::const_iterator n = m_avgcounts.find(i->first);
- if (n != m_avgcounts.end()) {
- if(n->second >= 1)
- avgcount = n->second;
- }
- o << " " << i->first << ": ";
- s32 clampsize = 40;
- s32 space = clampsize - i->first.size();
- for(s32 j = 0; j < space; j++) {
- if (j % 2 == 0 && j < space - 1)
- o << "-";
- else
- o << " ";
- }
- o << (i->second / avgcount);
- o << std::endl;
- }
- }
+ // Returns the line count
+ int print(std::ostream &o, u32 page = 1, u32 pagecount = 1);
+ void getPage(GraphValues &o, u32 page, u32 pagecount);
- typedef std::map<std::string, float> GraphValues;
void graphAdd(const std::string &id, float value)
{
@@ -175,6 +86,7 @@ private:
std::map<std::string, float> m_data;
std::map<std::string, int> m_avgcounts;
std::map<std::string, float> m_graphvalues;
+ u64 m_start_time;
};
enum ScopeProfilerType{
diff --git a/src/remoteplayer.h b/src/remoteplayer.h
index ea118e604..831bfe956 100644
--- a/src/remoteplayer.h
+++ b/src/remoteplayer.h
@@ -110,12 +110,7 @@ public:
bool checkModified() const { return m_dirty || inventory.checkModified(); }
- void setModified(const bool x)
- {
- m_dirty = x;
- if (!x)
- inventory.setModified(x);
- }
+ inline void setModified(const bool x) { m_dirty = x; }
void setLocalAnimations(v2s32 frames[4], float frame_speed)
{
@@ -135,6 +130,9 @@ public:
u16 protocol_version = 0;
+ // v1 for clients older than 5.1.0-dev
+ u16 formspec_version = 1;
+
session_t getPeerId() const { return m_peer_id; }
void setPeerId(session_t peer_id) { m_peer_id = peer_id; }
diff --git a/src/rollback_interface.cpp b/src/rollback_interface.cpp
index 3ac15544c..c00206e98 100644
--- a/src/rollback_interface.cpp
+++ b/src/rollback_interface.cpp
@@ -37,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
RollbackNode::RollbackNode(Map *map, v3s16 p, IGameDef *gamedef)
{
const NodeDefManager *ndef = gamedef->ndef();
- MapNode n = map->getNodeNoEx(p);
+ MapNode n = map->getNode(p);
name = ndef->get(n).name;
param1 = n.param1;
param2 = n.param2;
@@ -132,14 +132,19 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam
// Make sure position is loaded from disk
map->emergeBlock(getContainerPos(p, MAP_BLOCKSIZE), false);
// Check current node
- MapNode current_node = map->getNodeNoEx(p);
+ MapNode current_node = map->getNode(p);
std::string current_name = ndef->get(current_node).name;
// If current node not the new node, it's bad
if (current_name != n_new.name) {
return false;
}
// Create rollback node
- MapNode n(ndef, n_old.name, n_old.param1, n_old.param2);
+ content_t id = CONTENT_IGNORE;
+ if (!ndef->getId(n_old.name, id)) {
+ // The old node is not registered
+ return false;
+ }
+ MapNode n(id, n_old.param1, n_old.param2);
// Set rollback node
try {
if (!map->addNodeWithEvent(p, n)) {
@@ -171,7 +176,7 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam
MapEditEvent event;
event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
event.p = p;
- map->dispatchEvent(&event);
+ map->dispatchEvent(event);
} catch (InvalidPositionException &e) {
infostream << "RollbackAction::applyRevert(): "
<< "InvalidPositionException: " << e.what()
@@ -203,7 +208,7 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam
<< inventory_location << std::endl;
return false;
}
-
+
// If item was added, take away item, otherwise add removed item
if (inventory_add) {
// Silently ignore different current item
diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp
index 793485e25..cb0253c32 100644
--- a/src/script/common/c_content.cpp
+++ b/src/script/common/c_content.cpp
@@ -21,6 +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 "cpp_api/s_node.h"
#include "lua_api/l_object.h"
#include "lua_api/l_item.h"
@@ -163,11 +164,7 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i)
lua_pushboolean(L, i.liquids_pointable);
lua_setfield(L, -2, "liquids_pointable");
if (i.type == ITEM_TOOL) {
- push_tool_capabilities(L, ToolCapabilities(
- i.tool_capabilities->full_punch_interval,
- i.tool_capabilities->max_drop_level,
- i.tool_capabilities->groupcaps,
- i.tool_capabilities->damageGroups));
+ push_tool_capabilities(L, *i.tool_capabilities);
lua_setfield(L, -2, "tool_capabilities");
}
push_groups(L, i.groups);
@@ -182,7 +179,7 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i)
/******************************************************************************/
void read_object_properties(lua_State *L, int index,
- ObjectProperties *prop, IItemDefManager *idef)
+ ServerActiveObject *sao, ObjectProperties *prop, IItemDefManager *idef)
{
if(index < 0)
index = lua_gettop(L) + 1 + index;
@@ -190,10 +187,24 @@ void read_object_properties(lua_State *L, int index,
return;
int hp_max = 0;
- if (getintfield(L, -1, "hp_max", hp_max))
+ if (getintfield(L, -1, "hp_max", hp_max)) {
prop->hp_max = (u16)rangelim(hp_max, 0, U16_MAX);
- getintfield(L, -1, "breath_max", prop->breath_max);
+ if (prop->hp_max < sao->getHP()) {
+ PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP);
+ sao->setHP(prop->hp_max, reason);
+ if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER)
+ sao->getEnv()->getGameDef()->SendPlayerHPOrDie((PlayerSAO *)sao, reason);
+ }
+ }
+
+ if (getintfield(L, -1, "breath_max", prop->breath_max)) {
+ if (sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
+ PlayerSAO *player = (PlayerSAO *)sao;
+ if (prop->breath_max < player->getBreath())
+ player->setBreath(prop->breath_max);
+ }
+ }
getboolfield(L, -1, "physical", prop->physical);
getboolfield(L, -1, "collide_with_objects", prop->collideWithObjects);
@@ -1093,7 +1104,7 @@ MapNode readnode(lua_State *L, int index, const NodeDefManager *ndef)
lua_getfield(L, index, "name");
if (!lua_isstring(L, -1))
throw LuaError("Node name is not set or is not a string!");
- const char *name = lua_tostring(L, -1);
+ std::string name = lua_tostring(L, -1);
lua_pop(L, 1);
u8 param1 = 0;
@@ -1108,7 +1119,11 @@ MapNode readnode(lua_State *L, int index, const NodeDefManager *ndef)
param2 = lua_tonumber(L, -1);
lua_pop(L, 1);
- return {ndef, name, param1, param2};
+ content_t id = CONTENT_IGNORE;
+ if (!ndef->getId(name, id))
+ throw LuaError("\"" + name + "\" is not a registered node!");
+
+ return {id, param1, param2};
}
/******************************************************************************/
@@ -1234,7 +1249,8 @@ void push_tool_capabilities(lua_State *L,
{
lua_newtable(L);
setfloatfield(L, -1, "full_punch_interval", toolcap.full_punch_interval);
- setintfield(L, -1, "max_drop_level", toolcap.max_drop_level);
+ setintfield(L, -1, "max_drop_level", toolcap.max_drop_level);
+ setintfield(L, -1, "punch_attack_uses", toolcap.punch_attack_uses);
// Create groupcaps table
lua_newtable(L);
// For each groupcap
@@ -1356,6 +1372,7 @@ ToolCapabilities read_tool_capabilities(
ToolCapabilities toolcap;
getfloatfield(L, table, "full_punch_interval", toolcap.full_punch_interval);
getintfield(L, table, "max_drop_level", toolcap.max_drop_level);
+ getintfield(L, table, "punch_attack_uses", toolcap.punch_attack_uses);
lua_getfield(L, table, "groupcaps");
if(lua_istable(L, -1)){
int table_groupcaps = lua_gettop(L);
@@ -1510,13 +1527,15 @@ void read_groups(lua_State *L, int index, ItemGroupList &result)
return;
result.clear();
lua_pushnil(L);
- if(index < 0)
+ if (index < 0)
index -= 1;
- while(lua_next(L, index) != 0){
+ while (lua_next(L, index) != 0) {
// key at index -2 and value at index -1
std::string name = luaL_checkstring(L, -2);
int rating = luaL_checkinteger(L, -1);
- result[name] = rating;
+ // zero rating indicates not in the group
+ if (rating != 0)
+ result[name] = rating;
// removes value, keeps key for next iteration
lua_pop(L, 1);
}
diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h
index f3a653682..9e755682f 100644
--- a/src/script/common/c_content.h
+++ b/src/script/common/c_content.h
@@ -62,6 +62,7 @@ struct HitParams;
struct EnumString;
struct NoiseParams;
class Schematic;
+class ServerActiveObject;
ContentFeatures read_content_features (lua_State *L, int index);
@@ -107,6 +108,7 @@ void push_item_definition_full (lua_State *L,
const ItemDefinition &i);
void read_object_properties (lua_State *L, int index,
+ ServerActiveObject *sao,
ObjectProperties *prop,
IItemDefManager *idef);
void push_object_properties (lua_State *L,
diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp
index dfd3f5cea..b9d6f0494 100644
--- a/src/script/common/c_converter.cpp
+++ b/src/script/common/c_converter.cpp
@@ -540,9 +540,9 @@ v3s16 getv3s16field_default(lua_State *L, int table,
}
void setstringfield(lua_State *L, int table,
- const char *fieldname, const char *value)
+ const char *fieldname, const std::string &value)
{
- lua_pushstring(L, value);
+ lua_pushlstring(L, value.c_str(), value.length());
if(table < 0)
table -= 1;
lua_setfield(L, table, fieldname);
diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h
index 87bc35ac6..f84494c8d 100644
--- a/src/script/common/c_converter.h
+++ b/src/script/common/c_converter.h
@@ -91,7 +91,7 @@ std::string checkstringfield(lua_State *L, int table,
const char *fieldname);
void setstringfield(lua_State *L, int table,
- const char *fieldname, const char *value);
+ const char *fieldname, const std::string &value);
void setintfield(lua_State *L, int table,
const char *fieldname, int value);
void setfloatfield(lua_State *L, int table,
diff --git a/src/script/common/helper.cpp b/src/script/common/helper.cpp
index 59bde57ab..f53a2b7e8 100644
--- a/src/script/common/helper.cpp
+++ b/src/script/common/helper.cpp
@@ -107,9 +107,10 @@ template <> v2f LuaHelper::readParam(lua_State *L, int index)
template <> std::string LuaHelper::readParam(lua_State *L, int index)
{
+ size_t length;
std::string result;
- const char *str = luaL_checkstring(L, index);
- result.append(str);
+ const char *str = luaL_checklstring(L, index, &length);
+ result.assign(str, length);
return result;
}
diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp
index a8ed902dd..caa335d76 100644
--- a/src/script/cpp_api/s_base.cpp
+++ b/src/script/cpp_api/s_base.cpp
@@ -232,6 +232,13 @@ void ScriptApiBase::loadModFromMemory(const std::string &mod_name)
void ScriptApiBase::runCallbacksRaw(int nargs,
RunCallbacksMode mode, const char *fxn)
{
+#ifndef SERVER
+ // Hard fail for bad guarded callbacks
+ // Only run callbacks when the scripting enviroment is loaded
+ FATAL_ERROR_IF(m_type == ScriptingType::Client &&
+ !getClient()->modsLoaded(), fxn);
+#endif
+
#ifdef SCRIPTAPI_LOCK_DEBUG
assert(m_lock_recursion_count > 0);
#endif
@@ -404,6 +411,10 @@ void ScriptApiBase::pushPlayerHPChangeReason(lua_State *L, const PlayerHPChangeR
objectrefGetOrCreate(L, reason.object);
lua_setfield(L, -2, "object");
}
+ if (!reason.node.empty()) {
+ lua_pushstring(L, reason.node.c_str());
+ lua_setfield(L, -2, "node");
+ }
}
Server* ScriptApiBase::getServer()
diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp
index 8af9f9bf6..26c7e8cd4 100644
--- a/src/script/cpp_api/s_entity.cpp
+++ b/src/script/cpp_api/s_entity.cpp
@@ -157,7 +157,7 @@ std::string ScriptApiEntity::luaentity_GetStaticdata(u16 id)
}
void ScriptApiEntity::luaentity_GetProperties(u16 id,
- ObjectProperties *prop)
+ ServerActiveObject *self, ObjectProperties *prop)
{
SCRIPTAPI_PRECHECKHEADER
@@ -170,11 +170,11 @@ void ScriptApiEntity::luaentity_GetProperties(u16 id,
prop->hp_max = 10;
// Deprecated: read object properties directly
- read_object_properties(L, -1, prop, getServer()->idef());
+ read_object_properties(L, -1, self, prop, getServer()->idef());
// Read initial_properties
lua_getfield(L, -1, "initial_properties");
- read_object_properties(L, -1, prop, getServer()->idef());
+ read_object_properties(L, -1, self, prop, getServer()->idef());
lua_pop(L, 1);
}
diff --git a/src/script/cpp_api/s_entity.h b/src/script/cpp_api/s_entity.h
index 966c2745e..cc08c46e8 100644
--- a/src/script/cpp_api/s_entity.h
+++ b/src/script/cpp_api/s_entity.h
@@ -35,7 +35,7 @@ public:
void luaentity_Remove(u16 id);
std::string luaentity_GetStaticdata(u16 id);
void luaentity_GetProperties(u16 id,
- ObjectProperties *prop);
+ ServerActiveObject *self, ObjectProperties *prop);
void luaentity_Step(u16 id, float dtime);
bool luaentity_Punch(u16 id,
ServerActiveObject *puncher, float time_from_last_punch,
diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp
index f8cef98b7..ab3b5fe46 100644
--- a/src/script/cpp_api/s_env.cpp
+++ b/src/script/cpp_api/s_env.cpp
@@ -151,6 +151,10 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env)
bool simple_catch_up = true;
getboolfield(L, current_abm, "catch_up", simple_catch_up);
+ lua_getfield(L, current_abm, "action");
+ luaL_checktype(L, current_abm + 1, LUA_TFUNCTION);
+ lua_pop(L, 1);
+
LuaABM *abm = new LuaABM(L, id, trigger_contents, required_neighbors,
trigger_interval, trigger_chance, simple_catch_up);
@@ -200,6 +204,10 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env)
bool run_at_every_load = getboolfield_default(L, current_lbm,
"run_at_every_load", false);
+ lua_getfield(L, current_lbm, "action");
+ luaL_checktype(L, current_lbm + 1, LUA_TFUNCTION);
+ lua_pop(L, 1);
+
LuaLBM *lbm = new LuaLBM(L, id, trigger_contents, name,
run_at_every_load);
diff --git a/src/script/cpp_api/s_node.cpp b/src/script/cpp_api/s_node.cpp
index 719f53a6b..d93a4c3ad 100644
--- a/src/script/cpp_api/s_node.cpp
+++ b/src/script/cpp_api/s_node.cpp
@@ -246,7 +246,7 @@ void ScriptApiNode::node_on_receive_fields(v3s16 p,
const NodeDefManager *ndef = getServer()->ndef();
// If node doesn't exist, we don't know what callback to call
- MapNode node = getEnv()->getMap().getNodeNoEx(p);
+ MapNode node = getEnv()->getMap().getNode(p);
if (node.getContent() == CONTENT_IGNORE)
return;
diff --git a/src/script/cpp_api/s_nodemeta.cpp b/src/script/cpp_api/s_nodemeta.cpp
index b49bb8170..c081e9fc4 100644
--- a/src/script/cpp_api/s_nodemeta.cpp
+++ b/src/script/cpp_api/s_nodemeta.cpp
@@ -38,7 +38,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowMove(
const NodeDefManager *ndef = getServer()->ndef();
// If node doesn't exist, we don't know what callback to call
- MapNode node = getEnv()->getMap().getNodeNoEx(ma.to_inv.p);
+ MapNode node = getEnv()->getMap().getNode(ma.to_inv.p);
if (node.getContent() == CONTENT_IGNORE)
return 0;
@@ -76,7 +76,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowPut(
const NodeDefManager *ndef = getServer()->ndef();
// If node doesn't exist, we don't know what callback to call
- MapNode node = getEnv()->getMap().getNodeNoEx(ma.to_inv.p);
+ MapNode node = getEnv()->getMap().getNode(ma.to_inv.p);
if (node.getContent() == CONTENT_IGNORE)
return 0;
@@ -112,7 +112,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowTake(
const NodeDefManager *ndef = getServer()->ndef();
// If node doesn't exist, we don't know what callback to call
- MapNode node = getEnv()->getMap().getNodeNoEx(ma.from_inv.p);
+ MapNode node = getEnv()->getMap().getNode(ma.from_inv.p);
if (node.getContent() == CONTENT_IGNORE)
return 0;
@@ -148,7 +148,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnMove(
const NodeDefManager *ndef = getServer()->ndef();
// If node doesn't exist, we don't know what callback to call
- MapNode node = getEnv()->getMap().getNodeNoEx(ma.from_inv.p);
+ MapNode node = getEnv()->getMap().getNode(ma.from_inv.p);
if (node.getContent() == CONTENT_IGNORE)
return;
@@ -181,7 +181,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnPut(
const NodeDefManager *ndef = getServer()->ndef();
// If node doesn't exist, we don't know what callback to call
- MapNode node = getEnv()->getMap().getNodeNoEx(ma.to_inv.p);
+ MapNode node = getEnv()->getMap().getNode(ma.to_inv.p);
if (node.getContent() == CONTENT_IGNORE)
return;
@@ -212,7 +212,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnTake(
const NodeDefManager *ndef = getServer()->ndef();
// If node doesn't exist, we don't know what callback to call
- MapNode node = getEnv()->getMap().getNodeNoEx(ma.from_inv.p);
+ MapNode node = getEnv()->getMap().getNode(ma.from_inv.p);
if (node.getContent() == CONTENT_IGNORE)
return;
diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp
index e9067a54c..b90b3aa2c 100644
--- a/src/script/cpp_api/s_security.cpp
+++ b/src/script/cpp_api/s_security.cpp
@@ -113,7 +113,6 @@ void ScriptApiSecurity::initializeSecurity()
"setupvalue",
"setmetatable",
"upvalueid",
- "upvaluejoin",
"sethook",
"debug",
"setlocal",
@@ -244,6 +243,7 @@ void ScriptApiSecurity::initializeSecurityClient()
"rawset",
"select",
"setfenv",
+ // getmetatable can be used to escape the sandbox
"setmetatable",
"tonumber",
"tostring",
@@ -265,6 +265,7 @@ void ScriptApiSecurity::initializeSecurityClient()
};
static const char *debug_whitelist[] = {
"getinfo",
+ "traceback"
};
#if USE_LUAJIT
diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp
index 3b461a2a3..1ce2f9d45 100644
--- a/src/script/cpp_api/s_server.cpp
+++ b/src/script/cpp_api/s_server.cpp
@@ -168,3 +168,25 @@ void ScriptApiServer::on_shutdown()
runCallbacks(0, RUN_CALLBACKS_MODE_FIRST);
}
+std::string ScriptApiServer::formatChatMessage(const std::string &name,
+ const std::string &message)
+{
+ SCRIPTAPI_PRECHECKHEADER
+
+ // Push function onto stack
+ lua_getglobal(L, "core");
+ lua_getfield(L, -1, "format_chat_message");
+
+ // Push arguments onto stack
+ lua_pushstring(L, name.c_str());
+ lua_pushstring(L, message.c_str());
+
+ // Actually call the function
+ lua_call(L, 2, 1);
+
+ // Fetch return value
+ std::string ret = lua_tostring(L, -1);
+ lua_pop(L, 1);
+
+ return ret;
+}
diff --git a/src/script/cpp_api/s_server.h b/src/script/cpp_api/s_server.h
index 769939d3f..a4cede84d 100644
--- a/src/script/cpp_api/s_server.h
+++ b/src/script/cpp_api/s_server.h
@@ -36,14 +36,18 @@ public:
// Calls on_shutdown handlers
void on_shutdown();
+ // Calls core.format_chat_message
+ std::string formatChatMessage(const std::string &name,
+ const std::string &message);
+
/* auth */
bool getAuth(const std::string &playername,
- std::string *dst_password,
- std::set<std::string> *dst_privs);
+ std::string *dst_password,
+ std::set<std::string> *dst_privs);
void createAuth(const std::string &playername,
- const std::string &password);
+ const std::string &password);
bool setPassword(const std::string &playername,
- const std::string &password);
+ const std::string &password);
private:
void getAuthHandler();
void readPrivileges(int index, std::set<std::string> &result);
diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp
index d53d74aa8..908c766b0 100644
--- a/src/script/lua_api/l_areastore.cpp
+++ b/src/script/lua_api/l_areastore.cpp
@@ -185,6 +185,7 @@ int LuaAreaStore::l_insert_area(lua_State *L)
if (lua_isnumber(L, 5))
a.id = lua_tonumber(L, 5);
+ // Insert & assign a new ID if necessary
if (!ast->insertArea(&a))
return 0;
diff --git a/src/script/lua_api/l_camera.cpp b/src/script/lua_api/l_camera.cpp
index 462006777..80071b3b8 100644
--- a/src/script/lua_api/l_camera.cpp
+++ b/src/script/lua_api/l_camera.cpp
@@ -31,19 +31,24 @@ LuaCamera::LuaCamera(Camera *m) : m_camera(m)
void LuaCamera::create(lua_State *L, Camera *m)
{
+ lua_getglobal(L, "core");
+ luaL_checktype(L, -1, LUA_TTABLE);
+ int objectstable = lua_gettop(L);
+ lua_getfield(L, -1, "camera");
+
+ // Duplication check
+ if (lua_type(L, -1) == LUA_TUSERDATA) {
+ lua_pop(L, 1);
+ return;
+ }
+
LuaCamera *o = new LuaCamera(m);
*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
luaL_getmetatable(L, className);
lua_setmetatable(L, -2);
- int camera_object = lua_gettop(L);
-
- lua_getglobal(L, "core");
- luaL_checktype(L, -1, LUA_TTABLE);
- int coretable = lua_gettop(L);
-
- lua_pushvalue(L, camera_object);
- lua_setfield(L, coretable, "camera");
+ lua_pushvalue(L, lua_gettop(L));
+ lua_setfield(L, objectstable, "camera");
}
int LuaCamera::l_set_camera_mode(lua_State *L)
diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp
index 6d9d832b7..6345fc75f 100644
--- a/src/script/lua_api/l_client.cpp
+++ b/src/script/lua_api/l_client.cpp
@@ -210,17 +210,13 @@ int ModApiClient::l_get_language(lua_State *L)
int ModApiClient::l_get_wielded_item(lua_State *L)
{
Client *client = getClient(L);
+ LocalPlayer *player = client->getEnv().getLocalPlayer();
+ if (!player)
+ return 0;
- Inventory local_inventory(client->idef());
- client->getLocalInventory(local_inventory);
-
- InventoryList *mlist = local_inventory.getList("main");
-
- if (mlist && client->getPlayerItem() < mlist->getSize()) {
- LuaItemStack::create(L, mlist->getItem(client->getPlayerItem()));
- } else {
- LuaItemStack::create(L, ItemStack());
- }
+ ItemStack selected_item;
+ player->getWieldedItem(&selected_item, nullptr);
+ LuaItemStack::create(L, selected_item);
return 1;
}
diff --git a/src/script/lua_api/l_craft.cpp b/src/script/lua_api/l_craft.cpp
index 0899b945e..18622ee00 100644
--- a/src/script/lua_api/l_craft.cpp
+++ b/src/script/lua_api/l_craft.cpp
@@ -294,7 +294,7 @@ int ModApiCraft::l_clear_craft(lua_State *L)
std::string type = getstringfield_default(L, table, "type", "shaped");
CraftOutput c_output(output, 0);
if (!output.empty()) {
- if (craftdef->clearCraftRecipesByOutput(c_output, getServer(L))) {
+ if (craftdef->clearCraftsByOutput(c_output, getServer(L))) {
lua_pushboolean(L, true);
return 1;
}
@@ -351,7 +351,13 @@ int ModApiCraft::l_clear_craft(lua_State *L)
throw LuaError("Unknown crafting definition type: \"" + type + "\"");
}
- if (!craftdef->clearCraftRecipesByInput(method, width, recipe, getServer(L))) {
+ std::vector<ItemStack> items;
+ items.reserve(recipe.size());
+ for (const auto &item : recipe)
+ items.emplace_back(item, 1, 0, getServer(L)->idef());
+ CraftInput input(method, width, items);
+
+ if (!craftdef->clearCraftsByInput(input, getServer(L))) {
warningstream << "No craft recipe matches input" << std::endl;
lua_pushboolean(L, false);
return 1;
diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp
index 711bd3fdd..a56b1cb0b 100644
--- a/src/script/lua_api/l_env.cpp
+++ b/src/script/lua_api/l_env.cpp
@@ -350,7 +350,7 @@ int ModApiEnvMod::l_get_node(lua_State *L)
// pos
v3s16 pos = read_v3s16(L, 1);
// Do it
- MapNode n = env->getMap().getNodeNoEx(pos);
+ MapNode n = env->getMap().getNode(pos);
// Return node
pushnode(L, n, env->getGameDef()->ndef());
return 1;
@@ -366,7 +366,7 @@ int ModApiEnvMod::l_get_node_or_nil(lua_State *L)
v3s16 pos = read_v3s16(L, 1);
// Do it
bool pos_ok;
- MapNode n = env->getMap().getNodeNoEx(pos, &pos_ok);
+ MapNode n = env->getMap().getNode(pos, &pos_ok);
if (pos_ok) {
// Return node
pushnode(L, n, env->getGameDef()->ndef());
@@ -392,7 +392,7 @@ int ModApiEnvMod::l_get_node_light(lua_State *L)
u32 dnr = time_to_daynight_ratio(time_of_day, true);
bool is_position_ok;
- MapNode n = env->getMap().getNodeNoEx(pos, &is_position_ok);
+ MapNode n = env->getMap().getNode(pos, &is_position_ok);
if (is_position_ok) {
const NodeDefManager *ndef = env->getGameDef()->ndef();
lua_pushinteger(L, n.getLightBlend(dnr, ndef));
@@ -417,7 +417,7 @@ int ModApiEnvMod::l_place_node(lua_State *L)
MapNode n = readnode(L, 2, ndef);
// Don't attempt to load non-loaded area as of now
- MapNode n_old = env->getMap().getNodeNoEx(pos);
+ MapNode n_old = env->getMap().getNode(pos);
if(n_old.getContent() == CONTENT_IGNORE){
lua_pushboolean(L, false);
return 1;
@@ -446,7 +446,7 @@ int ModApiEnvMod::l_dig_node(lua_State *L)
v3s16 pos = read_v3s16(L, 1);
// Don't attempt to load non-loaded area as of now
- MapNode n = env->getMap().getNodeNoEx(pos);
+ MapNode n = env->getMap().getNode(pos);
if(n.getContent() == CONTENT_IGNORE){
lua_pushboolean(L, false);
return 1;
@@ -469,7 +469,7 @@ int ModApiEnvMod::l_punch_node(lua_State *L)
v3s16 pos = read_v3s16(L, 1);
// Don't attempt to load non-loaded area as of now
- MapNode n = env->getMap().getNodeNoEx(pos);
+ MapNode n = env->getMap().getNode(pos);
if(n.getContent() == CONTENT_IGNORE){
lua_pushboolean(L, false);
return 1;
@@ -491,7 +491,7 @@ int ModApiEnvMod::l_get_node_max_level(lua_State *L)
}
v3s16 pos = read_v3s16(L, 1);
- MapNode n = env->getMap().getNodeNoEx(pos);
+ MapNode n = env->getMap().getNode(pos);
lua_pushnumber(L, n.getMaxLevel(env->getGameDef()->ndef()));
return 1;
}
@@ -506,7 +506,7 @@ int ModApiEnvMod::l_get_node_level(lua_State *L)
}
v3s16 pos = read_v3s16(L, 1);
- MapNode n = env->getMap().getNodeNoEx(pos);
+ MapNode n = env->getMap().getNode(pos);
lua_pushnumber(L, n.getLevel(env->getGameDef()->ndef()));
return 1;
}
@@ -522,7 +522,7 @@ int ModApiEnvMod::l_set_node_level(lua_State *L)
u8 level = 1;
if(lua_isnumber(L, 2))
level = lua_tonumber(L, 2);
- MapNode n = env->getMap().getNodeNoEx(pos);
+ MapNode n = env->getMap().getNode(pos);
lua_pushnumber(L, n.setLevel(env->getGameDef()->ndef(), level));
env->setNode(pos, n);
return 1;
@@ -539,7 +539,7 @@ int ModApiEnvMod::l_add_node_level(lua_State *L)
u8 level = 1;
if(lua_isnumber(L, 2))
level = lua_tonumber(L, 2);
- MapNode n = env->getMap().getNodeNoEx(pos);
+ MapNode n = env->getMap().getNode(pos);
lua_pushnumber(L, n.addLevel(env->getGameDef()->ndef(), level));
env->setNode(pos, n);
return 1;
@@ -780,7 +780,7 @@ int ModApiEnvMod::l_find_node_near(lua_State *L)
std::vector<v3s16> list = FacePositionCache::getFacePositions(d);
for (const v3s16 &i : list) {
v3s16 p = pos + i;
- content_t c = env->getMap().getNodeNoEx(p).getContent();
+ content_t c = env->getMap().getNode(p).getContent();
if (CONTAINS(filter, c)) {
push_v3s16(L, p);
return 1;
@@ -832,7 +832,7 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L)
for (s16 y = minp.Y; y <= maxp.Y; y++)
for (s16 z = minp.Z; z <= maxp.Z; z++) {
v3s16 p(x, y, z);
- content_t c = env->getMap().getNodeNoEx(p).getContent();
+ content_t c = env->getMap().getNode(p).getContent();
std::vector<content_t>::iterator it = std::find(filter.begin(), filter.end(), c);
if (it != filter.end()) {
@@ -898,10 +898,10 @@ int ModApiEnvMod::l_find_nodes_in_area_under_air(lua_State *L)
for (s16 z = minp.Z; z <= maxp.Z; z++) {
s16 y = minp.Y;
v3s16 p(x, y, z);
- content_t c = env->getMap().getNodeNoEx(p).getContent();
+ content_t c = env->getMap().getNode(p).getContent();
for (; y <= maxp.Y; y++) {
v3s16 psurf(x, y + 1, z);
- content_t csurf = env->getMap().getNodeNoEx(psurf).getContent();
+ content_t csurf = env->getMap().getNode(psurf).getContent();
if (c != CONTENT_AIR && csurf == CONTENT_AIR &&
CONTAINS(filter, c)) {
push_v3s16(L, v3s16(x, y, z));
@@ -1035,7 +1035,7 @@ int ModApiEnvMod::l_fix_light(lua_State *L)
for (auto &modified_block : modified_blocks)
event.modified_blocks.insert(modified_block.first);
- map.dispatchEvent(&event);
+ map.dispatchEvent(event);
}
lua_pushboolean(L, success);
@@ -1144,7 +1144,7 @@ int ModApiEnvMod::l_delete_area(lua_State *L)
}
}
- map.dispatchEvent(&event);
+ map.dispatchEvent(event);
lua_pushboolean(L, success);
return 1;
}
diff --git a/src/script/lua_api/l_http.cpp b/src/script/lua_api/l_http.cpp
index ac261cd60..2ff651cb5 100644
--- a/src/script/lua_api/l_http.cpp
+++ b/src/script/lua_api/l_http.cpp
@@ -53,9 +53,8 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req)
lua_getfield(L, 1, "post_data");
if (lua_istable(L, 2)) {
lua_pushnil(L);
- while (lua_next(L, 2) != 0)
- {
- req.post_fields[luaL_checkstring(L, -2)] = luaL_checkstring(L, -1);
+ while (lua_next(L, 2) != 0) {
+ req.post_fields[readParam<std::string>(L, -2)] = readParam<std::string>(L, -1);
lua_pop(L, 1);
}
} else if (lua_isstring(L, 2)) {
@@ -66,10 +65,8 @@ void ModApiHttp::read_http_fetch_request(lua_State *L, HTTPFetchRequest &req)
lua_getfield(L, 1, "extra_headers");
if (lua_istable(L, 2)) {
lua_pushnil(L);
- while (lua_next(L, 2) != 0)
- {
- const char *header = luaL_checkstring(L, -1);
- req.extra_headers.emplace_back(header);
+ while (lua_next(L, 2) != 0) {
+ req.extra_headers.emplace_back(readParam<std::string>(L, -1));
lua_pop(L, 1);
}
}
@@ -83,7 +80,7 @@ void ModApiHttp::push_http_fetch_result(lua_State *L, HTTPFetchResult &res, bool
setboolfield(L, -1, "timeout", res.timeout);
setboolfield(L, -1, "completed", completed);
setintfield(L, -1, "code", res.response_code);
- setstringfield(L, -1, "data", res.data.c_str());
+ setstringfield(L, -1, "data", res.data);
}
// http_api.fetch_async(HTTPRequest definition)
@@ -94,7 +91,7 @@ int ModApiHttp::l_http_fetch_async(lua_State *L)
HTTPFetchRequest req;
read_http_fetch_request(L, req);
- actionstream << "Mod performs HTTP request with URL " << req.url << std::endl;
+ infostream << "Mod performs HTTP request with URL " << req.url << std::endl;
httpfetch_async(req);
// Convert handle to hex string since lua can't handle 64-bit integers
diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp
index e41d23fd1..f9708b560 100644
--- a/src/script/lua_api/l_item.cpp
+++ b/src/script/lua_api/l_item.cpp
@@ -175,6 +175,16 @@ int LuaItemStack::l_set_metadata(lua_State *L)
return 1;
}
+// get_description(self)
+int LuaItemStack::l_get_description(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+ LuaItemStack *o = checkobject(L, 1);
+ std::string desc = o->m_stack.getDescription(getGameDef(L)->idef());
+ lua_pushstring(L, desc.c_str());
+ return 1;
+}
+
// clear(self) -> true
int LuaItemStack::l_clear(lua_State *L)
{
@@ -470,6 +480,7 @@ const luaL_Reg LuaItemStack::methods[] = {
luamethod(LuaItemStack, get_meta),
luamethod(LuaItemStack, get_metadata),
luamethod(LuaItemStack, set_metadata),
+ luamethod(LuaItemStack, get_description),
luamethod(LuaItemStack, clear),
luamethod(LuaItemStack, replace),
luamethod(LuaItemStack, to_string),
diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h
index 5ff715b2a..6fab58045 100644
--- a/src/script/lua_api/l_item.h
+++ b/src/script/lua_api/l_item.h
@@ -66,6 +66,9 @@ private:
// set_metadata(self, string)
static int l_set_metadata(lua_State *L);
+ // get_description(self)
+ static int l_get_description(lua_State *L);
+
// clear(self) -> true
static int l_clear(lua_State *L);
diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp
index 7444d0e88..3e14e48e4 100644
--- a/src/script/lua_api/l_localplayer.cpp
+++ b/src/script/lua_api/l_localplayer.cpp
@@ -30,20 +30,24 @@ LuaLocalPlayer::LuaLocalPlayer(LocalPlayer *m) : m_localplayer(m)
void LuaLocalPlayer::create(lua_State *L, LocalPlayer *m)
{
+ lua_getglobal(L, "core");
+ luaL_checktype(L, -1, LUA_TTABLE);
+ int objectstable = lua_gettop(L);
+ lua_getfield(L, -1, "localplayer");
+
+ // Duplication check
+ if (lua_type(L, -1) == LUA_TUSERDATA) {
+ lua_pop(L, 1);
+ return;
+ }
+
LuaLocalPlayer *o = new LuaLocalPlayer(m);
*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
luaL_getmetatable(L, className);
lua_setmetatable(L, -2);
- // Keep localplayer object stack id
- int localplayer_object = lua_gettop(L);
-
- lua_getglobal(L, "core");
- luaL_checktype(L, -1, LUA_TTABLE);
- int coretable = lua_gettop(L);
-
- lua_pushvalue(L, localplayer_object);
- lua_setfield(L, coretable, "localplayer");
+ lua_pushvalue(L, lua_gettop(L));
+ lua_setfield(L, objectstable, "localplayer");
}
int LuaLocalPlayer::l_get_velocity(lua_State *L)
diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp
index 2557f448a..76db7ed13 100644
--- a/src/script/lua_api/l_mainmenu.cpp
+++ b/src/script/lua_api/l_mainmenu.cpp
@@ -107,6 +107,21 @@ int ModApiMainMenu::l_update_formspec(lua_State *L)
}
/******************************************************************************/
+int ModApiMainMenu::l_set_formspec_prepend(lua_State *L)
+{
+ GUIEngine *engine = getGuiEngine(L);
+ sanity_check(engine != NULL);
+
+ if (engine->m_startgame)
+ return 0;
+
+ std::string formspec(luaL_checkstring(L, 1));
+ engine->setFormspecPrepend(formspec);
+
+ return 0;
+}
+
+/******************************************************************************/
int ModApiMainMenu::l_start(lua_State *L)
{
GUIEngine* engine = getGuiEngine(L);
@@ -867,6 +882,16 @@ bool ModApiMainMenu::mayModifyPath(const std::string &path)
return false;
}
+
+/******************************************************************************/
+int ModApiMainMenu::l_may_modify_path(lua_State *L)
+{
+ const char *target = luaL_checkstring(L, 1);
+ std::string absolute_destination = fs::RemoveRelativePathComponents(target);
+ lua_pushboolean(L, ModApiMainMenu::mayModifyPath(absolute_destination));
+ return 1;
+}
+
/******************************************************************************/
int ModApiMainMenu::l_show_path_select_dialog(lua_State *L)
{
@@ -1031,6 +1056,7 @@ int ModApiMainMenu::l_do_async_callback(lua_State *L)
void ModApiMainMenu::Initialize(lua_State *L, int top)
{
API_FCT(update_formspec);
+ API_FCT(set_formspec_prepend);
API_FCT(set_clouds);
API_FCT(get_textlist_index);
API_FCT(get_table_index);
@@ -1057,6 +1083,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top)
API_FCT(delete_dir);
API_FCT(copy_dir);
API_FCT(extract_zip);
+ API_FCT(may_modify_path);
API_FCT(get_mainmenu_path);
API_FCT(show_path_select_dialog);
API_FCT(download_file);
@@ -1086,6 +1113,7 @@ void ModApiMainMenu::InitializeAsync(lua_State *L, int top)
API_FCT(delete_dir);
API_FCT(copy_dir);
//API_FCT(extract_zip); //TODO remove dependency to GuiEngine
+ API_FCT(may_modify_path);
API_FCT(download_file);
//API_FCT(gettext); (gettext lib isn't threadsafe)
}
diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h
index 4a664359a..b2ca49320 100644
--- a/src/script/lua_api/l_mainmenu.h
+++ b/src/script/lua_api/l_mainmenu.h
@@ -104,6 +104,8 @@ private:
static int l_update_formspec(lua_State *L);
+ static int l_set_formspec_prepend(lua_State *L);
+
static int l_get_screen_info(lua_State *L);
//filesystem
@@ -130,6 +132,8 @@ private:
static int l_extract_zip(lua_State *L);
+ static int l_may_modify_path(lua_State *L);
+
static int l_download_file(lua_State *L);
static int l_get_video_drivers(lua_State *L);
diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp
index 92ed4377e..2e0cba8dd 100644
--- a/src/script/lua_api/l_mapgen.cpp
+++ b/src/script/lua_api/l_mapgen.cpp
@@ -405,7 +405,16 @@ Biome *read_biome_def(lua_State *L, int index, const NodeDefManager *ndef)
nn.push_back(getstringfield_default(L, index, "node_river_water", ""));
nn.push_back(getstringfield_default(L, index, "node_riverbed", ""));
nn.push_back(getstringfield_default(L, index, "node_dust", ""));
- nn.push_back(getstringfield_default(L, index, "node_cave_liquid", ""));
+
+ size_t nnames = getstringlistfield(L, index, "node_cave_liquid", &nn);
+ // If no cave liquids defined, set list to "ignore" to trigger old hardcoded
+ // cave liquid behaviour.
+ if (nnames == 0) {
+ nn.emplace_back("ignore");
+ nnames = 1;
+ }
+ b->m_nnlistsizes.push_back(nnames);
+
nn.push_back(getstringfield_default(L, index, "node_dungeon", ""));
nn.push_back(getstringfield_default(L, index, "node_dungeon_alt", ""));
nn.push_back(getstringfield_default(L, index, "node_dungeon_stair", ""));
@@ -1745,6 +1754,83 @@ int ModApiMapgen::l_serialize_schematic(lua_State *L)
return 1;
}
+// read_schematic(schematic, options={...})
+int ModApiMapgen::l_read_schematic(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+
+ SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr;
+
+ //// Read options
+ std::string write_yslice = getstringfield_default(L, 2, "write_yslice_prob", "all");
+
+ //// Get schematic
+ bool was_loaded = false;
+ Schematic *schem = (Schematic *)get_objdef(L, 1, schemmgr);
+ if (!schem) {
+ schem = load_schematic(L, 1, NULL, NULL);
+ was_loaded = true;
+ }
+ if (!schem) {
+ errorstream << "read_schematic: failed to get schematic" << std::endl;
+ return 0;
+ }
+ lua_pop(L, 2);
+
+ //// Create the Lua table
+ u32 numnodes = schem->size.X * schem->size.Y * schem->size.Z;
+ const std::vector<std::string> &names = schem->m_nodenames;
+
+ lua_createtable(L, 0, (write_yslice == "none") ? 2 : 3);
+
+ // Create the size field
+ push_v3s16(L, schem->size);
+ lua_setfield(L, 1, "size");
+
+ // Create the yslice_prob field
+ if (write_yslice != "none") {
+ lua_createtable(L, schem->size.Y, 0);
+ for (u16 y = 0; y != schem->size.Y; ++y) {
+ u8 probability = schem->slice_probs[y] & MTSCHEM_PROB_MASK;
+ if (probability < MTSCHEM_PROB_ALWAYS || write_yslice != "low") {
+ lua_createtable(L, 0, 2);
+ lua_pushinteger(L, y);
+ lua_setfield(L, 3, "ypos");
+ lua_pushinteger(L, probability * 2);
+ lua_setfield(L, 3, "prob");
+ lua_rawseti(L, 2, y + 1);
+ }
+ }
+ lua_setfield(L, 1, "yslice_prob");
+ }
+
+ // Create the data field
+ lua_createtable(L, numnodes, 0); // data table
+ for (u32 i = 0; i < numnodes; ++i) {
+ MapNode node = schem->schemdata[i];
+ u8 probability = node.param1 & MTSCHEM_PROB_MASK;
+ bool force_place = node.param1 & MTSCHEM_FORCE_PLACE;
+ lua_createtable(L, 0, force_place ? 4 : 3);
+ lua_pushstring(L, names[schem->schemdata[i].getContent()].c_str());
+ lua_setfield(L, 3, "name");
+ lua_pushinteger(L, probability * 2);
+ lua_setfield(L, 3, "prob");
+ lua_pushinteger(L, node.param2);
+ lua_setfield(L, 3, "param2");
+ if (force_place) {
+ lua_pushboolean(L, 1);
+ lua_setfield(L, 3, "force_place");
+ }
+ lua_rawseti(L, 2, i + 1);
+ }
+ lua_setfield(L, 1, "data");
+
+ if (was_loaded)
+ delete schem;
+
+ return 1;
+}
+
void ModApiMapgen::Initialize(lua_State *L, int top)
{
@@ -1784,4 +1870,5 @@ void ModApiMapgen::Initialize(lua_State *L, int top)
API_FCT(place_schematic);
API_FCT(place_schematic_on_vmanip);
API_FCT(serialize_schematic);
+ API_FCT(read_schematic);
}
diff --git a/src/script/lua_api/l_mapgen.h b/src/script/lua_api/l_mapgen.h
index 1339791f3..4a6a9ccf4 100644
--- a/src/script/lua_api/l_mapgen.h
+++ b/src/script/lua_api/l_mapgen.h
@@ -131,6 +131,9 @@ private:
// serialize_schematic(schematic, format, options={...})
static int l_serialize_schematic(lua_State *L);
+ // read_schematic(schematic, options={...})
+ static int l_read_schematic(lua_State *L);
+
public:
static void Initialize(lua_State *L, int top);
diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp
index 22fc61782..229ce73db 100644
--- a/src/script/lua_api/l_nodemeta.cpp
+++ b/src/script/lua_api/l_nodemeta.cpp
@@ -68,7 +68,7 @@ void NodeMetaRef::reportMetadataChange(const std::string *name)
event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
event.p = m_p;
event.is_private_change = name && meta && meta->isPrivate(*name);
- m_env->getMap().dispatchEvent(&event);
+ m_env->getMap().dispatchEvent(event);
}
// Exported functions
diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp
index b3ed39c7c..efdb345c9 100644
--- a/src/script/lua_api/l_object.cpp
+++ b/src/script/lua_api/l_object.cpp
@@ -170,8 +170,8 @@ int ObjectRef::l_punch(lua_State *L)
ObjectRef *puncher_ref = checkobject(L, 2);
ServerActiveObject *co = getobject(ref);
ServerActiveObject *puncher = getobject(puncher_ref);
- if (co == NULL) return 0;
- if (puncher == NULL) return 0;
+ if (!co || !puncher)
+ return 0;
v3f dir;
if (lua_type(L, 5) != LUA_TTABLE)
dir = co->getBasePosition() - puncher->getBasePosition();
@@ -187,12 +187,14 @@ int ObjectRef::l_punch(lua_State *L)
u16 dst_origin_hp = puncher->getHP();
// Do it
- co->punch(dir, &toolcap, puncher, time_from_last_punch);
+ u16 wear = co->punch(dir, &toolcap, puncher, time_from_last_punch);
+ lua_pushnumber(L, wear);
// If the punched is a player, and its HP changed
if (src_original_hp != co->getHP() &&
co->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
- getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co, PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
+ getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co,
+ PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
}
// If the puncher is a player, and its HP changed
@@ -201,7 +203,7 @@ int ObjectRef::l_punch(lua_State *L)
getServer(L)->SendPlayerHPOrDie((PlayerSAO *)puncher,
PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, co));
}
- return 0;
+ return 1;
}
// right_click(self, clicker); clicker = an another ObjectRef
@@ -307,8 +309,9 @@ int ObjectRef::l_get_wield_list(lua_State *L)
NO_MAP_LOCK_REQUIRED;
ObjectRef *ref = checkobject(L, 1);
ServerActiveObject *co = getobject(ref);
- if (co == NULL) return 0;
- // Do it
+ if (!co)
+ return 0;
+
lua_pushstring(L, co->getWieldList().c_str());
return 1;
}
@@ -319,8 +322,9 @@ int ObjectRef::l_get_wield_index(lua_State *L)
NO_MAP_LOCK_REQUIRED;
ObjectRef *ref = checkobject(L, 1);
ServerActiveObject *co = getobject(ref);
- if (co == NULL) return 0;
- // Do it
+ if (!co)
+ return 0;
+
lua_pushinteger(L, co->getWieldIndex() + 1);
return 1;
}
@@ -331,13 +335,15 @@ int ObjectRef::l_get_wielded_item(lua_State *L)
NO_MAP_LOCK_REQUIRED;
ObjectRef *ref = checkobject(L, 1);
ServerActiveObject *co = getobject(ref);
- if (co == NULL) {
+ if (!co) {
// Empty ItemStack
LuaItemStack::create(L, ItemStack());
return 1;
}
- // Do it
- LuaItemStack::create(L, co->getWieldedItem());
+
+ ItemStack selected_item;
+ co->getWieldedItem(&selected_item, nullptr);
+ LuaItemStack::create(L, selected_item);
return 1;
}
@@ -352,7 +358,7 @@ int ObjectRef::l_set_wielded_item(lua_State *L)
ItemStack item = read_item(L, 2, getServer(L)->idef());
bool success = co->setWieldedItem(item);
if (success && co->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
- getServer(L)->SendInventory(((PlayerSAO*)co));
+ getServer(L)->SendInventory((PlayerSAO *)co, true);
}
lua_pushboolean(L, success);
return 1;
@@ -583,6 +589,24 @@ int ObjectRef::l_get_eye_offset(lua_State *L)
return 2;
}
+// send_mapblock(self, pos)
+int ObjectRef::l_send_mapblock(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+ ObjectRef *ref = checkobject(L, 1);
+
+ RemotePlayer *player = getplayer(ref);
+ if (!player)
+ return 0;
+ v3s16 p = read_v3s16(L, 2);
+
+ session_t peer_id = player->getPeerId();
+ bool r = getServer(L)->SendBlock(peer_id, p);
+
+ lua_pushboolean(L, r);
+ return 1;
+}
+
// set_animation_frame_speed(self, frame_speed)
int ObjectRef::l_set_animation_frame_speed(lua_State *L)
{
@@ -731,17 +755,14 @@ int ObjectRef::l_set_properties(lua_State *L)
NO_MAP_LOCK_REQUIRED;
ObjectRef *ref = checkobject(L, 1);
ServerActiveObject *co = getobject(ref);
- if (co == NULL) return 0;
+ if (!co)
+ return 0;
+
ObjectProperties *prop = co->accessObjectProperties();
if (!prop)
return 0;
- read_object_properties(L, 2, prop, getServer(L)->idef());
- if (prop->hp_max < co->getHP()) {
- PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP);
- co->setHP(prop->hp_max, reason);
- if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER)
- getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co, reason);
- }
+
+ read_object_properties(L, 2, co, prop, getServer(L)->idef());
co->notifyObjectPropertiesModified();
return 0;
}
@@ -1074,6 +1095,27 @@ int ObjectRef::l_get_player_velocity(lua_State *L)
return 1;
}
+// add_player_velocity(self, {x=num, y=num, z=num})
+int ObjectRef::l_add_player_velocity(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+ ObjectRef *ref = checkobject(L, 1);
+ v3f vel = checkFloatPos(L, 2);
+
+ RemotePlayer *player = getplayer(ref);
+ PlayerSAO *co = getplayersao(ref);
+ if (!player || !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);
+ return 0;
+}
+
// get_look_dir(self)
int ObjectRef::l_get_look_dir(lua_State *L)
{
@@ -1210,6 +1252,37 @@ int ObjectRef::l_set_look_yaw(lua_State *L)
return 1;
}
+// set_fov(self, degrees[, is_multiplier])
+int ObjectRef::l_set_fov(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+ ObjectRef *ref = checkobject(L, 1);
+ RemotePlayer *player = getplayer(ref);
+ if (!player)
+ return 0;
+
+ player->setFov({ static_cast<f32>(luaL_checknumber(L, 2)), readParam<bool>(L, 3) });
+ getServer(L)->SendPlayerFov(player->getPeerId());
+
+ return 0;
+}
+
+// get_fov(self)
+int ObjectRef::l_get_fov(lua_State *L)
+{
+ NO_MAP_LOCK_REQUIRED;
+ ObjectRef *ref = checkobject(L, 1);
+ RemotePlayer *player = getplayer(ref);
+ if (!player)
+ return 0;
+
+ PlayerFovSpec fov_spec = player->getFov();
+ lua_pushnumber(L, fov_spec.fov);
+ lua_pushboolean(L, fov_spec.is_multiplier);
+
+ return 2;
+}
+
// set_breath(self, breath)
int ObjectRef::l_set_breath(lua_State *L)
{
@@ -1239,6 +1312,9 @@ int ObjectRef::l_get_breath(lua_State *L)
// set_attribute(self, attribute, value)
int ObjectRef::l_set_attribute(lua_State *L)
{
+ log_deprecated(L,
+ "Deprecated call to set_attribute, use MetaDataRef methods instead.");
+
ObjectRef *ref = checkobject(L, 1);
PlayerSAO* co = getplayersao(ref);
if (co == NULL)
@@ -1257,6 +1333,9 @@ int ObjectRef::l_set_attribute(lua_State *L)
// get_attribute(self, attribute)
int ObjectRef::l_get_attribute(lua_State *L)
{
+ log_deprecated(L,
+ "Deprecated call to get_attribute, use MetaDataRef methods instead.");
+
ObjectRef *ref = checkobject(L, 1);
PlayerSAO* co = getplayersao(ref);
if (co == NULL)
@@ -1907,6 +1986,7 @@ luaL_Reg ObjectRef::methods[] = {
luamethod(ObjectRef, is_player_connected),
luamethod(ObjectRef, get_player_name),
luamethod(ObjectRef, get_player_velocity),
+ luamethod(ObjectRef, add_player_velocity),
luamethod(ObjectRef, get_look_dir),
luamethod(ObjectRef, get_look_pitch),
luamethod(ObjectRef, get_look_yaw),
@@ -1916,6 +1996,8 @@ luaL_Reg ObjectRef::methods[] = {
luamethod(ObjectRef, set_look_vertical),
luamethod(ObjectRef, set_look_yaw),
luamethod(ObjectRef, set_look_pitch),
+ luamethod(ObjectRef, get_fov),
+ luamethod(ObjectRef, set_fov),
luamethod(ObjectRef, get_breath),
luamethod(ObjectRef, set_breath),
luamethod(ObjectRef, get_attribute),
@@ -1951,5 +2033,6 @@ luaL_Reg ObjectRef::methods[] = {
luamethod(ObjectRef, get_local_animation),
luamethod(ObjectRef, set_eye_offset),
luamethod(ObjectRef, get_eye_offset),
+ luamethod(ObjectRef, send_mapblock),
{0,0}
};
diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h
index c7d963d87..e817e1d33 100644
--- a/src/script/lua_api/l_object.h
+++ b/src/script/lua_api/l_object.h
@@ -212,6 +212,12 @@ private:
// get_player_velocity(self)
static int l_get_player_velocity(lua_State *L);
+ // add_player_velocity(self, {x=num, y=num, z=num})
+ static int l_add_player_velocity(lua_State *L);
+
+ // get_fov(self)
+ static int l_get_fov(lua_State *L);
+
// get_look_dir(self)
static int l_get_look_dir(lua_State *L);
@@ -229,6 +235,9 @@ private:
// get_look_yaw2(self)
static int l_get_look_horizontal(lua_State *L);
+ // set_fov(self, degrees, is_multiplier)
+ static int l_set_fov(lua_State *L);
+
// set_look_vertical(self, radians)
static int l_set_look_vertical(lua_State *L);
@@ -351,4 +360,6 @@ private:
// get_nametag_attributes(self)
static int l_get_nametag_attributes(lua_State *L);
+ // send_mapblock(pos)
+ static int l_send_mapblock(lua_State *L);
};
diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp
index 6017a5475..7c083e652 100644
--- a/src/script/lua_api/l_server.cpp
+++ b/src/script/lua_api/l_server.cpp
@@ -233,6 +233,10 @@ int ModApiServer::l_get_player_information(lua_State *L)
lua_pushnumber(L, prot_vers);
lua_settable(L, table);
+ lua_pushstring(L, "formspec_version");
+ lua_pushnumber(L, player->formspec_version);
+ lua_settable(L, table);
+
#ifndef NDEBUG
lua_pushstring(L,"serialization_version");
lua_pushnumber(L, ser_vers);
diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp
index c92983bd3..fd73d21d1 100644
--- a/src/script/lua_api/l_vmanip.cpp
+++ b/src/script/lua_api/l_vmanip.cpp
@@ -126,7 +126,7 @@ int LuaVoxelManip::l_write_to_map(lua_State *L)
for (const auto &modified_block : o->modified_blocks)
event.modified_blocks.insert(modified_block.first);
- map->dispatchEvent(&event);
+ map->dispatchEvent(event);
o->modified_blocks.clear();
return 0;
diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp
index 86e5f2874..c3e0ca373 100644
--- a/src/script/scripting_client.cpp
+++ b/src/script/scripting_client.cpp
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_client.h"
#include "lua_api/l_env.h"
#include "lua_api/l_item.h"
+#include "lua_api/l_itemstackmeta.h"
#include "lua_api/l_minimap.h"
#include "lua_api/l_modchannels.h"
#include "lua_api/l_particles_local.h"
@@ -67,6 +68,7 @@ ClientScripting::ClientScripting(Client *client):
void ClientScripting::InitializeModApi(lua_State *L, int top)
{
LuaItemStack::Register(L);
+ ItemStackMetaRef::Register(L);
StorageRef::Register(L);
LuaMinimap::Register(L);
NodeMetaRef::RegisterClient(L);
@@ -84,8 +86,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top)
void ClientScripting::on_client_ready(LocalPlayer *localplayer)
{
- lua_State *L = getStack();
- LuaLocalPlayer::create(L, localplayer);
+ LuaLocalPlayer::create(getStack(), localplayer);
}
void ClientScripting::on_camera_ready(Camera *camera)
diff --git a/src/serialization.cpp b/src/serialization.cpp
index ac6fc0db2..36ddb467c 100644
--- a/src/serialization.cpp
+++ b/src/serialization.cpp
@@ -20,9 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "serialization.h"
#include "util/serialize.h"
-#if defined(_WIN32) && !defined(WIN32_NO_ZLIB_WINAPI)
- #define ZLIB_WINAPI
-#endif
+
#include "zlib.h"
/* report a zlib or i/o error */
diff --git a/src/server.cpp b/src/server.cpp
index 172bb4744..4aa8375c8 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -220,6 +220,7 @@ Server::Server(
m_itemdef(createItemDefManager()),
m_nodedef(createNodeDefManager()),
m_craftdef(createCraftDefManager()),
+ m_thread(new ServerThread(this)),
m_uptime(0),
m_clients(m_con),
m_admin_chat(iface),
@@ -305,6 +306,11 @@ Server::~Server()
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();
+ }
}
void Server::init()
@@ -321,9 +327,6 @@ void Server::init()
if (!loadGameConfAndInitWorld(m_path_world, m_gamespec))
throw ServerError("Failed to initialize world");
- // Create server thread
- m_thread = new ServerThread(this);
-
// Create emerge manager
m_emerge = new EmergeManager(this);
@@ -472,7 +475,6 @@ void Server::step(float dtime)
void Server::AsyncRunStep(bool initial_step)
{
- g_profiler->add("Server::AsyncRunStep (num)", 1);
float dtime;
{
@@ -488,10 +490,7 @@ void Server::AsyncRunStep(bool initial_step)
if((dtime < 0.001) && !initial_step)
return;
- g_profiler->add("Server::AsyncRunStep with dtime (num)", 1);
-
- //infostream<<"Server steps "<<dtime<<std::endl;
- //infostream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl;
+ ScopeProfiler sp(g_profiler, "Server::AsyncRunStep()", SPT_AVG);
{
MutexAutoLock lock1(m_step_dtime_mutex);
@@ -537,8 +536,6 @@ void Server::AsyncRunStep(bool initial_step)
}
m_env->reportMaxLagEstimate(max_lag);
// Step environment
- ScopeProfiler sp(g_profiler, "SEnv step");
- ScopeProfiler sp2(g_profiler, "SEnv step avg", SPT_AVG);
m_env->step(dtime);
}
@@ -628,124 +625,27 @@ void Server::AsyncRunStep(bool initial_step)
m_clients.lock();
const RemoteClientMap &clients = m_clients.getClientList();
- ScopeProfiler sp(g_profiler, "Server: checking added and deleted objs");
-
- // Radius inside which objects are active
- static thread_local const s16 radius =
- g_settings->getS16("active_object_send_range_blocks") * MAP_BLOCKSIZE;
-
- // Radius inside which players are active
- static thread_local const bool is_transfer_limited =
- g_settings->exists("unlimited_player_transfer_distance") &&
- !g_settings->getBool("unlimited_player_transfer_distance");
- static thread_local const s16 player_transfer_dist =
- g_settings->getS16("player_transfer_distance") * MAP_BLOCKSIZE;
- s16 player_radius = player_transfer_dist;
- if (player_radius == 0 && is_transfer_limited)
- player_radius = radius;
+ ScopeProfiler sp(g_profiler, "Server: update objects within range");
for (const auto &client_it : clients) {
RemoteClient *client = client_it.second;
- // If definitions and textures have not been sent, don't
- // send objects either
if (client->getState() < CS_DefinitionsSent)
continue;
- RemotePlayer *player = m_env->getPlayer(client->peer_id);
- if (!player) {
- // This can happen if the client timeouts somehow
+ // This can happen if the client times out somehow
+ if (!m_env->getPlayer(client->peer_id))
continue;
- }
- PlayerSAO *playersao = player->getPlayerSAO();
+ PlayerSAO *playersao = getPlayerSAO(client->peer_id);
if (!playersao)
continue;
- s16 my_radius = MYMIN(radius, playersao->getWantedRange() * MAP_BLOCKSIZE);
- if (my_radius <= 0) my_radius = radius;
- //infostream << "Server: Active Radius " << my_radius << std::endl;
-
- std::queue<u16> removed_objects;
- std::queue<u16> added_objects;
- m_env->getRemovedActiveObjects(playersao, my_radius, player_radius,
- client->m_known_objects, removed_objects);
- m_env->getAddedActiveObjects(playersao, my_radius, player_radius,
- client->m_known_objects, added_objects);
-
- // Ignore if nothing happened
- if (removed_objects.empty() && added_objects.empty()) {
- continue;
- }
-
- std::string data_buffer;
-
- char buf[4];
-
- // Handle removed objects
- writeU16((u8*)buf, removed_objects.size());
- data_buffer.append(buf, 2);
- while (!removed_objects.empty()) {
- // Get object
- u16 id = removed_objects.front();
- ServerActiveObject* obj = m_env->getActiveObject(id);
-
- // Add to data buffer for sending
- writeU16((u8*)buf, id);
- data_buffer.append(buf, 2);
-
- // Remove from known objects
- client->m_known_objects.erase(id);
-
- if(obj && obj->m_known_by_count > 0)
- obj->m_known_by_count--;
- removed_objects.pop();
- }
-
- // Handle added objects
- writeU16((u8*)buf, added_objects.size());
- data_buffer.append(buf, 2);
- while (!added_objects.empty()) {
- // Get object
- u16 id = added_objects.front();
- ServerActiveObject* obj = m_env->getActiveObject(id);
-
- // Get object type
- u8 type = ACTIVEOBJECT_TYPE_INVALID;
- if (!obj)
- warningstream << FUNCTION_NAME << ": NULL object" << std::endl;
- else
- type = obj->getSendType();
-
- // Add to data buffer for sending
- writeU16((u8*)buf, id);
- data_buffer.append(buf, 2);
- writeU8((u8*)buf, type);
- data_buffer.append(buf, 1);
-
- if(obj)
- data_buffer.append(serializeLongString(
- obj->getClientInitializationData(client->net_proto_version)));
- else
- data_buffer.append(serializeLongString(""));
-
- // Add to known objects
- client->m_known_objects.insert(id);
-
- if(obj)
- obj->m_known_by_count++;
-
- added_objects.pop();
- }
-
- u32 pktSize = SendActiveObjectRemoveAdd(client->peer_id, data_buffer);
- verbosestream << "Server: Sent object remove/add: "
- << removed_objects.size() << " removed, "
- << added_objects.size() << " added, "
- << "packet size is " << pktSize << std::endl;
+ SendActiveObjectRemoveAdd(client, playersao);
}
m_clients.unlock();
+ // 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;
@@ -764,7 +664,7 @@ void Server::AsyncRunStep(bool initial_step)
*/
{
MutexAutoLock envlock(m_env_mutex);
- ScopeProfiler sp(g_profiler, "Server: sending object messages");
+ ScopeProfiler sp(g_profiler, "Server: send SAO messages");
// Key = object id
// Value = data sent by object
@@ -794,19 +694,33 @@ void Server::AsyncRunStep(bool initial_step)
// Route data to every client
for (const auto &client_it : clients) {
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 is not known by client, skip it
+ // If object does not exist or is not known by client, skip it
u16 id = buffered_message.first;
- if (client->m_known_objects.find(id) == client->m_known_objects.end())
+ ServerActiveObject *sao = m_env->getActiveObject(id);
+ if (!sao || client->m_known_objects.find(id) == client->m_known_objects.end())
continue;
// Get message list of object
std::vector<ActiveObjectMessage>* list = buffered_message.second;
// 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 (sao->getId() == player->getId())
+ continue;
+
+ // Do not send position updates for attached players
+ // as long the parent is known to the client
+ ServerActiveObject *parent = sao->getParent();
+ if (parent && client->m_known_objects.find(parent->getId()) !=
+ client->m_known_objects.end())
+ continue;
+ }
// Compose the full new data with header
std::string new_data;
// Add object id
@@ -974,7 +888,7 @@ void Server::AsyncRunStep(bool initial_step)
counter = 0.0;
MutexAutoLock lock(m_env_mutex);
- ScopeProfiler sp(g_profiler, "Server: saving stuff");
+ ScopeProfiler sp(g_profiler, "Server: map saving (sum)");
// Save ban file
if (m_banmanager->isModified()) {
@@ -1065,7 +979,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id)
SendPlayerInventoryFormspec(peer_id);
// Send inventory
- SendInventory(playersao);
+ SendInventory(playersao, false);
// Send HP or death screen
if (playersao->isDead())
@@ -1097,9 +1011,9 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id)
return playersao;
}
-inline void Server::handleCommand(NetworkPacket* pkt)
+inline void Server::handleCommand(NetworkPacket *pkt)
{
- const ToServerCommandHandler& opHandle = toServerCommandTable[pkt->getCommand()];
+ const ToServerCommandHandler &opHandle = toServerCommandTable[pkt->getCommand()];
(this->*opHandle.handler)(pkt);
}
@@ -1108,7 +1022,7 @@ void Server::ProcessData(NetworkPacket *pkt)
// Environment is locked first.
MutexAutoLock envlock(m_env_mutex);
- ScopeProfiler sp(g_profiler, "Server::ProcessData");
+ ScopeProfiler sp(g_profiler, "Server: Process network packet (sum)");
u32 peer_id = pkt->getPeerId();
try {
@@ -1195,12 +1109,12 @@ void Server::setTimeOfDay(u32 time)
m_time_of_day_send_timer = 0;
}
-void Server::onMapEditEvent(MapEditEvent *event)
+void Server::onMapEditEvent(const MapEditEvent &event)
{
- if (m_ignore_map_edit_events_area.contains(event->getArea()))
+ if (m_ignore_map_edit_events_area.contains(event.getArea()))
return;
- MapEditEvent *e = event->clone();
- m_unsent_map_edit_queue.push(e);
+
+ m_unsent_map_edit_queue.push(new MapEditEvent(event));
}
Inventory* Server::getInventory(const InventoryLocation &loc)
@@ -1242,26 +1156,22 @@ Inventory* Server::getInventory(const InventoryLocation &loc)
return NULL;
}
-void Server::setInventoryModified(const InventoryLocation &loc, bool playerSend)
+void Server::setInventoryModified(const InventoryLocation &loc)
{
switch(loc.type){
case InventoryLocation::UNDEFINED:
break;
case InventoryLocation::PLAYER:
{
- if (!playerSend)
- return;
RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
if (!player)
return;
- PlayerSAO *playersao = player->getPlayerSAO();
- if(!playersao)
- return;
-
- SendInventory(playersao);
+ player->setModified(true);
+ player->inventory.setModified(true);
+ // Updates are sent in ServerEnvironment::step()
}
break;
case InventoryLocation::NODEMETA:
@@ -1269,12 +1179,12 @@ void Server::setInventoryModified(const InventoryLocation &loc, bool playerSend)
MapEditEvent event;
event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
event.p = loc.p;
- m_env->getMap().dispatchEvent(&event);
+ m_env->getMap().dispatchEvent(event);
}
break;
case InventoryLocation::DETACHED:
{
- sendDetachedInventory(loc.name,PEER_ID_INEXISTENT);
+ // Updates are sent in ServerEnvironment::step()
}
break;
default:
@@ -1428,10 +1338,10 @@ void Server::SendMovement(session_t peer_id)
void Server::SendPlayerHPOrDie(PlayerSAO *playersao, const PlayerHPChangeReason &reason)
{
- if (!g_settings->getBool("enable_damage"))
+ if (playersao->isImmortal())
return;
- session_t peer_id = playersao->getPeerID();
+ session_t peer_id = playersao->getPeerID();
bool is_alive = playersao->getHP() > 0;
if (is_alive)
@@ -1535,21 +1445,27 @@ void Server::SendNodeDef(session_t peer_id,
Non-static send methods
*/
-void Server::SendInventory(PlayerSAO* playerSAO)
+void Server::SendInventory(PlayerSAO *sao, bool incremental)
{
- UpdateCrafting(playerSAO->getPlayer());
+ RemotePlayer *player = sao->getPlayer();
+
+ // Do not send new format to old clients
+ incremental &= player->protocol_version >= 38;
+
+ UpdateCrafting(player);
/*
Serialize it
*/
- NetworkPacket pkt(TOCLIENT_INVENTORY, 0, playerSAO->getPeerID());
-
- std::ostringstream os;
- playerSAO->getInventory()->serialize(os);
+ NetworkPacket pkt(TOCLIENT_INVENTORY, 0, sao->getPeerID());
- std::string s = os.str();
+ std::ostringstream os(std::ios::binary);
+ sao->getInventory()->serialize(os, incremental);
+ sao->getInventory()->setModified(false);
+ player->setModified(true);
+ const std::string &s = os.str();
pkt.putRawString(s.c_str(), s.size());
Send(&pkt);
}
@@ -1573,9 +1489,9 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message)
}
void Server::SendShowFormspecMessage(session_t peer_id, const std::string &formspec,
- const std::string &formname)
+ const std::string &formname)
{
- NetworkPacket pkt(TOCLIENT_SHOW_FORMSPEC, 0 , peer_id);
+ NetworkPacket pkt(TOCLIENT_SHOW_FORMSPEC, 0, peer_id);
if (formspec.empty()){
//the client should close the formspec
//but make sure there wasn't another one open in meantime
@@ -1586,7 +1502,7 @@ void Server::SendShowFormspecMessage(session_t peer_id, const std::string &forms
pkt.putLongString("");
} else {
m_formspec_state_data[peer_id] = formname;
- pkt.putLongString(FORMSPEC_VERSION_STRING + formspec);
+ pkt.putLongString(formspec);
}
pkt << formname;
@@ -1868,6 +1784,16 @@ void Server::SendMovePlayer(session_t peer_id)
Send(&pkt);
}
+void Server::SendPlayerFov(session_t peer_id)
+{
+ NetworkPacket pkt(TOCLIENT_FOV, 4 + 1, peer_id);
+
+ PlayerFovSpec fov_spec = m_env->getPlayer(peer_id)->getFov();
+ pkt << fov_spec.fov << fov_spec.is_multiplier;
+
+ Send(&pkt);
+}
+
void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4],
f32 animation_speed)
{
@@ -1915,7 +1841,8 @@ void Server::SendPlayerInventoryFormspec(session_t peer_id)
return;
NetworkPacket pkt(TOCLIENT_INVENTORY_FORMSPEC, 0, peer_id);
- pkt.putLongString(FORMSPEC_VERSION_STRING + player->inventory_formspec);
+ pkt.putLongString(player->inventory_formspec);
+
Send(&pkt);
}
@@ -1927,16 +1854,107 @@ void Server::SendPlayerFormspecPrepend(session_t peer_id)
return;
NetworkPacket pkt(TOCLIENT_FORMSPEC_PREPEND, 0, peer_id);
- pkt << FORMSPEC_VERSION_STRING + player->formspec_prepend;
+ pkt << player->formspec_prepend;
Send(&pkt);
}
-u32 Server::SendActiveObjectRemoveAdd(session_t peer_id, const std::string &datas)
+void Server::SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersao)
{
- NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD, datas.size(), peer_id);
- pkt.putRawString(datas.c_str(), datas.size());
+ // Radius inside which objects are active
+ static thread_local const s16 radius =
+ g_settings->getS16("active_object_send_range_blocks") * MAP_BLOCKSIZE;
+
+ // Radius inside which players are active
+ static thread_local const bool is_transfer_limited =
+ g_settings->exists("unlimited_player_transfer_distance") &&
+ !g_settings->getBool("unlimited_player_transfer_distance");
+
+ static thread_local const s16 player_transfer_dist =
+ g_settings->getS16("player_transfer_distance") * MAP_BLOCKSIZE;
+
+ s16 player_radius = player_transfer_dist == 0 && is_transfer_limited ?
+ radius : player_transfer_dist;
+
+ s16 my_radius = MYMIN(radius, playersao->getWantedRange() * MAP_BLOCKSIZE);
+ if (my_radius <= 0)
+ my_radius = radius;
+
+ std::queue<u16> removed_objects, added_objects;
+ m_env->getRemovedActiveObjects(playersao, my_radius, player_radius,
+ client->m_known_objects, removed_objects);
+ m_env->getAddedActiveObjects(playersao, my_radius, player_radius,
+ client->m_known_objects, added_objects);
+
+ int removed_count = removed_objects.size();
+ int added_count = added_objects.size();
+
+ if (removed_objects.empty() && added_objects.empty())
+ return;
+
+ char buf[4];
+ std::string data;
+
+ // Handle removed objects
+ writeU16((u8*)buf, removed_objects.size());
+ data.append(buf, 2);
+ while (!removed_objects.empty()) {
+ // Get object
+ u16 id = removed_objects.front();
+ ServerActiveObject* obj = m_env->getActiveObject(id);
+
+ // Add to data buffer for sending
+ writeU16((u8*)buf, id);
+ data.append(buf, 2);
+
+ // Remove from known objects
+ client->m_known_objects.erase(id);
+
+ if (obj && obj->m_known_by_count > 0)
+ obj->m_known_by_count--;
+
+ removed_objects.pop();
+ }
+
+ // Handle added objects
+ writeU16((u8*)buf, added_objects.size());
+ data.append(buf, 2);
+ while (!added_objects.empty()) {
+ // Get object
+ u16 id = added_objects.front();
+ ServerActiveObject *obj = m_env->getActiveObject(id);
+ added_objects.pop();
+
+ if (!obj) {
+ warningstream << FUNCTION_NAME << ": NULL object id="
+ << (int)id << std::endl;
+ continue;
+ }
+
+ // Get object type
+ u8 type = obj->getSendType();
+
+ // Add to data buffer for sending
+ writeU16((u8*)buf, id);
+ data.append(buf, 2);
+ writeU8((u8*)buf, type);
+ data.append(buf, 1);
+
+ data.append(serializeLongString(
+ obj->getClientInitializationData(client->net_proto_version)));
+
+ // Add to known objects
+ client->m_known_objects.insert(id);
+
+ obj->m_known_by_count++;
+ }
+
+ NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD, data.size(), client->peer_id);
+ pkt.putRawString(data.c_str(), data.size());
Send(&pkt);
- return pkt.getSize();
+
+ verbosestream << "Server::SendActiveObjectRemoveAdd: "
+ << removed_count << " removed, " << added_count << " added, "
+ << "packet size is " << pkt.getSize() << std::endl;
}
void Server::SendActiveObjectMessages(session_t peer_id, const std::string &datas,
@@ -1960,6 +1978,13 @@ void Server::SendCSMRestrictionFlags(session_t peer_id)
Send(&pkt);
}
+void Server::SendPlayerSpeed(session_t peer_id, const v3f &added_vel)
+{
+ NetworkPacket pkt(TOCLIENT_PLAYER_SPEED, 0, peer_id);
+ pkt << added_vel;
+ Send(&pkt);
+}
+
s32 Server::playSound(const SimpleSoundSpec &spec,
const ServerSoundParams &params)
{
@@ -2253,14 +2278,12 @@ void Server::SendBlocks(float dtime)
MutexAutoLock envlock(m_env_mutex);
//TODO check if one big lock could be faster then multiple small ones
- ScopeProfiler sp(g_profiler, "Server: sel and send blocks to clients");
-
std::vector<PrioritySortedBlockTransfer> queue;
u32 total_sending = 0;
{
- ScopeProfiler sp2(g_profiler, "Server: selecting blocks for sending");
+ ScopeProfiler sp2(g_profiler, "Server::SendBlocks(): Collect list");
std::vector<session_t> clients = m_clients.getClientIDs();
@@ -2289,16 +2312,16 @@ void Server::SendBlocks(float dtime)
u32 max_blocks_to_send = (m_env->getPlayerCount() + g_settings->getU32("max_users")) *
g_settings->getU32("max_simultaneous_block_sends_per_client") / 4 + 1;
+ ScopeProfiler sp(g_profiler, "Server::SendBlocks(): Send to clients");
+ Map &map = m_env->getMap();
+
for (const PrioritySortedBlockTransfer &block_to_send : queue) {
if (total_sending >= max_blocks_to_send)
break;
- MapBlock *block = nullptr;
- try {
- block = m_env->getMap().getBlockNoCreate(block_to_send.pos);
- } catch (const InvalidPositionException &e) {
+ MapBlock *block = map.getBlockNoCreateNoEx(block_to_send.pos);
+ if (!block)
continue;
- }
RemoteClient *client = m_clients.lockedGetClientNoEx(block_to_send.peer_id,
CS_Active);
@@ -2314,6 +2337,25 @@ void Server::SendBlocks(float dtime)
m_clients.unlock();
}
+bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos)
+{
+ MapBlock *block = m_env->getMap().getBlockNoCreateNoEx(blockpos);
+ if (!block)
+ return false;
+
+ m_clients.lock();
+ RemoteClient *client = m_clients.lockedGetClientNoEx(peer_id, CS_Active);
+ if (!client || client->isBlockSent(blockpos)) {
+ m_clients.unlock();
+ return false;
+ }
+ SendBlockNoLock(peer_id, block, client->serialization_version,
+ client->net_proto_version);
+ m_clients.unlock();
+
+ return true;
+}
+
void Server::fillMediaCache()
{
infostream<<"Server: Calculating media file checksums"<<std::endl;
@@ -2555,6 +2597,9 @@ void Server::sendDetachedInventory(const std::string &name, session_t peer_id)
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
@@ -2576,8 +2621,9 @@ void Server::sendDetachedInventory(const std::string &name, session_t peer_id)
// Serialization & NetworkPacket isn't a love story
std::ostringstream os(std::ios_base::binary);
inv_it->second->serialize(os);
+ inv_it->second->setModified(false);
- std::string os_str = os.str();
+ const std::string &os_str = os.str();
pkt << static_cast<u16>(os_str.size()); // HACK: to keep compatibility with 5.0.0 clients
pkt.putRawString(os_str);
}
@@ -2588,11 +2634,16 @@ void Server::sendDetachedInventory(const std::string &name, session_t peer_id)
Send(&pkt);
}
-void Server::sendDetachedInventories(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;
- //Inventory *inv = i->second;
+ if (incremental) {
+ Inventory *inv = detached_inventory.second;
+ if (!inv || !inv->checkModified())
+ continue;
+ }
+
sendDetachedInventory(name, peer_id);
}
}
@@ -2803,6 +2854,11 @@ void Server::UpdateCrafting(RemotePlayer *player)
if (!clist || clist->getSize() == 0)
return;
+ if (!clist->checkModified()) {
+ verbosestream << "Skip Server::UpdateCrafting(): list unmodified" << std::endl;
+ return;
+ }
+
// Get a preview for crafting
ItemStack preview;
InventoryLocation loc;
@@ -2843,28 +2899,28 @@ std::wstring Server::handleChat(const std::string &name, const std::wstring &wna
{
// If something goes wrong, this player is to blame
RollbackScopeActor rollback_scope(m_rollback,
- std::string("player:") + name);
+ std::string("player:") + name);
if (g_settings->getBool("strip_color_codes"))
wmessage = unescape_enriched(wmessage);
if (player) {
switch (player->canSendChatMessage()) {
- case RPLAYER_CHATRESULT_FLOODING: {
- std::wstringstream ws;
- ws << L"You cannot send more messages. You are limited to "
- << g_settings->getFloat("chat_message_limit_per_10sec")
- << L" messages per 10 seconds.";
- return ws.str();
- }
- case RPLAYER_CHATRESULT_KICK:
- DenyAccess_Legacy(player->getPeerId(),
- L"You have been kicked due to message flooding.");
- return L"";
- case RPLAYER_CHATRESULT_OK:
- break;
- default:
- FATAL_ERROR("Unhandled chat filtering result found.");
+ case RPLAYER_CHATRESULT_FLOODING: {
+ std::wstringstream ws;
+ ws << L"You cannot send more messages. You are limited to "
+ << g_settings->getFloat("chat_message_limit_per_10sec")
+ << L" messages per 10 seconds.";
+ return ws.str();
+ }
+ case RPLAYER_CHATRESULT_KICK:
+ DenyAccess_Legacy(player->getPeerId(),
+ L"You have been kicked due to message flooding.");
+ return L"";
+ case RPLAYER_CHATRESULT_OK:
+ break;
+ default:
+ FATAL_ERROR("Unhandled chat filtering result found.");
}
}
@@ -2892,10 +2948,8 @@ 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 {
- line += L"<";
- line += wname;
- line += L"> ";
- line += wmessage;
+ line += narrow_to_wide(m_script->formatChatMessage(name,
+ wide_to_narrow(wmessage)));
}
/*
@@ -3187,7 +3241,7 @@ bool Server::hudSetHotbarItemcount(RemotePlayer *player, s32 hotbar_itemcount)
return true;
}
-void Server::hudSetHotbarImage(RemotePlayer *player, std::string name)
+void Server::hudSetHotbarImage(RemotePlayer *player, const std::string &name)
{
if (!player)
return;
@@ -3196,7 +3250,7 @@ void Server::hudSetHotbarImage(RemotePlayer *player, std::string name)
SendHUDSetParam(player->getPeerId(), HUD_PARAM_HOTBAR_IMAGE, name);
}
-void Server::hudSetHotbarSelectedImage(RemotePlayer *player, std::string name)
+void Server::hudSetHotbarSelectedImage(RemotePlayer *player, const std::string &name)
{
if (!player)
return;
@@ -3352,7 +3406,9 @@ Inventory* Server::createDetachedInventory(const std::string &name, const std::s
Inventory *inv = new Inventory(m_itemdef);
sanity_check(inv);
m_detached_inventories[name] = inv;
- m_detached_inventories_player[name] = player;
+ if (!player.empty())
+ m_detached_inventories_player[name] = player;
+
//TODO find a better way to do this
sendDetachedInventory(name,PEER_ID_INEXISTENT);
return inv;
@@ -3367,6 +3423,9 @@ bool Server::removeDetachedInventory(const std::string &name)
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());
@@ -3492,52 +3551,71 @@ v3f Server::findSpawnPos()
{
ServerMap &map = m_env->getServerMap();
v3f nodeposf;
- if (g_settings->getV3FNoEx("static_spawnpoint", nodeposf)) {
+ if (g_settings->getV3FNoEx("static_spawnpoint", nodeposf))
return nodeposf * BS;
- }
bool is_good = false;
// Limit spawn range to mapgen edges (determined by 'mapgen_limit')
s32 range_max = map.getMapgenParams()->getSpawnRangeMax();
// Try to find a good place a few times
- for(s32 i = 0; i < 4000 && !is_good; i++) {
+ for (s32 i = 0; i < 4000 && !is_good; i++) {
s32 range = MYMIN(1 + i, range_max);
// We're going to try to throw the player to this position
v2s16 nodepos2d = v2s16(
-range + (myrand() % (range * 2)),
-range + (myrand() % (range * 2)));
-
// Get spawn level at point
s16 spawn_level = m_emerge->getSpawnLevelAtPoint(nodepos2d);
- // Continue if MAX_MAP_GENERATION_LIMIT was returned by
- // the mapgen to signify an unsuitable spawn position
- if (spawn_level == MAX_MAP_GENERATION_LIMIT)
+ // Continue if MAX_MAP_GENERATION_LIMIT was returned by the mapgen to
+ // signify an unsuitable spawn position, or if outside limits.
+ if (spawn_level >= MAX_MAP_GENERATION_LIMIT ||
+ spawn_level <= -MAX_MAP_GENERATION_LIMIT)
continue;
v3s16 nodepos(nodepos2d.X, spawn_level, nodepos2d.Y);
-
+ // Consecutive empty nodes
s32 air_count = 0;
- for (s32 i = 0; i < 10; i++) {
+
+ // Search upwards from 'spawn level' for 2 consecutive empty nodes, to
+ // avoid obstructions in already-generated mapblocks.
+ // In ungenerated mapblocks consisting of 'ignore' nodes, there will be
+ // no obstructions, but mapgen decorations are generated after spawn so
+ // the player may end up inside one.
+ for (s32 i = 0; i < 8; i++) {
v3s16 blockpos = getNodeBlockPos(nodepos);
map.emergeBlock(blockpos, true);
- content_t c = map.getNodeNoEx(nodepos).getContent();
- if (c == CONTENT_AIR || c == CONTENT_IGNORE) {
+ content_t c = map.getNode(nodepos).getContent();
+
+ // In generated mapblocks allow spawn in all 'airlike' drawtype nodes.
+ // In ungenerated mapblocks allow spawn in 'ignore' nodes.
+ if (m_nodedef->get(c).drawtype == NDT_AIRLIKE || c == CONTENT_IGNORE) {
air_count++;
if (air_count >= 2) {
+ // Spawn in lower empty node
+ nodepos.Y--;
nodeposf = intToFloat(nodepos, BS);
// Don't spawn the player outside map boundaries
if (objectpos_over_limit(nodeposf))
- continue;
+ // Exit this loop, positions above are probably over limit
+ break;
+
+ // Good position found, cause an exit from main loop
is_good = true;
break;
}
+ } else {
+ air_count = 0;
}
nodepos.Y++;
}
}
- return nodeposf;
+ if (is_good)
+ return nodeposf;
+
+ // No suitable spawn point found, return fallback 0,0,0
+ return v3f(0.0f, 0.0f, 0.0f);
}
void Server::requestShutdown(const std::string &msg, bool reconnect, float delay)
@@ -3653,10 +3731,7 @@ void dedicated_server_loop(Server &server, bool &kill)
for(;;) {
// This is kind of a hack but can be done like this
// because server.step() is very light
- {
- ScopeProfiler sp(g_profiler, "dedicated server sleep");
- sleep_ms((int)(steplen*1000.0));
- }
+ sleep_ms((int)(steplen*1000.0));
server.step(steplen);
if (server.isShutdownRequested() || kill)
diff --git a/src/server.h b/src/server.h
index 0a3e48072..d61840871 100644
--- a/src/server.h
+++ b/src/server.h
@@ -189,13 +189,13 @@ public:
This is accessed by the map, which is inside the environment,
so it shouldn't be a problem.
*/
- void onMapEditEvent(MapEditEvent *event);
+ 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, bool playerSend = true);
+ void setInventoryModified(const InventoryLocation &loc);
// Connection must be locked when called
std::wstring getStatusString();
@@ -296,8 +296,8 @@ public:
bool hudChange(RemotePlayer *player, u32 id, HudElementStat stat, void *value);
bool hudSetFlags(RemotePlayer *player, u32 flags, u32 mask);
bool hudSetHotbarItemcount(RemotePlayer *player, s32 hotbar_itemcount);
- void hudSetHotbarImage(RemotePlayer *player, std::string name);
- void hudSetHotbarSelectedImage(RemotePlayer *player, std::string name);
+ void hudSetHotbarImage(RemotePlayer *player, const std::string &name);
+ void hudSetHotbarSelectedImage(RemotePlayer *player, const std::string &name);
Address getPeerAddress(session_t peer_id);
@@ -333,8 +333,12 @@ public:
void SendPlayerHPOrDie(PlayerSAO *player, const PlayerHPChangeReason &reason);
void SendPlayerBreath(PlayerSAO *sao);
- void SendInventory(PlayerSAO* playerSAO);
+ void SendInventory(PlayerSAO *playerSAO, bool incremental);
void SendMovePlayer(session_t peer_id);
+ void SendPlayerSpeed(session_t peer_id, const v3f &added_vel);
+ void SendPlayerFov(session_t peer_id);
+
+ void sendDetachedInventories(session_t peer_id, bool incremental);
virtual bool registerModStorage(ModMetadata *storage);
virtual void unregisterModStorage(const std::string &name);
@@ -344,6 +348,9 @@ public:
bool sendModChannelMessage(const std::string &channel, const std::string &message);
ModChannel *getModChannel(const std::string &channel);
+ // Send block to specific player only
+ bool SendBlock(session_t peer_id, const v3s16 &blockpos);
+
// Bind address
Address m_bind_addr;
@@ -439,7 +446,6 @@ private:
const std::vector<std::string> &tosend);
void sendDetachedInventory(const std::string &name, session_t peer_id);
- void sendDetachedInventories(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,
@@ -464,7 +470,7 @@ private:
bool vertical, const std::string &texture,
const struct TileAnimationParams &animation, u8 glow);
- u32 SendActiveObjectRemoveAdd(session_t peer_id, const std::string &datas);
+ void SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersao);
void SendActiveObjectMessages(session_t peer_id, const std::string &datas,
bool reliable = true);
void SendCSMRestrictionFlags(session_t peer_id);
@@ -477,7 +483,7 @@ private:
void RespawnPlayer(session_t peer_id);
void DeleteClient(session_t peer_id, ClientDeletionReason reason);
void UpdateCrafting(RemotePlayer *player);
- bool checkInteractDistance(RemotePlayer *player, const f32 d, const std::string what);
+ bool checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what);
void handleChatInterfaceEvent(ChatEvent *evt);
@@ -510,7 +516,6 @@ private:
/*
Variables
*/
-
// World directory
std::string m_path_world;
// Subgame specification
@@ -570,7 +575,6 @@ private:
/*
Threads
*/
-
// A buffer for time steps
// step() increments and AsyncRunStep() run by m_thread reads it.
float m_step_dtime = 0.0f;
@@ -585,14 +589,14 @@ private:
/*
Time related stuff
*/
-
// 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
- */
+ Client interface
+ */
ClientInterface m_clients;
/*
diff --git a/src/server/activeobjectmgr.cpp b/src/server/activeobjectmgr.cpp
index 56febd76e..984ae7794 100644
--- a/src/server/activeobjectmgr.cpp
+++ b/src/server/activeobjectmgr.cpp
@@ -44,8 +44,7 @@ void ActiveObjectMgr::clear(const std::function<bool(ServerActiveObject *, u16)>
void ActiveObjectMgr::step(
float dtime, const std::function<void(ServerActiveObject *)> &f)
{
- g_profiler->avg("Server::ActiveObjectMgr: num of objects",
- m_active_objects.size());
+ g_profiler->avg("ActiveObjectMgr: SAO count [#]", m_active_objects.size());
for (auto &ao_it : m_active_objects) {
f(ao_it.second);
}
@@ -115,11 +114,12 @@ void ActiveObjectMgr::removeObject(u16 id)
void ActiveObjectMgr::getObjectsInsideRadius(
const v3f &pos, float radius, std::vector<u16> &result)
{
+ 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.getDistanceFrom(pos) > radius)
+ if (objectpos.getDistanceFromSQ(pos) > r2)
continue;
result.push_back(id);
}
diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp
index 0a83c4a38..333d32ff5 100644
--- a/src/serverenvironment.cpp
+++ b/src/serverenvironment.cpp
@@ -384,6 +384,9 @@ void ActiveBlockList::update(std::vector<PlayerSAO*> &active_players,
ServerEnvironment
*/
+// Random device to seed pseudo random generators.
+static std::random_device seed;
+
ServerEnvironment::ServerEnvironment(ServerMap *map,
ServerScripting *scriptIface, Server *server,
const std::string &path_world):
@@ -391,7 +394,8 @@ ServerEnvironment::ServerEnvironment(ServerMap *map,
m_map(map),
m_script(scriptIface),
m_server(server),
- m_path_world(path_world)
+ m_path_world(path_world),
+ m_rgen(seed())
{
// Determine which database backend to use
std::string conf_path = path_world + DIR_DELIM + "world.mt";
@@ -546,7 +550,7 @@ 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().getNodeNoEx(iterator.m_current_node_pos);
+ MapNode n = getMap().getNode(iterator.m_current_node_pos);
// Return non-air
if (n.param0 != CONTENT_AIR) {
@@ -910,7 +914,7 @@ public:
c = n.getContent();
} else {
// otherwise consult the map
- MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
+ MapNode n = map->getNode(p1 + block->getPosRelative());
c = n.getContent();
}
if (CONTAINS(aabm.required_neighbors, c))
@@ -1004,7 +1008,7 @@ void ServerEnvironment::addLoadingBlockModifierDef(LoadingBlockModifierDef *lbm)
bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
{
const NodeDefManager *ndef = m_server->ndef();
- MapNode n_old = m_map->getNodeNoEx(p);
+ MapNode n_old = m_map->getNode(p);
const ContentFeatures &cf_old = ndef->get(n_old);
@@ -1037,7 +1041,7 @@ bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
bool ServerEnvironment::removeNode(v3s16 p)
{
const NodeDefManager *ndef = m_server->ndef();
- MapNode n_old = m_map->getNodeNoEx(p);
+ MapNode n_old = m_map->getNode(p);
// Call destructor
if (ndef->get(n_old).has_on_destruct)
@@ -1196,6 +1200,7 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode)
void ServerEnvironment::step(float dtime)
{
+ ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
/* Step time of day */
stepTimeOfDay(dtime);
@@ -1220,7 +1225,7 @@ void ServerEnvironment::step(float dtime)
Handle players
*/
{
- ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG);
+ ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
for (RemotePlayer *player : m_players) {
// Ignore disconnected players
if (player->getPeerId() == PEER_ID_INEXISTENT)
@@ -1235,7 +1240,7 @@ void ServerEnvironment::step(float dtime)
Manage active block list
*/
if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
- ScopeProfiler sp(g_profiler, "SEnv: manage act. block list avg per interval", SPT_AVG);
+ ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
/*
Get player block positions
*/
@@ -1301,7 +1306,7 @@ void ServerEnvironment::step(float dtime)
Mess around in active blocks
*/
if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
- ScopeProfiler sp(g_profiler, "SEnv: mess in act. blocks avg per interval", SPT_AVG);
+ ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
float dtime = m_cache_nodetimer_interval;
@@ -1338,47 +1343,56 @@ void ServerEnvironment::step(float dtime)
}
}
- if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval))
- do { // breakable
- if (m_active_block_interval_overload_skip > 0) {
- ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips");
- m_active_block_interval_overload_skip--;
- break;
- }
- ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
- TimeTaker timer("modify in active blocks per interval");
-
- // Initialize handling of ActiveBlockModifiers
- ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
-
- int blocks_scanned = 0;
- int abms_run = 0;
- int blocks_cached = 0;
- for (const v3s16 &p : m_active_blocks.m_abm_list) {
- MapBlock *block = m_map->getBlockNoCreateNoEx(p);
- if (!block)
- continue;
+ if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
+ ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
+ TimeTaker timer("modify in active blocks per interval");
- // Set current time as timestamp
- block->setTimestampNoChangedFlag(m_game_time);
+ // Initialize handling of ActiveBlockModifiers
+ ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
- /* Handle ActiveBlockModifiers */
- abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
- }
- g_profiler->avg("SEnv: active blocks", m_active_blocks.m_abm_list.size());
- g_profiler->avg("SEnv: active blocks cached", blocks_cached);
- g_profiler->avg("SEnv: active blocks scanned for ABMs", blocks_scanned);
- g_profiler->avg("SEnv: ABMs run", abms_run);
+ int blocks_scanned = 0;
+ int abms_run = 0;
+ int blocks_cached = 0;
+
+ std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
+
+ // Shuffle the active blocks so that each block gets an equal chance
+ // of having its ABMs run.
+ std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
+ std::shuffle(output.begin(), output.end(), m_rgen);
+
+ int i = 0;
+ // The time budget for ABMs is 20%.
+ u32 max_time_ms = m_cache_abm_interval * 1000 / 5;
+ for (const v3s16 &p : output) {
+ MapBlock *block = m_map->getBlockNoCreateNoEx(p);
+ if (!block)
+ continue;
+
+ i++;
+
+ // Set current time as timestamp
+ block->setTimestampNoChangedFlag(m_game_time);
+
+ /* Handle ActiveBlockModifiers */
+ abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
+
+ u32 time_ms = timer.getTimerTime();
- u32 time_ms = timer.stop(true);
- u32 max_time_ms = 200;
if (time_ms > max_time_ms) {
- warningstream<<"active block modifiers took "
- <<time_ms<<"ms (longer than "
- <<max_time_ms<<"ms)"<<std::endl;
- m_active_block_interval_overload_skip = (time_ms / max_time_ms) + 1;
+ warningstream << "active block modifiers took "
+ << time_ms << "ms (processed " << i << " of "
+ << output.size() << " active blocks)" << std::endl;
+ break;
}
- }while(0);
+ }
+ g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
+ g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
+ g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
+ g_profiler->avg("ServerEnv: ABMs run", abms_run);
+
+ timer.stop(true);
+ }
/*
Step script environment (run global on_step())
@@ -1389,7 +1403,7 @@ void ServerEnvironment::step(float dtime)
Step active objects
*/
{
- ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG);
+ ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
// This helps the objects to send data at the same time
bool send_recommended = false;
@@ -1418,7 +1432,6 @@ void ServerEnvironment::step(float dtime)
Manage active objects
*/
if (m_object_management_interval.step(dtime, 0.5)) {
- ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG);
removeRemovedObjects();
}
@@ -1441,6 +1454,19 @@ void ServerEnvironment::step(float dtime)
++i;
}
}
+
+ // Send outdated player inventories
+ for (RemotePlayer *player : m_players) {
+ if (player->getPeerId() == PEER_ID_INEXISTENT)
+ continue;
+
+ PlayerSAO *sao = player->getPlayerSAO();
+ if (sao && player->inventory.checkModified())
+ m_server->SendInventory(sao, true);
+ }
+
+ // Send outdated detached inventories
+ m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
}
u32 ServerEnvironment::addParticleSpawner(float exptime)
@@ -1673,6 +1699,8 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
*/
void ServerEnvironment::removeRemovedObjects()
{
+ ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
+
auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
// This shouldn't happen but check it
if (!obj) {
diff --git a/src/serverenvironment.h b/src/serverenvironment.h
index 77adcf627..cc4ecd797 100644
--- a/src/serverenvironment.h
+++ b/src/serverenvironment.h
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "server/activeobjectmgr.h"
#include "util/numeric.h"
#include <set>
+#include <random>
class IGameDef;
class ServerMap;
@@ -446,7 +447,6 @@ private:
IntervalLimiter m_active_blocks_management_interval;
IntervalLimiter m_active_block_modifier_interval;
IntervalLimiter m_active_blocks_nodemetadata_interval;
- int m_active_block_interval_overload_skip = 0;
// Time from the beginning of the game in seconds.
// Incremented in step().
u32 m_game_time = 0;
@@ -470,6 +470,9 @@ private:
PlayerDatabase *m_player_database = nullptr;
AuthDatabase *m_auth_database = nullptr;
+ // Pseudo random generator for shuffling, etc.
+ std::mt19937 m_rgen;
+
// Particles
IntervalLimiter m_particle_management_interval;
std::unordered_map<u32, float> m_particle_spawners;
diff --git a/src/serverobject.cpp b/src/serverobject.cpp
index 4eebc0da4..1ed33f66b 100644
--- a/src/serverobject.cpp
+++ b/src/serverobject.cpp
@@ -68,25 +68,16 @@ float ServerActiveObject::getMinimumSavedMovement()
return 2.0*BS;
}
-ItemStack ServerActiveObject::getWieldedItem() const
+ItemStack ServerActiveObject::getWieldedItem(ItemStack *selected, ItemStack *hand) const
{
- const Inventory *inv = getInventory();
- if(inv)
- {
- const InventoryList *list = inv->getList(getWieldList());
- if(list && (getWieldIndex() < (s32)list->getSize()))
- return list->getItem(getWieldIndex());
- }
+ *selected = ItemStack();
+ if (hand)
+ *hand = ItemStack();
+
return ItemStack();
}
bool ServerActiveObject::setWieldedItem(const ItemStack &item)
{
- if(Inventory *inv = getInventory()) {
- if (InventoryList *list = inv->getList(getWieldList())) {
- list->changeItem(getWieldIndex(), item);
- return true;
- }
- }
return false;
}
diff --git a/src/serverobject.h b/src/serverobject.h
index 4a9430107..48689fcb4 100644
--- a/src/serverobject.h
+++ b/src/serverobject.h
@@ -133,10 +133,10 @@ public:
{return true;}
// Returns tool wear
- virtual int punch(v3f dir,
- const ToolCapabilities *toolcap=NULL,
- ServerActiveObject *puncher=NULL,
- float time_from_last_punch=1000000)
+ virtual u16 punch(v3f dir,
+ const ToolCapabilities *toolcap = nullptr,
+ ServerActiveObject *puncher = nullptr,
+ float time_from_last_punch = 1000000.0f)
{ return 0; }
virtual void rightClick(ServerActiveObject *clicker)
{}
@@ -147,7 +147,7 @@ public:
virtual void setArmorGroups(const ItemGroupList &armor_groups)
{}
- virtual const ItemGroupList &getArmorGroups()
+ virtual const ItemGroupList &getArmorGroups() const
{ static ItemGroupList rv; return rv; }
virtual void setPhysicsOverride(float physics_override_speed, float physics_override_jump, float physics_override_gravity)
{}
@@ -161,17 +161,7 @@ public:
{}
virtual void getBonePosition(const std::string &bone, v3f *position, v3f *lotation)
{}
- virtual void setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation)
- {}
- virtual void getAttachment(int *parent_id, std::string *bone, v3f *position, v3f *rotation)
- {}
- virtual void clearChildAttachments() {}
- virtual void clearParentAttachment() {}
- virtual void addAttachmentChild(int child_id)
- {}
- virtual void removeAttachmentChild(int child_id)
- {}
- virtual const std::unordered_set<int> &getAttachmentChildIds()
+ virtual const std::unordered_set<int> &getAttachmentChildIds() const
{ static std::unordered_set<int> rv; return rv; }
virtual ServerActiveObject *getParent() const { return nullptr; }
virtual ObjectProperties* accessObjectProperties()
@@ -180,9 +170,7 @@ public:
{}
// Inventory and wielded item
- virtual Inventory* getInventory()
- { return NULL; }
- virtual const Inventory* getInventory() const
+ virtual Inventory *getInventory() const
{ return NULL; }
virtual InventoryLocation getInventoryLocation() const
{ return InventoryLocation(); }
@@ -190,9 +178,10 @@ public:
{}
virtual std::string getWieldList() const
{ return ""; }
- virtual int getWieldIndex() const
+ virtual u16 getWieldIndex() const
{ return 0; }
- virtual ItemStack getWieldedItem() const;
+ virtual ItemStack getWieldedItem(ItemStack *selected,
+ ItemStack *hand = nullptr) const;
virtual bool setWieldedItem(const ItemStack &item);
inline void attachParticleSpawner(u32 id)
{
diff --git a/src/settings.cpp b/src/settings.cpp
index 66c17e12d..c1fe41fa3 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -69,7 +69,9 @@ Settings & Settings::operator = (const Settings &other)
bool Settings::checkNameValid(const std::string &name)
{
bool valid = name.find_first_of("=\"{}#") == std::string::npos;
- if (valid) valid = trim(name) == name;
+ if (valid)
+ valid = std::find_if(name.begin(), name.end(), ::isspace) == name.end();
+
if (!valid) {
errorstream << "Invalid setting name \"" << name << "\""
<< std::endl;
@@ -906,17 +908,20 @@ bool Settings::setNoiseParams(const std::string &name,
bool Settings::remove(const std::string &name)
{
- MutexAutoLock lock(m_mutex);
+ // Lock as short as possible, unlock before doCallbacks()
+ m_mutex.lock();
SettingEntries::iterator it = m_settings.find(name);
if (it != m_settings.end()) {
delete it->second.group;
m_settings.erase(it);
+ m_mutex.unlock();
doCallbacks(name);
return true;
}
+ m_mutex.unlock();
return false;
}
diff --git a/src/settings_translation_file.cpp b/src/settings_translation_file.cpp
index 9cc5c0f0e..eea045ae3 100644
--- a/src/settings_translation_file.cpp
+++ b/src/settings_translation_file.cpp
@@ -231,7 +231,7 @@ fake_function() {
gettext("FSAA");
gettext("Experimental option, might cause visible spaces between blocks\nwhen set to higher number than 0.");
gettext("Undersampling");
- gettext("Undersampling is similar to using lower screen resolution, but it applies\nto the game world only, keeping the GUI intact.\nIt should give significant performance boost at the cost of less detailed image.");
+ gettext("Undersampling is similar to using a lower screen resolution, but it applies\nto the game world only, keeping the GUI intact.\nIt should give a significant performance boost at the cost of less detailed image.\nHigher values result in a less detailed image.");
gettext("Shaders");
gettext("Shaders");
gettext("Shaders allow advanced visual effects and may increase performance on some video\ncards.\nThis only works with the OpenGL video backend.");
@@ -265,9 +265,9 @@ fake_function() {
gettext("Waving Nodes");
gettext("Waving water");
gettext("Set to true enables waving water.\nRequires shaders to be enabled.");
- gettext("Waving water height");
- gettext("Waving water length");
- gettext("Waving water speed");
+ gettext("Waving water wave height");
+ gettext("Waving water wavelength");
+ gettext("Waving water wave speed");
gettext("Waving leaves");
gettext("Set to true enables waving leaves.\nRequires shaders to be enabled.");
gettext("Waving plants");
@@ -283,8 +283,8 @@ fake_function() {
gettext("Open the pause menu when the window's focus is lost. Does not pause if a formspec is\nopen.");
gettext("Viewing range");
gettext("View distance in nodes.");
- gettext("Near plane");
- gettext("Camera near plane distance in nodes, between 0 and 0.5\nMost users will not need to change this.\nIncreasing can reduce artifacting on weaker GPUs.\n0.1 = Default, 0.25 = Good value for weaker tablets.");
+ gettext("Near clipping plane");
+ gettext("Camera 'near clipping plane' distance in nodes, between 0 and 0.5.\nMost users will not need to change this.\nIncreasing can reduce artifacting on weaker GPUs.\n0.1 = Default, 0.25 = Good value for weaker tablets.");
gettext("Screen width");
gettext("Width component of the initial window size.");
gettext("Screen height");
@@ -524,6 +524,8 @@ fake_function() {
gettext("If enabled, disable cheat prevention in multiplayer.");
gettext("Rollback recording");
gettext("If enabled, actions are recorded for rollback.\nThis option is only read when server starts.");
+ gettext("Chat message format");
+ gettext("Format of player chat messages. The following strings are valid placeholders:\n@name, @message, @timestamp (optional)");
gettext("Shutdown message");
gettext("A message to be displayed to all clients when the server shuts down.");
gettext("Crash message");
@@ -554,17 +556,29 @@ fake_function() {
gettext("Kick players who sent more than X messages per 10 seconds.");
gettext("Physics");
gettext("Default acceleration");
+ gettext("Horizontal and vertical acceleration on ground or when climbing,\nin nodes per second per second.");
gettext("Acceleration in air");
+ gettext("Horizontal acceleration in air when jumping or falling,\nin nodes per second per second.");
gettext("Fast mode acceleration");
+ gettext("Horizontal and vertical acceleration in fast mode,\nin nodes per second per second.");
gettext("Walking speed");
+ gettext("Walking and flying speed, in nodes per second.");
gettext("Sneaking speed");
+ gettext("Sneaking speed, in nodes per second.");
gettext("Fast mode speed");
+ gettext("Walking, flying and climbing speed in fast mode, in nodes per second.");
gettext("Climbing speed");
+ gettext("Vertical climbing speed, in nodes per second.");
gettext("Jumping speed");
+ gettext("Initial vertical speed when jumping, in nodes per second.");
gettext("Liquid fluidity");
+ gettext("Decrease this to increase liquid resistence to movement.");
gettext("Liquid fluidity smoothing");
- gettext("Liquid sinking speed");
+ gettext("Maximum liquid resistence. Controls deceleration when entering liquid at\nhigh speed.");
+ gettext("Liquid sinking");
+ gettext("Controls sinking speed in liquid.");
gettext("Gravity");
+ gettext("Acceleration of gravity, in nodes per second per second.");
gettext("Advanced");
gettext("Deprecated Lua API handling");
gettext("Handling for deprecated lua api calls:\n- legacy: (try to) mimic old behaviour (default for release).\n- log: mimic and log backtrace of deprecated call (default for debug).\n- error: abort on usage of deprecated call (suggested for mod developers).");
@@ -638,6 +652,8 @@ fake_function() {
gettext("Set the language. Leave empty to use the system language.\nA restart is required after changing this.");
gettext("Debug log level");
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("IPv6");
gettext("IPv6 support.");
gettext("Advanced");
@@ -657,7 +673,7 @@ fake_function() {
gettext("Print the engine's profiling data in regular intervals (in seconds).\n0 = disable. Useful for developers.");
gettext("Mapgen");
gettext("Mapgen name");
- gettext("Name of map generator to be used when creating a new world.\nCreating a world in the main menu will override this.\nCurrent stable mapgens:\nv5, v6, v7 (except floatlands), singlenode.\n'stable' means the terrain shape in an existing world will not be changed\nin the future. Note that biomes are defined by games and may still change.");
+ gettext("Name of map generator to be used when creating a new world.\nCreating a world in the main menu will override this.\nCurrent mapgens in a highly unstable state:\n- The optional floatlands of v7 (disabled by default).");
gettext("Water level");
gettext("Water surface level of the world.");
gettext("Max block generate distance");
@@ -666,8 +682,6 @@ fake_function() {
gettext("Limit of map generation, in nodes, in all 6 directions from (0, 0, 0).\nOnly mapchunks completely within the mapgen limit are generated.\nValue is stored per-world.");
gettext("Mapgen flags");
gettext("Global map generation attributes.\nIn Mapgen v6 the 'decorations' flag controls all decorations except trees\nand junglegrass, in all other mapgens this flag controls all decorations.");
- gettext("Projecting dungeons");
- gettext("Whether dungeons occasionally project from the terrain.");
gettext("Biome API temperature and humidity noise parameters");
gettext("Heat noise");
gettext("Temperature variation for biomes.");
@@ -685,7 +699,7 @@ fake_function() {
gettext("Large cave depth");
gettext("Y of upper limit of large caves.");
gettext("Lava depth");
- gettext("Y of upper limit of lava in large caves.");
+ gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves.");
gettext("Cavern limit");
gettext("Y-level of cavern upper limit.");
gettext("Cavern taper");
@@ -711,11 +725,13 @@ fake_function() {
gettext("3D noise defining giant caverns.");
gettext("Ground noise");
gettext("3D noise defining terrain.");
+ gettext("Dungeon noise");
+ gettext("3D noise that determines number of dungeons per mapchunk.");
gettext("Mapgen V6");
gettext("Mapgen V6 specific flags");
- gettext("Map generation attributes specific to Mapgen v6.\nThe 'snowbiomes' flag enables the new 5 biome system.\nWhen the new biome system is enabled jungles are automatically enabled and\nthe 'jungles' flag is ignored.");
+ gettext("Map generation attributes specific to Mapgen v6.\nThe 'snowbiomes' flag enables the new 5 biome system.\nWhen the 'snowbiomes' flag is enabled jungles are automatically enabled and\nthe 'jungles' flag is ignored.");
gettext("Desert noise threshold");
- gettext("Deserts occur when np_biome exceeds this value.\nWhen the new biome system is enabled, this is ignored.");
+ gettext("Deserts occur when np_biome exceeds this value.\nWhen the 'snowbiomes' flag is enabled, this is ignored.");
gettext("Beach noise threshold");
gettext("Sandy beaches occur when np_beach exceeds this value.");
gettext("Dungeon minimum Y");
@@ -755,7 +771,7 @@ fake_function() {
gettext("Large cave depth");
gettext("Y of upper limit of large caves.");
gettext("Lava depth");
- gettext("Y of upper limit of lava in large caves.");
+ gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves.");
gettext("Floatland mountain density");
gettext("Controls the density of mountain-type floatlands.\nIs a noise offset added to the 'mgv7_np_mountain' noise value.");
gettext("Floatland mountain height");
@@ -805,17 +821,25 @@ fake_function() {
gettext("First of two 3D noises that together define tunnels.");
gettext("Cave2 noise");
gettext("Second of two 3D noises that together define tunnels.");
+ gettext("Dungeon noise");
+ gettext("3D noise that determines number of dungeons per mapchunk.");
gettext("Mapgen Carpathian");
gettext("Mapgen Carpathian specific flags");
gettext("Map generation attributes specific to Mapgen Carpathian.");
gettext("Base ground level");
gettext("Defines the base ground level.");
+ gettext("River channel width");
+ gettext("Defines the width of the river channel.");
+ gettext("River channel depth");
+ gettext("Defines the depth of the river channel.");
+ gettext("River valley width");
+ gettext("Defines the width of the river valley.");
gettext("Cave width");
gettext("Controls width of tunnels, a smaller value creates wider tunnels.");
gettext("Large cave depth");
gettext("Y of upper limit of large caves.");
gettext("Lava depth");
- gettext("Y of upper limit of lava in large caves.");
+ gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves.");
gettext("Cavern limit");
gettext("Y-level of cavern upper limit.");
gettext("Cavern taper");
@@ -849,6 +873,8 @@ fake_function() {
gettext("2D noise that controls the shape/size of ridged mountains.");
gettext("Step mountain size noise");
gettext("2D noise that controls the shape/size of step mountains.");
+ gettext("River noise");
+ gettext("2D noise that locates the river valleys and channels.");
gettext("Mountain variation noise");
gettext("3D noise for mountain overhangs, cliffs, etc. Usually small variations.");
gettext("Cave1 noise");
@@ -857,6 +883,8 @@ fake_function() {
gettext("Second of two 3D noises that together define tunnels.");
gettext("Cavern noise");
gettext("3D noise defining giant caverns.");
+ gettext("Dungeon noise");
+ gettext("3D noise that determines number of dungeons per mapchunk.");
gettext("Mapgen Flat");
gettext("Mapgen Flat specific flags");
gettext("Map generation attributes specific to Mapgen flat.\nOccasional lakes and hills can be added to the flat world.");
@@ -865,7 +893,7 @@ fake_function() {
gettext("Large cave depth");
gettext("Y of upper limit of large caves.");
gettext("Lava depth");
- gettext("Y of upper limit of lava in large caves.");
+ gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves.");
gettext("Cave width");
gettext("Controls width of tunnels, a smaller value creates wider tunnels.");
gettext("Lake threshold");
@@ -889,13 +917,17 @@ fake_function() {
gettext("First of two 3D noises that together define tunnels.");
gettext("Cave2 noise");
gettext("Second of two 3D noises that together define tunnels.");
+ gettext("Dungeon noise");
+ gettext("3D noise that determines number of dungeons per mapchunk.");
gettext("Mapgen Fractal");
+ gettext("Mapgen Fractal specific flags");
+ gettext("Map generation attributes specific to Mapgen flat.\n'terrain' enables the generation of non-fractal terrain:\nocean, islands and underground.");
gettext("Cave width");
gettext("Controls width of tunnels, a smaller value creates wider tunnels.");
gettext("Large cave depth");
gettext("Y of upper limit of large caves.");
gettext("Lava depth");
- gettext("Y of upper limit of lava in large caves.");
+ gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves.");
gettext("Dungeon minimum Y");
gettext("Lower Y limit of dungeons.");
gettext("Dungeon maximum Y");
@@ -927,6 +959,8 @@ fake_function() {
gettext("First of two 3D noises that together define tunnels.");
gettext("Cave2 noise");
gettext("Second of two 3D noises that together define tunnels.");
+ gettext("Dungeon noise");
+ gettext("3D noise that determines number of dungeons per mapchunk.");
gettext("Mapgen Valleys");
gettext("Mapgen Valleys specific flags");
gettext("Map generation attributes specific to Mapgen Valleys.\n'altitude_chill': Reduces heat with altitude.\n'humid_rivers': Increases humidity around rivers.\n'vary_river_depth': If enabled, low humidity and high heat causes rivers\nto become shallower and occasionally dry.\n'altitude_dry': Reduces humidity with altitude.");
@@ -935,7 +969,7 @@ fake_function() {
gettext("Large cave depth");
gettext("Depth below which you'll find large caves.");
gettext("Lava depth");
- gettext("Y of upper limit of lava in large caves.");
+ gettext("Deprecated, define and locate cave liquids using biome definitions instead.\nY of upper limit of lava in large caves.");
gettext("Cavern upper limit");
gettext("Depth below which you'll find giant caverns.");
gettext("Cavern taper");
@@ -973,6 +1007,8 @@ fake_function() {
gettext("Amplifies the valleys.");
gettext("Valley slope");
gettext("Slope and fill work together to modify the heights.");
+ gettext("Dungeon noise");
+ gettext("3D noise that determines number of dungeons per mapchunk.");
gettext("Advanced");
gettext("Chunk size");
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.");
@@ -985,7 +1021,7 @@ fake_function() {
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("Number of emerge threads");
- gettext("Number of emerge threads to use.\nEmpty or 0 value:\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'.\nFor many users the optimum setting may be '1'.");
+ 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("Online Content Repository");
gettext("ContentDB URL");
gettext("The URL for the content repository");
diff --git a/src/staticobject.cpp b/src/staticobject.cpp
index b331ac2f2..bebca12ec 100644
--- a/src/staticobject.cpp
+++ b/src/staticobject.cpp
@@ -77,6 +77,15 @@ void StaticObjectList::serialize(std::ostream &os)
}
void StaticObjectList::deSerialize(std::istream &is)
{
+ if (m_active.size()) {
+ errorstream << "StaticObjectList::deSerialize(): "
+ << "deserializing objects while " << m_active.size()
+ << " active objects already exist (not cleared). "
+ << m_stored.size() << " stored objects _were_ cleared"
+ << std::endl;
+ }
+ m_stored.clear();
+
// version
u8 version = readU8(is);
// count
diff --git a/src/tool.cpp b/src/tool.cpp
index becb574b0..d911c518f 100644
--- a/src/tool.cpp
+++ b/src/tool.cpp
@@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include "tool.h"
+#include "itemdef.h"
#include "itemgroup.h"
#include "log.h"
#include "inventory.h"
@@ -55,7 +56,10 @@ void ToolGroupCap::fromJson(const Json::Value &json)
void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const
{
- writeU8(os, 4); // protocol_version >= 37
+ if (protocol_version >= 38)
+ writeU8(os, 5);
+ else
+ writeU8(os, 4); // proto == 37
writeF32(os, full_punch_interval);
writeS16(os, max_drop_level);
writeU32(os, groupcaps.size());
@@ -78,6 +82,9 @@ void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const
os << serializeString(damageGroup.first);
writeS16(os, damageGroup.second);
}
+
+ if (protocol_version >= 38)
+ writeU16(os, rangelim(punch_attack_uses, 0, U16_MAX));
}
void ToolCapabilities::deSerialize(std::istream &is)
@@ -110,6 +117,9 @@ void ToolCapabilities::deSerialize(std::istream &is)
s16 rating = readS16(is);
damageGroups[name] = rating;
}
+
+ if (version >= 5)
+ punch_attack_uses = readU16(is);
}
void ToolCapabilities::serializeJson(std::ostream &os) const
@@ -117,6 +127,7 @@ void ToolCapabilities::serializeJson(std::ostream &os) const
Json::Value root;
root["full_punch_interval"] = full_punch_interval;
root["max_drop_level"] = max_drop_level;
+ root["punch_attack_uses"] = punch_attack_uses;
Json::Value groupcaps_object;
for (auto groupcap : groupcaps) {
@@ -143,6 +154,8 @@ void ToolCapabilities::deserializeJson(std::istream &is)
full_punch_interval = root["full_punch_interval"].asFloat();
if (root["max_drop_level"].isInt())
max_drop_level = root["max_drop_level"].asInt();
+ if (root["punch_attack_uses"].isInt())
+ punch_attack_uses = root["punch_attack_uses"].asInt();
Json::Value &groupcaps_object = root["groupcaps"];
if (groupcaps_object.isObject()) {
@@ -172,14 +185,16 @@ void ToolCapabilities::deserializeJson(std::istream &is)
DigParams getDigParams(const ItemGroupList &groups,
const ToolCapabilities *tp)
{
- // Group dig_immediate has fixed time and no wear
- switch (itemgroup_get(groups, "dig_immediate")) {
- case 2:
- return DigParams(true, 0.5, 0, "dig_immediate");
- case 3:
- return DigParams(true, 0, 0, "dig_immediate");
- default:
- break;
+ // Group dig_immediate defaults to fixed time and no wear
+ if (tp->groupcaps.find("dig_immediate") == tp->groupcaps.cend()) {
+ switch (itemgroup_get(groups, "dig_immediate")) {
+ case 2:
+ return DigParams(true, 0.5, 0, "dig_immediate");
+ case 3:
+ return DigParams(true, 0, 0, "dig_immediate");
+ default:
+ break;
+ }
}
// Values to be returned (with a bit of conversion)
@@ -224,16 +239,20 @@ HitParams getHitParams(const ItemGroupList &armor_groups,
const ToolCapabilities *tp, float time_from_last_punch)
{
s16 damage = 0;
- float full_punch_interval = tp->full_punch_interval;
+ float result_wear = 0.0f;
+ float punch_interval_multiplier =
+ rangelim(time_from_last_punch / tp->full_punch_interval, 0.0f, 1.0f);
for (const auto &damageGroup : tp->damageGroups) {
s16 armor = itemgroup_get(armor_groups, damageGroup.first);
- damage += damageGroup.second
- * rangelim(time_from_last_punch / full_punch_interval, 0.0, 1.0)
- * armor / 100.0;
+ damage += damageGroup.second * punch_interval_multiplier * armor / 100.0;
}
- return {damage, 0};
+ if (tp->punch_attack_uses > 0)
+ result_wear = 1.0f / tp->punch_attack_uses * punch_interval_multiplier;
+
+ u16 wear_i = U16_MAX * result_wear;
+ return {damage, wear_i};
}
HitParams getHitParams(const ItemGroupList &armor_groups,
@@ -275,4 +294,16 @@ PunchDamageResult getPunchDamage(
return result;
}
+f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_hand)
+{
+ float max_d = def_selected.range;
+ float max_d_hand = def_hand.range;
+
+ if (max_d < 0 && max_d_hand >= 0)
+ max_d = max_d_hand;
+ else if (max_d < 0)
+ max_d = 4.0f;
+
+ return max_d;
+}
diff --git a/src/tool.h b/src/tool.h
index 00fae4881..59dd501f5 100644
--- a/src/tool.h
+++ b/src/tool.h
@@ -25,6 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "itemgroup.h"
#include <json/json.h>
+struct ItemDefinition;
+
struct ToolGroupCap
{
std::unordered_map<int, float> times;
@@ -58,17 +60,20 @@ struct ToolCapabilities
int max_drop_level;
ToolGCMap groupcaps;
DamageGroup damageGroups;
+ int punch_attack_uses;
ToolCapabilities(
- float full_punch_interval_=1.4,
- int max_drop_level_=1,
+ float full_punch_interval_ = 1.4f,
+ int max_drop_level_ = 1,
const ToolGCMap &groupcaps_ = ToolGCMap(),
- const DamageGroup &damageGroups_ = DamageGroup()
+ const DamageGroup &damageGroups_ = DamageGroup(),
+ int punch_attack_uses_ = 0
):
full_punch_interval(full_punch_interval_),
max_drop_level(max_drop_level_),
groupcaps(groupcaps_),
- damageGroups(damageGroups_)
+ damageGroups(damageGroups_),
+ punch_attack_uses(punch_attack_uses_)
{}
void serialize(std::ostream &os, u16 version) const;
@@ -101,9 +106,9 @@ DigParams getDigParams(const ItemGroupList &groups,
struct HitParams
{
s16 hp;
- s16 wear;
+ u16 wear;
- HitParams(s16 hp_=0, s16 wear_=0):
+ HitParams(s16 hp_ = 0, u16 wear_ = 0):
hp(hp_),
wear(wear_)
{}
@@ -132,3 +137,5 @@ PunchDamageResult getPunchDamage(
const ItemStack *punchitem,
float time_from_last_punch
);
+
+f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_hand);
diff --git a/src/translation.cpp b/src/translation.cpp
index 7ddd95591..d17467ce7 100644
--- a/src/translation.cpp
+++ b/src/translation.cpp
@@ -149,6 +149,8 @@ void Translations::loadTranslation(const std::string &data)
<< wide_to_utf8(oword1) << "\"" << std::endl;
}
- m_translations[textdomain + L"|" + oword1] = oword2;
+ std::wstring translation_index = textdomain + L"|";
+ translation_index.append(oword1);
+ m_translations[translation_index] = oword2;
}
}
diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt
index 71aa1fa56..82f9a4a13 100644
--- a/src/unittest/CMakeLists.txt
+++ b/src/unittest/CMakeLists.txt
@@ -10,6 +10,7 @@ set (UNITTEST_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/test_connection.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_filepath.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp
diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp
index 3ac8ffb19..a783ccd32 100644
--- a/src/unittest/test.cpp
+++ b/src/unittest/test.cpp
@@ -650,12 +650,12 @@ struct TestMapSector: public TestBase
// Create one with no heightmaps
ServerMapSector sector(&parent, v2s16(1,1));
- UASSERT(sector.getBlockNoCreateNoEx(0) == 0);
- UASSERT(sector.getBlockNoCreateNoEx(1) == 0);
+ UASSERT(sector.getBlockNoCreateNoEx(0) == nullptr);
+ UASSERT(sector.getBlockNoCreateNoEx(1) == nullptr);
MapBlock * bref = sector.createBlankBlock(-2);
- UASSERT(sector.getBlockNoCreateNoEx(0) == 0);
+ UASSERT(sector.getBlockNoCreateNoEx(0) == nullptr);
UASSERT(sector.getBlockNoCreateNoEx(-2) == bref);
//TODO: Check for AlreadyExistsException
diff --git a/src/unittest/test_areastore.cpp b/src/unittest/test_areastore.cpp
index 62d446f5c..691cd69d2 100644
--- a/src/unittest/test_areastore.cpp
+++ b/src/unittest/test_areastore.cpp
@@ -128,11 +128,11 @@ void TestAreaStore::testSerialization()
VectorAreaStore store;
Area a(v3s16(-1, 0, 1), v3s16(0, 1, 2));
- a.data = "Area A";
+ a.data = "Area AA";
store.insertArea(&a);
Area b(v3s16(123, 456, 789), v3s16(32000, 100, 10));
- b.data = "Area B";
+ b.data = "Area BB";
store.insertArea(&b);
std::ostringstream os;
@@ -143,20 +143,31 @@ void TestAreaStore::testSerialization()
"\x00\x02" // Count
"\xFF\xFF\x00\x00\x00\x01" // Area A min edge
"\x00\x00\x00\x01\x00\x02" // Area A max edge
- "\x00\x06" // Area A data length
- "Area A" // Area A data
+ "\x00\x07" // Area A data length
+ "Area AA" // Area A data
"\x00\x7B\x00\x64\x00\x0A" // Area B min edge (last two swapped with max edge for sorting)
"\x7D\x00\x01\xC8\x03\x15" // Area B max edge (^)
- "\x00\x06" // Area B data length
- "Area B", // Area B data
+ "\x00\x07" // Area B data length
+ "Area BB" // Area B data
+ "\x00\x00\x00\x00" // ID A = 0
+ "\x00\x00\x00\x01", // ID B = 1
1 + 2 +
- 6 + 6 + 2 + 6 +
- 6 + 6 + 2 + 6);
- UASSERTEQ(std::string, str, str_wanted);
+ (6 + 6 + 2 + 7) * 2 + // min/max edge, length, data
+ 2 * 4); // Area IDs
+
+ UASSERTEQ(const std::string &, str, str_wanted);
std::istringstream is(str);
store.deserialize(is);
- UASSERTEQ(size_t, store.size(), 4); // deserialize() doesn't clear the store
+ // deserialize() doesn't clear the store
+ // But existing IDs are overridden
+ UASSERTEQ(size_t, store.size(), 2);
+
+ Area c(v3s16(33, -2, -6), v3s16(4, 77, -76));
+ c.data = "Area CC";
+ store.insertArea(&c);
+
+ UASSERTEQ(u32, c.id, 2);
}
diff --git a/src/unittest/test_inventory.cpp b/src/unittest/test_inventory.cpp
index 1a783afae..5f71636c4 100644
--- a/src/unittest/test_inventory.cpp
+++ b/src/unittest/test_inventory.cpp
@@ -33,8 +33,9 @@ public:
void testSerializeDeserialize(IItemDefManager *idef);
- static const char *serialized_inventory;
- static const char *serialized_inventory_2;
+ static const char *serialized_inventory_in;
+ static const char *serialized_inventory_out;
+ static const char *serialized_inventory_inc;
};
static TestInventory g_test_instance;
@@ -49,7 +50,7 @@ void TestInventory::runTests(IGameDef *gamedef)
void TestInventory::testSerializeDeserialize(IItemDefManager *idef)
{
Inventory inv(idef);
- std::istringstream is(serialized_inventory, std::ios::binary);
+ std::istringstream is(serialized_inventory_in, std::ios::binary);
inv.deSerialize(is);
UASSERT(inv.getList("0"));
@@ -62,82 +63,64 @@ void TestInventory::testSerializeDeserialize(IItemDefManager *idef)
inv.getList("main")->setWidth(5);
std::ostringstream inv_os(std::ios::binary);
- inv.serialize(inv_os);
- UASSERTEQ(std::string, inv_os.str(), serialized_inventory_2);
+ inv.serialize(inv_os, false);
+ UASSERTEQ(std::string, inv_os.str(), serialized_inventory_out);
+
+ inv.setModified(false);
+ inv_os.str("");
+ inv_os.clear();
+ inv.serialize(inv_os, true);
+ UASSERTEQ(std::string, inv_os.str(), serialized_inventory_inc);
+
+ ItemStack leftover = inv.getList("main")->takeItem(7, 99 - 12);
+ ItemStack wanted = ItemStack("default:dirt", 99 - 12, 0, idef);
+ UASSERT(leftover == wanted);
+ leftover = inv.getList("main")->getItem(7);
+ wanted.count = 12;
+ UASSERT(leftover == wanted);
}
-const char *TestInventory::serialized_inventory =
- "List 0 32\n"
+const char *TestInventory::serialized_inventory_in =
+ "List 0 10\n"
"Width 3\n"
"Empty\n"
"Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
"Item default:cobble 61\n"
"Empty\n"
"Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
"Item default:dirt 71\n"
"Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
"Item default:dirt 99\n"
"Item default:cobble 38\n"
"Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
+ "EndInventoryList\n"
+ "List abc 1\n"
+ "Item default:stick 3\n"
+ "Width 0\n"
"EndInventoryList\n"
"EndInventory\n";
-const char *TestInventory::serialized_inventory_2 =
- "List main 32\n"
+const char *TestInventory::serialized_inventory_out =
+ "List main 10\n"
"Width 5\n"
"Empty\n"
"Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
"Item default:cobble 61\n"
"Empty\n"
"Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
"Item default:dirt 71\n"
"Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
"Item default:dirt 99\n"
"Item default:cobble 38\n"
"Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
- "Empty\n"
"EndInventoryList\n"
+ "List abc 1\n"
+ "Width 0\n"
+ "Item default:stick 3\n"
+ "EndInventoryList\n"
+ "EndInventory\n";
+
+const char *TestInventory::serialized_inventory_inc =
+ "KeepList main\n"
+ "KeepList abc\n"
"EndInventory\n";
diff --git a/src/unittest/test_irrptr.cpp b/src/unittest/test_irrptr.cpp
new file mode 100644
index 000000000..aa857ff46
--- /dev/null
+++ b/src/unittest/test_irrptr.cpp
@@ -0,0 +1,131 @@
+/*
+Minetest
+Copyright (C) 2018 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "test.h"
+
+#include "exceptions.h"
+#include "irr_ptr.h"
+
+class TestIrrPtr : public TestBase
+{
+public:
+ TestIrrPtr() { TestManager::registerTestModule(this); }
+ const char *getName() { return "TestIrrPtr"; }
+
+ void runTests(IGameDef *gamedef);
+
+ void testRefCounting();
+ void testSelfAssignment();
+ void testNullHandling();
+};
+
+static TestIrrPtr g_test_instance;
+
+void TestIrrPtr::runTests(IGameDef *gamedef)
+{
+ TEST(testRefCounting);
+ TEST(testSelfAssignment);
+ TEST(testNullHandling);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define UASSERT_REFERENCE_COUNT(object, value, info) \
+ UTEST((object)->getReferenceCount() == value, \
+ info "Reference count is %d instead of " #value, \
+ (object)->getReferenceCount())
+
+void TestIrrPtr::testRefCounting()
+{
+ IReferenceCounted *obj = new IReferenceCounted(); // RC=1
+ obj->grab();
+ UASSERT_REFERENCE_COUNT(obj, 2, "Pre-condition failed: ");
+ {
+ irr_ptr<IReferenceCounted> p1{obj}; // move semantics
+ UASSERT(p1.get() == obj);
+ UASSERT_REFERENCE_COUNT(obj, 2, );
+
+ irr_ptr<IReferenceCounted> p2{p1}; // copy ctor
+ UASSERT(p1.get() == obj);
+ UASSERT(p2.get() == obj);
+ UASSERT_REFERENCE_COUNT(obj, 3, );
+
+ irr_ptr<IReferenceCounted> p3{std::move(p1)}; // move ctor
+ UASSERT(p1.get() == nullptr);
+ UASSERT(p3.get() == obj);
+ UASSERT_REFERENCE_COUNT(obj, 3, );
+
+ p1 = std::move(p2); // move assignment
+ UASSERT(p1.get() == obj);
+ UASSERT(p2.get() == nullptr);
+ UASSERT_REFERENCE_COUNT(obj, 3, );
+
+ p2 = p3; // copy assignment
+ UASSERT(p2.get() == obj);
+ UASSERT(p3.get() == obj);
+ UASSERT_REFERENCE_COUNT(obj, 4, );
+
+ p1.release();
+ UASSERT(p1.get() == nullptr);
+ UASSERT_REFERENCE_COUNT(obj, 4, );
+ }
+ UASSERT_REFERENCE_COUNT(obj, 2, );
+ obj->drop();
+ UTEST(obj->drop(), "Dropping failed: reference count is %d",
+ obj->getReferenceCount());
+}
+
+void TestIrrPtr::testSelfAssignment()
+{
+ irr_ptr<IReferenceCounted> p1{new IReferenceCounted()};
+ UASSERT(p1);
+ UASSERT_REFERENCE_COUNT(p1, 1, );
+ p1 = p1;
+ UASSERT(p1);
+ UASSERT_REFERENCE_COUNT(p1, 1, );
+ p1 = std::move(p1);
+ UASSERT(p1);
+ UASSERT_REFERENCE_COUNT(p1, 1, );
+}
+
+void TestIrrPtr::testNullHandling()
+{
+ // In the case of an error, it will probably crash with SEGV.
+ // Nevertheless, UASSERTs are used to catch possible corner cases.
+ irr_ptr<IReferenceCounted> p1{new IReferenceCounted()};
+ UASSERT(p1);
+ irr_ptr<IReferenceCounted> p2;
+ UASSERT(!p2);
+ irr_ptr<IReferenceCounted> p3{p2};
+ UASSERT(!p2);
+ UASSERT(!p3);
+ irr_ptr<IReferenceCounted> p4{std::move(p2)};
+ UASSERT(!p2);
+ UASSERT(!p4);
+ p2 = p2;
+ UASSERT(!p2);
+ p2 = std::move(p2);
+ UASSERT(!p2);
+ p3 = p2;
+ UASSERT(!p2);
+ UASSERT(!p3);
+ p3 = std::move(p2);
+ UASSERT(!p2);
+ UASSERT(!p3);
+}
diff --git a/src/unittest/test_servermodmanager.cpp b/src/unittest/test_servermodmanager.cpp
index 72ac7c6bf..0757323f4 100644
--- a/src/unittest/test_servermodmanager.cpp
+++ b/src/unittest/test_servermodmanager.cpp
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "test.h"
#include <algorithm>
#include "server/mods.h"
+#include "settings.h"
#include "test_config.h"
class TestServerModManager : public TestBase
@@ -85,6 +86,10 @@ void TestServerModManager::runTests(IGameDef *gamedef)
void TestServerModManager::testCreation()
{
+ std::string path = std::string(TEST_WORLDDIR) + DIR_DELIM + "world.mt";
+ Settings world_config;
+ world_config.set("gameid", "minimal");
+ UASSERTEQ(bool, world_config.updateConfigFile(path.c_str()), true);
ServerModManager sm(TEST_WORLDDIR);
}
diff --git a/src/unittest/test_world/do_not_remove.txt b/src/unittest/test_world/do_not_remove.txt
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/unittest/test_world/do_not_remove.txt
diff --git a/src/unittest/test_world/world.mt b/src/unittest/test_world/world.mt
deleted file mode 100644
index ab9b5413a..000000000
--- a/src/unittest/test_world/world.mt
+++ /dev/null
@@ -1 +0,0 @@
-gameid = minimal
diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt
index bf208693b..199d3aeaa 100644
--- a/src/util/CMakeLists.txt
+++ b/src/util/CMakeLists.txt
@@ -7,6 +7,7 @@ set(UTIL_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/ieee_float.cpp
${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/quicktune.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sha1.cpp
${CMAKE_CURRENT_SOURCE_DIR}/sha256.c
@@ -14,4 +15,3 @@ set(UTIL_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/srp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/timetaker.cpp
PARENT_SCOPE)
-
diff --git a/src/util/areastore.cpp b/src/util/areastore.cpp
index 50d237bba..cea526336 100644
--- a/src/util/areastore.cpp
+++ b/src/util/areastore.cpp
@@ -64,6 +64,11 @@ const Area *AreaStore::getArea(u32 id) const
void AreaStore::serialize(std::ostream &os) const
{
+ // WARNING:
+ // Before 5.1.0-dev: version != 0 throws SerializationError
+ // After 5.1.0-dev: version >= 5 throws SerializationError
+ // Forwards-compatibility is assumed before version 5.
+
writeU8(os, 0); // Serialisation version
// TODO: Compression?
@@ -75,27 +80,41 @@ void AreaStore::serialize(std::ostream &os) const
writeU16(os, a.data.size());
os.write(a.data.data(), a.data.size());
}
+
+ // Serialize IDs
+ for (const auto &it : areas_map)
+ writeU32(os, it.second.id);
}
void AreaStore::deserialize(std::istream &is)
{
u8 ver = readU8(is);
- if (ver != 0)
+ // Assume forwards-compatibility before version 5
+ if (ver >= 5)
throw SerializationError("Unknown AreaStore "
"serialization version!");
u16 num_areas = readU16(is);
+ std::vector<Area> areas;
for (u32 i = 0; i < num_areas; ++i) {
- Area a;
+ Area a(U32_MAX);
a.minedge = readV3S16(is);
a.maxedge = readV3S16(is);
u16 data_len = readU16(is);
char *data = new char[data_len];
is.read(data, data_len);
a.data = std::string(data, data_len);
- insertArea(&a);
+ areas.emplace_back(a);
delete [] data;
}
+
+ bool read_ids = is.good(); // EOF for old formats
+
+ for (auto &area : areas) {
+ if (read_ids)
+ area.id = readU32(is);
+ insertArea(&area);
+ }
}
void AreaStore::invalidateCache()
@@ -105,6 +124,19 @@ void AreaStore::invalidateCache()
}
}
+u32 AreaStore::getNextId() const
+{
+ u32 free_id = 0;
+ for (const auto &area : areas_map) {
+ if (area.first > free_id)
+ return free_id; // Found gap
+
+ free_id = area.first + 1;
+ }
+ // End of map
+ return free_id;
+}
+
void AreaStore::setCacheParams(bool enabled, u8 block_radius, size_t limit)
{
m_cache_enabled = enabled;
diff --git a/src/util/areastore.h b/src/util/areastore.h
index 24840210e..150a043db 100644
--- a/src/util/areastore.h
+++ b/src/util/areastore.h
@@ -37,15 +37,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
struct Area {
- Area() = default;
+ Area(u32 area_id) : id(area_id) {}
- Area(const v3s16 &mine, const v3s16 &maxe) :
- minedge(mine), maxedge(maxe)
+ Area(const v3s16 &mine, const v3s16 &maxe, u32 area_id = U32_MAX) :
+ id(area_id), minedge(mine), maxedge(maxe)
{
sortBoxVerticies(minedge, maxedge);
}
- u32 id = U32_MAX;
+ u32 id;
v3s16 minedge, maxedge;
std::string data;
};
@@ -109,7 +109,7 @@ protected:
virtual void getAreasForPosImpl(std::vector<Area *> *result, v3s16 pos) = 0;
/// Returns the next area ID and increments it.
- u32 getNextId() { return m_next_id++; }
+ u32 getNextId() const;
// Note: This can't be an unordered_map, since all
// references would be invalidated on rehash.
@@ -125,8 +125,6 @@ private:
/// If you modify this, call invalidateCache()
u8 m_cacheblock_radius = 64;
LRUCache<v3s16, std::vector<Area *> > m_res_cache;
-
- u32 m_next_id = 0;
};
diff --git a/src/util/hex.h b/src/util/hex.h
index df22539a5..708f33024 100644
--- a/src/util/hex.h
+++ b/src/util/hex.h
@@ -26,6 +26,8 @@ static const char hex_chars[] = "0123456789abcdef";
static inline std::string hex_encode(const char *data, unsigned int data_size)
{
std::string ret;
+ ret.reserve(data_size * 2);
+
char buf2[3];
buf2[2] = '\0';
diff --git a/src/quicktune.cpp b/src/util/quicktune.cpp
index 37d4933de..37d4933de 100644
--- a/src/quicktune.cpp
+++ b/src/util/quicktune.cpp
diff --git a/src/quicktune.h b/src/util/quicktune.h
index 1943d19c2..1943d19c2 100644
--- a/src/quicktune.h
+++ b/src/util/quicktune.h
diff --git a/src/quicktune_shortcutter.h b/src/util/quicktune_shortcutter.h
index 70a7b70b3..70a7b70b3 100644
--- a/src/quicktune_shortcutter.h
+++ b/src/util/quicktune_shortcutter.h
diff --git a/src/voxelalgorithms.cpp b/src/voxelalgorithms.cpp
index 1f3abf1be..62fd68890 100644
--- a/src/voxelalgorithms.cpp
+++ b/src/voxelalgorithms.cpp
@@ -529,7 +529,7 @@ void update_lighting_nodes(Map *map,
for (const v3s16 &neighbor_dir : neighbor_dirs) {
v3s16 p2 = p + neighbor_dir;
bool is_valid;
- MapNode n2 = map->getNodeNoEx(p2, &is_valid);
+ MapNode n2 = map->getNode(p2, &is_valid);
if (is_valid) {
u8 spread = n2.getLight(bank, ndef);
// If it is sure that the neighbor won't be
@@ -566,7 +566,7 @@ void update_lighting_nodes(Map *map,
MapNode n2;
- n2 = map->getNodeNoEx(n2pos, &is_valid_position);
+ n2 = map->getNode(n2pos, &is_valid_position);
if (!is_valid_position)
break;
@@ -598,7 +598,7 @@ void update_lighting_nodes(Map *map,
MapNode n2;
- n2 = map->getNodeNoEx(n2pos, &is_valid_position);
+ n2 = map->getNode(n2pos, &is_valid_position);
if (!is_valid_position)
break;
@@ -668,7 +668,7 @@ bool is_light_locally_correct(Map *map, const NodeDefManager *ndef,
LightBank bank, v3s16 pos)
{
bool is_valid_position;
- MapNode n = map->getNodeNoEx(pos, &is_valid_position);
+ MapNode n = map->getNode(pos, &is_valid_position);
const ContentFeatures &f = ndef->get(n);
if (f.param_type != CPT_LIGHT) {
return true;
@@ -677,7 +677,7 @@ bool is_light_locally_correct(Map *map, const NodeDefManager *ndef,
assert(f.light_source <= LIGHT_MAX);
u8 brightest_neighbor = f.light_source + 1;
for (const v3s16 &neighbor_dir : neighbor_dirs) {
- MapNode n2 = map->getNodeNoEx(pos + neighbor_dir,
+ MapNode n2 = map->getNode(pos + neighbor_dir,
&is_valid_position);
u8 light2 = n2.getLight(bank, ndef);
if (brightest_neighbor < light2) {