diff options
Diffstat (limited to 'src')
317 files changed, 37450 insertions, 17766 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 93083f369..614e81908 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,7 @@ +cmake_minimum_required(VERSION 2.6) + project(minetest) -cmake_minimum_required( VERSION 2.6 ) -INCLUDE(CheckCSourceRuns) INCLUDE(CheckIncludeFiles) # Add custom SemiDebug build mode @@ -22,83 +22,69 @@ set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING FORCE ) + # Set some random things default to not being visible in the GUI mark_as_advanced(EXECUTABLE_OUTPUT_PATH LIBRARY_OUTPUT_PATH) -option(ENABLE_CURL "Enable cURL support for fetching media" 1) -if (NOT ENABLE_CURL) - mark_as_advanced(CLEAR CURL_LIBRARY CURL_INCLUDE_DIR) -endif(NOT ENABLE_CURL) +option(ENABLE_CURL "Enable cURL support for fetching media" TRUE) +set(USE_CURL FALSE) -if( ENABLE_CURL ) +if(ENABLE_CURL) find_package(CURL) -endif( ENABLE_CURL ) -set(USE_CURL 0) -if (CURL_FOUND AND ENABLE_CURL) - message(STATUS "cURL support enabled") - set(USE_CURL 1) -endif(CURL_FOUND AND ENABLE_CURL) + if (CURL_FOUND) + message(STATUS "cURL support enabled.") + set(USE_CURL TRUE) + endif() +else() + mark_as_advanced(CLEAR CURL_LIBRARY CURL_INCLUDE_DIR) +endif() -# user-visible option to enable/disable gettext usage -OPTION(ENABLE_GETTEXT "Use GetText for internationalization" 0) -# this is only set to 1 if gettext is enabled _and_ available -set(USE_GETTEXT 0) +option(ENABLE_GETTEXT "Use GetText for internationalization" FALSE) +set(USE_GETTEXT FALSE) if(ENABLE_GETTEXT) find_package(GettextLib) + if(GETTEXT_FOUND) + if(WIN32) + message(STATUS "GetText library: ${GETTEXT_LIBRARY}") + message(STATUS "GetText DLL: ${GETTEXT_DLL}") + message(STATUS "GetText iconv DLL: ${GETTEXT_ICONV_DLL}") + endif() + set(USE_GETTEXT TRUE) + message(STATUS "GetText enabled; locales found: ${GETTEXT_AVAILABLE_LOCALES}") + endif(GETTEXT_FOUND) else() - MARK_AS_ADVANCED(GETTEXT_ICONV_DLL GETTEXT_INCLUDE_DIR GETTEXT_LIBRARY GETTEXT_MSGFMT) + mark_as_advanced(GETTEXT_ICONV_DLL GETTEXT_INCLUDE_DIR GETTEXT_LIBRARY GETTEXT_MSGFMT) + message(STATUS "GetText disabled.") endif() -if(GETTEXT_FOUND AND ENABLE_GETTEXT) - message(STATUS "gettext include path: ${GETTEXT_INCLUDE_DIR}") - message(STATUS "gettext msgfmt path: ${GETTEXT_MSGFMT}") - if(WIN32) - message(STATUS "gettext library: ${GETTEXT_LIBRARY}") - message(STATUS "gettext dll: ${GETTEXT_DLL}") - message(STATUS "gettext iconv dll: ${GETTEXT_ICONV_DLL}") - endif() - set(USE_GETTEXT 1) - message(STATUS "GetText enabled; locales found: ${GETTEXT_AVAILABLE_LOCALES}") -elseif(GETTEXT_FOUND AND NOT ENABLE_GETTEXT) - MESSAGE(STATUS "GetText found but disabled;") -else(GETTEXT_FOUND AND ENABLE_GETTEXT) - message(STATUS "GetText disabled") -endif(GETTEXT_FOUND AND ENABLE_GETTEXT) - -# user visible option to enable/disable sound -OPTION(ENABLE_SOUND "Enable sound" ON) - -# this is only set to 1 if sound is enabled _and_ available -set(USE_SOUND 0) -set(SOUND_PROBLEM 0) - -if(ENABLE_SOUND AND BUILD_CLIENT) + +option(ENABLE_SOUND "Enable sound" TRUE) +set(USE_SOUND FALSE) + +if(BUILD_CLIENT AND ENABLE_SOUND) # Sound libraries find_package(OpenAL) find_package(Vorbis) if(NOT OPENAL_FOUND) message(STATUS "Sound enabled, but OpenAL not found!") - set(SOUND_PROBLEM 1) - MARK_AS_ADVANCED(CLEAR OPENAL_LIBRARY OPENAL_INCLUDE_DIR) + mark_as_advanced(CLEAR OPENAL_LIBRARY OPENAL_INCLUDE_DIR) endif() if(NOT VORBIS_FOUND) message(STATUS "Sound enabled, but Vorbis libraries not found!") - set(SOUND_PROBLEM 1) - MARK_AS_ADVANCED(CLEAR OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY) + mark_as_advanced(CLEAR OGG_INCLUDE_DIR VORBIS_INCLUDE_DIR OGG_LIBRARY VORBIS_LIBRARY VORBISFILE_LIBRARY) endif() if(OPENAL_FOUND AND VORBIS_FOUND) - set(USE_SOUND 1) - message(STATUS "Sound enabled") + set(USE_SOUND TRUE) + message(STATUS "Sound enabled.") + else() + message(FATAL_ERROR "Sound enabled, but cannot be used.\n" + "To continue, either fill in the required paths or disable sound. (-DENABLE_SOUND=0)") endif() -endif(ENABLE_SOUND AND BUILD_CLIENT) - -if(SOUND_PROBLEM) - message(FATAL_ERROR "Sound enabled, but cannot be used.\n" - "To continue, either fill in the required paths or disable sound. (-DENABLE_SOUND=0)") endif() + if(USE_SOUND) set(sound_SRCS sound_openal.cpp) set(SOUND_INCLUDE_DIRS @@ -112,18 +98,110 @@ if(USE_SOUND) ) endif() -option(ENABLE_FREETYPE "Enable freetype2 (truetype fonts and basic unicode support)" OFF) -set(USE_FREETYPE 0) + +option(ENABLE_GLES "Enable OpenGL ES support" FALSE) +mark_as_advanced(ENABLE_GLES) +if(ENABLE_GLES) + find_package(OpenGLES2) +endif() + + +option(ENABLE_FREETYPE "Enable FreeType2 (TrueType fonts and basic unicode support)" TRUE) +set(USE_FREETYPE FALSE) + if(ENABLE_FREETYPE) - set(USE_FREETYPE 1) +## +## Note: FindFreetype.cmake seems to have been fixed in recent versions of +## CMake. If issues persist, re-enable this workaround specificially for the +## failing platforms. +## +# if(UNIX) +# include(FindPkgConfig) +# if(PKG_CONFIG_FOUND) +# pkg_check_modules(FREETYPE QUIET freetype2) +# if(FREETYPE_FOUND) +# SET(FREETYPE_PKGCONFIG_FOUND TRUE) +# SET(FREETYPE_LIBRARY ${FREETYPE_LIBRARIES}) +# # Because CMake is idiotic +# string(REPLACE ";" " " FREETYPE_CFLAGS_STR ${FREETYPE_CFLAGS}) +# string(REPLACE ";" " " FREETYPE_LDFLAGS_STR ${FREETYPE_LDFLAGS}) +# endif(FREETYPE_FOUND) +# endif(PKG_CONFIG_FOUND) +# endif(UNIX) +# if(NOT FREETYPE_FOUND) +# find_package(Freetype) +# endif() + find_package(Freetype) + if(FREETYPE_FOUND) + message(STATUS "Freetype enabled.") + set(USE_FREETYPE TRUE) + set(CGUITTFONT_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cguittfont") + set(CGUITTFONT_LIBRARY cguittfont) + endif() endif(ENABLE_FREETYPE) + +find_package(Lua REQUIRED) + +find_package(GMP REQUIRED) + +option(ENABLE_LEVELDB "Enable LevelDB backend" TRUE) +set(USE_LEVELDB FALSE) + +if(ENABLE_LEVELDB) + find_library(LEVELDB_LIBRARY leveldb) + find_path(LEVELDB_INCLUDE_DIR db.h PATH_SUFFIXES leveldb) + if(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) + set(USE_LEVELDB TRUE) + message(STATUS "LevelDB backend enabled.") + include_directories(${LEVELDB_INCLUDE_DIR}) + else() + message(STATUS "LevelDB not found!") + endif() +endif(ENABLE_LEVELDB) + + +OPTION(ENABLE_REDIS "Enable Redis backend" TRUE) +set(USE_REDIS FALSE) + +if(ENABLE_REDIS) + find_library(REDIS_LIBRARY hiredis) + find_path(REDIS_INCLUDE_DIR hiredis.h PATH_SUFFIXES hiredis) + if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) + set(USE_REDIS TRUE) + message(STATUS "Redis backend enabled.") + include_directories(${REDIS_INCLUDE_DIR}) + else(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) + message(STATUS "Redis not found!") + endif(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) +endif(ENABLE_REDIS) + + +find_package(SQLite3 REQUIRED) +find_package(Json REQUIRED) + +OPTION(ENABLE_SPATIAL "Enable SpatialIndex AreaStore backend" TRUE) +set(USE_SPATIAL FALSE) + +if(ENABLE_SPATIAL) + find_library(SPATIAL_LIBRARY spatialindex) + find_path(SPATIAL_INCLUDE_DIR spatialindex/SpatialIndex.h) + if(SPATIAL_LIBRARY AND SPATIAL_INCLUDE_DIR) + set(USE_SPATIAL TRUE) + message(STATUS "SpatialIndex AreaStore backend enabled.") + include_directories(${SPATIAL_INCLUDE_DIR}) + else(SPATIAL_LIBRARY AND SPATIAL_INCLUDE_DIR) + message(STATUS "SpatialIndex not found!") + endif(SPATIAL_LIBRARY AND SPATIAL_INCLUDE_DIR) +endif(ENABLE_SPATIAL) + + if(NOT MSVC) - set(USE_GPROF 0 CACHE BOOL "Use -pg flag for g++") + set(USE_GPROF FALSE CACHE BOOL "Use -pg flag for g++") endif() # Use cmake_config.h -add_definitions ( -DUSE_CMAKE_CONFIG_H ) +add_definitions(-DUSE_CMAKE_CONFIG_H) if(WIN32) # Windows @@ -132,8 +210,10 @@ if(WIN32) # Surpress some useless warnings add_definitions ( /D "_CRT_SECURE_NO_DEPRECATE" /W1 ) else() # Probably MinGW = GCC - set(PLATFORM_LIBS ws2_32.lib) + set(PLATFORM_LIBS "") endif() + set(PLATFORM_LIBS ws2_32.lib shlwapi.lib ${PLATFORM_LIBS}) + # Zlib stuff set(ZLIB_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/../../zlib/zlib-1.2.5" CACHE PATH "Zlib include directory") @@ -150,7 +230,7 @@ if(WIN32) CACHE PATH "freetype include dir") set(FREETYPE_LIBRARY "${PROJECT_SOURCE_DIR}/../../freetype2/objs/win32/vc2005/freetype247.lib" CACHE FILEPATH "Path to freetype247.lib") - endif(USE_FREETYPE) + 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)") @@ -166,9 +246,9 @@ else() find_package(BZip2 REQUIRED) find_package(PNG REQUIRED) if(APPLE) - FIND_LIBRARY(CARBON_LIB Carbon) - FIND_LIBRARY(COCOA_LIB Cocoa) - FIND_LIBRARY(IOKIT_LIB IOKit) + find_library(CARBON_LIB Carbon) + find_library(COCOA_LIB Cocoa) + find_library(IOKIT_LIB IOKit) mark_as_advanced( CARBON_LIB COCOA_LIB @@ -184,184 +264,51 @@ else() else() set(PLATFORM_LIBS -lrt ${PLATFORM_LIBS}) endif(APPLE) - #set(CLIENT_PLATFORM_LIBS -lXxf86vm) + # This way Xxf86vm is found on OpenBSD too find_library(XXF86VM_LIBRARY Xxf86vm) mark_as_advanced(XXF86VM_LIBRARY) set(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${XXF86VM_LIBRARY}) -endif() - -find_package(SQLite3 REQUIRED) -find_package(Json REQUIRED) - -option(ENABLE_GLES "Enable OpenGL ES support" 0) -mark_as_advanced(ENABLE_GLES) -if(ENABLE_GLES) - find_package(OpenGLES2) -endif(ENABLE_GLES) - -if(USE_FREETYPE) - if(UNIX) - include(FindPkgConfig) - if(PKG_CONFIG_FOUND) - pkg_check_modules(FREETYPE QUIET freetype2) - if(FREETYPE_FOUND) - SET(FREETYPE_PKGCONFIG_FOUND TRUE) - SET(FREETYPE_LIBRARY ${FREETYPE_LIBRARIES}) - # because cmake is idiotic - string(REPLACE ";" " " FREETYPE_CFLAGS_STR ${FREETYPE_CFLAGS}) - string(REPLACE ";" " " FREETYPE_LDFLAGS_STR ${FREETYPE_LDFLAGS}) - endif(FREETYPE_FOUND) - endif(PKG_CONFIG_FOUND) - endif(UNIX) - if(NOT FREETYPE_FOUND) - find_package(Freetype REQUIRED) - endif(NOT FREETYPE_FOUND) - set(CGUITTFONT_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cguittfont") - set(CGUITTFONT_LIBRARY cguittfont) -endif(USE_FREETYPE) - -if (NOT DISABLE_LUAJIT) - find_library(LUA_LIBRARY luajit - NAMES luajit-5.1) - find_path(LUA_INCLUDE_DIR luajit.h - NAMES luajit.h - PATH_SUFFIXES luajit-2.0) - message (STATUS "LuaJIT library: ${LUA_LIBRARY}") - message (STATUS "LuaJIT headers: ${LUA_INCLUDE_DIR}") -else (NOT ${DISABLE_LUAJIT} MATCHES "1") - message (STATUS "LuaJIT detection disabled! (DISABLE_LUAJIT=1)") - set(LUA_LIBRARY "") - set(LUA_INCLUDE_DIR "") -endif (NOT DISABLE_LUAJIT) - -set(USE_LUAJIT 0) -if(LUA_LIBRARY AND LUA_INCLUDE_DIR) - message (STATUS "LuaJIT found, checking for broken versions...") - if(CMAKE_CROSSCOMPILING) - message(WARNING "Cross-compiling enabled, assuming LuaJIT is not broken") - set(VALID_LUAJIT_VERSION 1) - else(CMAKE_CROSSCOMPILING) - set(BACKUP_REQUIRED_INCS CMAKE_REQUIRED_INCLUDES) - set(CMAKE_REQUIRED_INCLUDES "${CMAKE_REQUIRED_INCLUDES} ${LUA_INCLUDE_DIR}") - CHECK_C_SOURCE_RUNS(" - #include <luajit.h> - #include <stdio.h> - #include <string.h> - - #define ARRAYSIZE(a) (sizeof(a) / sizeof((a)[0])) - - static char *broken_luajit_versions[] = { - \"LuaJIT 2.0.0-beta7\", - \"LuaJIT 2.0.0-beta6\", - \"LuaJIT 2.0.0-beta5\", - \"LuaJIT 2.0.0-beta4\", - \"LuaJIT 2.0.0-beta3\", - \"LuaJIT 2.0.0-beta2\", - \"LuaJIT 2.0.0-beta1\" - }; - - int main(int argc, char *argv[]) { - unsigned int i; - for (i = 0; i < ARRAYSIZE(broken_luajit_versions); i++) { - if (strcmp(LUAJIT_VERSION, broken_luajit_versions[i]) == 0) { - return 1; - } - } - return 0; - } - " - VALID_LUAJIT_VERSION) - set(CMAKE_REQUIRED_INCLUDES BACKUP_REQUIRED_INCS) - endif(CMAKE_CROSSCOMPILING) - if (VALID_LUAJIT_VERSION) - message (STATUS "LuaJIT version ok") - set(USE_LUAJIT 1) - else (VALID_LUAJIT_VERSION) - message (STATUS "LuaJIT versions till 2.0.0beta7 known to be broken, update to at least beta8") - set(USE_LUAJIT 0) - endif (VALID_LUAJIT_VERSION) -endif (LUA_LIBRARY AND LUA_INCLUDE_DIR) - -if(NOT USE_LUAJIT) - message (STATUS "LuaJIT not found, using bundled Lua.") - set(LUA_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/lua/src") - set(LUA_LIBRARY "lua") - add_subdirectory(lua) -endif(NOT USE_LUAJIT) - -mark_as_advanced(LUA_LIBRARY) -mark_as_advanced(LUA_INCLUDE_DIR) - -set(USE_LEVELDB 0) - -OPTION(ENABLE_LEVELDB "Enable LevelDB backend") - -if(ENABLE_LEVELDB) - find_library(LEVELDB_LIBRARY leveldb) - find_path(LEVELDB_INCLUDE_DIR db.h PATH_SUFFIXES leveldb) - message (STATUS "LevelDB library: ${LEVELDB_LIBRARY}") - message (STATUS "LevelDB headers: ${LEVELDB_INCLUDE_DIR}") - if(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) - set(USE_LEVELDB 1) - message(STATUS "LevelDB backend enabled") - include_directories(${LEVELDB_INCLUDE_DIR}) - else(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) - set(USE_LEVELDB 0) - message(STATUS "LevelDB not found!") - endif(LEVELDB_LIBRARY AND LEVELDB_INCLUDE_DIR) -endif(ENABLE_LEVELDB) -set(USE_REDIS 0) - -OPTION(ENABLE_REDIS "Enable redis backend" 0) - -if(ENABLE_REDIS) - find_library(REDIS_LIBRARY hiredis) - find_path(REDIS_INCLUDE_DIR hiredis.h PATH_SUFFIXES hiredis) - message(STATUS "redis library: ${REDIS_LIBRARY}") - message(STATUS "redis headers: ${REDIS_INCLUDE_DIR}") - if(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) - set(USE_REDIS 1) - message(STATUS "redis backend enabled") - include_directories(${REDIS_INCLUDE_DIR}) - else(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) - set(USE_REDIS 0) - message(STATUS "redis not found!") - endif(REDIS_LIBRARY AND REDIS_INCLUDE_DIR) -endif(ENABLE_REDIS) + # Prefer local iconv if installed + find_library(ICONV_LIBRARY iconv) + mark_as_advanced(ICONV_LIBRARY) + if (ICONV_LIBRARY) + set(PLATFORM_LIBS ${PLATFORM_LIBS} ${ICONV_LIBRARY}) + endif() +endif() -CHECK_INCLUDE_FILES(endian.h HAVE_ENDIAN_H) -if(NOT HAVE_ENDIAN_H) - set(HAVE_ENDIAN_H 0) -endif(NOT HAVE_ENDIAN_H) +check_include_files(endian.h HAVE_ENDIAN_H) configure_file( "${PROJECT_SOURCE_DIR}/cmake_config.h.in" "${PROJECT_BINARY_DIR}/cmake_config.h" ) + # Add a target that always rebuilds cmake_config_githash.h add_custom_target(GenerateVersion COMMAND ${CMAKE_COMMAND} -D "GENERATE_VERSION_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}" -D "GENERATE_VERSION_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}" -D "VERSION_STRING=${VERSION_STRING}" - -D "VERSION_EXTRA=${VERSION_EXTRA}" + -D "DEVELOPMENT_BUILD=${DEVELOPMENT_BUILD}" -P "${CMAKE_SOURCE_DIR}/cmake/Modules/GenerateVersion.cmake" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + add_subdirectory(jthread) +add_subdirectory(network) add_subdirectory(script) +add_subdirectory(unittest) add_subdirectory(util) set(common_SRCS + areastore.cpp ban.cpp - base64.cpp cavegen.cpp clientiface.cpp collision.cpp - connection.cpp content_abm.cpp content_mapnode.cpp content_nodemeta.cpp @@ -406,10 +353,12 @@ set(common_SRCS nodemetadata.cpp nodetimer.cpp noise.cpp + objdef.cpp object_properties.cpp pathfinder.cpp player.cpp porting.cpp + profiler.cpp quicktune.cpp rollback.cpp rollback_interface.cpp @@ -418,25 +367,26 @@ set(common_SRCS serverlist.cpp serverobject.cpp settings.cpp - sha1.cpp socket.cpp sound.cpp staticobject.cpp subgame.cpp - test.cpp tool.cpp treegen.cpp version.cpp voxel.cpp voxelalgorithms.cpp + ${common_network_SRCS} ${JTHREAD_SRCS} ${common_SCRIPT_SRCS} ${UTIL_SRCS} + ${UNITTEST_SRCS} ) + # This gives us the icon and file version information if(WIN32) - set(WINRESOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../misc/winresource.rc) + set(WINRESOURCE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../misc/winresource.rc") if(MINGW) if(NOT CMAKE_RC_COMPILER) set(CMAKE_RC_COMPILER "windres.exe") @@ -453,10 +403,17 @@ if(WIN32) endif(MINGW) endif() + # Client sources -set(minetest_SRCS +if (BUILD_CLIENT) + add_subdirectory(client) +endif(BUILD_CLIENT) + +set(client_SRCS + ${client_SRCS} ${common_SRCS} ${sound_SRCS} + ${client_network_SRCS} camera.cpp chat.cpp client.cpp @@ -478,29 +435,32 @@ set(minetest_SRCS guiFormSpecMenu.cpp guiKeyChangeMenu.cpp guiPasswordChange.cpp + guiscalingfilter.cpp guiTable.cpp guiVolumeChange.cpp hud.cpp + imagefilters.cpp + intlGUIEditBox.cpp keycode.cpp localplayer.cpp main.cpp mapblock_mesh.cpp mesh.cpp + minimap.cpp particles.cpp shader.cpp sky.cpp - tile.cpp wieldmesh.cpp - ${minetest_SCRIPT_SRCS} + ${client_SCRIPT_SRCS} ) -list(SORT minetest_SRCS) +list(SORT client_SRCS) # Server sources -set(minetestserver_SRCS +set(server_SRCS ${common_SRCS} main.cpp ) -list(SORT minetestserver_SRCS) +list(SORT server_SRCS) include_directories( ${PROJECT_BINARY_DIR} @@ -513,29 +473,28 @@ include_directories( ${SOUND_INCLUDE_DIRS} ${SQLITE3_INCLUDE_DIR} ${LUA_INCLUDE_DIR} + ${GMP_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${PROJECT_SOURCE_DIR}/script ) + if(USE_FREETYPE) - include_directories( - ${FREETYPE_INCLUDE_DIRS} - ${CGUITTFONT_INCLUDE_DIR} - ) -endif(USE_FREETYPE) + include_directories(${FREETYPE_INCLUDE_DIRS} ${CGUITTFONT_INCLUDE_DIR}) +endif() if(USE_CURL) - include_directories( - ${CURL_INCLUDE_DIR} - ) -endif(USE_CURL) + include_directories(${CURL_INCLUDE_DIR}) +endif() + set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/bin") + if(BUILD_CLIENT) - add_executable(${PROJECT_NAME} ${minetest_SRCS}) + add_executable(${PROJECT_NAME} ${client_SRCS}) add_dependencies(${PROJECT_NAME} GenerateVersion) - set(minetest_LIBS + set(client_LIBS ${PROJECT_NAME} ${ZLIB_LIBRARIES} ${IRRLICHT_LIBRARY} @@ -548,6 +507,7 @@ if(BUILD_CLIENT) ${SOUND_LIBRARIES} ${SQLITE3_LIBRARY} ${LUA_LIBRARY} + ${GMP_LIBRARY} ${JSON_LIBRARY} ${OPENGLES2_LIBRARIES} ${PLATFORM_LIBS} @@ -555,12 +515,12 @@ if(BUILD_CLIENT) ) if(APPLE) target_link_libraries( - ${minetest_LIBS} + ${client_LIBS} ${ICONV_LIBRARY} ) else() target_link_libraries( - ${minetest_LIBS} + ${client_LIBS} ) endif() if(USE_CURL) @@ -568,30 +528,34 @@ if(BUILD_CLIENT) ${PROJECT_NAME} ${CURL_LIBRARY} ) - endif(USE_CURL) + endif() if(USE_FREETYPE) if(FREETYPE_PKGCONFIG_FOUND) set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "${FREETYPE_CFLAGS_STR}" ) - endif(FREETYPE_PKGCONFIG_FOUND) + endif() target_link_libraries( ${PROJECT_NAME} ${FREETYPE_LIBRARY} ${CGUITTFONT_LIBRARY} ) - endif(USE_FREETYPE) + endif() if (USE_LEVELDB) target_link_libraries(${PROJECT_NAME} ${LEVELDB_LIBRARY}) - endif(USE_LEVELDB) + endif() if (USE_REDIS) target_link_libraries(${PROJECT_NAME} ${REDIS_LIBRARY}) - endif(USE_REDIS) + endif() + if (USE_SPATIAL) + target_link_libraries(${PROJECT_NAME} ${SPATIAL_LIBRARY}) + endif() endif(BUILD_CLIENT) + if(BUILD_SERVER) - add_executable(${PROJECT_NAME}server ${minetestserver_SRCS}) + add_executable(${PROJECT_NAME}server ${server_SRCS}) add_dependencies(${PROJECT_NAME}server GenerateVersion) target_link_libraries( ${PROJECT_NAME}server @@ -600,26 +564,30 @@ if(BUILD_SERVER) ${JSON_LIBRARY} ${GETTEXT_LIBRARY} ${LUA_LIBRARY} + ${GMP_LIBRARY} ${PLATFORM_LIBS} ) + set_target_properties(${PROJECT_NAME}server PROPERTIES + COMPILE_DEFINITIONS "SERVER") if (USE_LEVELDB) target_link_libraries(${PROJECT_NAME}server ${LEVELDB_LIBRARY}) - endif(USE_LEVELDB) + endif() if (USE_REDIS) target_link_libraries(${PROJECT_NAME}server ${REDIS_LIBRARY}) - endif(USE_REDIS) + endif() + if (USE_SPATIAL) + target_link_libraries(${PROJECT_NAME}server ${SPATIAL_LIBRARY}) + endif() if(USE_CURL) target_link_libraries( ${PROJECT_NAME}server ${CURL_LIBRARY} ) - endif(USE_CURL) + endif() endif(BUILD_SERVER) -# # Set some optimizations and tweaks -# include(CheckCXXCompilerFlag) @@ -640,12 +608,6 @@ if(MSVC) # Flags for C files (sqlite) # /MT = Link statically with standard library stuff set(CMAKE_C_FLAGS_RELEASE "/O2 /Ob2 /MT") - - if(BUILD_SERVER) - set_target_properties(${PROJECT_NAME}server PROPERTIES - COMPILE_DEFINITIONS "SERVER") - endif(BUILD_SERVER) - else() # Probably GCC if(APPLE) @@ -660,6 +622,7 @@ else() if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") # clang does not understand __extern_always_inline but libc headers use it set(OTHER_FLAGS "${OTHER_FLAGS} \"-D__extern_always_inline=extern __always_inline\"") + set(OTHER_FLAGS "${OTHER_FLAGS} -Wsign-compare") endif() if(MINGW) @@ -678,20 +641,11 @@ else() if(USE_GPROF) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg") endif() - - if(BUILD_SERVER) - set_target_properties(${PROJECT_NAME}server PROPERTIES - COMPILE_DEFINITIONS "SERVER") - endif(BUILD_SERVER) - endif() -#MESSAGE(STATUS "CMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE}") -#MESSAGE(STATUS "CMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG}") -# # Installation -# + if(WIN32) if(USE_SOUND) if(OPENAL_DLL) @@ -728,14 +682,27 @@ if(WIN32) endif() if(BUILD_CLIENT) - install(TARGETS ${PROJECT_NAME} DESTINATION ${BINDIR}) + install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION ${BINDIR} + LIBRARY DESTINATION ${BINDIR} + ARCHIVE DESTINATION ${BINDIR} + BUNDLE DESTINATION . + ) + + if(APPLE) + install(CODE " + set(BU_CHMOD_BUNDLE_ITEMS ON) + include(BundleUtilities) + fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/${BUNDLE_PATH}\" \"\" \"\${CMAKE_INSTALL_PREFIX}/${BINDIR}\") + " COMPONENT Runtime) + endif() if(USE_GETTEXT) foreach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) set_mo_paths(MO_BUILD_PATH MO_DEST_PATH ${LOCALE}) set(MO_BUILD_PATH "${MO_BUILD_PATH}/${PROJECT_NAME}.mo") install(FILES ${MO_BUILD_PATH} DESTINATION ${MO_DEST_PATH}) - endforeach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) + endforeach() endif() if(WIN32) @@ -749,19 +716,19 @@ if(BUILD_CLIENT) if(DEFINED GETTEXT_ICONV_DLL) install(FILES ${GETTEXT_ICONV_DLL} DESTINATION ${BINDIR}) endif() - endif(USE_GETTEXT) + endif() endif() endif(BUILD_CLIENT) if(BUILD_SERVER) install(TARGETS ${PROJECT_NAME}server DESTINATION ${BINDIR}) -endif(BUILD_SERVER) +endif() if (USE_GETTEXT) set(MO_FILES) foreach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) - set(PO_FILE_PATH "${GETTEXT_PO_PATH}/${LOCALE}/minetest.po") + set(PO_FILE_PATH "${GETTEXT_PO_PATH}/${LOCALE}/${PROJECT_NAME}.po") set_mo_paths(MO_BUILD_PATH MO_DEST_PATH ${LOCALE}) set(MO_FILE_PATH "${MO_BUILD_PATH}/${PROJECT_NAME}.mo") @@ -778,20 +745,15 @@ if (USE_GETTEXT) ) set(MO_FILES ${MO_FILES} ${MO_FILE_PATH}) - endforeach(LOCALE ${GETTEXT_AVAILABLE_LOCALES}) + endforeach() add_custom_target(translations ALL COMMENT "mo update" DEPENDS ${MO_FILES}) -endif(USE_GETTEXT) +endif() + # Subdirectories if (BUILD_CLIENT AND USE_FREETYPE) add_subdirectory(cguittfont) -endif (BUILD_CLIENT AND USE_FREETYPE) - -if (JSON_FOUND) -else (JSON_FOUND) - add_subdirectory(json) -endif (JSON_FOUND) +endif() -#end diff --git a/src/activeobject.h b/src/activeobject.h index 46880fc7f..48f078d3f 100644 --- a/src/activeobject.h +++ b/src/activeobject.h @@ -23,7 +23,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irr_aabb3d.h" #include <string> -#define ACTIVEOBJECT_TYPE_INVALID 0 +enum ActiveObjectType { + ACTIVEOBJECT_TYPE_INVALID = 0, + ACTIVEOBJECT_TYPE_TEST = 1, +// Deprecated stuff + ACTIVEOBJECT_TYPE_ITEM = 2, + ACTIVEOBJECT_TYPE_RAT = 3, + ACTIVEOBJECT_TYPE_OERKKI1 = 4, + ACTIVEOBJECT_TYPE_FIREFLY = 5, + ACTIVEOBJECT_TYPE_MOBV2 = 6, +// End deprecated stuff + ACTIVEOBJECT_TYPE_LUAENTITY = 7, +// Special type, not stored as a static object + ACTIVEOBJECT_TYPE_PLAYER = 100, +// Special type, only exists as CAO + ACTIVEOBJECT_TYPE_GENERIC = 101, +}; // Other types are defined in content_object.h struct ActiveObjectMessage @@ -60,7 +75,7 @@ public: m_id = id; } - virtual u8 getType() const = 0; + virtual ActiveObjectType getType() const = 0; virtual bool getCollisionBox(aabb3f *toset) = 0; virtual bool collideWithObjects() = 0; protected: diff --git a/src/areastore.cpp b/src/areastore.cpp new file mode 100644 index 000000000..f9362c4a6 --- /dev/null +++ b/src/areastore.cpp @@ -0,0 +1,343 @@ +/* +Minetest +Copyright (C) 2015 est31 <mtest31@outlook.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "areastore.h" +#include "util/serialize.h" +#include "util/container.h" + +#if USE_SPATIAL + #include <spatialindex/SpatialIndex.h> + #include <spatialindex/RTree.h> + #include <spatialindex/Point.h> +#endif + +#define AST_SMALLER_EQ_AS(p, q) (((p).X <= (q).X) && ((p).Y <= (q).Y) && ((p).Z <= (q).Z)) + +#define AST_OVERLAPS_IN_DIMENSION(amine, amaxe, b, d) \ + (!(((amine).d > (b)->maxedge.d) || ((amaxe).d < (b)->minedge.d))) + +#define AST_CONTAINS_PT(a, p) (AST_SMALLER_EQ_AS((a)->minedge, (p)) && \ + AST_SMALLER_EQ_AS((p), (a)->maxedge)) + +#define AST_CONTAINS_AREA(amine, amaxe, b) \ + (AST_SMALLER_EQ_AS((amine), (b)->minedge) \ + && AST_SMALLER_EQ_AS((b)->maxedge, (amaxe))) + +#define AST_AREAS_OVERLAP(amine, amaxe, b) \ + (AST_OVERLAPS_IN_DIMENSION((amine), (amaxe), (b), X) && \ + AST_OVERLAPS_IN_DIMENSION((amine), (amaxe), (b), Y) && \ + AST_OVERLAPS_IN_DIMENSION((amine), (amaxe), (b), Z)) + +u16 AreaStore::size() const +{ + return areas_map.size(); +} + +u32 AreaStore::getFreeId(v3s16 minedge, v3s16 maxedge) +{ + int keep_on = 100; + while (keep_on--) { + m_highest_id++; + // Handle overflows, we dont want to return 0 + if (m_highest_id == AREA_ID_INVALID) + m_highest_id++; + if (areas_map.find(m_highest_id) == areas_map.end()) + return m_highest_id; + } + // search failed + return AREA_ID_INVALID; +} + +const Area *AreaStore::getArea(u32 id) const +{ + const Area *res = NULL; + std::map<u32, Area>::const_iterator itr = areas_map.find(id); + if (itr != areas_map.end()) { + res = &itr->second; + } + return res; +} + +#if 0 +Currently, serialisation is commented out. This is because of multiple reasons: +1. Why do we store the areastore into a file, why not into the database? +2. We don't use libspatial's serialisation, but we should, or perhaps not, because + it would remove the ability to switch. Perhaps write migration routines? +3. Various things need fixing, e.g. the size is serialized as + c++ implementation defined size_t +bool AreaStore::deserialize(std::istream &is) +{ + u8 ver = readU8(is); + if (ver != 1) + return false; + u16 count_areas = readU16(is); + for (u16 i = 0; i < count_areas; i++) { + // deserialize an area + Area a; + a.id = readU32(is); + a.minedge = readV3S16(is); + a.maxedge = readV3S16(is); + a.datalen = readU16(is); + a.data = new char[a.datalen]; + is.read((char *) a.data, a.datalen); + insertArea(a); + } + return true; +} + + +static bool serialize_area(void *ostr, Area *a) +{ + std::ostream &os = *((std::ostream *) ostr); + writeU32(os, a->id); + writeV3S16(os, a->minedge); + writeV3S16(os, a->maxedge); + writeU16(os, a->datalen); + os.write(a->data, a->datalen); + + return false; +} + + +void AreaStore::serialize(std::ostream &os) const +{ + // write initial data + writeU8(os, 1); // serialisation version + writeU16(os, areas_map.size()); //DANGER: not platform independent + forEach(&serialize_area, &os); +} + +#endif + +void AreaStore::invalidateCache() +{ + if (cache_enabled) { + m_res_cache.invalidate(); + } +} + +void AreaStore::setCacheParams(bool enabled, u8 block_radius, size_t limit) +{ + cache_enabled = enabled; + m_cacheblock_radius = MYMAX(block_radius, 16); + m_res_cache.setLimit(MYMAX(limit, 20)); + invalidateCache(); +} + +void AreaStore::cacheMiss(void *data, const v3s16 &mpos, std::vector<Area *> *dest) +{ + AreaStore *as = (AreaStore *)data; + u8 r = as->m_cacheblock_radius; + + // get the points at the edges of the mapblock + v3s16 minedge(mpos.X * r, mpos.Y * r, mpos.Z * r); + v3s16 maxedge( + minedge.X + r - 1, + minedge.Y + r - 1, + minedge.Z + r - 1); + + as->getAreasInArea(dest, minedge, maxedge, true); + + /* infostream << "Cache miss with " << dest->size() << " areas, between (" + << minedge.X << ", " << minedge.Y << ", " << minedge.Z + << ") and (" + << maxedge.X << ", " << maxedge.Y << ", " << maxedge.Z + << ")" << std::endl; // */ +} + +void AreaStore::getAreasForPos(std::vector<Area *> *result, v3s16 pos) +{ + if (cache_enabled) { + v3s16 mblock = getContainerPos(pos, m_cacheblock_radius); + const std::vector<Area *> *pre_list = m_res_cache.lookupCache(mblock); + + size_t s_p_l = pre_list->size(); + for (size_t i = 0; i < s_p_l; i++) { + Area *b = (*pre_list)[i]; + if (AST_CONTAINS_PT(b, pos)) { + result->push_back(b); + } + } + } else { + return getAreasForPosImpl(result, pos); + } +} + + +//// +// VectorAreaStore +//// + + +void VectorAreaStore::insertArea(const Area &a) +{ + areas_map[a.id] = a; + m_areas.push_back(&(areas_map[a.id])); + invalidateCache(); +} + +void VectorAreaStore::reserve(size_t count) +{ + m_areas.reserve(count); +} + +bool VectorAreaStore::removeArea(u32 id) +{ + std::map<u32, Area>::iterator itr = areas_map.find(id); + if (itr != areas_map.end()) { + size_t msiz = m_areas.size(); + for (size_t i = 0; i < msiz; i++) { + Area * b = m_areas[i]; + if (b->id == id) { + areas_map.erase(itr); + m_areas.erase(m_areas.begin() + i); + invalidateCache(); + return true; + } + } + // we should never get here, it means we did find it in map, + // but not in the vector + } + return false; +} + +void VectorAreaStore::getAreasForPosImpl(std::vector<Area *> *result, v3s16 pos) +{ + size_t msiz = m_areas.size(); + for (size_t i = 0; i < msiz; i++) { + Area *b = m_areas[i]; + if (AST_CONTAINS_PT(b, pos)) { + result->push_back(b); + } + } +} + +void VectorAreaStore::getAreasInArea(std::vector<Area *> *result, + v3s16 minedge, v3s16 maxedge, bool accept_overlap) +{ + size_t msiz = m_areas.size(); + for (size_t i = 0; i < msiz; i++) { + Area * b = m_areas[i]; + if (accept_overlap ? AST_AREAS_OVERLAP(minedge, maxedge, b) : + AST_CONTAINS_AREA(minedge, maxedge, b)) { + result->push_back(b); + } + } +} + +#if 0 +bool VectorAreaStore::forEach(bool (*callback)(void *args, Area *a), void *args) const +{ + size_t msiz = m_areas.size(); + for (size_t i = 0; i < msiz; i++) { + if (callback(args, m_areas[i])) { + return true; + } + } + return false; +} +#endif + +#if USE_SPATIAL + +static inline SpatialIndex::Region get_spatial_region(const v3s16 minedge, + const v3s16 maxedge) +{ + const double p_low[] = {(double)minedge.X, + (double)minedge.Y, (double)minedge.Z}; + const double p_high[] = {(double)maxedge.X, (double)maxedge.Y, + (double)maxedge.Z}; + return SpatialIndex::Region(p_low, p_high, 3); +} + +static inline SpatialIndex::Point get_spatial_point(const v3s16 pos) +{ + const double p[] = {(double)pos.X, (double)pos.Y, (double)pos.Z}; + return SpatialIndex::Point(p, 3); +} + + +void SpatialAreaStore::insertArea(const Area &a) +{ + areas_map[a.id] = a; + m_tree->insertData(0, NULL, get_spatial_region(a.minedge, a.maxedge), a.id); + invalidateCache(); +} + +bool SpatialAreaStore::removeArea(u32 id) +{ + std::map<u32, Area>::iterator itr = areas_map.find(id); + if (itr != areas_map.end()) { + Area *a = &itr->second; + bool result = m_tree->deleteData(get_spatial_region(a->minedge, + a->maxedge), id); + invalidateCache(); + return result; + } else { + return false; + } +} + +void SpatialAreaStore::getAreasForPosImpl(std::vector<Area *> *result, v3s16 pos) +{ + VectorResultVisitor visitor(result, this); + m_tree->pointLocationQuery(get_spatial_point(pos), visitor); +} + +void SpatialAreaStore::getAreasInArea(std::vector<Area *> *result, + v3s16 minedge, v3s16 maxedge, bool accept_overlap) +{ + VectorResultVisitor visitor(result, this); + if (accept_overlap) { + m_tree->intersectsWithQuery(get_spatial_region(minedge, maxedge), + visitor); + } else { + m_tree->containsWhatQuery(get_spatial_region(minedge, maxedge), visitor); + } +} + +#if 0 +bool SpatialAreaStore::forEach(bool (*callback)(void *args, Area *a), void *args) const +{ + // TODO ?? (this is only needed for serialisation, but libspatial has its own serialisation) + return false; +} +#endif + +SpatialAreaStore::~SpatialAreaStore() +{ + delete m_tree; +} + +SpatialAreaStore::SpatialAreaStore() +{ + m_storagemanager = + SpatialIndex::StorageManager::createNewMemoryStorageManager(); + SpatialIndex::id_type id; + m_tree = SpatialIndex::RTree::createNewRTree( + *m_storagemanager, + .7, // Fill factor + 100, // Index capacity + 100, // Leaf capacity + 3, // dimension :) + SpatialIndex::RTree::RV_RSTAR, + id); +} + +#endif diff --git a/src/areastore.h b/src/areastore.h new file mode 100644 index 000000000..57d96450b --- /dev/null +++ b/src/areastore.h @@ -0,0 +1,196 @@ +/* +Minetest +Copyright (C) 2015 est31 <mtest31@outlook.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef AREASTORE_H_ +#define AREASTORE_H_ + +#include "irr_v3d.h" +#include "noise.h" // for PcgRandom +#include <map> +#include <list> +#include <vector> +#include <istream> +#include "util/container.h" +#include "util/numeric.h" +#ifndef ANDROID + #include "cmake_config.h" +#endif +#if USE_SPATIAL + #include <spatialindex/SpatialIndex.h> + #include "util/serialize.h" +#endif + +#define AST_EXTREMIFY(min, max, pa, pb) \ + (min).X = MYMIN((pa).X, (pb).X); \ + (min).Y = MYMIN((pa).Y, (pb).Y); \ + (min).Z = MYMIN((pa).Z, (pb).Z); \ + (max).X = MYMAX((pa).X, (pb).X); \ + (max).Y = MYMAX((pa).Y, (pb).Y); \ + (max).Z = MYMAX((pa).Z, (pb).Z); + +#define AREA_ID_INVALID 0 + +struct Area { + Area(const v3s16 &minedge, const v3s16 &maxedge) + { + this->minedge = minedge; + this->maxedge = maxedge; + } + + Area() {} + + void extremifyEdges() + { + v3s16 nminedge; + v3s16 nmaxedge; + + AST_EXTREMIFY(nminedge, nmaxedge, minedge, maxedge) + + maxedge = nmaxedge; + minedge = nminedge; + } + + u32 id; + v3s16 minedge; + v3s16 maxedge; + std::string data; +}; + +std::vector<std::string> get_areastore_typenames(); + +class AreaStore { +protected: + // TODO change to unordered_map when we can + std::map<u32, Area> areas_map; + void invalidateCache(); + virtual void getAreasForPosImpl(std::vector<Area *> *result, v3s16 pos) = 0; + bool cache_enabled; // don't write to this from subclasses, only read. +public: + virtual void insertArea(const Area &a) = 0; + virtual void reserve(size_t count) {}; + virtual bool removeArea(u32 id) = 0; + void getAreasForPos(std::vector<Area *> *result, v3s16 pos); + virtual void getAreasInArea(std::vector<Area *> *result, + v3s16 minedge, v3s16 maxedge, bool accept_overlap) = 0; + +#if 0 + // calls a passed function for every stored area, until the + // callback returns true. If that happens, it returns true, + // if the search is exhausted, it returns false + virtual bool forEach(bool (*callback)(void *args, Area *a), void *args) const = 0; +#endif + + virtual ~AreaStore() + {} + + AreaStore() : + cache_enabled(true), + m_cacheblock_radius(64), + m_res_cache(1000, &cacheMiss, this), + m_highest_id(0) + { + } + + void setCacheParams(bool enabled, u8 block_radius, size_t limit); + + u32 getFreeId(v3s16 minedge, v3s16 maxedge); + const Area *getArea(u32 id) const; + u16 size() const; +#if 0 + bool deserialize(std::istream &is); + void serialize(std::ostream &is) const; +#endif +private: + static void cacheMiss(void *data, const v3s16 &mpos, std::vector<Area *> *dest); + u8 m_cacheblock_radius; // if you modify this, call invalidateCache() + LRUCache<v3s16, std::vector<Area *> > m_res_cache; + u32 m_highest_id; + +}; + + +class VectorAreaStore : public AreaStore { +protected: + virtual void getAreasForPosImpl(std::vector<Area *> *result, v3s16 pos); +public: + virtual void insertArea(const Area &a); + virtual void reserve(size_t count); + virtual bool removeArea(u32 id); + virtual void getAreasInArea(std::vector<Area *> *result, + v3s16 minedge, v3s16 maxedge, bool accept_overlap); + // virtual bool forEach(bool (*callback)(void *args, Area *a), void *args) const; +private: + std::vector<Area *> m_areas; +}; + +#if USE_SPATIAL + +class SpatialAreaStore : public AreaStore { +protected: + virtual void getAreasForPosImpl(std::vector<Area *> *result, v3s16 pos); +public: + SpatialAreaStore(); + virtual void insertArea(const Area &a); + virtual bool removeArea(u32 id); + virtual void getAreasInArea(std::vector<Area *> *result, + v3s16 minedge, v3s16 maxedge, bool accept_overlap); + // virtual bool forEach(bool (*callback)(void *args, Area *a), void *args) const; + + virtual ~SpatialAreaStore(); +private: + SpatialIndex::ISpatialIndex *m_tree; + SpatialIndex::IStorageManager *m_storagemanager; + + class VectorResultVisitor : public SpatialIndex::IVisitor { + private: + SpatialAreaStore *m_store; + std::vector<Area *> *m_result; + public: + VectorResultVisitor(std::vector<Area *> *result, SpatialAreaStore *store) + { + m_store = store; + m_result = result; + } + + virtual void visitNode(const SpatialIndex::INode &in) + { + } + + virtual void visitData(const SpatialIndex::IData &in) + { + u32 id = in.getIdentifier(); + + std::map<u32, Area>::iterator itr = m_store->areas_map.find(id); + assert(itr != m_store->areas_map.end()); + m_result->push_back(&itr->second); + } + + virtual void visitData(std::vector<const SpatialIndex::IData *> &v) + { + for (size_t i = 0; i < v.size(); i++) + visitData(*(v[i])); + } + + ~VectorResultVisitor() {} + }; +}; + +#endif + +#endif /* AREASTORE_H_ */ diff --git a/src/ban.cpp b/src/ban.cpp index 55d9b22fe..7c1a68d45 100644 --- a/src/ban.cpp +++ b/src/ban.cpp @@ -56,7 +56,7 @@ void BanManager::load() infostream<<"BanManager: failed loading from "<<m_banfilepath<<std::endl; throw SerializationError("BanManager::load(): Couldn't open file"); } - + while(!is.eof() && is.good()) { std::string line; @@ -74,18 +74,14 @@ void BanManager::load() void BanManager::save() { JMutexAutoLock lock(m_mutex); - infostream<<"BanManager: saving to "<<m_banfilepath<<std::endl; + infostream << "BanManager: saving to " << m_banfilepath << std::endl; std::ostringstream ss(std::ios_base::binary); - for(std::map<std::string, std::string>::iterator - i = m_ips.begin(); - i != m_ips.end(); i++) - { - ss << i->first << "|" << i->second << "\n"; - } + for (StringMap::iterator it = m_ips.begin(); it != m_ips.end(); ++it) + ss << it->first << "|" << it->second << "\n"; - if(!fs::safeWriteToFile(m_banfilepath, ss.str())) { - infostream<<"BanManager: failed saving to "<<m_banfilepath<<std::endl; + if (!fs::safeWriteToFile(m_banfilepath, ss.str())) { + infostream << "BanManager: failed saving to " << m_banfilepath << std::endl; throw SerializationError("BanManager::save(): Couldn't write file"); } @@ -102,25 +98,23 @@ std::string BanManager::getBanDescription(const std::string &ip_or_name) { JMutexAutoLock lock(m_mutex); std::string s = ""; - for(std::map<std::string, std::string>::iterator - i = m_ips.begin(); - i != m_ips.end(); i++) - { - if(i->first == ip_or_name || i->second == ip_or_name - || ip_or_name == "") - s += i->first + "|" + i->second + ", "; + for (StringMap::iterator it = m_ips.begin(); it != m_ips.end(); ++it) { + if (it->first == ip_or_name || it->second == ip_or_name + || ip_or_name == "") { + s += it->first + "|" + it->second + ", "; + } } - s = s.substr(0, s.size()-2); + s = s.substr(0, s.size() - 2); return s; } std::string BanManager::getBanName(const std::string &ip) { JMutexAutoLock lock(m_mutex); - std::map<std::string, std::string>::iterator i = m_ips.find(ip); - if(i == m_ips.end()) + StringMap::iterator it = m_ips.find(ip); + if (it == m_ips.end()) return ""; - return i->second; + return it->second; } void BanManager::add(const std::string &ip, const std::string &name) @@ -133,19 +127,16 @@ void BanManager::add(const std::string &ip, const std::string &name) void BanManager::remove(const std::string &ip_or_name) { JMutexAutoLock lock(m_mutex); - for(std::map<std::string, std::string>::iterator - i = m_ips.begin(); - i != m_ips.end();) - { - if((i->first == ip_or_name) || (i->second == ip_or_name)) { - m_ips.erase(i++); + for (StringMap::iterator it = m_ips.begin(); it != m_ips.end();) { + if ((it->first == ip_or_name) || (it->second == ip_or_name)) { + m_ips.erase(it++); } else { - ++i; + ++it; } } m_modified = true; } - + bool BanManager::isModified() { @@ -20,8 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef BAN_HEADER #define BAN_HEADER -#include <map> -#include <string> +#include "util/string.h" #include "jthread/jthread.h" #include "jthread/jmutex.h" #include "exceptions.h" @@ -43,7 +42,7 @@ public: private: JMutex m_mutex; std::string m_banfilepath; - std::map<std::string, std::string> m_ips; + StringMap m_ips; bool m_modified; }; diff --git a/src/camera.cpp b/src/camera.cpp index 5200f71ba..0c6d03e4e 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -20,14 +20,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "camera.h" #include "debug.h" #include "client.h" -#include "main.h" // for g_settings #include "map.h" -#include "clientmap.h" // MapDrawControl +#include "clientmap.h" // MapDrawControl #include "player.h" #include <cmath> #include "settings.h" #include "wieldmesh.h" -#include "noise.h" // easeCurve +#include "noise.h" // easeCurve #include "gamedef.h" #include "sound.h" #include "event.h" @@ -93,10 +92,9 @@ Camera::Camera(scene::ISceneManager* smgr, MapDrawControl& draw_control, // all other 3D scene nodes and before the GUI. m_wieldmgr = smgr->createNewSceneManager(); m_wieldmgr->addCameraSceneNode(); - m_wieldnode = new WieldMeshSceneNode(m_wieldmgr->getRootSceneNode(), m_wieldmgr, -1, true); + m_wieldnode = new WieldMeshSceneNode(m_wieldmgr->getRootSceneNode(), m_wieldmgr, -1, false); m_wieldnode->setItem(ItemStack(), m_gamedef); m_wieldnode->drop(); // m_wieldmgr grabbed it - m_wieldlightnode = m_wieldmgr->addLightSceneNode(NULL, v3f(0.0, 50.0, 0.0)); /* TODO: Add a callback function so these can be updated when a setting * changes. At this point in time it doesn't matter (e.g. /set @@ -119,34 +117,22 @@ Camera::~Camera() m_wieldmgr->drop(); } -bool Camera::successfullyCreated(std::wstring& error_message) +bool Camera::successfullyCreated(std::string &error_message) { - if (m_playernode == NULL) - { - error_message = L"Failed to create the player scene node"; - return false; - } - if (m_headnode == NULL) - { - error_message = L"Failed to create the head scene node"; - return false; - } - if (m_cameranode == NULL) - { - error_message = L"Failed to create the camera scene node"; - return false; - } - if (m_wieldmgr == NULL) - { - error_message = L"Failed to create the wielded item scene manager"; - return false; - } - if (m_wieldnode == NULL) - { - error_message = L"Failed to create the wielded item scene node"; - return false; + if (!m_playernode) { + error_message = "Failed to create the player scene node"; + } else if (!m_headnode) { + error_message = "Failed to create the head scene node"; + } else if (!m_cameranode) { + error_message = "Failed to create the camera scene node"; + } else if (!m_wieldmgr) { + error_message = "Failed to create the wielded item scene manager"; + } else if (!m_wieldnode) { + error_message = "Failed to create the wielded item scene node"; + } else { + error_message.clear(); } - return true; + return error_message.empty(); } // Returns the fractional part of x @@ -175,55 +161,37 @@ void Camera::step(f32 dtime) { //f32 offset = dtime * m_view_bobbing_speed * 0.035; f32 offset = dtime * m_view_bobbing_speed * 0.030; - if (m_view_bobbing_state == 2) - { -#if 0 + if (m_view_bobbing_state == 2) { // Animation is getting turned off - if (m_view_bobbing_anim < 0.5) + if (m_view_bobbing_anim < 0.25) { m_view_bobbing_anim -= offset; - else - m_view_bobbing_anim += offset; - if (m_view_bobbing_anim <= 0 || m_view_bobbing_anim >= 1) - { - m_view_bobbing_anim = 0; - m_view_bobbing_state = 0; - } -#endif -#if 1 - // Animation is getting turned off - if(m_view_bobbing_anim < 0.25) - { - m_view_bobbing_anim -= offset; - } else if(m_view_bobbing_anim > 0.75) { + } else if (m_view_bobbing_anim > 0.75) { m_view_bobbing_anim += offset; } - if(m_view_bobbing_anim < 0.5) - { + + if (m_view_bobbing_anim < 0.5) { m_view_bobbing_anim += offset; - if(m_view_bobbing_anim > 0.5) + if (m_view_bobbing_anim > 0.5) m_view_bobbing_anim = 0.5; } else { m_view_bobbing_anim -= offset; - if(m_view_bobbing_anim < 0.5) + if (m_view_bobbing_anim < 0.5) m_view_bobbing_anim = 0.5; } - if(m_view_bobbing_anim <= 0 || m_view_bobbing_anim >= 1 || - fabs(m_view_bobbing_anim - 0.5) < 0.01) - { + + if (m_view_bobbing_anim <= 0 || m_view_bobbing_anim >= 1 || + fabs(m_view_bobbing_anim - 0.5) < 0.01) { m_view_bobbing_anim = 0; m_view_bobbing_state = 0; } -#endif } - else - { + else { float was = m_view_bobbing_anim; m_view_bobbing_anim = my_modf(m_view_bobbing_anim + offset); bool step = (was == 0 || (was < 0.5f && m_view_bobbing_anim >= 0.5f) || (was > 0.5f && m_view_bobbing_anim <= 0.5f)); - if(step) - { + if(step) { MtEvent *e = new SimpleTriggerEvent("ViewBobbingStep"); m_gamedef->event()->put(e); } @@ -482,11 +450,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, m_wieldnode->setPosition(wield_position); m_wieldnode->setRotation(wield_rotation); - // Shine light upon the wield mesh - video::SColor black(255,0,0,0); - m_wieldmgr->setAmbientLight(player->light_color.getInterpolated(black, 0.7)); - m_wieldlightnode->getLightData().DiffuseColor = player->light_color.getInterpolated(black, 0.3); - m_wieldlightnode->setPosition(v3f(30+5*sin(2*player->getYaw()*M_PI/180), -50, 0)); + m_wieldnode->setColor(player->light_color); // Render distance feedback loop updateViewingRange(frametime, busytime); diff --git a/src/camera.h b/src/camera.h index 3f10b87d7..006f4b3ce 100644 --- a/src/camera.h +++ b/src/camera.h @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "inventory.h" #include "mesh.h" -#include "tile.h" +#include "client/tile.h" #include "util/numeric.h" #include <ICameraSceneNode.h> @@ -110,7 +110,7 @@ public: } // Checks if the constructor was able to create the scene nodes - bool successfullyCreated(std::wstring& error_message); + bool successfullyCreated(std::string &error_message); // Step the camera: updates the viewing range and view bobbing. void step(f32 dtime); @@ -159,7 +159,6 @@ private: scene::ISceneManager* m_wieldmgr; WieldMeshSceneNode* m_wieldnode; - scene::ILightSceneNode* m_wieldlightnode; // draw control MapDrawControl& m_draw_control; diff --git a/src/cavegen.cpp b/src/cavegen.cpp index 22bf03d17..8372f70b5 100644 --- a/src/cavegen.cpp +++ b/src/cavegen.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "map.h" #include "mapgen.h" +#include "mapgen_v5.h" #include "mapgen_v6.h" #include "mapgen_v7.h" #include "cavegen.h" @@ -27,28 +28,272 @@ with this program; if not, write to the Free Software Foundation, Inc., NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0.6, 2.0); -/////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////// Caves V5 -CaveV6::CaveV6(MapgenV6 *mg, PseudoRandom *ps, PseudoRandom *ps2, bool is_large_cave) { - this->mg = mg; - this->vm = mg->vm; - this->ndef = mg->ndef; - this->water_level = mg->water_level; - this->large_cave = is_large_cave; - this->ps = ps; - this->ps2 = ps2; +CaveV5::CaveV5(MapgenV5 *mg, PseudoRandom *ps) +{ + this->mg = mg; + this->vm = mg->vm; + this->ndef = mg->ndef; + this->water_level = mg->water_level; + this->ps = ps; + this->c_water_source = mg->c_water_source; + this->c_lava_source = mg->c_lava_source; + this->c_ice = mg->c_ice; + this->np_caveliquids = &nparams_caveliquids; + + dswitchint = ps->range(1, 14); + flooded = ps->range(1, 2) == 2; + + part_max_length_rs = ps->range(2, 4); + tunnel_routepoints = ps->range(5, ps->range(15, 30)); + min_tunnel_diameter = 5; + max_tunnel_diameter = ps->range(7, ps->range(8, 24)); + + large_cave_is_flat = (ps->range(0, 1) == 0); +} + + +void CaveV5::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) +{ + node_min = nmin; + node_max = nmax; + main_direction = v3f(0, 0, 0); + + // Allowed route area size in nodes + ar = node_max - node_min + v3s16(1, 1, 1); + // Area starting point in nodes + of = node_min; + + // Allow a bit more + //(this should be more than the maximum radius of the tunnel) + s16 insure = 10; + s16 more = MYMAX(MAP_BLOCKSIZE - max_tunnel_diameter / 2 - insure, 1); + ar += v3s16(1,0,1) * more * 2; + of -= v3s16(1,0,1) * more; + + route_y_min = 0; + // Allow half a diameter + 7 over stone surface + route_y_max = -of.Y + max_stone_y + max_tunnel_diameter / 2 + 7; + + // Limit maximum to area + route_y_max = rangelim(route_y_max, 0, ar.Y - 1); + + s16 min = 0; + if (node_min.Y < water_level && node_max.Y > water_level) { + min = water_level - max_tunnel_diameter/3 - of.Y; + route_y_max = water_level + max_tunnel_diameter/3 - of.Y; + } + route_y_min = ps->range(min, min + max_tunnel_diameter); + route_y_min = rangelim(route_y_min, 0, route_y_max); + + s16 route_start_y_min = route_y_min; + s16 route_start_y_max = route_y_max; + + route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1); + route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1); + + // Randomize starting position + orp = v3f( + (float)(ps->next() % ar.X) + 0.5, + (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5, + (float)(ps->next() % ar.Z) + 0.5 + ); + + // Add generation notify begin event + v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + GenNotifyType notifytype = GENNOTIFY_LARGECAVE_BEGIN; + mg->gennotify.addEvent(notifytype, abs_pos); + + // Generate some tunnel starting from orp + for (u16 j = 0; j < tunnel_routepoints; j++) + makeTunnel(j % dswitchint == 0); + + // Add generation notify end event + abs_pos = v3s16(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + notifytype = GENNOTIFY_LARGECAVE_END; + mg->gennotify.addEvent(notifytype, abs_pos); +} + + +void CaveV5::makeTunnel(bool dirswitch) +{ + // Randomize size + s16 min_d = min_tunnel_diameter; + s16 max_d = max_tunnel_diameter; + rs = ps->range(min_d, max_d); + s16 rs_part_max_length_rs = rs * part_max_length_rs; + + v3s16 maxlen; + maxlen = v3s16( + rs_part_max_length_rs, + rs_part_max_length_rs / 2, + rs_part_max_length_rs + ); + + v3f vec; + // Jump downward sometimes + vec = v3f( + (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2, + (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2, + (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2 + ); + + // Do not make caves that are above ground. + // It is only necessary to check the startpoint and endpoint. + v3s16 orpi(orp.X, orp.Y, orp.Z); + v3s16 veci(vec.X, vec.Y, vec.Z); + v3s16 p; + + p = orpi + veci + of + rs / 2; + if (p.Z >= node_min.Z && p.Z <= node_max.Z && + p.X >= node_min.X && p.X <= node_max.X) { + u32 index = (p.Z - node_min.Z) * mg->ystride + (p.X - node_min.X); + s16 h = mg->heightmap[index]; + if (h < p.Y) + return; + } else if (p.Y > water_level) { + return; // If it's not in our heightmap, use a simple heuristic + } + + p = orpi + of + rs / 2; + if (p.Z >= node_min.Z && p.Z <= node_max.Z && + p.X >= node_min.X && p.X <= node_max.X) { + u32 index = (p.Z - node_min.Z) * mg->ystride + (p.X - node_min.X); + s16 h = mg->heightmap[index]; + if (h < p.Y) + return; + } else if (p.Y > water_level) { + return; + } + + vec += main_direction; + + v3f rp = orp + vec; + if (rp.X < 0) + rp.X = 0; + else if (rp.X >= ar.X) + rp.X = ar.X - 1; + + if (rp.Y < route_y_min) + rp.Y = route_y_min; + else if (rp.Y >= route_y_max) + rp.Y = route_y_max - 1; + + if (rp.Z < 0) + rp.Z = 0; + else if (rp.Z >= ar.Z) + rp.Z = ar.Z - 1; + + vec = rp - orp; + + float veclen = vec.getLength(); + if (veclen < 0.05) + veclen = 1.0; + + // Every second section is rough + bool randomize_xz = (ps->range(1, 2) == 1); + + // Carve routes + for (float f = 0; f < 1.0; f += 1.0 / veclen) + carveRoute(vec, f, randomize_xz); + + orp = rp; +} + + +void CaveV5::carveRoute(v3f vec, float f, bool randomize_xz) +{ + MapNode airnode(CONTENT_AIR); + MapNode waternode(c_water_source); + MapNode lavanode(c_lava_source); + + v3s16 startp(orp.X, orp.Y, orp.Z); + startp += of; + + float nval = NoisePerlin3D(np_caveliquids, startp.X, + startp.Y, startp.Z, mg->seed); + MapNode liquidnode = nval < 0.40 ? lavanode : waternode; + + v3f fp = orp + vec * f; + fp.X += 0.1 * ps->range(-10, 10); + fp.Z += 0.1 * ps->range(-10, 10); + v3s16 cp(fp.X, fp.Y, fp.Z); + + s16 d0 = -rs/2; + s16 d1 = d0 + rs; + if (randomize_xz) { + d0 += ps->range(-1, 1); + d1 += ps->range(-1, 1); + } + + for (s16 z0 = d0; z0 <= d1; z0++) { + s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1); + for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) { + s16 maxabsxz = MYMAX(abs(x0), abs(z0)); + + s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1); + + for (s16 y0 = -si2; y0 <= si2; y0++) { + if (large_cave_is_flat) { + // Make large caves not so tall + if (rs > 7 && abs(y0) >= rs / 3) + continue; + } + + v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0); + p += of; + + if (vm->m_area.contains(p) == false) + continue; + + u32 i = vm->m_area.index(p); + content_t c = vm->m_data[i].getContent(); + if (!ndef->get(c).is_ground_content) + continue; + + int full_ymin = node_min.Y - MAP_BLOCKSIZE; + int full_ymax = node_max.Y + MAP_BLOCKSIZE; + + if (flooded && full_ymin < water_level && + full_ymax > water_level) + vm->m_data[i] = (p.Y <= water_level) ? + waternode : airnode; + else if (flooded && full_ymax < water_level) + vm->m_data[i] = (p.Y < startp.Y - 4) ? + liquidnode : airnode; + else + vm->m_data[i] = airnode; + } + } + } +} + + +///////////////////////////////////////// Caves V6 + + +CaveV6::CaveV6(MapgenV6 *mg, PseudoRandom *ps, PseudoRandom *ps2, bool is_large_cave) +{ + this->mg = mg; + this->vm = mg->vm; + this->ndef = mg->ndef; + this->water_level = mg->water_level; + this->large_cave = is_large_cave; + this->ps = ps; + this->ps2 = ps2; this->c_water_source = mg->c_water_source; this->c_lava_source = mg->c_lava_source; min_tunnel_diameter = 2; max_tunnel_diameter = ps->range(2, 6); - dswitchint = ps->range(1, 14); - flooded = true; + dswitchint = ps->range(1, 14); + flooded = true; if (large_cave) { - part_max_length_rs = ps->range(2,4); - tunnel_routepoints = ps->range(5, ps->range(15,30)); + part_max_length_rs = ps->range(2,4); + tunnel_routepoints = ps->range(5, ps->range(15,30)); min_tunnel_diameter = 5; max_tunnel_diameter = ps->range(7, ps->range(8,24)); } else { @@ -60,7 +305,8 @@ CaveV6::CaveV6(MapgenV6 *mg, PseudoRandom *ps, PseudoRandom *ps2, bool is_large_ } -void CaveV6::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) { +void CaveV6::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) +{ node_min = nmin; node_max = nmax; max_stone_y = max_stone_height; @@ -127,7 +373,8 @@ void CaveV6::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) { } -void CaveV6::makeTunnel(bool dirswitch) { +void CaveV6::makeTunnel(bool dirswitch) +{ if (dirswitch && !large_cave) { main_direction = v3f( ((float)(ps->next() % 20) - (float)10) / 10, @@ -141,37 +388,72 @@ void CaveV6::makeTunnel(bool dirswitch) { s16 min_d = min_tunnel_diameter; s16 max_d = max_tunnel_diameter; rs = ps->range(min_d, max_d); + s16 rs_part_max_length_rs = rs * part_max_length_rs; v3s16 maxlen; if (large_cave) { maxlen = v3s16( - rs * part_max_length_rs, - rs * part_max_length_rs / 2, - rs * part_max_length_rs + rs_part_max_length_rs, + rs_part_max_length_rs / 2, + rs_part_max_length_rs ); } else { maxlen = v3s16( - rs * part_max_length_rs, - ps->range(1, rs * part_max_length_rs), - rs * part_max_length_rs + rs_part_max_length_rs, + ps->range(1, rs_part_max_length_rs), + rs_part_max_length_rs ); } v3f vec( - (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2, - (float)(ps->next() % (maxlen.Y * 1)) - (float)maxlen.Y / 2, - (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2 + (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2, + (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2, + (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2 ); // Jump downward sometimes if (!large_cave && ps->range(0, 12) == 0) { vec = v3f( - (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2, + (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2, (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y, - (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2 + (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2 ); } + // Do not make caves that are entirely above ground, to fix + // shadow bugs caused by overgenerated large caves. + // It is only necessary to check the startpoint and endpoint. + v3s16 orpi(orp.X, orp.Y, orp.Z); + v3s16 veci(vec.X, vec.Y, vec.Z); + s16 h1; + s16 h2; + + v3s16 p1 = orpi + veci + of + rs / 2; + if (p1.Z >= node_min.Z && p1.Z <= node_max.Z && + p1.X >= node_min.X && p1.X <= node_max.X) { + u32 index1 = (p1.Z - node_min.Z) * mg->ystride + + (p1.X - node_min.X); + h1 = mg->heightmap[index1]; + } else { + h1 = water_level; // If not in heightmap + } + + v3s16 p2 = orpi + of + rs / 2; + if (p2.Z >= node_min.Z && p2.Z <= node_max.Z && + p2.X >= node_min.X && p2.X <= node_max.X) { + u32 index2 = (p2.Z - node_min.Z) * mg->ystride + + (p2.X - node_min.X); + h2 = mg->heightmap[index2]; + } else { + h2 = water_level; + } + + // If startpoint and endpoint are above ground, + // disable placing of nodes in carveRoute while + // still running all pseudorandom calls to ensure + // caves consistent with existing worlds. + bool tunnel_above_ground = p1.Y > h1 && p2.Y > h2; + vec += main_direction; v3f rp = orp + vec; @@ -202,13 +484,14 @@ void CaveV6::makeTunnel(bool dirswitch) { // Carve routes for (float f = 0; f < 1.0; f += 1.0 / veclen) - carveRoute(vec, f, randomize_xz); + carveRoute(vec, f, randomize_xz, tunnel_above_ground); orp = rp; } -void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz) { +void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_ground) +{ MapNode airnode(CONTENT_AIR); MapNode waternode(c_water_source); MapNode lavanode(c_lava_source); @@ -231,6 +514,9 @@ void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz) { for (s16 z0 = d0; z0 <= d1; z0++) { s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1); for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) { + if (tunnel_above_ground) + continue; + s16 maxabsxz = MYMAX(abs(x0), abs(z0)); s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1); for (s16 y0 = -si2; y0 <= si2; y0++) { @@ -255,17 +541,18 @@ void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz) { int full_ymin = node_min.Y - MAP_BLOCKSIZE; int full_ymax = node_max.Y + MAP_BLOCKSIZE; - if (flooded && full_ymin < water_level && full_ymax > water_level) { - vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode; + if (flooded && full_ymin < water_level && + full_ymax > water_level) { + vm->m_data[i] = (p.Y <= water_level) ? + waternode : airnode; } else if (flooded && full_ymax < water_level) { - vm->m_data[i] = (p.Y < startp.Y - 2) ? lavanode : airnode; + vm->m_data[i] = (p.Y < startp.Y - 2) ? + lavanode : airnode; } else { vm->m_data[i] = airnode; } } else { - // Don't replace air or water or lava or ignore - if (c == CONTENT_IGNORE || c == CONTENT_AIR || - c == c_water_source || c == c_lava_source) + if (c == CONTENT_IGNORE || c == CONTENT_AIR) continue; vm->m_data[i] = airnode; @@ -279,13 +566,14 @@ void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz) { ///////////////////////////////////////// Caves V7 -CaveV7::CaveV7(MapgenV7 *mg, PseudoRandom *ps, bool is_large_cave) { - this->mg = mg; - this->vm = mg->vm; - this->ndef = mg->ndef; - this->water_level = mg->water_level; - this->large_cave = is_large_cave; - this->ps = ps; + +CaveV7::CaveV7(MapgenV7 *mg, PseudoRandom *ps) +{ + this->mg = mg; + this->vm = mg->vm; + this->ndef = mg->ndef; + this->water_level = mg->water_level; + this->ps = ps; this->c_water_source = mg->c_water_source; this->c_lava_source = mg->c_lava_source; this->c_ice = mg->c_ice; @@ -294,23 +582,17 @@ CaveV7::CaveV7(MapgenV7 *mg, PseudoRandom *ps, bool is_large_cave) { dswitchint = ps->range(1, 14); flooded = ps->range(1, 2) == 2; - if (large_cave) { - part_max_length_rs = ps->range(2, 4); - tunnel_routepoints = ps->range(5, ps->range(15, 30)); - min_tunnel_diameter = 5; - max_tunnel_diameter = ps->range(7, ps->range(8, 24)); - } else { - part_max_length_rs = ps->range(2, 9); - tunnel_routepoints = ps->range(10, ps->range(15, 30)); - min_tunnel_diameter = 2; - max_tunnel_diameter = ps->range(2, 6); - } + part_max_length_rs = ps->range(2, 4); + tunnel_routepoints = ps->range(5, ps->range(15, 30)); + min_tunnel_diameter = 5; + max_tunnel_diameter = ps->range(7, ps->range(8, 24)); large_cave_is_flat = (ps->range(0, 1) == 0); } -void CaveV7::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) { +void CaveV7::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) +{ node_min = nmin; node_max = nmax; max_stone_y = max_stone_height; @@ -335,15 +617,13 @@ void CaveV7::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) { // Limit maximum to area route_y_max = rangelim(route_y_max, 0, ar.Y - 1); - if (large_cave) { - s16 min = 0; - if (node_min.Y < water_level && node_max.Y > water_level) { - min = water_level - max_tunnel_diameter/3 - of.Y; - route_y_max = water_level + max_tunnel_diameter/3 - of.Y; - } - route_y_min = ps->range(min, min + max_tunnel_diameter); - route_y_min = rangelim(route_y_min, 0, route_y_max); + s16 min = 0; + if (node_min.Y < water_level && node_max.Y > water_level) { + min = water_level - max_tunnel_diameter/3 - of.Y; + route_y_max = water_level + max_tunnel_diameter/3 - of.Y; } + route_y_min = ps->range(min, min + max_tunnel_diameter); + route_y_min = rangelim(route_y_min, 0, route_y_max); s16 route_start_y_min = route_y_min; s16 route_start_y_max = route_y_max; @@ -360,8 +640,7 @@ void CaveV7::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) { // Add generation notify begin event v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); - GenNotifyType notifytype = large_cave ? - GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN; + GenNotifyType notifytype = GENNOTIFY_LARGECAVE_BEGIN; mg->gennotify.addEvent(notifytype, abs_pos); // Generate some tunnel starting from orp @@ -370,86 +649,60 @@ void CaveV7::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) { // Add generation notify end event abs_pos = v3s16(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); - notifytype = large_cave ? - GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; + notifytype = GENNOTIFY_LARGECAVE_END; mg->gennotify.addEvent(notifytype, abs_pos); } -void CaveV7::makeTunnel(bool dirswitch) { - if (dirswitch && !large_cave) { - main_direction = v3f( - ((float)(ps->next() % 20) - (float)10) / 10, - ((float)(ps->next() % 20) - (float)10) / 30, - ((float)(ps->next() % 20) - (float)10) / 10 - ); - main_direction *= (float)ps->range(0, 10) / 10; - } - +void CaveV7::makeTunnel(bool dirswitch) +{ // Randomize size s16 min_d = min_tunnel_diameter; s16 max_d = max_tunnel_diameter; rs = ps->range(min_d, max_d); + s16 rs_part_max_length_rs = rs * part_max_length_rs; v3s16 maxlen; - if (large_cave) { - maxlen = v3s16( - rs * part_max_length_rs, - rs * part_max_length_rs / 2, - rs * part_max_length_rs - ); - } else { - maxlen = v3s16( - rs * part_max_length_rs, - ps->range(1, rs * part_max_length_rs), - rs * part_max_length_rs - ); - } + maxlen = v3s16( + rs_part_max_length_rs, + rs_part_max_length_rs / 2, + rs_part_max_length_rs + ); v3f vec; // Jump downward sometimes - if (!large_cave && ps->range(0, 12) == 0) { - vec = v3f( - (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2, - (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y, - (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2 - ); - } else { - vec = v3f( - (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2, - (float)(ps->next() % (maxlen.Y * 1)) - (float)maxlen.Y / 2, - (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2 - ); - } + vec = v3f( + (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2, + (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2, + (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2 + ); - // Do not make large caves that are above ground. + // Do not make caves that are above ground. // It is only necessary to check the startpoint and endpoint. - if (large_cave) { - v3s16 orpi(orp.X, orp.Y, orp.Z); - v3s16 veci(vec.X, vec.Y, vec.Z); - v3s16 p; + v3s16 orpi(orp.X, orp.Y, orp.Z); + v3s16 veci(vec.X, vec.Y, vec.Z); + v3s16 p; - p = orpi + veci + of + rs / 2; - if (p.Z >= node_min.Z && p.Z <= node_max.Z && + p = orpi + veci + of + rs / 2; + if (p.Z >= node_min.Z && p.Z <= node_max.Z && p.X >= node_min.X && p.X <= node_max.X) { - u32 index = (p.Z - node_min.Z) * mg->ystride + (p.X - node_min.X); - s16 h = mg->ridge_heightmap[index]; - if (h < p.Y) - return; - } else if (p.Y > water_level) { - return; // If it's not in our heightmap, use a simple heuristic - } + u32 index = (p.Z - node_min.Z) * mg->ystride + (p.X - node_min.X); + s16 h = mg->ridge_heightmap[index]; + if (h < p.Y) + return; + } else if (p.Y > water_level) { + return; // If it's not in our heightmap, use a simple heuristic + } - p = orpi + of + rs / 2; - if (p.Z >= node_min.Z && p.Z <= node_max.Z && + p = orpi + of + rs / 2; + if (p.Z >= node_min.Z && p.Z <= node_max.Z && p.X >= node_min.X && p.X <= node_max.X) { - u32 index = (p.Z - node_min.Z) * mg->ystride + (p.X - node_min.X); - s16 h = mg->ridge_heightmap[index]; - if (h < p.Y) - return; - } else if (p.Y > water_level) { + u32 index = (p.Z - node_min.Z) * mg->ystride + (p.X - node_min.X); + s16 h = mg->ridge_heightmap[index]; + if (h < p.Y) return; - } + } else if (p.Y > water_level) { + return; } vec += main_direction; @@ -479,20 +732,16 @@ void CaveV7::makeTunnel(bool dirswitch) { // Every second section is rough bool randomize_xz = (ps->range(1, 2) == 1); - // Make a ravine every once in a while if it's long enough - //float xylen = vec.X * vec.X + vec.Z * vec.Z; - //disable ravines for now - bool is_ravine = false; //(xylen > 500.0) && !large_cave && (ps->range(1, 8) == 1); - // Carve routes for (float f = 0; f < 1.0; f += 1.0 / veclen) - carveRoute(vec, f, randomize_xz, is_ravine); + carveRoute(vec, f, randomize_xz); orp = rp; } -void CaveV7::carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine) { +void CaveV7::carveRoute(v3f vec, float f, bool randomize_xz) +{ MapNode airnode(CONTENT_AIR); MapNode waternode(c_water_source); MapNode lavanode(c_lava_source); @@ -501,8 +750,9 @@ void CaveV7::carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine) { startp += of; float nval = NoisePerlin3D(np_caveliquids, startp.X, - startp.Y, startp.Z, mg->seed); - MapNode liquidnode = nval < 0.40 ? lavanode : waternode; + startp.Y, startp.Z, mg->seed); + MapNode liquidnode = (nval < 0.40 && node_max.Y < MGV7_LAVA_DEPTH) ? + lavanode : waternode; v3f fp = orp + vec * f; fp.X += 0.1 * ps->range(-10, 10); @@ -516,22 +766,14 @@ void CaveV7::carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine) { d1 += ps->range(-1, 1); } - bool flat_cave_floor = !large_cave && ps->range(0, 2) == 2; - bool should_make_cave_hole = ps->range(1, 10) == 1; - for (s16 z0 = d0; z0 <= d1; z0++) { s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1); for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) { s16 maxabsxz = MYMAX(abs(x0), abs(z0)); - s16 si2 = is_ravine ? MYMIN(ps->range(25, 26), ar.Y) : - rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1); + s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1); for (s16 y0 = -si2; y0 <= si2; y0++) { - // Make better floors in small caves - if(flat_cave_floor && y0 <= -rs/2 && rs<=7) - continue; - if (large_cave_is_flat) { // Make large caves not so tall if (rs > 7 && abs(y0) >= rs / 3) @@ -541,42 +783,26 @@ void CaveV7::carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine) { v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0); p += of; - if (!is_ravine && mg->heightmap && should_make_cave_hole && - p.X <= node_max.X && p.Z <= node_max.Z) { - int maplen = node_max.X - node_min.X + 1; - int idx = (p.Z - node_min.Z) * maplen + (p.X - node_min.X); - if (p.Y >= mg->heightmap[idx] - 2) - continue; - } - if (vm->m_area.contains(p) == false) continue; u32 i = vm->m_area.index(p); - - // Don't replace air, water, lava, or ice content_t c = vm->m_data[i].getContent(); - if (!ndef->get(c).is_ground_content || c == CONTENT_AIR || - c == c_water_source || c == c_lava_source || c == c_ice) + if (!ndef->get(c).is_ground_content) continue; - if (large_cave) { - int full_ymin = node_min.Y - MAP_BLOCKSIZE; - int full_ymax = node_max.Y + MAP_BLOCKSIZE; - - if (flooded && full_ymin < water_level && full_ymax > water_level) - vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode; - else if (flooded && full_ymax < water_level) - vm->m_data[i] = (p.Y < startp.Y - 4) ? liquidnode : airnode; - else - vm->m_data[i] = airnode; - } else { - if (c == CONTENT_IGNORE) - continue; - + int full_ymin = node_min.Y - MAP_BLOCKSIZE; + int full_ymax = node_max.Y + MAP_BLOCKSIZE; + + if (flooded && full_ymin < water_level && + full_ymax > water_level) + vm->m_data[i] = (p.Y <= water_level) ? + waternode : airnode; + else if (flooded && full_ymax < water_level) + vm->m_data[i] = (p.Y < startp.Y - 4) ? + liquidnode : airnode; + else vm->m_data[i] = airnode; - vm->m_flags[i] |= VMANIP_FLAG_CAVE; - } } } } diff --git a/src/cavegen.h b/src/cavegen.h index 7371df6fa..b9662587b 100644 --- a/src/cavegen.h +++ b/src/cavegen.h @@ -21,10 +21,57 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CAVEGEN_HEADER #define VMANIP_FLAG_CAVE VOXELFLAG_CHECKED1 +#define MGV7_LAVA_DEPTH -256 +class MapgenV5; class MapgenV6; class MapgenV7; +class CaveV5 { +public: + MapgenV5 *mg; + MMVManip *vm; + INodeDefManager *ndef; + + NoiseParams *np_caveliquids; + + s16 min_tunnel_diameter; + s16 max_tunnel_diameter; + u16 tunnel_routepoints; + int dswitchint; + int part_max_length_rs; + + bool large_cave_is_flat; + bool flooded; + + s16 max_stone_y; + v3s16 node_min; + v3s16 node_max; + + v3f orp; // starting point, relative to caved space + v3s16 of; // absolute coordinates of caved space + v3s16 ar; // allowed route area + s16 rs; // tunnel radius size + v3f main_direction; + + s16 route_y_min; + s16 route_y_max; + + PseudoRandom *ps; + + content_t c_water_source; + content_t c_lava_source; + content_t c_ice; + + int water_level; + + CaveV5() {} + CaveV5(MapgenV5 *mg, PseudoRandom *ps); + void makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height); + void makeTunnel(bool dirswitch); + void carveRoute(v3f vec, float f, bool randomize_xz); +}; + class CaveV6 { public: MapgenV6 *mg; @@ -66,7 +113,7 @@ public: CaveV6(MapgenV6 *mg, PseudoRandom *ps, PseudoRandom *ps2, bool large_cave); void makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height); void makeTunnel(bool dirswitch); - void carveRoute(v3f vec, float f, bool randomize_xz); + void carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_ground); }; class CaveV7 { @@ -83,7 +130,6 @@ public: int dswitchint; int part_max_length_rs; - bool large_cave; bool large_cave_is_flat; bool flooded; @@ -109,10 +155,10 @@ public: int water_level; CaveV7() {} - CaveV7(MapgenV7 *mg, PseudoRandom *ps, bool large_cave); + CaveV7(MapgenV7 *mg, PseudoRandom *ps); void makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height); void makeTunnel(bool dirswitch); - void carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine); + void carveRoute(v3f vec, float f, bool randomize_xz); }; #endif diff --git a/src/cguittfont/CGUITTFont.cpp b/src/cguittfont/CGUITTFont.cpp index f6606996e..2342eb748 100644 --- a/src/cguittfont/CGUITTFont.cpp +++ b/src/cguittfont/CGUITTFont.cpp @@ -29,6 +29,7 @@ */ #include <irrlicht.h> +#include <iostream> #include "CGUITTFont.h" namespace irr @@ -64,8 +65,24 @@ scene::SMesh CGUITTFont::shared_plane_; // +/** Checks that no dimension of the FT_BitMap object is negative. If either is + * negative, abort execution. + */ +inline void checkFontBitmapSize(const FT_Bitmap &bits) +{ + if ((s32)bits.rows < 0 || (s32)bits.width < 0) { + std::cout << "Insane font glyph size. File: " + << __FILE__ << " Line " << __LINE__ + << std::endl; + abort(); + } +} + video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const { + // Make sure our casts to s32 in the loops below will not cause problems + checkFontBitmapSize(bits); + // Determine what our texture size should be. // Add 1 because textures are inclusive-exclusive. core::dimension2du d(bits.width + 1, bits.rows + 1); @@ -87,10 +104,11 @@ video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVide const u32 image_pitch = image->getPitch() / sizeof(u16); u16* image_data = (u16*)image->lock(); u8* glyph_data = bits.buffer; - for (int y = 0; y < bits.rows; ++y) + + for (s32 y = 0; y < (s32)bits.rows; ++y) { u16* row = image_data; - for (int x = 0; x < bits.width; ++x) + for (s32 x = 0; x < (s32)bits.width; ++x) { // Monochrome bitmaps store 8 pixels per byte. The left-most pixel is the bit 0x80. // So, we go through the data each bit at a time. @@ -116,10 +134,10 @@ video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVide const u32 image_pitch = image->getPitch() / sizeof(u32); u32* image_data = (u32*)image->lock(); u8* glyph_data = bits.buffer; - for (int y = 0; y < bits.rows; ++y) + for (s32 y = 0; y < (s32)bits.rows; ++y) { u8* row = glyph_data; - for (int x = 0; x < bits.width; ++x) + for (s32 x = 0; x < (s32)bits.width; ++x) { image_data[y * image_pitch + x] |= static_cast<u32>(255.0f * (static_cast<float>(*row++) / gray_count)) << 24; //data[y * image_pitch + x] |= ((u32)(*bitsdata++) << 24); diff --git a/src/cguittfont/CMakeLists.txt b/src/cguittfont/CMakeLists.txt index 21448ecb8..7717a2f91 100644 --- a/src/cguittfont/CMakeLists.txt +++ b/src/cguittfont/CMakeLists.txt @@ -27,3 +27,4 @@ target_link_libraries( ${FREETYPE_LIBRARY} ${ZLIB_LIBRARIES} # needed by freetype, repeated here for safety ) + diff --git a/src/chat.cpp b/src/chat.cpp index 1fb872b85..50391d39b 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -83,7 +83,7 @@ u32 ChatBuffer::getScrollback() const const ChatLine& ChatBuffer::getLine(u32 index) const { - assert(index < getLineCount()); + assert(index < getLineCount()); // pre-condition return m_unformatted[index]; } @@ -107,7 +107,8 @@ void ChatBuffer::deleteOldest(u32 count) // keep m_formatted in sync if (del_formatted < m_formatted.size()) { - assert(m_formatted[del_formatted].first); + + sanity_check(m_formatted[del_formatted].first); ++del_formatted; while (del_formatted < m_formatted.size() && !m_formatted[del_formatted].first) @@ -510,7 +511,7 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa { std::wstring completion = narrow_to_wide(*i); if (prefix_start == 0) - completion += L":"; + completion += L": "; completions.push_back(completion); } } @@ -540,7 +541,7 @@ void ChatPrompt::nickCompletion(const std::list<std::string>& names, bool backwa } } } - std::wstring replacement = completions[replacement_index] + L" "; + std::wstring replacement = completions[replacement_index]; if (word_end < m_line.size() && isspace(word_end)) ++word_end; diff --git a/src/client.cpp b/src/client.cpp index 2f70d624c..946f4f1c4 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -22,40 +22,36 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <sstream> #include <IFileSystem.h> #include "jthread/jmutexautolock.h" +#include "util/auth.h" #include "util/directiontables.h" #include "util/pointedthing.h" #include "util/serialize.h" #include "util/string.h" -#include "strfnd.h" +#include "util/srp.h" #include "client.h" -#include "clientserver.h" -#include "main.h" +#include "network/clientopcodes.h" #include "filesys.h" #include "porting.h" -#include "mapsector.h" #include "mapblock_mesh.h" #include "mapblock.h" +#include "minimap.h" #include "settings.h" #include "profiler.h" #include "gettext.h" #include "log.h" #include "nodemetadata.h" -#include "nodedef.h" #include "itemdef.h" #include "shader.h" -#include "base64.h" #include "clientmap.h" #include "clientmedia.h" #include "sound.h" #include "IMeshCache.h" -#include "serialization.h" #include "config.h" #include "version.h" #include "drawscene.h" -#include "subgame.h" -#include "server.h" -#include "database.h" #include "database-sqlite3.h" +#include "serialization.h" +#include "guiscalingfilter.h" extern gui::IGUIEnvironment* guienv; @@ -104,7 +100,7 @@ void MeshUpdateQueue::addBlock(v3s16 p, MeshMakeData *data, bool ack_block_to_se { DSTACK(__FUNCTION_NAME); - assert(data); + assert(data); // pre-condition JMutexAutoLock lock(m_mutex); @@ -143,7 +139,7 @@ void MeshUpdateQueue::addBlock(v3s16 p, MeshMakeData *data, bool ack_block_to_se // Returned pointer must be deleted // Returns NULL if queue is empty -QueuedMeshUpdate * MeshUpdateQueue::pop() +QueuedMeshUpdate *MeshUpdateQueue::pop() { JMutexAutoLock lock(m_mutex); @@ -166,35 +162,21 @@ QueuedMeshUpdate * MeshUpdateQueue::pop() MeshUpdateThread */ -void * MeshUpdateThread::Thread() +void MeshUpdateThread::enqueueUpdate(v3s16 p, MeshMakeData *data, + bool ack_block_to_server, bool urgent) { - ThreadStarted(); - - log_register_thread("MeshUpdateThread"); - - DSTACK(__FUNCTION_NAME); - - BEGIN_DEBUG_EXCEPTION_HANDLER - - porting::setThreadName("MeshUpdateThread"); + m_queue_in.addBlock(p, data, ack_block_to_server, urgent); + deferUpdate(); +} - while(!StopRequested()) - { - QueuedMeshUpdate *q = m_queue_in.pop(); - if(q == NULL) - { - sleep_ms(3); - continue; - } +void MeshUpdateThread::doUpdate() +{ + QueuedMeshUpdate *q; + while ((q = m_queue_in.pop())) { ScopeProfiler sp(g_profiler, "Client: Mesh making"); MapBlockMesh *mesh_new = new MapBlockMesh(q->data, m_camera_offset); - if(mesh_new->getMesh()->getMeshBufferCount() == 0) - { - delete mesh_new; - mesh_new = NULL; - } MeshUpdateResult r; r.p = q->p; @@ -205,10 +187,6 @@ void * MeshUpdateThread::Thread() delete q; } - - END_DEBUG_EXCEPTION_HANDLER(errorstream) - - return NULL; } /* @@ -239,7 +217,7 @@ Client::Client( m_nodedef(nodedef), m_sound(sound), m_event(event), - m_mesh_update_thread(this), + m_mesh_update_thread(), m_env( new ClientMap(this, this, control, device->getSceneManager()->getRootSceneNode(), @@ -251,6 +229,7 @@ Client::Client( m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, ipv6, this), m_device(device), m_server_ser_ver(SER_FMT_VER_INVALID), + m_proto_ver(0), m_playeritem(0), m_inventory_updated(false), m_inventory_from_server(NULL), @@ -262,7 +241,10 @@ Client::Client( m_highlighted_pos(0,0,0), m_map_seed(0), m_password(password), + m_chosen_auth_mech(AUTH_MECHANISM_NONE), + m_auth_data(NULL), m_access_denied(false), + m_access_denied_reconnect(false), m_itemdef_received(false), m_nodedef_received(false), m_media_downloader(new ClientMediaDownloader()), @@ -271,28 +253,27 @@ Client::Client( m_time_of_day_update_timer(0), m_recommended_send_interval(0.1), m_removed_sounds_check_timer(0), - m_state(LC_Created) + m_state(LC_Created), + m_localdb(NULL) { - /* - Add local player - */ - { - Player *player = new LocalPlayer(this, playername); + // Add local player + m_env.addPlayer(new LocalPlayer(this, playername)); - m_env.addPlayer(player); - } + m_mapper = new Mapper(device, this); + m_cache_save_interval = g_settings->getU16("server_map_save_interval"); m_cache_smooth_lighting = g_settings->getBool("smooth_lighting"); + m_cache_enable_shaders = g_settings->getBool("enable_shaders"); } void Client::Stop() { //request all client managed threads to stop m_mesh_update_thread.Stop(); - if (localdb != NULL) { - actionstream << "Local map saving ended" << std::endl; - localdb->endSave(); - delete localserver; + // Save local server map + if (m_localdb) { + infostream << "Local map saving ended." << std::endl; + m_localdb->endSave(); } } @@ -310,7 +291,7 @@ Client::~Client() m_mesh_update_thread.Stop(); m_mesh_update_thread.Wait(); - while(!m_mesh_update_thread.m_queue_out.empty()) { + while (!m_mesh_update_thread.m_queue_out.empty()) { MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); delete r.mesh; } @@ -319,20 +300,22 @@ Client::~Client() delete m_inventory_from_server; // Delete detached inventories - for(std::map<std::string, Inventory*>::iterator + for (std::map<std::string, Inventory*>::iterator i = m_detached_inventories.begin(); - i != m_detached_inventories.end(); i++){ + i != m_detached_inventories.end(); ++i) { delete i->second; } // cleanup 3d model meshes on client shutdown while (m_device->getSceneManager()->getMeshCache()->getMeshCount() != 0) { - scene::IAnimatedMesh * mesh = + scene::IAnimatedMesh *mesh = m_device->getSceneManager()->getMeshCache()->getMeshByIndex(0); if (mesh != NULL) m_device->getSceneManager()->getMeshCache()->removeMesh(mesh); } + + delete m_mapper; } void Client::connect(Address address, @@ -385,139 +368,41 @@ void Client::step(float dtime) } } -#if 0 - { - /* - Delete unused sectors - - NOTE: This jams the game for a while because deleting sectors - clear caches - */ - - float &counter = m_delete_unused_sectors_timer; - counter -= dtime; - if(counter <= 0.0) - { - // 3 minute interval - //counter = 180.0; - counter = 60.0; - - //JMutexAutoLock lock(m_env_mutex); //bulk comment-out - - core::list<v3s16> deleted_blocks; - - float delete_unused_sectors_timeout = - g_settings->getFloat("client_delete_unused_sectors_timeout"); - - // Delete sector blocks - /*u32 num = m_env.getMap().unloadUnusedData - (delete_unused_sectors_timeout, - true, &deleted_blocks);*/ - - // Delete whole sectors - m_env.getMap().unloadUnusedData - (delete_unused_sectors_timeout, - &deleted_blocks); - - if(deleted_blocks.size() > 0) - { - /*infostream<<"Client: Deleted blocks of "<<num - <<" unused sectors"<<std::endl;*/ - /*infostream<<"Client: Deleted "<<num - <<" unused sectors"<<std::endl;*/ - - /* - Send info to server - */ - - // Env is locked so con can be locked. - //JMutexAutoLock lock(m_con_mutex); //bulk comment-out - - core::list<v3s16>::Iterator i = deleted_blocks.begin(); - core::list<v3s16> sendlist; - for(;;) - { - if(sendlist.size() == 255 || i == deleted_blocks.end()) - { - if(sendlist.size() == 0) - break; - /* - [0] u16 command - [2] u8 count - [3] v3s16 pos_0 - [3+6] v3s16 pos_1 - ... - */ - u32 replysize = 2+1+6*sendlist.size(); - SharedBuffer<u8> reply(replysize); - writeU16(&reply[0], TOSERVER_DELETEDBLOCKS); - reply[2] = sendlist.size(); - u32 k = 0; - for(core::list<v3s16>::Iterator - j = sendlist.begin(); - j != sendlist.end(); j++) - { - writeV3S16(&reply[2+1+6*k], *j); - k++; - } - m_con.Send(PEER_ID_SERVER, 1, reply, true); - - if(i == deleted_blocks.end()) - break; - - sendlist.clear(); - } - - sendlist.push_back(*i); - i++; - } - } - } - } -#endif // UGLY hack to fix 2 second startup delay caused by non existent // server client startup synchronization in local server or singleplayer mode static bool initial_step = true; if (initial_step) { initial_step = false; } - else if(m_state == LC_Created) - { + else if(m_state == LC_Created) { float &counter = m_connection_reinit_timer; counter -= dtime; - if(counter <= 0.0) - { + if(counter <= 0.0) { counter = 2.0; - //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out - Player *myplayer = m_env.getLocalPlayer(); - assert(myplayer != NULL); - // Send TOSERVER_INIT - // [0] u16 TOSERVER_INIT + FATAL_ERROR_IF(myplayer == NULL, "Local player not found in environment."); + + // Send TOSERVER_INIT_LEGACY + // [0] u16 TOSERVER_INIT_LEGACY // [2] u8 SER_FMT_VER_HIGHEST_READ // [3] u8[20] player_name // [23] u8[28] password (new in some version) // [51] u16 minimum supported network protocol version (added sometime) // [53] u16 maximum supported network protocol version (added later than the previous one) - SharedBuffer<u8> data(2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2+2); - writeU16(&data[0], TOSERVER_INIT); - writeU8(&data[2], SER_FMT_VER_HIGHEST_READ); - - memset((char*)&data[3], 0, PLAYERNAME_SIZE); - snprintf((char*)&data[3], PLAYERNAME_SIZE, "%s", myplayer->getName()); - /*infostream<<"Client: sending initial password hash: \""<<m_password<<"\"" - <<std::endl;*/ + char pName[PLAYERNAME_SIZE]; + char pPassword[PASSWORD_SIZE]; + memset(pName, 0, PLAYERNAME_SIZE * sizeof(char)); + memset(pPassword, 0, PASSWORD_SIZE * sizeof(char)); - memset((char*)&data[23], 0, PASSWORD_SIZE); - snprintf((char*)&data[23], PASSWORD_SIZE, "%s", m_password.c_str()); + std::string hashed_password = translatePassword(myplayer->getName(), m_password); + snprintf(pName, PLAYERNAME_SIZE, "%s", myplayer->getName()); + snprintf(pPassword, PASSWORD_SIZE, "%s", hashed_password.c_str()); - writeU16(&data[51], CLIENT_PROTOCOL_VERSION_MIN); - writeU16(&data[53], CLIENT_PROTOCOL_VERSION_MAX); - - // Send as unreliable - Send(1, data, false); + sendLegacyInit(pName, pPassword); + if (LATEST_PROTOCOL_VERSION >= 25) + sendInit(myplayer->getName()); } // Not connected, return @@ -532,29 +417,23 @@ void Client::step(float dtime) Run Map's timers and unload unused data */ const float map_timer_and_unload_dtime = 5.25; - if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) - { + if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime)) { ScopeProfiler sp(g_profiler, "Client: map timer and unload"); - std::list<v3s16> deleted_blocks; + std::vector<v3s16> deleted_blocks; m_env.getMap().timerUpdate(map_timer_and_unload_dtime, - g_settings->getFloat("client_unload_unused_data_timeout"), - &deleted_blocks); - - /*if(deleted_blocks.size() > 0) - infostream<<"Client: Unloaded "<<deleted_blocks.size() - <<" unused blocks"<<std::endl;*/ + g_settings->getFloat("client_unload_unused_data_timeout"), + g_settings->getS32("client_mapblock_limit"), + &deleted_blocks); /* Send info to server NOTE: This loop is intentionally iterated the way it is. */ - std::list<v3s16>::iterator i = deleted_blocks.begin(); - std::list<v3s16> sendlist; - for(;;) - { - if(sendlist.size() == 255 || i == deleted_blocks.end()) - { + std::vector<v3s16>::iterator i = deleted_blocks.begin(); + std::vector<v3s16> sendlist; + for(;;) { + if(sendlist.size() == 255 || i == deleted_blocks.end()) { if(sendlist.empty()) break; /* @@ -564,19 +443,8 @@ void Client::step(float dtime) [3+6] v3s16 pos_1 ... */ - u32 replysize = 2+1+6*sendlist.size(); - SharedBuffer<u8> reply(replysize); - writeU16(&reply[0], TOSERVER_DELETEDBLOCKS); - reply[2] = sendlist.size(); - u32 k = 0; - for(std::list<v3s16>::iterator - j = sendlist.begin(); - j != sendlist.end(); ++j) - { - writeV3S16(&reply[2+1+6*k], *j); - k++; - } - m_con.Send(PEER_ID_SERVER, 2, reply, true); + + sendDeletedBlocks(sendlist); if(i == deleted_blocks.end()) break; @@ -592,62 +460,52 @@ void Client::step(float dtime) /* Handle environment */ - { - // Control local player (0ms) - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player != NULL); - player->applyControl(dtime); + // Control local player (0ms) + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player != NULL); + player->applyControl(dtime); - // Step environment - m_env.step(dtime); + // Step environment + m_env.step(dtime); - /* - Get events - */ - for(;;) - { - ClientEnvEvent event = m_env.getClientEvent(); - if(event.type == CEE_NONE) - { - break; - } - else if(event.type == CEE_PLAYER_DAMAGE) - { - if(m_ignore_damage_timer <= 0) - { - u8 damage = event.player_damage.amount; - - if(event.player_damage.send_to_server) - sendDamage(damage); - - // Add to ClientEvent queue - ClientEvent event; - event.type = CE_PLAYER_DAMAGE; - event.player_damage.amount = damage; - m_client_event_queue.push_back(event); - } - } - else if(event.type == CEE_PLAYER_BREATH) - { - u16 breath = event.player_breath.amount; - sendBreath(breath); + /* + Get events + */ + for(;;) { + ClientEnvEvent event = m_env.getClientEvent(); + if(event.type == CEE_NONE) { + break; + } + else if(event.type == CEE_PLAYER_DAMAGE) { + if(m_ignore_damage_timer <= 0) { + u8 damage = event.player_damage.amount; + + if(event.player_damage.send_to_server) + sendDamage(damage); + + // Add to ClientEvent queue + ClientEvent event; + event.type = CE_PLAYER_DAMAGE; + event.player_damage.amount = damage; + m_client_event_queue.push(event); } } + else if(event.type == CEE_PLAYER_BREATH) { + u16 breath = event.player_breath.amount; + sendBreath(breath); + } } /* Print some info */ - { - float &counter = m_avg_rtt_timer; - counter += dtime; - if(counter >= 10) - { - counter = 0.0; - // connectedAndInitialized() is true, peer exists. - float avg_rtt = getRTT(); - infostream<<"Client: avg_rtt="<<avg_rtt<<std::endl; - } + float &counter = m_avg_rtt_timer; + counter += dtime; + if(counter >= 10) { + counter = 0.0; + // connectedAndInitialized() is true, peer exists. + float avg_rtt = getRTT(); + infostream << "Client: avg_rtt=" << avg_rtt << std::endl; } /* @@ -668,48 +526,52 @@ void Client::step(float dtime) */ { int num_processed_meshes = 0; - while(!m_mesh_update_thread.m_queue_out.empty()) + while (!m_mesh_update_thread.m_queue_out.empty()) { num_processed_meshes++; + + MinimapMapblock *minimap_mapblock = NULL; + bool do_mapper_update = true; + MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_frontNoEx(); MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(r.p); - if(block) - { + if (block) { // Delete the old mesh - if(block->mesh != NULL) - { - // TODO: Remove hardware buffers of meshbuffers of block->mesh + if (block->mesh != NULL) { delete block->mesh; block->mesh = NULL; } - // Replace with the new mesh - block->mesh = r.mesh; + if (r.mesh) { + minimap_mapblock = r.mesh->moveMinimapMapblock(); + if (minimap_mapblock == NULL) + do_mapper_update = false; + } + + if (r.mesh && r.mesh->getMesh()->getMeshBufferCount() == 0) { + delete r.mesh; + } else { + // Replace with the new mesh + block->mesh = r.mesh; + } } else { delete r.mesh; } - if(r.ack_block_to_server) - { + + if (do_mapper_update) + m_mapper->addBlock(r.p, minimap_mapblock); + + if (r.ack_block_to_server) { /* Acknowledge block + [0] u8 count + [1] v3s16 pos_0 */ - /* - [0] u16 command - [2] u8 count - [3] v3s16 pos_0 - [3+6] v3s16 pos_1 - ... - */ - u32 replysize = 2+1+6; - SharedBuffer<u8> reply(replysize); - writeU16(&reply[0], TOSERVER_GOTBLOCKS); - reply[2] = 1; - writeV3S16(&reply[3], r.p); - // Send as reliable - m_con.Send(PEER_ID_SERVER, 2, reply, true); + sendGotBlocks(r.p); } } - if(num_processed_meshes > 0) + + if (num_processed_meshes > 0) g_profiler->graphAdd("num_processed_meshes", num_processed_meshes); } @@ -771,42 +633,36 @@ void Client::step(float dtime) Handle removed remotely initiated sounds */ m_removed_sounds_check_timer += dtime; - if(m_removed_sounds_check_timer >= 2.32) - { + if(m_removed_sounds_check_timer >= 2.32) { m_removed_sounds_check_timer = 0; // Find removed sounds and clear references to them - std::set<s32> removed_server_ids; + std::vector<s32> removed_server_ids; for(std::map<s32, int>::iterator i = m_sounds_server_to_client.begin(); - i != m_sounds_server_to_client.end();) - { + i != m_sounds_server_to_client.end();) { s32 server_id = i->first; int client_id = i->second; i++; - if(!m_sound->soundExists(client_id)){ + if(!m_sound->soundExists(client_id)) { m_sounds_server_to_client.erase(server_id); m_sounds_client_to_server.erase(client_id); m_sounds_to_objects.erase(client_id); - removed_server_ids.insert(server_id); + removed_server_ids.push_back(server_id); } } + // Sync to server - if(!removed_server_ids.empty()) - { - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOSERVER_REMOVED_SOUNDS); - size_t server_ids = removed_server_ids.size(); - assert(server_ids <= 0xFFFF); - writeU16(os, (u16) (server_ids & 0xFFFF)); - for(std::set<s32>::iterator i = removed_server_ids.begin(); - i != removed_server_ids.end(); i++) - writeS32(os, *i); - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(1, data, true); + if(!removed_server_ids.empty()) { + sendRemovedSounds(removed_server_ids); } } + + // Write server map + if (m_localdb && m_localdb_save_interval.step(dtime, + m_cache_save_interval)) { + m_localdb->endSave(); + m_localdb->beginSave(); + } } bool Client::loadMedia(const std::string &data, const std::string &filename) @@ -833,7 +689,9 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) // Create an irrlicht memory file io::IReadFile *rfile = irrfs->createMemoryReadFile( *data_rw, data_rw.getSize(), "_tempreadfile"); - assert(rfile); + + FATAL_ERROR_IF(!rfile, "Could not create irrlicht memory file."); + // Read image video::IImage *img = vdrv->createImageFromFile(rfile); if(!img){ @@ -888,14 +746,19 @@ bool Client::loadMedia(const std::string &data, const std::string &filename) // Virtual methods from con::PeerHandler void Client::peerAdded(con::Peer *peer) { - infostream<<"Client::peerAdded(): peer->id=" - <<peer->id<<std::endl; + infostream << "Client::peerAdded(): peer->id=" + << peer->id << std::endl; } void Client::deletingPeer(con::Peer *peer, bool timeout) { - infostream<<"Client::deletingPeer(): " + infostream << "Client::deletingPeer(): " "Server Peer is getting deleted " - <<"(timeout="<<timeout<<")"<<std::endl; + << "(timeout=" << timeout << ")" << std::endl; + + if (timeout) { + m_access_denied = true; + m_access_denied_reason = gettext("Connection timed out."); + } } /* @@ -906,73 +769,55 @@ void Client::deletingPeer(con::Peer *peer, bool timeout) string name } */ -void Client::request_media(const std::list<std::string> &file_requests) +void Client::request_media(const std::vector<std::string> &file_requests) { std::ostringstream os(std::ios_base::binary); writeU16(os, TOSERVER_REQUEST_MEDIA); size_t file_requests_size = file_requests.size(); - assert(file_requests_size <= 0xFFFF); - writeU16(os, (u16) (file_requests_size & 0xFFFF)); - for(std::list<std::string>::const_iterator i = file_requests.begin(); + FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests"); + + // Packet dynamicly resized + NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0); + + pkt << (u16) (file_requests_size & 0xFFFF); + + for(std::vector<std::string>::const_iterator i = file_requests.begin(); i != file_requests.end(); ++i) { - os<<serializeString(*i); + pkt << (*i); } - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(1, data, true); - infostream<<"Client: Sending media request list to server (" - <<file_requests.size()<<" files)"<<std::endl; + Send(&pkt); + + infostream << "Client: Sending media request list to server (" + << file_requests.size() << " files. packet size)" << std::endl; } void Client::received_media() { - // notify server we received everything - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOSERVER_RECEIVED_MEDIA); - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(1, data, true); - infostream<<"Client: Notifying server that we received all media" - <<std::endl; + NetworkPacket pkt(TOSERVER_RECEIVED_MEDIA, 0); + Send(&pkt); + infostream << "Client: Notifying server that we received all media" + << std::endl; } void Client::initLocalMapSaving(const Address &address, const std::string &hostname, bool is_local_server) { - localdb = NULL; - - if (!g_settings->getBool("enable_local_map_saving") || is_local_server) + if (!g_settings->getBool("enable_local_map_saving") || is_local_server) { return; + } const std::string world_path = porting::path_user + DIR_DELIM + "worlds" + DIR_DELIM + "server_" + hostname + "_" + to_string(address.getPort()); - SubgameSpec gamespec; - - if (!getWorldExists(world_path)) { - gamespec = findSubgame(g_settings->get("default_game")); - if (!gamespec.isValid()) - gamespec = findSubgame("minimal"); - } else { - gamespec = findWorldSubgame(world_path); - } + fs::CreateAllDirs(world_path); - if (!gamespec.isValid()) { - errorstream << "Couldn't find subgame for local map saving." << std::endl; - return; - } - - localserver = new Server(world_path, gamespec, false, false); - localdb = new Database_SQLite3(&(ServerMap&)localserver->getMap(), world_path); - localdb->beginSave(); + m_localdb = new Database_SQLite3(world_path); + m_localdb->beginSave(); actionstream << "Local map saving started, map will be saved at '" << world_path << "'" << std::endl; } @@ -987,16 +832,14 @@ void Client::ReceiveAll() if(porting::getTimeMs() > start_ms + 100) break; - try{ + try { Receive(); g_profiler->graphAdd("client_received_packets", 1); } - catch(con::NoIncomingDataException &e) - { + catch(con::NoIncomingDataException &e) { break; } - catch(con::InvalidIncomingDataException &e) - { + catch(con::InvalidIncomingDataException &e) { infostream<<"Client::ReceiveAll(): " "InvalidIncomingDataException: what()=" <<e.what()<<std::endl; @@ -1007,27 +850,26 @@ void Client::ReceiveAll() void Client::Receive() { DSTACK(__FUNCTION_NAME); - SharedBuffer<u8> data; - u16 sender_peer_id; - u32 datasize = m_con.Receive(sender_peer_id, data); - ProcessData(*data, datasize, sender_peer_id); + NetworkPacket pkt; + m_con.Receive(&pkt); + ProcessData(&pkt); +} + +inline void Client::handleCommand(NetworkPacket* pkt) +{ + const ToClientCommandHandler& opHandle = toClientCommandTable[pkt->getCommand()]; + (this->*opHandle.handler)(pkt); } /* sender_peer_id given to this shall be quaranteed to be a valid peer */ -void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id) +void Client::ProcessData(NetworkPacket *pkt) { DSTACK(__FUNCTION_NAME); - // Ignore packets that don't even fit a command - if(datasize < 2) - { - m_packetcounter.add(60000); - return; - } - - ToClientCommand command = (ToClientCommand)readU16(&data[0]); + ToClientCommand command = (ToClientCommand) pkt->getCommand(); + u32 sender_peer_id = pkt->getPeerId(); //infostream<<"Client: received command="<<command<<std::endl; m_packetcounter.add((u16)command); @@ -1036,1231 +878,374 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id) If this check is removed, be sure to change the queue system to know the ids */ - if(sender_peer_id != PEER_ID_SERVER) - { - infostream<<"Client::ProcessData(): Discarding data not " - "coming from server: peer_id="<<sender_peer_id - <<std::endl; + if(sender_peer_id != PEER_ID_SERVER) { + infostream << "Client::ProcessData(): Discarding data not " + "coming from server: peer_id=" << sender_peer_id + << std::endl; return; } - u8 ser_version = m_server_ser_ver; - - if(command == TOCLIENT_INIT) - { - if(datasize < 3) - return; - - u8 deployed = data[2]; - - infostream<<"Client: TOCLIENT_INIT received with " - "deployed="<<((int)deployed&0xff)<<std::endl; - - if(!ser_ver_supported(deployed)) - { - infostream<<"Client: TOCLIENT_INIT: Server sent " - <<"unsupported ser_fmt_ver"<<std::endl; - return; - } - - m_server_ser_ver = deployed; - - // Get player position - v3s16 playerpos_s16(0, BS*2+BS*20, 0); - if(datasize >= 2+1+6) - playerpos_s16 = readV3S16(&data[2+1]); - v3f playerpos_f = intToFloat(playerpos_s16, BS) - v3f(0, BS/2, 0); - - - // Set player position - Player *player = m_env.getLocalPlayer(); - assert(player != NULL); - player->setPosition(playerpos_f); - - if(datasize >= 2+1+6+8) - { - // Get map seed - m_map_seed = readU64(&data[2+1+6]); - infostream<<"Client: received map seed: "<<m_map_seed<<std::endl; - } - - if(datasize >= 2+1+6+8+4) - { - // Get map seed - m_recommended_send_interval = readF1000(&data[2+1+6+8]); - infostream<<"Client: received recommended send interval " - <<m_recommended_send_interval<<std::endl; - } - - // Reply to server - u32 replysize = 2; - SharedBuffer<u8> reply(replysize); - writeU16(&reply[0], TOSERVER_INIT2); - // Send as reliable - m_con.Send(PEER_ID_SERVER, 1, reply, true); - - m_state = LC_Init; - + // Command must be handled into ToClientCommandHandler + if (command >= TOCLIENT_NUM_MSG_TYPES) { + infostream << "Client: Ignoring unknown command " + << command << std::endl; return; } - if(command == TOCLIENT_ACCESS_DENIED) - { - // The server didn't like our password. Note, this needs - // to be processed even if the serialisation format has - // not been agreed yet, the same as TOCLIENT_INIT. - m_access_denied = true; - m_access_denied_reason = L"Unknown"; - if(datasize >= 4) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - m_access_denied_reason = deSerializeWideString(is); - } + /* + * Those packets are handled before m_server_ser_ver is set, it's normal + * But we must use the new ToClientConnectionState in the future, + * as a byte mask + */ + if(toClientCommandTable[command].state == TOCLIENT_STATE_NOT_CONNECTED) { + handleCommand(pkt); return; } - if(ser_version == SER_FMT_VER_INVALID) - { - infostream<<"Client: Server serialization" + if(m_server_ser_ver == SER_FMT_VER_INVALID) { + infostream << "Client: Server serialization" " format invalid or not initialized." - " Skipping incoming command="<<command<<std::endl; + " Skipping incoming command=" << command << std::endl; return; } /* Handle runtime commands */ - // there's no sane reason why we shouldn't have a player and - // almost everyone needs a player reference - Player *player = m_env.getLocalPlayer(); - assert(player != NULL); - - if(command == TOCLIENT_REMOVENODE) - { - if(datasize < 8) - return; - v3s16 p; - p.X = readS16(&data[2]); - p.Y = readS16(&data[4]); - p.Z = readS16(&data[6]); - removeNode(p); - } - else if(command == TOCLIENT_ADDNODE) - { - if(datasize < 8 + MapNode::serializedLength(ser_version)) - return; - - v3s16 p; - p.X = readS16(&data[2]); - p.Y = readS16(&data[4]); - p.Z = readS16(&data[6]); - - MapNode n; - n.deSerialize(&data[8], ser_version); - - bool remove_metadata = true; - u32 index = 8 + MapNode::serializedLength(ser_version); - if ((datasize >= index+1) && data[index]){ - remove_metadata = false; - } - - addNode(p, n, remove_metadata); - } - else if(command == TOCLIENT_BLOCKDATA) - { - // Ignore too small packet - if(datasize < 8) - return; - - v3s16 p; - p.X = readS16(&data[2]); - p.Y = readS16(&data[4]); - p.Z = readS16(&data[6]); - - std::string datastring((char*)&data[8], datasize-8); - std::istringstream istr(datastring, std::ios_base::binary); - - MapSector *sector; - MapBlock *block; - - v2s16 p2d(p.X, p.Z); - sector = m_env.getMap().emergeSector(p2d); - - assert(sector->getPos() == p2d); - - block = sector->getBlockNoCreateNoEx(p.Y); - if(block) - { - /* - Update an existing block - */ - block->deSerialize(istr, ser_version, false); - block->deSerializeNetworkSpecific(istr); - } - else - { - /* - Create a new block - */ - block = new MapBlock(&m_env.getMap(), p, this); - block->deSerialize(istr, ser_version, false); - block->deSerializeNetworkSpecific(istr); - sector->insertBlock(block); - } - - if (localdb != NULL) { - ((ServerMap&) localserver->getMap()).saveBlock(block, localdb); - } - - /* - Add it to mesh update queue and set it to be acknowledged after update. - */ - addUpdateMeshTaskWithEdge(p, true); - } - else if(command == TOCLIENT_INVENTORY) - { - if(datasize < 3) - return; - - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - player->inventory.deSerialize(is); - - m_inventory_updated = true; - - delete m_inventory_from_server; - m_inventory_from_server = new Inventory(player->inventory); - m_inventory_from_server_age = 0.0; - - } - else if(command == TOCLIENT_TIME_OF_DAY) - { - if(datasize < 4) - return; - - u16 time_of_day = readU16(&data[2]); - time_of_day = time_of_day % 24000; - float time_speed = 0; - - if(datasize >= 2 + 2 + 4) - { - time_speed = readF1000(&data[4]); - } - else { - // Old message; try to approximate speed of time by ourselves - float time_of_day_f = (float)time_of_day / 24000.0; - float tod_diff_f = 0; - - if(time_of_day_f < 0.2 && m_last_time_of_day_f > 0.8) - tod_diff_f = time_of_day_f - m_last_time_of_day_f + 1.0; - else - tod_diff_f = time_of_day_f - m_last_time_of_day_f; - - m_last_time_of_day_f = time_of_day_f; - float time_diff = m_time_of_day_update_timer; - m_time_of_day_update_timer = 0; - - if(m_time_of_day_set){ - time_speed = (3600.0*24.0) * tod_diff_f / time_diff; - infostream<<"Client: Measured time_of_day speed (old format): " - <<time_speed<<" tod_diff_f="<<tod_diff_f - <<" time_diff="<<time_diff<<std::endl; - } - } - - // Update environment - m_env.setTimeOfDay(time_of_day); - m_env.setTimeOfDaySpeed(time_speed); - m_time_of_day_set = true; - - u32 dr = m_env.getDayNightRatio(); - infostream<<"Client: time_of_day="<<time_of_day - <<" time_speed="<<time_speed - <<" dr="<<dr<<std::endl; - } - else if(command == TOCLIENT_CHAT_MESSAGE) - { - /* - u16 command - u16 length - wstring message - */ - u8 buf[6]; - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - // Read stuff - is.read((char*) buf, 2); - u16 len = readU16(buf); - - std::wstring message; - for(unsigned int i=0; i<len; i++) - { - is.read((char*)buf, 2); - message += (wchar_t)readU16(buf); - } - - m_chat_queue.push_back(message); - } - else if(command == TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD) - { - /* - u16 command - u16 count of removed objects - for all removed objects { - u16 id - } - u16 count of added objects - for all added objects { - u16 id - u8 type - u32 initialization data length - string initialization data - } - */ - - char buf[6]; - // Get all data except the command number - std::string datastring((char*)&data[2], datasize-2); - // Throw them in an istringstream - std::istringstream is(datastring, std::ios_base::binary); - - // Read removed objects - is.read(buf, 2); - u16 removed_count = readU16((u8*)buf); - for(unsigned int i=0; i<removed_count; i++) - { - is.read(buf, 2); - u16 id = readU16((u8*)buf); - m_env.removeActiveObject(id); - } - - // Read added objects - is.read(buf, 2); - u16 added_count = readU16((u8*)buf); - for(unsigned int i=0; i<added_count; i++) - { - is.read(buf, 2); - u16 id = readU16((u8*)buf); - is.read(buf, 1); - u8 type = readU8((u8*)buf); - std::string data = deSerializeLongString(is); - // Add it - m_env.addActiveObject(id, type, data); - } - } - else if(command == TOCLIENT_ACTIVE_OBJECT_MESSAGES) - { - /* - u16 command - for all objects - { - u16 id - u16 message length - string message - } - */ - char buf[6]; - // Get all data except the command number - std::string datastring((char*)&data[2], datasize-2); - // Throw them in an istringstream - std::istringstream is(datastring, std::ios_base::binary); - - while(is.eof() == false) - { - is.read(buf, 2); - u16 id = readU16((u8*)buf); - if(is.eof()) - break; - is.read(buf, 2); - size_t message_size = readU16((u8*)buf); - std::string message; - message.reserve(message_size); - for(unsigned int i=0; i<message_size; i++) - { - is.read(buf, 1); - message.append(buf, 1); - } - // Pass on to the environment - m_env.processActiveObjectMessage(id, message); - } - } - else if(command == TOCLIENT_MOVEMENT) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - player->movement_acceleration_default = readF1000(is) * BS; - player->movement_acceleration_air = readF1000(is) * BS; - player->movement_acceleration_fast = readF1000(is) * BS; - player->movement_speed_walk = readF1000(is) * BS; - player->movement_speed_crouch = readF1000(is) * BS; - player->movement_speed_fast = readF1000(is) * BS; - player->movement_speed_climb = readF1000(is) * BS; - player->movement_speed_jump = readF1000(is) * BS; - player->movement_liquid_fluidity = readF1000(is) * BS; - player->movement_liquid_fluidity_smooth = readF1000(is) * BS; - player->movement_liquid_sink = readF1000(is) * BS; - player->movement_gravity = readF1000(is) * BS; - } - else if(command == TOCLIENT_HP) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - u8 oldhp = player->hp; - u8 hp = readU8(is); - player->hp = hp; - - if(hp < oldhp) - { - // Add to ClientEvent queue - ClientEvent event; - event.type = CE_PLAYER_DAMAGE; - event.player_damage.amount = oldhp - hp; - m_client_event_queue.push_back(event); - } - } - else if(command == TOCLIENT_BREATH) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - player->setBreath(readU16(is)); - } - else if(command == TOCLIENT_MOVE_PLAYER) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - v3f pos = readV3F1000(is); - f32 pitch = readF1000(is); - f32 yaw = readF1000(is); - player->setPosition(pos); - - infostream<<"Client got TOCLIENT_MOVE_PLAYER" - <<" pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")" - <<" pitch="<<pitch - <<" yaw="<<yaw - <<std::endl; - - /* - Add to ClientEvent queue. - This has to be sent to the main program because otherwise - it would just force the pitch and yaw values to whatever - the camera points to. - */ - ClientEvent event; - event.type = CE_PLAYER_FORCE_MOVE; - event.player_force_move.pitch = pitch; - event.player_force_move.yaw = yaw; - m_client_event_queue.push_back(event); - - // Ignore damage for a few seconds, so that the player doesn't - // get damage from falling on ground - m_ignore_damage_timer = 3.0; - } - else if(command == TOCLIENT_PLAYERITEM) - { - infostream<<"Client: WARNING: Ignoring TOCLIENT_PLAYERITEM"<<std::endl; - } - else if(command == TOCLIENT_DEATHSCREEN) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); + handleCommand(pkt); +} - bool set_camera_point_target = readU8(is); - v3f camera_point_target = readV3F1000(is); +void Client::Send(NetworkPacket* pkt) +{ + m_con.Send(PEER_ID_SERVER, + serverCommandFactoryTable[pkt->getCommand()].channel, + pkt, + serverCommandFactoryTable[pkt->getCommand()].reliable); +} - ClientEvent event; - event.type = CE_DEATHSCREEN; - event.deathscreen.set_camera_point_target = set_camera_point_target; - event.deathscreen.camera_point_target_x = camera_point_target.X; - event.deathscreen.camera_point_target_y = camera_point_target.Y; - event.deathscreen.camera_point_target_z = camera_point_target.Z; - m_client_event_queue.push_back(event); +void Client::interact(u8 action, const PointedThing& pointed) +{ + if(m_state != LC_Ready) { + errorstream << "Client::interact() " + "Canceled (not connected)" + << std::endl; + return; } - else if(command == TOCLIENT_ANNOUNCE_MEDIA) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - int num_files = readU16(is); - - infostream<<"Client: Received media announcement: packet size: " - <<datasize<<std::endl; - - if (m_media_downloader == NULL || - m_media_downloader->isStarted()) { - const char *problem = m_media_downloader ? - "we already saw another announcement" : - "all media has been received already"; - errorstream<<"Client: Received media announcement but " - <<problem<<"! " - <<" files="<<num_files - <<" size="<<datasize<<std::endl; - return; - } - // Mesh update thread must be stopped while - // updating content definitions - assert(!m_mesh_update_thread.IsRunning()); + /* + [0] u16 command + [2] u8 action + [3] u16 item + [5] u32 length of the next item + [9] serialized PointedThing + 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 + */ - for(int i=0; i<num_files; i++) - { - std::string name = deSerializeString(is); - std::string sha1_base64 = deSerializeString(is); - std::string sha1_raw = base64_decode(sha1_base64); - m_media_downloader->addFile(name, sha1_raw); - } + NetworkPacket pkt(TOSERVER_INTERACT, 1 + 2 + 0); - std::vector<std::string> remote_media; - try { - Strfnd sf(deSerializeString(is)); - while(!sf.atend()) { - std::string baseurl = trim(sf.next(",")); - if(baseurl != "") - m_media_downloader->addRemoteServer(baseurl); - } - } - catch(SerializationError& e) { - // not supported by server or turned off - } + pkt << action; + pkt << (u16)getPlayerItem(); - m_media_downloader->step(this); - } - else if(command == TOCLIENT_MEDIA) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); + std::ostringstream tmp_os(std::ios::binary); + pointed.serialize(tmp_os); - /* - u16 command - u16 total number of file bunches - u16 index of this bunch - u32 number of files in this bunch - for each file { - u16 length of name - string name - u32 length of data - data - } - */ - int num_bunches = readU16(is); - int bunch_i = readU16(is); - u32 num_files = readU32(is); - infostream<<"Client: Received files: bunch "<<bunch_i<<"/" - <<num_bunches<<" files="<<num_files - <<" size="<<datasize<<std::endl; - - if (num_files == 0) - return; + pkt.putLongString(tmp_os.str()); - if (m_media_downloader == NULL || - !m_media_downloader->isStarted()) { - const char *problem = m_media_downloader ? - "media has not been requested" : - "all media has been received already"; - errorstream<<"Client: Received media but " - <<problem<<"! " - <<" bunch "<<bunch_i<<"/"<<num_bunches - <<" files="<<num_files - <<" size="<<datasize<<std::endl; - return; - } + Send(&pkt); +} - // Mesh update thread must be stopped while - // updating content definitions - assert(!m_mesh_update_thread.IsRunning()); +void Client::deleteAuthData() +{ + if (!m_auth_data) + return; - for(unsigned int i=0; i<num_files; i++){ - std::string name = deSerializeString(is); - std::string data = deSerializeLongString(is); - m_media_downloader->conventionalTransferDone( - name, data, this); - } - } - else if(command == TOCLIENT_TOOLDEF) - { - infostream<<"Client: WARNING: Ignoring TOCLIENT_TOOLDEF"<<std::endl; - } - else if(command == TOCLIENT_NODEDEF) - { - infostream<<"Client: Received node definitions: packet size: " - <<datasize<<std::endl; - - // Mesh update thread must be stopped while - // updating content definitions - assert(!m_mesh_update_thread.IsRunning()); - - // Decompress node definitions - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary); - std::ostringstream tmp_os; - decompressZlib(tmp_is, tmp_os); - - // Deserialize node definitions - std::istringstream tmp_is2(tmp_os.str()); - m_nodedef->deSerialize(tmp_is2); - m_nodedef_received = true; - } - else if(command == TOCLIENT_CRAFTITEMDEF) - { - infostream<<"Client: WARNING: Ignoring TOCLIENT_CRAFTITEMDEF"<<std::endl; - } - else if(command == TOCLIENT_ITEMDEF) - { - infostream<<"Client: Received item definitions: packet size: " - <<datasize<<std::endl; - - // Mesh update thread must be stopped while - // updating content definitions - assert(!m_mesh_update_thread.IsRunning()); - - // Decompress item definitions - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary); - std::ostringstream tmp_os; - decompressZlib(tmp_is, tmp_os); - - // Deserialize node definitions - std::istringstream tmp_is2(tmp_os.str()); - m_itemdef->deSerialize(tmp_is2); - m_itemdef_received = true; - } - else if(command == TOCLIENT_PLAY_SOUND) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - s32 server_id = readS32(is); - std::string name = deSerializeString(is); - float gain = readF1000(is); - int type = readU8(is); // 0=local, 1=positional, 2=object - v3f pos = readV3F1000(is); - u16 object_id = readU16(is); - bool loop = readU8(is); - // Start playing - int client_id = -1; - switch(type){ - case 0: // local - client_id = m_sound->playSound(name, loop, gain); + switch (m_chosen_auth_mech) { + case AUTH_MECHANISM_FIRST_SRP: break; - case 1: // positional - client_id = m_sound->playSoundAt(name, loop, gain, pos); + case AUTH_MECHANISM_SRP: + case AUTH_MECHANISM_LEGACY_PASSWORD: + srp_user_delete((SRPUser *) m_auth_data); + m_auth_data = NULL; break; - case 2: { // object - ClientActiveObject *cao = m_env.getActiveObject(object_id); - if(cao) - pos = cao->getPosition(); - client_id = m_sound->playSoundAt(name, loop, gain, pos); - // TODO: Set up sound to move with object - break; } - default: + case AUTH_MECHANISM_NONE: break; - } - if(client_id != -1){ - m_sounds_server_to_client[server_id] = client_id; - m_sounds_client_to_server[client_id] = server_id; - if(object_id != 0) - m_sounds_to_objects[client_id] = object_id; - } - } - else if(command == TOCLIENT_STOP_SOUND) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - s32 server_id = readS32(is); - std::map<s32, int>::iterator i = - m_sounds_server_to_client.find(server_id); - if(i != m_sounds_server_to_client.end()){ - int client_id = i->second; - m_sound->stopSound(client_id); - } } - else if(command == TOCLIENT_PRIVILEGES) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - m_privileges.clear(); - infostream<<"Client: Privileges updated: "; - u16 num_privileges = readU16(is); - for(unsigned int i=0; i<num_privileges; i++){ - std::string priv = deSerializeString(is); - m_privileges.insert(priv); - infostream<<priv<<" "; - } - infostream<<std::endl; - } - else if(command == TOCLIENT_INVENTORY_FORMSPEC) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - // Store formspec in LocalPlayer - player->inventory_formspec = deSerializeLongString(is); - } - else if(command == TOCLIENT_DETACHED_INVENTORY) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - std::string name = deSerializeString(is); + m_chosen_auth_mech = AUTH_MECHANISM_NONE; +} - infostream<<"Client: Detached inventory update: \""<<name<<"\""<<std::endl; - Inventory *inv = NULL; - if(m_detached_inventories.count(name) > 0) - inv = m_detached_inventories[name]; - else{ - inv = new Inventory(m_itemdef); - m_detached_inventories[name] = inv; - } - inv->deSerialize(is); - } - else if(command == TOCLIENT_SHOW_FORMSPEC) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); +AuthMechanism Client::choseAuthMech(const u32 mechs) +{ + if (mechs & AUTH_MECHANISM_SRP) + return AUTH_MECHANISM_SRP; - std::string formspec = deSerializeLongString(is); - std::string formname = deSerializeString(is); + if (mechs & AUTH_MECHANISM_FIRST_SRP) + return AUTH_MECHANISM_FIRST_SRP; - ClientEvent event; - event.type = CE_SHOW_FORMSPEC; - // pointer is required as event is a struct only! - // adding a std:string to a struct isn't possible - event.show_formspec.formspec = new std::string(formspec); - event.show_formspec.formname = new std::string(formname); - m_client_event_queue.push_back(event); - } - else if(command == TOCLIENT_SPAWN_PARTICLE) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - v3f pos = readV3F1000(is); - v3f vel = readV3F1000(is); - v3f acc = readV3F1000(is); - float expirationtime = readF1000(is); - float size = readF1000(is); - bool collisiondetection = readU8(is); - std::string texture = deSerializeLongString(is); - bool vertical = false; - try { - vertical = readU8(is); - } catch (...) {} - - ClientEvent event; - event.type = CE_SPAWN_PARTICLE; - event.spawn_particle.pos = new v3f (pos); - event.spawn_particle.vel = new v3f (vel); - event.spawn_particle.acc = new v3f (acc); - event.spawn_particle.expirationtime = expirationtime; - event.spawn_particle.size = size; - event.spawn_particle.collisiondetection = collisiondetection; - event.spawn_particle.vertical = vertical; - event.spawn_particle.texture = new std::string(texture); - - m_client_event_queue.push_back(event); - } - else if(command == TOCLIENT_ADD_PARTICLESPAWNER) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - u16 amount = readU16(is); - float spawntime = readF1000(is); - v3f minpos = readV3F1000(is); - v3f maxpos = readV3F1000(is); - v3f minvel = readV3F1000(is); - v3f maxvel = readV3F1000(is); - v3f minacc = readV3F1000(is); - v3f maxacc = readV3F1000(is); - float minexptime = readF1000(is); - float maxexptime = readF1000(is); - float minsize = readF1000(is); - float maxsize = readF1000(is); - bool collisiondetection = readU8(is); - std::string texture = deSerializeLongString(is); - u32 id = readU32(is); - bool vertical = false; - try { - vertical = readU8(is); - } catch (...) {} - - ClientEvent event; - event.type = CE_ADD_PARTICLESPAWNER; - event.add_particlespawner.amount = amount; - event.add_particlespawner.spawntime = spawntime; - event.add_particlespawner.minpos = new v3f (minpos); - event.add_particlespawner.maxpos = new v3f (maxpos); - event.add_particlespawner.minvel = new v3f (minvel); - event.add_particlespawner.maxvel = new v3f (maxvel); - event.add_particlespawner.minacc = new v3f (minacc); - event.add_particlespawner.maxacc = new v3f (maxacc); - event.add_particlespawner.minexptime = minexptime; - event.add_particlespawner.maxexptime = maxexptime; - event.add_particlespawner.minsize = minsize; - event.add_particlespawner.maxsize = maxsize; - event.add_particlespawner.collisiondetection = collisiondetection; - event.add_particlespawner.vertical = vertical; - event.add_particlespawner.texture = new std::string(texture); - event.add_particlespawner.id = id; - - m_client_event_queue.push_back(event); - } - else if(command == TOCLIENT_DELETE_PARTICLESPAWNER) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); + if (mechs & AUTH_MECHANISM_LEGACY_PASSWORD) + return AUTH_MECHANISM_LEGACY_PASSWORD; - u32 id = readU16(is); + return AUTH_MECHANISM_NONE; +} - ClientEvent event; - event.type = CE_DELETE_PARTICLESPAWNER; - event.delete_particlespawner.id = id; +void Client::sendLegacyInit(const char* playerName, const char* playerPassword) +{ + NetworkPacket pkt(TOSERVER_INIT_LEGACY, + 1 + PLAYERNAME_SIZE + PASSWORD_SIZE + 2 + 2); - m_client_event_queue.push_back(event); - } - else if(command == TOCLIENT_HUDADD) - { - std::string datastring((char *)&data[2], datasize - 2); - std::istringstream is(datastring, std::ios_base::binary); - - u32 id = readU32(is); - u8 type = readU8(is); - v2f pos = readV2F1000(is); - std::string name = deSerializeString(is); - v2f scale = readV2F1000(is); - std::string text = deSerializeString(is); - u32 number = readU32(is); - u32 item = readU32(is); - u32 dir = readU32(is); - v2f align = readV2F1000(is); - v2f offset = readV2F1000(is); - v3f world_pos; - v2s32 size; - try{ - world_pos = readV3F1000(is); - }catch(SerializationError &e) {}; - try{ - size = readV2S32(is); - } catch(SerializationError &e) {}; - - ClientEvent event; - event.type = CE_HUDADD; - event.hudadd.id = id; - event.hudadd.type = type; - event.hudadd.pos = new v2f(pos); - event.hudadd.name = new std::string(name); - event.hudadd.scale = new v2f(scale); - event.hudadd.text = new std::string(text); - event.hudadd.number = number; - event.hudadd.item = item; - event.hudadd.dir = dir; - event.hudadd.align = new v2f(align); - event.hudadd.offset = new v2f(offset); - event.hudadd.world_pos = new v3f(world_pos); - event.hudadd.size = new v2s32(size); - m_client_event_queue.push_back(event); - } - else if(command == TOCLIENT_HUDRM) - { - std::string datastring((char *)&data[2], datasize - 2); - std::istringstream is(datastring, std::ios_base::binary); + pkt << (u8) SER_FMT_VER_HIGHEST_READ; + pkt.putRawString(playerName,PLAYERNAME_SIZE); + pkt.putRawString(playerPassword, PASSWORD_SIZE); + pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX; - u32 id = readU32(is); + Send(&pkt); +} - ClientEvent event; - event.type = CE_HUDRM; - event.hudrm.id = id; - m_client_event_queue.push_back(event); - } - else if(command == TOCLIENT_HUDCHANGE) - { - std::string sdata; - v2f v2fdata; - v3f v3fdata; - u32 intdata = 0; - v2s32 v2s32data; - - std::string datastring((char *)&data[2], datasize - 2); - std::istringstream is(datastring, std::ios_base::binary); - - u32 id = readU32(is); - u8 stat = (HudElementStat)readU8(is); - - if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE || - stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET) - v2fdata = readV2F1000(is); - else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT) - sdata = deSerializeString(is); - else if (stat == HUD_STAT_WORLD_POS) - v3fdata = readV3F1000(is); - else if (stat == HUD_STAT_SIZE ) - v2s32data = readV2S32(is); - else - intdata = readU32(is); - - ClientEvent event; - event.type = CE_HUDCHANGE; - event.hudchange.id = id; - event.hudchange.stat = (HudElementStat)stat; - event.hudchange.v2fdata = new v2f(v2fdata); - event.hudchange.v3fdata = new v3f(v3fdata); - event.hudchange.sdata = new std::string(sdata); - event.hudchange.data = intdata; - event.hudchange.v2s32data = new v2s32(v2s32data); - m_client_event_queue.push_back(event); - } - else if(command == TOCLIENT_HUD_SET_FLAGS) - { - std::string datastring((char *)&data[2], datasize - 2); - std::istringstream is(datastring, std::ios_base::binary); +void Client::sendInit(const std::string &playerName) +{ + NetworkPacket pkt(TOSERVER_INIT, 1 + 2 + 2 + (1 + playerName.size())); - u32 flags = readU32(is); - u32 mask = readU32(is); + // we don't support network compression yet + u16 supp_comp_modes = NETPROTO_COMPRESSION_NONE; + pkt << (u8) SER_FMT_VER_HIGHEST_READ << (u16) supp_comp_modes; + pkt << (u16) CLIENT_PROTOCOL_VERSION_MIN << (u16) CLIENT_PROTOCOL_VERSION_MAX; + pkt << playerName; - player->hud_flags &= ~mask; - player->hud_flags |= flags; - } - else if(command == TOCLIENT_HUD_SET_PARAM) - { - std::string datastring((char *)&data[2], datasize - 2); - std::istringstream is(datastring, std::ios_base::binary); + Send(&pkt); +} - u16 param = readU16(is); - std::string value = deSerializeString(is); +void Client::startAuth(AuthMechanism chosen_auth_mechanism) +{ + m_chosen_auth_mech = chosen_auth_mechanism; - if(param == HUD_PARAM_HOTBAR_ITEMCOUNT && value.size() == 4) { - s32 hotbar_itemcount = readS32((u8*) value.c_str()); - if(hotbar_itemcount > 0 && hotbar_itemcount <= HUD_HOTBAR_ITEMCOUNT_MAX) - player->hud_hotbar_itemcount = hotbar_itemcount; - } - else if (param == HUD_PARAM_HOTBAR_IMAGE) { - ((LocalPlayer *) player)->hotbar_image = value; - } - else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) { - ((LocalPlayer *) player)->hotbar_selected_image = value; + switch (chosen_auth_mechanism) { + case AUTH_MECHANISM_FIRST_SRP: { + // send srp verifier to server + NetworkPacket resp_pkt(TOSERVER_FIRST_SRP, 0); + char *salt, *bytes_v; + std::size_t len_salt, len_v; + salt = NULL; + getSRPVerifier(getPlayerName(), m_password, + &salt, &len_salt, &bytes_v, &len_v); + resp_pkt + << std::string((char*)salt, len_salt) + << std::string((char*)bytes_v, len_v) + << (u8)((m_password == "") ? 1 : 0); + free(salt); + free(bytes_v); + Send(&resp_pkt); + break; } - } - else if(command == TOCLIENT_SET_SKY) - { - std::string datastring((char *)&data[2], datasize - 2); - std::istringstream is(datastring, std::ios_base::binary); - - video::SColor *bgcolor = new video::SColor(readARGB8(is)); - std::string *type = new std::string(deSerializeString(is)); - u16 count = readU16(is); - std::vector<std::string> *params = new std::vector<std::string>; + case AUTH_MECHANISM_SRP: + case AUTH_MECHANISM_LEGACY_PASSWORD: { + u8 based_on = 1; - for(size_t i=0; i<count; i++) - params->push_back(deSerializeString(is)); + if (chosen_auth_mechanism == AUTH_MECHANISM_LEGACY_PASSWORD) { + m_password = translatePassword(getPlayerName(), m_password); + based_on = 0; + } - ClientEvent event; - event.type = CE_SET_SKY; - event.set_sky.bgcolor = bgcolor; - event.set_sky.type = type; - event.set_sky.params = params; - m_client_event_queue.push_back(event); + std::string playername_u = lowercase(getPlayerName()); + m_auth_data = srp_user_new(SRP_SHA256, SRP_NG_2048, + getPlayerName().c_str(), playername_u.c_str(), + (const unsigned char *) m_password.c_str(), + m_password.length(), NULL, NULL); + char *bytes_A = 0; + size_t len_A = 0; + srp_user_start_authentication((struct SRPUser *) m_auth_data, + NULL, NULL, 0, (unsigned char **) &bytes_A, &len_A); + + NetworkPacket resp_pkt(TOSERVER_SRP_BYTES_A, 0); + resp_pkt << std::string(bytes_A, len_A) << based_on; + Send(&resp_pkt); + break; + } + case AUTH_MECHANISM_NONE: + break; // not handled in this method } - else if(command == TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO) - { - std::string datastring((char *)&data[2], datasize - 2); - std::istringstream is(datastring, std::ios_base::binary); - - bool do_override = readU8(is); - float day_night_ratio_f = (float)readU16(is) / 65536; +} - ClientEvent event; - event.type = CE_OVERRIDE_DAY_NIGHT_RATIO; - event.override_day_night_ratio.do_override = do_override; - event.override_day_night_ratio.ratio_f = day_night_ratio_f; - m_client_event_queue.push_back(event); - } - else if(command == TOCLIENT_LOCAL_PLAYER_ANIMATIONS) - { - std::string datastring((char *)&data[2], datasize - 2); - std::istringstream is(datastring, std::ios_base::binary); +void Client::sendDeletedBlocks(std::vector<v3s16> &blocks) +{ + NetworkPacket pkt(TOSERVER_DELETEDBLOCKS, 1 + sizeof(v3s16) * blocks.size()); - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player != NULL); + pkt << (u8) blocks.size(); - player->local_animations[0] = readV2S32(is); - player->local_animations[1] = readV2S32(is); - player->local_animations[2] = readV2S32(is); - player->local_animations[3] = readV2S32(is); - player->local_animation_speed = readF1000(is); + u32 k = 0; + for(std::vector<v3s16>::iterator + j = blocks.begin(); + j != blocks.end(); ++j) { + pkt << *j; + k++; } - else if(command == TOCLIENT_EYE_OFFSET) - { - std::string datastring((char *)&data[2], datasize - 2); - std::istringstream is(datastring, std::ios_base::binary); - - LocalPlayer *player = m_env.getLocalPlayer(); - assert(player != NULL); - player->eye_offset_first = readV3F1000(is); - player->eye_offset_third = readV3F1000(is); - } - else - { - infostream<<"Client: Ignoring unknown command " - <<command<<std::endl; - } + Send(&pkt); } -void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable) +void Client::sendGotBlocks(v3s16 block) { - //JMutexAutoLock lock(m_con_mutex); //bulk comment-out - m_con.Send(PEER_ID_SERVER, channelnum, data, reliable); + NetworkPacket pkt(TOSERVER_GOTBLOCKS, 1 + 6); + pkt << (u8) 1 << block; + Send(&pkt); } -void Client::interact(u8 action, const PointedThing& pointed) +void Client::sendRemovedSounds(std::vector<s32> &soundList) { - if(m_state != LC_Ready){ - infostream<<"Client::interact() " - "cancelled (not connected)" - <<std::endl; - return; - } + size_t server_ids = soundList.size(); + assert(server_ids <= 0xFFFF); - std::ostringstream os(std::ios_base::binary); + NetworkPacket pkt(TOSERVER_REMOVED_SOUNDS, 2 + server_ids * 4); - /* - [0] u16 command - [2] u8 action - [3] u16 item - [5] u32 length of the next item - [9] serialized PointedThing - 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 - */ - writeU16(os, TOSERVER_INTERACT); - writeU8(os, action); - writeU16(os, getPlayerItem()); - std::ostringstream tmp_os(std::ios::binary); - pointed.serialize(tmp_os); - os<<serializeLongString(tmp_os.str()); + pkt << (u16) (server_ids & 0xFFFF); - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); + for(std::vector<s32>::iterator i = soundList.begin(); + i != soundList.end(); i++) + pkt << *i; - // Send as reliable - Send(0, data, true); + Send(&pkt); } void Client::sendNodemetaFields(v3s16 p, const std::string &formname, - const std::map<std::string, std::string> &fields) + const StringMap &fields) { - std::ostringstream os(std::ios_base::binary); - - writeU16(os, TOSERVER_NODEMETA_FIELDS); - writeV3S16(os, p); - os<<serializeString(formname); size_t fields_size = fields.size(); - assert(fields_size <= 0xFFFF); - writeU16(os, (u16) (fields_size & 0xFFFF)); - for(std::map<std::string, std::string>::const_iterator - i = fields.begin(); i != fields.end(); i++){ - const std::string &name = i->first; - const std::string &value = i->second; - os<<serializeString(name); - os<<serializeLongString(value); + + FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of nodemeta fields"); + + NetworkPacket pkt(TOSERVER_NODEMETA_FIELDS, 0); + + pkt << p << formname << (u16) (fields_size & 0xFFFF); + + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) { + const std::string &name = it->first; + const std::string &value = it->second; + pkt << name; + pkt.putLongString(value); } - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(0, data, true); + Send(&pkt); } void Client::sendInventoryFields(const std::string &formname, - const std::map<std::string, std::string> &fields) + const StringMap &fields) { - std::ostringstream os(std::ios_base::binary); - - writeU16(os, TOSERVER_INVENTORY_FIELDS); - os<<serializeString(formname); size_t fields_size = fields.size(); - assert(fields_size <= 0xFFFF); - writeU16(os, (u16) (fields_size & 0xFFFF)); - for(std::map<std::string, std::string>::const_iterator - i = fields.begin(); i != fields.end(); i++){ - const std::string &name = i->first; - const std::string &value = i->second; - os<<serializeString(name); - os<<serializeLongString(value); + FATAL_ERROR_IF(fields_size > 0xFFFF, "Unsupported number of inventory fields"); + + NetworkPacket pkt(TOSERVER_INVENTORY_FIELDS, 0); + pkt << formname << (u16) (fields_size & 0xFFFF); + + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) { + const std::string &name = it->first; + const std::string &value = it->second; + pkt << name; + pkt.putLongString(value); } - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(0, data, true); + Send(&pkt); } void Client::sendInventoryAction(InventoryAction *a) { std::ostringstream os(std::ios_base::binary); - u8 buf[12]; - - // Write command - writeU16(buf, TOSERVER_INVENTORY_ACTION); - os.write((char*)buf, 2); a->serialize(os); // Make data buffer std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(0, data, true); + + NetworkPacket pkt(TOSERVER_INVENTORY_ACTION, s.size()); + pkt.putRawString(s.c_str(),s.size()); + + Send(&pkt); } void Client::sendChatMessage(const std::wstring &message) { - std::ostringstream os(std::ios_base::binary); - u8 buf[12]; + NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); - // Write command - writeU16(buf, TOSERVER_CHAT_MESSAGE); - os.write((char*)buf, 2); + pkt << message; - // Write length - size_t messagesize = message.size(); - if (messagesize > 0xFFFF) { - messagesize = 0xFFFF; - } - writeU16(buf, (u16) messagesize); - os.write((char*)buf, 2); - - // Write string - for(unsigned int i=0; i<message.size(); i++) - { - u16 w = message[i]; - writeU16(buf, w); - os.write((char*)buf, 2); - } - - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(0, data, true); + Send(&pkt); } -void Client::sendChangePassword(const std::wstring &oldpassword, - const std::wstring &newpassword) +void Client::sendChangePassword(const std::string &oldpassword, + const std::string &newpassword) { Player *player = m_env.getLocalPlayer(); - if(player == NULL) + if (player == NULL) return; std::string playername = player->getName(); - std::string oldpwd = translatePassword(playername, oldpassword); - std::string newpwd = translatePassword(playername, newpassword); + if (m_proto_ver >= 25) { + // get into sudo mode and then send new password to server + m_password = oldpassword; + m_new_password = newpassword; + startAuth(choseAuthMech(m_sudo_auth_methods)); + } else { + std::string oldpwd = translatePassword(playername, oldpassword); + std::string newpwd = translatePassword(playername, newpassword); - std::ostringstream os(std::ios_base::binary); - u8 buf[2+PASSWORD_SIZE*2]; - /* - [0] u16 TOSERVER_PASSWORD - [2] u8[28] old password - [30] u8[28] new password - */ + NetworkPacket pkt(TOSERVER_PASSWORD_LEGACY, 2 * PASSWORD_SIZE); - writeU16(buf, TOSERVER_PASSWORD); - for(unsigned int i=0;i<PASSWORD_SIZE-1;i++) - { - buf[2+i] = i<oldpwd.length()?oldpwd[i]:0; - buf[30+i] = i<newpwd.length()?newpwd[i]:0; - } - buf[2+PASSWORD_SIZE-1] = 0; - buf[30+PASSWORD_SIZE-1] = 0; - os.write((char*)buf, 2+PASSWORD_SIZE*2); + for (u8 i = 0; i < PASSWORD_SIZE; i++) { + pkt << (u8) (i < oldpwd.length() ? oldpwd[i] : 0); + } - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(0, data, true); + for (u8 i = 0; i < PASSWORD_SIZE; i++) { + pkt << (u8) (i < newpwd.length() ? newpwd[i] : 0); + } + Send(&pkt); + } } void Client::sendDamage(u8 damage) { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOSERVER_DAMAGE); - writeU8(os, damage); - - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(0, data, true); + NetworkPacket pkt(TOSERVER_DAMAGE, sizeof(u8)); + pkt << damage; + Send(&pkt); } void Client::sendBreath(u16 breath) { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOSERVER_BREATH); - writeU16(os, breath); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(0, data, true); + NetworkPacket pkt(TOSERVER_BREATH, sizeof(u16)); + pkt << breath; + Send(&pkt); } void Client::sendRespawn() { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - - writeU16(os, TOSERVER_RESPAWN); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(0, data, true); + NetworkPacket pkt(TOSERVER_RESPAWN, 0); + Send(&pkt); } void Client::sendReady() { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOSERVER_CLIENT_READY); - writeU8(os,VERSION_MAJOR); - writeU8(os,VERSION_MINOR); - writeU8(os,VERSION_PATCH_ORIG); - writeU8(os,0); + NetworkPacket pkt(TOSERVER_CLIENT_READY, + 1 + 1 + 1 + 1 + 2 + sizeof(char) * strlen(g_version_hash)); - writeU16(os,strlen(minetest_version_hash)); - os.write(minetest_version_hash,strlen(minetest_version_hash)); + pkt << (u8) VERSION_MAJOR << (u8) VERSION_MINOR << (u8) VERSION_PATCH + << (u8) 0 << (u16) strlen(g_version_hash); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - Send(0, data, true); + pkt.putRawString(g_version_hash, (u16) strlen(g_version_hash)); + Send(&pkt); } void Client::sendPlayerPos() @@ -2292,7 +1277,7 @@ void Client::sendPlayerPos() // Set peer id if not set already if(myplayer->peer_id == PEER_ID_INEXISTENT) myplayer->peer_id = our_peer_id; - // Check that an existing peer_id is the same as the connection's + assert(myplayer->peer_id == our_peer_id); v3f pf = myplayer->getPosition(); @@ -2305,22 +1290,18 @@ void Client::sendPlayerPos() v3s32 speed(sf.X*100, sf.Y*100, sf.Z*100); /* Format: - [0] u16 command - [2] v3s32 position*100 - [2+12] v3s32 speed*100 - [2+12+12] s32 pitch*100 - [2+12+12+4] s32 yaw*100 - [2+12+12+4+4] u32 keyPressed + [0] v3s32 position*100 + [12] v3s32 speed*100 + [12+12] s32 pitch*100 + [12+12+4] s32 yaw*100 + [12+12+4+4] u32 keyPressed */ - SharedBuffer<u8> data(2+12+12+4+4+4); - writeU16(&data[0], TOSERVER_PLAYERPOS); - writeV3S32(&data[2], position); - writeV3S32(&data[2+12], speed); - writeS32(&data[2+12+12], pitch); - writeS32(&data[2+12+12+4], yaw); - writeU32(&data[2+12+12+4+4], keyPressed); - // Send as unreliable - Send(0, data, false); + + NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4); + + pkt << position << speed << pitch << yaw << keyPressed; + + Send(&pkt); } void Client::sendPlayerItem(u16 item) @@ -2334,33 +1315,28 @@ void Client::sendPlayerItem(u16 item) // Set peer id if not set already if(myplayer->peer_id == PEER_ID_INEXISTENT) myplayer->peer_id = our_peer_id; - // Check that an existing peer_id is the same as the connection's assert(myplayer->peer_id == our_peer_id); - SharedBuffer<u8> data(2+2); - writeU16(&data[0], TOSERVER_PLAYERITEM); - writeU16(&data[2], item); + NetworkPacket pkt(TOSERVER_PLAYERITEM, 2); - // Send as reliable - Send(0, data, true); + pkt << item; + + Send(&pkt); } void Client::removeNode(v3s16 p) { std::map<v3s16, MapBlock*> modified_blocks; - try - { + try { m_env.getMap().removeNodeAndUpdate(p, modified_blocks); } - catch(InvalidPositionException &e) - { + catch(InvalidPositionException &e) { } - for(std::map<v3s16, MapBlock * >::iterator + for(std::map<v3s16, MapBlock *>::iterator i = modified_blocks.begin(); - i != modified_blocks.end(); ++i) - { + i != modified_blocks.end(); ++i) { addUpdateMeshTaskWithEdge(i->first, false, true); } } @@ -2371,18 +1347,16 @@ void Client::addNode(v3s16 p, MapNode n, bool remove_metadata) std::map<v3s16, MapBlock*> modified_blocks; - try - { + try { //TimeTaker timer3("Client::addNode(): addNodeAndUpdate"); m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata); } - catch(InvalidPositionException &e) - {} + catch(InvalidPositionException &e) { + } - for(std::map<v3s16, MapBlock * >::iterator + for(std::map<v3s16, MapBlock *>::iterator i = modified_blocks.begin(); - i != modified_blocks.end(); ++i) - { + i != modified_blocks.end(); ++i) { addUpdateMeshTaskWithEdge(i->first, false, true); } } @@ -2455,7 +1429,8 @@ Inventory* Client::getInventory(const InventoryLocation &loc) } break; default: - assert(0); + FATAL_ERROR("Invalid inventory location type."); + break; } return NULL; } @@ -2576,7 +1551,8 @@ bool Client::getChatMessage(std::wstring &message) { if(m_chat_queue.size() == 0) return false; - message = m_chat_queue.pop_front(); + message = m_chat_queue.front(); + m_chat_queue.pop(); return true; } @@ -2592,14 +1568,14 @@ void Client::typeChatMessage(const std::wstring &message) // Show locally if (message[0] == L'/') { - m_chat_queue.push_back((std::wstring)L"issued command: " + message); + m_chat_queue.push((std::wstring)L"issued command: " + message); } else { LocalPlayer *player = m_env.getLocalPlayer(); assert(player != NULL); std::wstring name = narrow_to_wide(player->getName()); - m_chat_queue.push_back((std::wstring)L"<" + name + L"> " + message); + m_chat_queue.push((std::wstring)L"<" + name + L"> " + message); } } @@ -2613,7 +1589,7 @@ void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) Create a task to update the mesh of the block */ - MeshMakeData *data = new MeshMakeData(this); + MeshMakeData *data = new MeshMakeData(this, m_cache_enable_shaders); { //TimeTaker timer("data fill"); @@ -2626,7 +1602,7 @@ void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server, bool urgent) } // Add task to queue - m_mesh_update_thread.m_queue_in.addBlock(p, data, ack_to_server, urgent); + m_mesh_update_thread.enqueueUpdate(p, data, ack_to_server, urgent); } void Client::addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server, bool urgent) @@ -2662,7 +1638,7 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur try{ addUpdateMeshTask(blockpos, ack_to_server, urgent); } - catch(InvalidPositionException &e){} + catch(InvalidPositionException &e) {} // Leading edge if(nodepos.X == blockpos_relative.X){ @@ -2692,13 +1668,15 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur ClientEvent Client::getClientEvent() { - if(m_client_event_queue.size() == 0) - { - ClientEvent event; + ClientEvent event; + if(m_client_event_queue.size() == 0) { event.type = CE_NONE; - return event; } - return m_client_event_queue.pop_front(); + else { + event = m_client_event_queue.front(); + m_client_event_queue.pop(); + } + return event; } float Client::mediaReceiveProgress() @@ -2709,15 +1687,52 @@ float Client::mediaReceiveProgress() return 1.0; // downloader only exists when not yet done } -void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font) +typedef struct TextureUpdateArgs { + IrrlichtDevice *device; + gui::IGUIEnvironment *guienv; + u32 last_time_ms; + u16 last_percent; + const wchar_t* text_base; +} TextureUpdateArgs; + +void texture_update_progress(void *args, u32 progress, u32 max_progress) +{ + TextureUpdateArgs* targs = (TextureUpdateArgs*) args; + u16 cur_percent = ceil(progress / (double) max_progress * 100.); + + // update the loading menu -- if neccessary + bool do_draw = false; + u32 time_ms = targs->last_time_ms; + if (cur_percent != targs->last_percent) { + targs->last_percent = cur_percent; + time_ms = getTimeMs(); + // only draw when the user will notice something: + do_draw = (time_ms - targs->last_time_ms > 100); + } + + if (do_draw) { + targs->last_time_ms = time_ms; + std::basic_stringstream<wchar_t> strm; + strm << targs->text_base << " " << targs->last_percent << "%..."; + draw_load_screen(strm.str(), targs->device, targs->guienv, 0, + 72 + (u16) ((18. / 100.) * (double) targs->last_percent)); + } +} + +void Client::afterContentReceived(IrrlichtDevice *device) { infostream<<"Client::afterContentReceived() started"<<std::endl; - assert(m_itemdef_received); - assert(m_nodedef_received); - assert(mediaReceived()); + assert(m_itemdef_received); // pre-condition + assert(m_nodedef_received); // pre-condition + assert(mediaReceived()); // pre-condition const wchar_t* text = wgettext("Loading textures..."); + // Clear cached pre-scaled 2D GUI images, as this cache + // might have images with the same name but different + // content from previous sessions. + guiScalingCacheClear(device->getVideoDriver()); + // Rebuild inherited images and recreate textures infostream<<"- Rebuilding images and textures"<<std::endl; draw_load_screen(text,device, guienv, 0, 70); @@ -2727,22 +1742,32 @@ void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font) // Rebuild shaders infostream<<"- Rebuilding shaders"<<std::endl; text = wgettext("Rebuilding shaders..."); - draw_load_screen(text, device, guienv, 0, 75); + draw_load_screen(text, device, guienv, 0, 71); m_shsrc->rebuildShaders(); delete[] text; // Update node aliases infostream<<"- Updating node aliases"<<std::endl; text = wgettext("Initializing nodes..."); - draw_load_screen(text, device, guienv, 0, 80); + draw_load_screen(text, device, guienv, 0, 72); m_nodedef->updateAliases(m_itemdef); + std::string texture_path = g_settings->get("texture_path"); + if (texture_path != "" && fs::IsDir(texture_path)) + m_nodedef->applyTextureOverrides(texture_path + DIR_DELIM + "override.txt"); m_nodedef->setNodeRegistrationStatus(true); - m_nodedef->runNodeResolverCallbacks(); + m_nodedef->runNodeResolveCallbacks(); delete[] text; // Update node textures and assign shaders to each tile infostream<<"- Updating node textures"<<std::endl; - m_nodedef->updateTextures(this); + TextureUpdateArgs tu_args; + tu_args.device = device; + tu_args.guienv = guienv; + tu_args.last_time_ms = getTimeMs(); + tu_args.last_percent = 0; + tu_args.text_base = wgettext("Initializing nodes"); + m_nodedef->updateTextures(this, texture_update_progress, &tu_args); + delete[] tu_args.text_base; // Preload item textures and meshes if configured to if(g_settings->getBool("preload_item_visuals")) @@ -2800,28 +1825,56 @@ void Client::makeScreenshot(IrrlichtDevice *device) { irr::video::IVideoDriver *driver = device->getVideoDriver(); irr::video::IImage* const raw_image = driver->createScreenShot(); - if (raw_image) { - irr::video::IImage* const image = driver->createImage(video::ECF_R8G8B8, - raw_image->getDimension()); + + if (!raw_image) + return; + + time_t t = time(NULL); + struct tm *tm = localtime(&t); + + char timetstamp_c[64]; + strftime(timetstamp_c, sizeof(timetstamp_c), "%Y%m%d_%H%M%S", tm); + + std::string filename_base = g_settings->get("screenshot_path") + + DIR_DELIM + + std::string("screenshot_") + + std::string(timetstamp_c); + std::string filename_ext = ".png"; + std::string filename; + + // Try to find a unique filename + unsigned serial = 0; + + while (serial < SCREENSHOT_MAX_SERIAL_TRIES) { + filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext; + std::ifstream tmp(filename.c_str()); + if (!tmp.good()) + break; // File did not apparently exist, we'll go with it + serial++; + } + + if (serial == SCREENSHOT_MAX_SERIAL_TRIES) { + infostream << "Could not find suitable filename for screenshot" << std::endl; + } else { + irr::video::IImage* const image = + driver->createImage(video::ECF_R8G8B8, raw_image->getDimension()); if (image) { raw_image->copyTo(image); - irr::c8 filename[256]; - snprintf(filename, sizeof(filename), "%s" DIR_DELIM "screenshot_%u.png", - g_settings->get("screenshot_path").c_str(), - device->getTimer()->getRealTime()); + std::ostringstream sstr; - if (driver->writeImageToFile(image, filename)) { + if (driver->writeImageToFile(image, filename.c_str())) { sstr << "Saved screenshot to '" << filename << "'"; } else { sstr << "Failed to save screenshot '" << filename << "'"; } - m_chat_queue.push_back(narrow_to_wide(sstr.str())); + m_chat_queue.push(narrow_to_wide(sstr.str())); infostream << sstr.str() << std::endl; image->drop(); } - raw_image->drop(); } + + raw_image->drop(); } // IGameDef interface @@ -2853,9 +1906,10 @@ scene::ISceneManager* Client::getSceneManager() } u16 Client::allocateUnknownNodeId(const std::string &name) { - errorstream<<"Client::allocateUnknownNodeId(): " - <<"Client cannot allocate node IDs"<<std::endl; - assert(0); + errorstream << "Client::allocateUnknownNodeId(): " + << "Client cannot allocate node IDs" << std::endl; + FATAL_ERROR("Client allocated unknown node"); + return CONTENT_IGNORE; } ISoundManager* Client::getSoundManager() @@ -2874,14 +1928,13 @@ ParticleManager* Client::getParticleManager() scene::IAnimatedMesh* Client::getMesh(const std::string &filename) { - std::map<std::string, std::string>::const_iterator i = - m_mesh_data.find(filename); - if(i == m_mesh_data.end()){ - errorstream<<"Client::getMesh(): Mesh not found: \""<<filename<<"\"" - <<std::endl; + StringMap::const_iterator it = m_mesh_data.find(filename); + if (it == m_mesh_data.end()) { + errorstream << "Client::getMesh(): Mesh not found: \"" << filename + << "\"" << std::endl; return NULL; } - const std::string &data = i->second; + const std::string &data = it->second; scene::ISceneManager *smgr = m_device->getSceneManager(); // Create the mesh, remove it from cache and return it @@ -2890,7 +1943,7 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename) io::IFileSystem *irrfs = m_device->getFileSystem(); io::IReadFile *rfile = irrfs->createMemoryReadFile( *data_rw, data_rw.getSize(), filename.c_str()); - assert(rfile); + FATAL_ERROR_IF(!rfile, "Could not create/open RAM file"); scene::IAnimatedMesh *mesh = smgr->getMesh(rfile); rfile->drop(); @@ -2900,4 +1953,3 @@ scene::IAnimatedMesh* Client::getMesh(const std::string &filename) smgr->getMeshCache()->removeMesh(mesh); return mesh; } - diff --git a/src/client.h b/src/client.h index fd7e5f08d..547edfeab 100644 --- a/src/client.h +++ b/src/client.h @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef CLIENT_HEADER #define CLIENT_HEADER -#include "connection.h" +#include "network/connection.h" #include "environment.h" #include "irrlichttypes_extrabloated.h" #include "jthread/jmutex.h" @@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "localplayer.h" #include "hud.h" #include "particles.h" +#include "network/networkpacket.h" struct MeshMakeData; class MapBlockMesh; @@ -47,7 +48,8 @@ struct MapDrawControl; class MtEventManager; struct PointedThing; class Database; -class Server; +class Mapper; +struct MinimapMapblock; struct QueuedMeshUpdate { @@ -111,23 +113,27 @@ struct MeshUpdateResult } }; -class MeshUpdateThread : public JThread +class MeshUpdateThread : public UpdateThread { +private: + MeshUpdateQueue m_queue_in; + +protected: + const char *getName() + { return "MeshUpdateThread"; } + virtual void doUpdate(); + public: - MeshUpdateThread(IGameDef *gamedef): - m_gamedef(gamedef) + MeshUpdateThread() { } - void * Thread(); - - MeshUpdateQueue m_queue_in; + void enqueueUpdate(v3s16 p, MeshMakeData *data, + bool ack_block_to_server, bool urgent); MutexedQueue<MeshUpdateResult> m_queue_out; - IGameDef *m_gamedef; - v3s16 m_camera_offset; }; @@ -341,22 +347,77 @@ public: */ void step(float dtime); - void ProcessData(u8 *data, u32 datasize, u16 sender_peer_id); + /* + * Command Handlers + */ + + void handleCommand(NetworkPacket* pkt); + + void handleCommand_Null(NetworkPacket* pkt) {}; + void handleCommand_Deprecated(NetworkPacket* pkt); + void handleCommand_Hello(NetworkPacket* pkt); + void handleCommand_AuthAccept(NetworkPacket* pkt); + void handleCommand_AcceptSudoMode(NetworkPacket* pkt); + void handleCommand_DenySudoMode(NetworkPacket* pkt); + void handleCommand_InitLegacy(NetworkPacket* pkt); + void handleCommand_AccessDenied(NetworkPacket* pkt); + void handleCommand_RemoveNode(NetworkPacket* pkt); + void handleCommand_AddNode(NetworkPacket* pkt); + void handleCommand_BlockData(NetworkPacket* pkt); + void handleCommand_Inventory(NetworkPacket* pkt); + void handleCommand_TimeOfDay(NetworkPacket* pkt); + void handleCommand_ChatMessage(NetworkPacket* pkt); + void handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt); + void handleCommand_ActiveObjectMessages(NetworkPacket* pkt); + void handleCommand_Movement(NetworkPacket* pkt); + void handleCommand_HP(NetworkPacket* pkt); + void handleCommand_Breath(NetworkPacket* pkt); + void handleCommand_MovePlayer(NetworkPacket* pkt); + void handleCommand_PlayerItem(NetworkPacket* pkt); + void handleCommand_DeathScreen(NetworkPacket* pkt); + void handleCommand_AnnounceMedia(NetworkPacket* pkt); + void handleCommand_Media(NetworkPacket* pkt); + void handleCommand_ToolDef(NetworkPacket* pkt); + void handleCommand_NodeDef(NetworkPacket* pkt); + void handleCommand_CraftItemDef(NetworkPacket* pkt); + void handleCommand_ItemDef(NetworkPacket* pkt); + void handleCommand_PlaySound(NetworkPacket* pkt); + void handleCommand_StopSound(NetworkPacket* pkt); + void handleCommand_Privileges(NetworkPacket* pkt); + void handleCommand_InventoryFormSpec(NetworkPacket* pkt); + void handleCommand_DetachedInventory(NetworkPacket* pkt); + void handleCommand_ShowFormSpec(NetworkPacket* pkt); + void handleCommand_SpawnParticle(NetworkPacket* pkt); + void handleCommand_AddParticleSpawner(NetworkPacket* pkt); + void handleCommand_DeleteParticleSpawner(NetworkPacket* pkt); + void handleCommand_HudAdd(NetworkPacket* pkt); + void handleCommand_HudRemove(NetworkPacket* pkt); + void handleCommand_HudChange(NetworkPacket* pkt); + void handleCommand_HudSetFlags(NetworkPacket* pkt); + void handleCommand_HudSetParam(NetworkPacket* pkt); + void handleCommand_HudSetSky(NetworkPacket* pkt); + void handleCommand_OverrideDayNightRatio(NetworkPacket* pkt); + void handleCommand_LocalPlayerAnimations(NetworkPacket* pkt); + void handleCommand_EyeOffset(NetworkPacket* pkt); + void handleCommand_SrpBytesSandB(NetworkPacket* pkt); + + void ProcessData(NetworkPacket *pkt); + // Returns true if something was received bool AsyncProcessPacket(); bool AsyncProcessData(); - void Send(u16 channelnum, SharedBuffer<u8> data, bool reliable); + void Send(NetworkPacket* pkt); void interact(u8 action, const PointedThing& pointed); void sendNodemetaFields(v3s16 p, const std::string &formname, - const std::map<std::string, std::string> &fields); + const StringMap &fields); void sendInventoryFields(const std::string &formname, - const std::map<std::string, std::string> &fields); + const StringMap &fields); void sendInventoryAction(InventoryAction *a); void sendChatMessage(const std::wstring &message); - void sendChangePassword(const std::wstring &oldpassword, - const std::wstring &newpassword); + void sendChangePassword(const std::string &oldpassword, + const std::string &newpassword); void sendDamage(u8 damage); void sendBreath(u16 breath); void sendRespawn(); @@ -428,7 +489,9 @@ public: bool accessDenied() { return m_access_denied; } - std::wstring accessDeniedReason() + bool reconnectRequested() { return m_access_denied_reconnect; } + + std::string accessDeniedReason() { return m_access_denied_reason; } bool itemdefReceived() @@ -438,14 +501,20 @@ public: bool mediaReceived() { return m_media_downloader == NULL; } + u8 getProtoVersion() + { return m_proto_ver; } + float mediaReceiveProgress(); - void afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font); + void afterContentReceived(IrrlichtDevice *device); float getRTT(void); float getCurRate(void); float getAvgRate(void); + Mapper* getMapper () + { return m_mapper; } + // IGameDef interface virtual IItemDefManager* getItemDefManager(); virtual INodeDefManager* getNodeDefManager(); @@ -465,7 +534,7 @@ public: // Insert a media file appropriately into the appropriate manager bool loadMedia(const std::string &data, const std::string &filename); // Send a request for conventional media transfer - void request_media(const std::list<std::string> &file_requests); + void request_media(const std::vector<std::string> &file_requests); // Send a notification that no conventional media transfer is needed void received_media(); @@ -490,6 +559,21 @@ private: // Send the item number 'item' as player item to the server void sendPlayerItem(u16 item); + void deleteAuthData(); + // helper method shared with clientpackethandler + static AuthMechanism choseAuthMech(const u32 mechs); + + void sendLegacyInit(const char* playerName, const char* playerPassword); + void sendInit(const std::string &playerName); + void startAuth(AuthMechanism chosen_auth_mechanism); + void sendDeletedBlocks(std::vector<v3s16> &blocks); + void sendGotBlocks(v3s16 block); + void sendRemovedSounds(std::vector<s32> &soundList); + + // Helper function + inline std::string getPlayerName() + { return m_env.getLocalPlayer()->getName(); } + float m_packetcounter_timer; float m_connection_reinit_timer; float m_avg_rtt_timer; @@ -510,13 +594,21 @@ private: ParticleManager m_particle_manager; con::Connection m_con; IrrlichtDevice *m_device; + Mapper *m_mapper; // Server serialization version u8 m_server_ser_ver; + + // Used version of the protocol with server + // Values smaller than 25 only mean they are smaller than 25, + // and aren't accurate. We simply just don't know, because + // the server didn't send the version back then. + // If 0, server init hasn't been received yet. + u8 m_proto_ver; + u16 m_playeritem; bool m_inventory_updated; Inventory *m_inventory_from_server; float m_inventory_from_server_age; - std::set<v3s16> m_active_blocks; PacketCounter m_packetcounter; bool m_show_highlighted; // Block mesh animation parameters @@ -527,13 +619,28 @@ private: // 0 <= m_daynight_i < DAYNIGHT_CACHE_COUNT //s32 m_daynight_i; //u32 m_daynight_ratio; - Queue<std::wstring> m_chat_queue; + std::queue<std::wstring> m_chat_queue; + + // The authentication methods we can use to enter sudo mode (=change password) + u32 m_sudo_auth_methods; + // The seed returned by the server in TOCLIENT_INIT is stored here u64 m_map_seed; + + // Auth data + std::string m_playername; std::string m_password; + // If set, this will be sent (and cleared) upon a TOCLIENT_ACCEPT_SUDO_MODE + std::string m_new_password; + // Usable by auth mechanisms. + AuthMechanism m_chosen_auth_mech; + void * m_auth_data; + + bool m_access_denied; - std::wstring m_access_denied_reason; - Queue<ClientEvent> m_client_event_queue; + bool m_access_denied_reconnect; + std::string m_access_denied_reason; + std::queue<ClientEvent> m_client_event_queue; bool m_itemdef_received; bool m_nodedef_received; ClientMediaDownloader *m_media_downloader; @@ -563,18 +670,19 @@ private: std::map<std::string, Inventory*> m_detached_inventories; // Storage for mesh data for creating multiple instances of the same mesh - std::map<std::string, std::string> m_mesh_data; + StringMap m_mesh_data; // own state LocalClientState m_state; // Used for saving server map to disk client-side - Database *localdb; - Server *localserver; + Database *m_localdb; + IntervalLimiter m_localdb_save_interval; + u16 m_cache_save_interval; - // TODO: Add callback to update this when g_settings changes + // TODO: Add callback to update these when g_settings changes bool m_cache_smooth_lighting; + bool m_cache_enable_shaders; }; #endif // !CLIENT_HEADER - diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt new file mode 100644 index 000000000..a1ec37fe3 --- /dev/null +++ b/src/client/CMakeLists.txt @@ -0,0 +1,6 @@ +set(client_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/clientlauncher.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tile.cpp + PARENT_SCOPE +) + diff --git a/src/client/clientlauncher.cpp b/src/client/clientlauncher.cpp new file mode 100644 index 000000000..bad5c384c --- /dev/null +++ b/src/client/clientlauncher.cpp @@ -0,0 +1,726 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "mainmenumanager.h" +#include "debug.h" +#include "clouds.h" +#include "server.h" +#include "filesys.h" +#include "guiMainMenu.h" +#include "game.h" +#include "chat.h" +#include "gettext.h" +#include "profiler.h" +#include "log.h" +#include "serverlist.h" +#include "guiEngine.h" +#include "player.h" +#include "fontengine.h" +#include "clientlauncher.h" + +/* mainmenumanager.h + */ +gui::IGUIEnvironment *guienv = NULL; +gui::IGUIStaticText *guiroot = NULL; +MainMenuManager g_menumgr; + +bool noMenuActive() +{ + return g_menumgr.menuCount() == 0; +} + +// Passed to menus to allow disconnecting and exiting +MainGameCallback *g_gamecallback = NULL; + + +// Instance of the time getter +static TimeGetter *g_timegetter = NULL; + +u32 getTimeMs() +{ + if (g_timegetter == NULL) + return 0; + return g_timegetter->getTime(PRECISION_MILLI); +} + +u32 getTime(TimePrecision prec) { + if (g_timegetter == NULL) + return 0; + return g_timegetter->getTime(prec); +} + +ClientLauncher::~ClientLauncher() +{ + if (receiver) + delete receiver; + + if (input) + delete input; + + if (g_fontengine) + delete g_fontengine; + + if (device) + device->drop(); +} + + +bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) +{ + init_args(game_params, cmd_args); + + // List video modes if requested + if (list_video_modes) + return print_video_modes(); + + if (!init_engine(game_params.log_level)) { + errorstream << "Could not initialize game engine." << std::endl; + return false; + } + + // Create time getter + g_timegetter = new IrrlichtTimeGetter(device); + + // Speed tests (done after irrlicht is loaded to get timer) + if (cmd_args.getFlag("speedtests")) { + dstream << "Running speed tests" << std::endl; + speed_tests(); + return true; + } + + video::IVideoDriver *video_driver = device->getVideoDriver(); + if (video_driver == NULL) { + errorstream << "Could not initialize video driver." << std::endl; + return false; + } + + porting::setXorgClassHint(video_driver->getExposedVideoData(), PROJECT_NAME_C); + + /* + This changes the minimum allowed number of vertices in a VBO. + Default is 500. + */ + //driver->setMinHardwareBufferVertexCount(50); + + // Create game callback for menus + g_gamecallback = new MainGameCallback(device); + + device->setResizable(true); + + if (random_input) + input = new RandomInputHandler(); + else + input = new RealInputHandler(device, receiver); + + smgr = device->getSceneManager(); + smgr->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true); + + guienv = device->getGUIEnvironment(); + skin = guienv->getSkin(); + skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255, 255, 255, 255)); + skin->setColor(gui::EGDC_3D_LIGHT, video::SColor(0, 0, 0, 0)); + skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255, 30, 30, 30)); + skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255, 0, 0, 0)); + skin->setColor(gui::EGDC_HIGH_LIGHT, video::SColor(255, 70, 120, 50)); + skin->setColor(gui::EGDC_HIGH_LIGHT_TEXT, video::SColor(255, 255, 255, 255)); + + g_fontengine = new FontEngine(g_settings, guienv); + FATAL_ERROR_IF(g_fontengine == NULL, "Font engine creation failed."); + +#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2 + // Irrlicht 1.8 input colours + skin->setColor(gui::EGDC_EDITABLE, video::SColor(255, 128, 128, 128)); + skin->setColor(gui::EGDC_FOCUSED_EDITABLE, video::SColor(255, 96, 134, 49)); +#endif + + // Create the menu clouds + if (!g_menucloudsmgr) + g_menucloudsmgr = smgr->createNewSceneManager(); + if (!g_menuclouds) + g_menuclouds = new Clouds(g_menucloudsmgr->getRootSceneNode(), + g_menucloudsmgr, -1, rand(), 100); + g_menuclouds->update(v2f(0, 0), video::SColor(255, 200, 200, 255)); + scene::ICameraSceneNode* camera; + camera = g_menucloudsmgr->addCameraSceneNode(0, + v3f(0, 0, 0), v3f(0, 60, 100)); + camera->setFarValue(10000); + + /* + GUI stuff + */ + + ChatBackend chat_backend; + + // If an error occurs, this is set to something by menu(). + // It is then displayed before the menu shows on the next call to menu() + std::string error_message; + bool reconnect_requested = false; + + bool first_loop = true; + + /* + Menu-game loop + */ + bool retval = true; + bool *kill = porting::signal_handler_killstatus(); + + while (device->run() && !*kill && !g_gamecallback->shutdown_requested) + { + // Set the window caption + const wchar_t *text = wgettext("Main Menu"); + device->setWindowCaption((utf8_to_wide(PROJECT_NAME_C) + L" [" + text + L"]").c_str()); + delete[] text; + + try { // This is used for catching disconnects + + guienv->clear(); + + /* + We need some kind of a root node to be able to add + custom gui elements directly on the screen. + Otherwise they won't be automatically drawn. + */ + guiroot = guienv->addStaticText(L"", core::rect<s32>(0, 0, 10000, 10000)); + + bool game_has_run = launch_game(error_message, reconnect_requested, + game_params, cmd_args); + + // If skip_main_menu, we only want to startup once + if (skip_main_menu && !first_loop) + break; + + first_loop = false; + + if (!game_has_run) { + if (skip_main_menu) + break; + else + continue; + } + + // Break out of menu-game loop to shut down cleanly + if (!device->run() || *kill) { + if (g_settings_path != "") + g_settings->updateConfigFile(g_settings_path.c_str()); + break; + } + + if (current_playername.length() > PLAYERNAME_SIZE-1) { + error_message = gettext("Player name too long."); + playername = current_playername.substr(0, PLAYERNAME_SIZE-1); + g_settings->set("name", playername); + continue; + } + + device->getVideoDriver()->setTextureCreationFlag( + video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map")); + +#ifdef HAVE_TOUCHSCREENGUI + receiver->m_touchscreengui = new TouchScreenGUI(device, receiver); + g_touchscreengui = receiver->m_touchscreengui; +#endif + + the_game( + kill, + random_input, + input, + device, + worldspec.path, + current_playername, + current_password, + current_address, + current_port, + error_message, + chat_backend, + &reconnect_requested, + gamespec, + simple_singleplayer_mode + ); + smgr->clear(); + +#ifdef HAVE_TOUCHSCREENGUI + delete g_touchscreengui; + g_touchscreengui = NULL; + receiver->m_touchscreengui = NULL; +#endif + + } //try + catch (con::PeerNotFoundException &e) { + error_message = gettext("Connection error (timed out?)"); + errorstream << error_message << std::endl; + } + +#ifdef NDEBUG + catch (std::exception &e) { + std::string error_message = "Some exception: \""; + error_message += e.what(); + error_message += "\""; + errorstream << error_message << std::endl; + } +#endif + + // If no main menu, show error and exit + if (skip_main_menu) { + if (!error_message.empty()) { + verbosestream << "error_message = " + << error_message << std::endl; + retval = false; + } + break; + } + } // Menu-game loop + + g_menuclouds->drop(); + g_menucloudsmgr->drop(); + + return retval; +} + +void ClientLauncher::init_args(GameParams &game_params, const Settings &cmd_args) +{ + + skip_main_menu = cmd_args.getFlag("go"); + + // FIXME: This is confusing (but correct) + + /* If world_path is set then override it unless skipping the main menu using + * the --go command line param. Else, give preference to the address + * supplied on the command line + */ + address = g_settings->get("address"); + if (game_params.world_path != "" && !skip_main_menu) + address = ""; + else if (cmd_args.exists("address")) + address = cmd_args.get("address"); + + playername = g_settings->get("name"); + if (cmd_args.exists("name")) + playername = cmd_args.get("name"); + + list_video_modes = cmd_args.getFlag("videomodes"); + + use_freetype = g_settings->getBool("freetype"); + + random_input = g_settings->getBool("random_input") + || cmd_args.getFlag("random-input"); +} + +bool ClientLauncher::init_engine(int log_level) +{ + receiver = new MyEventReceiver(); + create_engine_device(log_level); + return device != NULL; +} + +bool ClientLauncher::launch_game(std::string &error_message, + bool reconnect_requested, GameParams &game_params, + const Settings &cmd_args) +{ + // Initialize menu data + MainMenuData menudata; + menudata.address = address; + menudata.name = playername; + menudata.port = itos(game_params.socket_port); + menudata.script_data.errormessage = error_message; + menudata.script_data.reconnect_requested = reconnect_requested; + + error_message.clear(); + + if (cmd_args.exists("password")) + menudata.password = cmd_args.get("password"); + + menudata.enable_public = g_settings->getBool("server_announce"); + + // If a world was commanded, append and select it + if (game_params.world_path != "") { + worldspec.gameid = getWorldGameId(game_params.world_path, true); + worldspec.name = _("[--world parameter]"); + + if (worldspec.gameid == "") { // Create new + worldspec.gameid = g_settings->get("default_game"); + worldspec.name += " [new]"; + } + worldspec.path = game_params.world_path; + } + + /* Show the GUI menu + */ + if (!skip_main_menu) { + main_menu(&menudata); + + // Skip further loading if there was an exit signal. + if (*porting::signal_handler_killstatus()) + return false; + + address = menudata.address; + int newport = stoi(menudata.port); + if (newport != 0) + game_params.socket_port = newport; + + simple_singleplayer_mode = menudata.simple_singleplayer_mode; + + std::vector<WorldSpec> worldspecs = getAvailableWorlds(); + + if (menudata.selected_world >= 0 + && menudata.selected_world < (int)worldspecs.size()) { + g_settings->set("selected_world_path", + worldspecs[menudata.selected_world].path); + worldspec = worldspecs[menudata.selected_world]; + } + } + + if (!menudata.script_data.errormessage.empty()) { + /* The calling function will pass this back into this function upon the + * next iteration (if any) causing it to be displayed by the GUI + */ + error_message = menudata.script_data.errormessage; + return false; + } + + if (menudata.name == "") + menudata.name = std::string("Guest") + itos(myrand_range(1000, 9999)); + else + playername = menudata.name; + + password = menudata.password; + + g_settings->set("name", playername); + + current_playername = playername; + current_password = password; + current_address = address; + current_port = game_params.socket_port; + + // If using simple singleplayer mode, override + if (simple_singleplayer_mode) { + assert(skip_main_menu == false); + current_playername = "singleplayer"; + current_password = ""; + current_address = ""; + current_port = myrand_range(49152, 65535); + } else if (address != "") { + ServerListSpec server; + server["name"] = menudata.servername; + server["address"] = menudata.address; + server["port"] = menudata.port; + server["description"] = menudata.serverdescription; + ServerList::insert(server); + } + + infostream << "Selected world: " << worldspec.name + << " [" << worldspec.path << "]" << std::endl; + + if (current_address == "") { // If local game + if (worldspec.path == "") { + error_message = gettext("No world selected and no address " + "provided. Nothing to do."); + errorstream << error_message << std::endl; + return false; + } + + if (!fs::PathExists(worldspec.path)) { + error_message = gettext("Provided world path doesn't exist: ") + + worldspec.path; + errorstream << error_message << std::endl; + return false; + } + + // Load gamespec for required game + gamespec = findWorldSubgame(worldspec.path); + if (!gamespec.isValid() && !game_params.game_spec.isValid()) { + error_message = gettext("Could not find or load game \"") + + worldspec.gameid + "\""; + errorstream << error_message << std::endl; + return false; + } + + if (porting::signal_handler_killstatus()) + return true; + + if (game_params.game_spec.isValid() && + game_params.game_spec.id != worldspec.gameid) { + errorstream << "WARNING: Overriding gamespec from \"" + << worldspec.gameid << "\" to \"" + << game_params.game_spec.id << "\"" << std::endl; + gamespec = game_params.game_spec; + } + + if (!gamespec.isValid()) { + error_message = gettext("Invalid gamespec."); + error_message += " (world.gameid=" + worldspec.gameid + ")"; + errorstream << error_message << std::endl; + return false; + } + } + + return true; +} + +void ClientLauncher::main_menu(MainMenuData *menudata) +{ + bool *kill = porting::signal_handler_killstatus(); + video::IVideoDriver *driver = device->getVideoDriver(); + + infostream << "Waiting for other menus" << std::endl; + while (device->run() && *kill == false) { + if (noMenuActive()) + break; + driver->beginScene(true, true, video::SColor(255, 128, 128, 128)); + guienv->drawAll(); + driver->endScene(); + // On some computers framerate doesn't seem to be automatically limited + sleep_ms(25); + } + infostream << "Waited for other menus" << std::endl; + + // Cursor can be non-visible when coming from the game +#ifndef ANDROID + device->getCursorControl()->setVisible(true); +#endif + + /* show main menu */ + GUIEngine mymenu(device, guiroot, &g_menumgr, smgr, menudata, *kill); + + smgr->clear(); /* leave scene manager in a clean state */ +} + +bool ClientLauncher::create_engine_device(int log_level) +{ + static const irr::ELOG_LEVEL irr_log_level[5] = { + ELL_NONE, + ELL_ERROR, + ELL_WARNING, + ELL_INFORMATION, +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) + ELL_INFORMATION +#else + ELL_DEBUG +#endif + }; + + // Resolution selection + bool fullscreen = g_settings->getBool("fullscreen"); + u16 screenW = g_settings->getU16("screenW"); + u16 screenH = g_settings->getU16("screenH"); + + // bpp, fsaa, vsync + bool vsync = g_settings->getBool("vsync"); + u16 bits = g_settings->getU16("fullscreen_bpp"); + u16 fsaa = g_settings->getU16("fsaa"); + + // Determine driver + video::E_DRIVER_TYPE driverType = video::EDT_OPENGL; + std::string driverstring = g_settings->get("video_driver"); + std::vector<video::E_DRIVER_TYPE> drivers + = porting::getSupportedVideoDrivers(); + u32 i; + for (i = 0; i != drivers.size(); i++) { + if (!strcasecmp(driverstring.c_str(), + porting::getVideoDriverName(drivers[i]))) { + driverType = drivers[i]; + break; + } + } + if (i == drivers.size()) { + errorstream << "Invalid video_driver specified; " + "defaulting to opengl" << std::endl; + } + + SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); + params.DriverType = driverType; + params.WindowSize = core::dimension2d<u32>(screenW, screenH); + params.Bits = bits; + params.AntiAlias = fsaa; + params.Fullscreen = fullscreen; + params.Stencilbuffer = false; + params.Vsync = vsync; + params.EventReceiver = receiver; + params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); +#ifdef __ANDROID__ + params.PrivateData = porting::app_global; + params.OGLES2ShaderPath = std::string(porting::path_user + DIR_DELIM + + "media" + DIR_DELIM + "Shaders" + DIR_DELIM).c_str(); +#endif + + device = createDeviceEx(params); + + if (device) { + // Map our log level to irrlicht engine one. + ILogger* irr_logger = device->getLogger(); + irr_logger->setLogLevel(irr_log_level[log_level]); + + porting::initIrrlicht(device); + } + + return device != NULL; +} + +void ClientLauncher::speed_tests() +{ + // volatile to avoid some potential compiler optimisations + volatile static s16 temp16; + volatile static f32 tempf; + static v3f tempv3f1; + static v3f tempv3f2; + static std::string tempstring; + static std::string tempstring2; + + tempv3f1 = v3f(); + tempv3f2 = v3f(); + tempstring = std::string(); + tempstring2 = std::string(); + + { + infostream << "The following test should take around 20ms." << std::endl; + TimeTaker timer("Testing std::string speed"); + const u32 jj = 10000; + for (u32 j = 0; j < jj; j++) { + tempstring = ""; + tempstring2 = ""; + const u32 ii = 10; + for (u32 i = 0; i < ii; i++) { + tempstring2 += "asd"; + } + for (u32 i = 0; i < ii+1; i++) { + tempstring += "asd"; + if (tempstring == tempstring2) + break; + } + } + } + + infostream << "All of the following tests should take around 100ms each." + << std::endl; + + { + TimeTaker timer("Testing floating-point conversion speed"); + tempf = 0.001; + for (u32 i = 0; i < 4000000; i++) { + temp16 += tempf; + tempf += 0.001; + } + } + + { + TimeTaker timer("Testing floating-point vector speed"); + + tempv3f1 = v3f(1, 2, 3); + tempv3f2 = v3f(4, 5, 6); + for (u32 i = 0; i < 10000000; i++) { + tempf += tempv3f1.dotProduct(tempv3f2); + tempv3f2 += v3f(7, 8, 9); + } + } + + { + TimeTaker timer("Testing std::map speed"); + + std::map<v2s16, f32> map1; + tempf = -324; + const s16 ii = 300; + for (s16 y = 0; y < ii; y++) { + for (s16 x = 0; x < ii; x++) { + map1[v2s16(x, y)] = tempf; + tempf += 1; + } + } + for (s16 y = ii - 1; y >= 0; y--) { + for (s16 x = 0; x < ii; x++) { + tempf = map1[v2s16(x, y)]; + } + } + } + + { + infostream << "Around 5000/ms should do well here." << std::endl; + TimeTaker timer("Testing mutex speed"); + + JMutex m; + u32 n = 0; + u32 i = 0; + do { + n += 10000; + for (; i < n; i++) { + m.Lock(); + m.Unlock(); + } + } + // Do at least 10ms + while(timer.getTimerTime() < 10); + + u32 dtime = timer.stop(); + u32 per_ms = n / dtime; + infostream << "Done. " << dtime << "ms, " << per_ms << "/ms" << std::endl; + } +} + +bool ClientLauncher::print_video_modes() +{ + IrrlichtDevice *nulldevice; + + bool vsync = g_settings->getBool("vsync"); + u16 fsaa = g_settings->getU16("fsaa"); + MyEventReceiver* receiver = new MyEventReceiver(); + + SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); + params.DriverType = video::EDT_NULL; + params.WindowSize = core::dimension2d<u32>(640, 480); + params.Bits = 24; + params.AntiAlias = fsaa; + params.Fullscreen = false; + params.Stencilbuffer = false; + params.Vsync = vsync; + params.EventReceiver = receiver; + params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); + + nulldevice = createDeviceEx(params); + + if (nulldevice == NULL) { + delete receiver; + return false; + } + + dstream << _("Available video modes (WxHxD):") << std::endl; + + video::IVideoModeList *videomode_list = nulldevice->getVideoModeList(); + + if (videomode_list != NULL) { + s32 videomode_count = videomode_list->getVideoModeCount(); + core::dimension2d<u32> videomode_res; + s32 videomode_depth; + for (s32 i = 0; i < videomode_count; ++i) { + videomode_res = videomode_list->getVideoModeResolution(i); + videomode_depth = videomode_list->getVideoModeDepth(i); + dstream << videomode_res.Width << "x" << videomode_res.Height + << "x" << videomode_depth << std::endl; + } + + dstream << _("Active video mode (WxHxD):") << std::endl; + videomode_res = videomode_list->getDesktopResolution(); + videomode_depth = videomode_list->getDesktopDepth(); + dstream << videomode_res.Width << "x" << videomode_res.Height + << "x" << videomode_depth << std::endl; + + } + + nulldevice->drop(); + delete receiver; + + return videomode_list != NULL; +} diff --git a/src/client/clientlauncher.h b/src/client/clientlauncher.h new file mode 100644 index 000000000..49ceefc52 --- /dev/null +++ b/src/client/clientlauncher.h @@ -0,0 +1,129 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef __CLIENT_LAUNCHER_H__ +#define __CLIENT_LAUNCHER_H__ + +#include "irrlichttypes_extrabloated.h" +#include "client/inputhandler.h" +#include "gameparams.h" + +// A small helper class +class TimeGetter +{ +public: + virtual u32 getTime(TimePrecision prec) = 0; +}; + +// A precise irrlicht one +class IrrlichtTimeGetter: public TimeGetter +{ +public: + IrrlichtTimeGetter(IrrlichtDevice *device): + m_device(device) + {} + u32 getTime(TimePrecision prec) + { + if (prec == PRECISION_MILLI) { + if (m_device == NULL) + return 0; + return m_device->getTimer()->getRealTime(); + } else { + return porting::getTime(prec); + } + } +private: + IrrlichtDevice *m_device; +}; +// Not so precise one which works without irrlicht +class SimpleTimeGetter: public TimeGetter +{ +public: + u32 getTime(TimePrecision prec) + { + return porting::getTime(prec); + } +}; + +class ClientLauncher +{ +public: + ClientLauncher() : + list_video_modes(false), + skip_main_menu(false), + use_freetype(false), + random_input(false), + address(""), + playername(""), + password(""), + device(NULL), + input(NULL), + receiver(NULL), + skin(NULL), + font(NULL), + simple_singleplayer_mode(false), + current_playername("inv£lid"), + current_password(""), + current_address("does-not-exist"), + current_port(0) + {} + + ~ClientLauncher(); + + bool run(GameParams &game_params, const Settings &cmd_args); + +protected: + void init_args(GameParams &game_params, const Settings &cmd_args); + bool init_engine(int log_level); + + bool launch_game(std::string &error_message, bool reconnect_requested, + GameParams &game_params, const Settings &cmd_args); + + void main_menu(MainMenuData *menudata); + bool create_engine_device(int log_level); + + void speed_tests(); + bool print_video_modes(); + + bool list_video_modes; + bool skip_main_menu; + bool use_freetype; + bool random_input; + std::string address; + std::string playername; + std::string password; + IrrlichtDevice *device; + InputHandler *input; + MyEventReceiver *receiver; + gui::IGUISkin *skin; + gui::IGUIFont *font; + scene::ISceneManager *smgr; + SubgameSpec gamespec; + WorldSpec worldspec; + bool simple_singleplayer_mode; + + // These are set up based on the menu and other things + // TODO: Are these required since there's already playername, password, etc + std::string current_playername; + std::string current_password; + std::string current_address; + int current_port; +}; + +#endif diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h new file mode 100644 index 000000000..a894e35aa --- /dev/null +++ b/src/client/inputhandler.h @@ -0,0 +1,435 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef __INPUT_HANDLER_H__ +#define __INPUT_HANDLER_H__ + +#include "irrlichttypes_extrabloated.h" + +class MyEventReceiver : public IEventReceiver +{ +public: + // This is the one method that we have to implement + virtual bool OnEvent(const SEvent& event) + { + /* + React to nothing here if a menu is active + */ + if (noMenuActive() == false) { +#ifdef HAVE_TOUCHSCREENGUI + if (m_touchscreengui != 0) { + m_touchscreengui->Toggle(false); + } +#endif + return g_menumgr.preprocessEvent(event); + } + + // Remember whether each key is down or up + if (event.EventType == irr::EET_KEY_INPUT_EVENT) { + if (event.KeyInput.PressedDown) { + keyIsDown.set(event.KeyInput); + keyWasDown.set(event.KeyInput); + } else { + keyIsDown.unset(event.KeyInput); + } + } + +#ifdef HAVE_TOUCHSCREENGUI + // case of touchscreengui we have to handle different events + if ((m_touchscreengui != 0) && + (event.EventType == irr::EET_TOUCH_INPUT_EVENT)) { + m_touchscreengui->translateEvent(event); + return true; + } +#endif + // handle mouse events + if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) { + if (noMenuActive() == false) { + left_active = false; + middle_active = false; + right_active = false; + } else { + left_active = event.MouseInput.isLeftPressed(); + middle_active = event.MouseInput.isMiddlePressed(); + right_active = event.MouseInput.isRightPressed(); + + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { + leftclicked = true; + } + if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN) { + rightclicked = true; + } + if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { + leftreleased = true; + } + if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP) { + rightreleased = true; + } + if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { + mouse_wheel += event.MouseInput.Wheel; + } + } + } else if (event.EventType == irr::EET_LOG_TEXT_EVENT) { + static const enum LogMessageLevel irr_loglev_conv[] = { + LMT_VERBOSE, // ELL_DEBUG + LMT_INFO, // ELL_INFORMATION + LMT_ACTION, // ELL_WARNING + LMT_ERROR, // ELL_ERROR + LMT_ERROR, // ELL_NONE + }; + assert(event.LogEvent.Level < ARRLEN(irr_loglev_conv)); + log_printline(irr_loglev_conv[event.LogEvent.Level], + std::string("Irrlicht: ") + (const char *)event.LogEvent.Text); + return true; + } + /* always return false in order to continue processing events */ + return false; + } + + bool IsKeyDown(const KeyPress &keyCode) const + { + return keyIsDown[keyCode]; + } + + // Checks whether a key was down and resets the state + bool WasKeyDown(const KeyPress &keyCode) + { + bool b = keyWasDown[keyCode]; + if (b) + keyWasDown.unset(keyCode); + return b; + } + + s32 getMouseWheel() + { + s32 a = mouse_wheel; + mouse_wheel = 0; + return a; + } + + void clearInput() + { + keyIsDown.clear(); + keyWasDown.clear(); + + leftclicked = false; + rightclicked = false; + leftreleased = false; + rightreleased = false; + + left_active = false; + middle_active = false; + right_active = false; + + mouse_wheel = 0; + } + + MyEventReceiver() + { + clearInput(); +#ifdef HAVE_TOUCHSCREENGUI + m_touchscreengui = NULL; +#endif + } + + bool leftclicked; + bool rightclicked; + bool leftreleased; + bool rightreleased; + + bool left_active; + bool middle_active; + bool right_active; + + s32 mouse_wheel; + +#ifdef HAVE_TOUCHSCREENGUI + TouchScreenGUI* m_touchscreengui; +#endif + +private: + // The current state of keys + KeyList keyIsDown; + // Whether a key has been pressed or not + KeyList keyWasDown; +}; + + +/* + Separated input handler +*/ + +class RealInputHandler : public InputHandler +{ +public: + RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver): + m_device(device), + m_receiver(receiver), + m_mousepos(0,0) + { + } + virtual bool isKeyDown(const KeyPress &keyCode) + { + return m_receiver->IsKeyDown(keyCode); + } + virtual bool wasKeyDown(const KeyPress &keyCode) + { + return m_receiver->WasKeyDown(keyCode); + } + virtual v2s32 getMousePos() + { + if (m_device->getCursorControl()) { + return m_device->getCursorControl()->getPosition(); + } + else { + return m_mousepos; + } + } + virtual void setMousePos(s32 x, s32 y) + { + if (m_device->getCursorControl()) { + m_device->getCursorControl()->setPosition(x, y); + } + else { + m_mousepos = v2s32(x,y); + } + } + + virtual bool getLeftState() + { + return m_receiver->left_active; + } + virtual bool getRightState() + { + return m_receiver->right_active; + } + + virtual bool getLeftClicked() + { + return m_receiver->leftclicked; + } + virtual bool getRightClicked() + { + return m_receiver->rightclicked; + } + virtual void resetLeftClicked() + { + m_receiver->leftclicked = false; + } + virtual void resetRightClicked() + { + m_receiver->rightclicked = false; + } + + virtual bool getLeftReleased() + { + return m_receiver->leftreleased; + } + virtual bool getRightReleased() + { + return m_receiver->rightreleased; + } + virtual void resetLeftReleased() + { + m_receiver->leftreleased = false; + } + virtual void resetRightReleased() + { + m_receiver->rightreleased = false; + } + + virtual s32 getMouseWheel() + { + return m_receiver->getMouseWheel(); + } + + void clear() + { + m_receiver->clearInput(); + } +private: + IrrlichtDevice *m_device; + MyEventReceiver *m_receiver; + v2s32 m_mousepos; +}; + +class RandomInputHandler : public InputHandler +{ +public: + RandomInputHandler() + { + leftdown = false; + rightdown = false; + leftclicked = false; + rightclicked = false; + leftreleased = false; + rightreleased = false; + keydown.clear(); + } + virtual bool isKeyDown(const KeyPress &keyCode) + { + return keydown[keyCode]; + } + virtual bool wasKeyDown(const KeyPress &keyCode) + { + return false; + } + virtual v2s32 getMousePos() + { + return mousepos; + } + virtual void setMousePos(s32 x, s32 y) + { + mousepos = v2s32(x, y); + } + + virtual bool getLeftState() + { + return leftdown; + } + virtual bool getRightState() + { + return rightdown; + } + + virtual bool getLeftClicked() + { + return leftclicked; + } + virtual bool getRightClicked() + { + return rightclicked; + } + virtual void resetLeftClicked() + { + leftclicked = false; + } + virtual void resetRightClicked() + { + rightclicked = false; + } + + virtual bool getLeftReleased() + { + return leftreleased; + } + virtual bool getRightReleased() + { + return rightreleased; + } + virtual void resetLeftReleased() + { + leftreleased = false; + } + virtual void resetRightReleased() + { + rightreleased = false; + } + + virtual s32 getMouseWheel() + { + return 0; + } + + virtual void step(float dtime) + { + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 40); + keydown.toggle(getKeySetting("keymap_jump")); + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 40); + keydown.toggle(getKeySetting("keymap_special1")); + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 40); + keydown.toggle(getKeySetting("keymap_forward")); + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 40); + keydown.toggle(getKeySetting("keymap_left")); + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 20); + mousespeed = v2s32(Rand(-20, 20), Rand(-15, 20)); + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 30); + leftdown = !leftdown; + if (leftdown) + leftclicked = true; + if (!leftdown) + leftreleased = true; + } + } + { + static float counter1 = 0; + counter1 -= dtime; + if (counter1 < 0.0) { + counter1 = 0.1 * Rand(1, 15); + rightdown = !rightdown; + if (rightdown) + rightclicked = true; + if (!rightdown) + rightreleased = true; + } + } + mousepos += mousespeed; + } + + s32 Rand(s32 min, s32 max) + { + return (myrand()%(max-min+1))+min; + } +private: + KeyList keydown; + v2s32 mousepos; + v2s32 mousespeed; + bool leftdown; + bool rightdown; + bool leftclicked; + bool rightclicked; + bool leftreleased; + bool rightreleased; +}; + +#endif diff --git a/src/tile.cpp b/src/client/tile.cpp index 81b362d61..a28b40c65 100644 --- a/src/tile.cpp +++ b/src/client/tile.cpp @@ -26,7 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "irrlichttypes_extrabloated.h" #include "debug.h" -#include "main.h" // for g_settings #include "filesys.h" #include "settings.h" #include "mesh.h" @@ -34,6 +33,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gamedef.h" #include "strfnd.h" #include "util/string.h" // for parseColorString() +#include "imagefilters.h" +#include "guiscalingfilter.h" +#include "nodedef.h" + #ifdef __ANDROID__ #include <GLES/gl.h> @@ -199,7 +202,7 @@ public: void insert(const std::string &name, video::IImage *img, bool prefer_local, video::IVideoDriver *driver) { - assert(img); + assert(img); // Pre-condition // Remove old image std::map<std::string, video::IImage*>::iterator n; n = m_images.find(name); @@ -329,7 +332,15 @@ public: */ video::ITexture* getTexture(u32 id); - video::ITexture* getTexture(const std::string &name, u32 *id); + video::ITexture* getTexture(const std::string &name, u32 *id = NULL); + + /* + Get a texture specifically intended for mesh + application, i.e. not HUD, compositing, or other 2D + use. This texture may be a different size and may + have had additional filters applied. + */ + video::ITexture* getTextureForMesh(const std::string &name, u32 *id); // Returns a pointer to the irrlicht device virtual IrrlichtDevice* getDevice() @@ -373,6 +384,9 @@ public: video::IImage* generateImage(const std::string &name); video::ITexture* getNormalTexture(const std::string &name); + video::SColor getTextureAverageColor(const std::string &name); + video::ITexture *getShaderFlagsTexture(bool normamap_present); + private: // The id of the thread that is allowed to use irrlicht directly @@ -407,7 +421,7 @@ private: // Textures that have been overwritten with other ones // but can't be deleted because the ITexture* might still be used - std::list<video::ITexture*> m_texture_trash; + std::vector<video::ITexture*> m_texture_trash; // Cached settings needed for making textures from meshes bool m_setting_trilinear_filter; @@ -423,7 +437,7 @@ IWritableTextureSource* createTextureSource(IrrlichtDevice *device) TextureSource::TextureSource(IrrlichtDevice *device): m_device(device) { - assert(m_device); + assert(m_device); // Pre-condition m_main_thread = get_current_thread_id(); @@ -455,10 +469,9 @@ TextureSource::~TextureSource() } m_textureinfo_cache.clear(); - for (std::list<video::ITexture*>::iterator iter = + for (std::vector<video::ITexture*>::iterator iter = m_texture_trash.begin(); iter != m_texture_trash.end(); - iter++) - { + iter++) { video::ITexture *t = *iter; //cleanup trashed texture @@ -598,7 +611,7 @@ u32 TextureSource::generateTexture(const std::string &name) } video::IVideoDriver *driver = m_device->getVideoDriver(); - assert(driver); + sanity_check(driver); video::IImage *img = generateImage(name); @@ -610,6 +623,7 @@ u32 TextureSource::generateTexture(const std::string &name) #endif // Create texture from resulting image tex = driver->addTexture(name.c_str(), img); + guiScalingCache(io::path(name.c_str()), driver, img); img->drop(); } @@ -661,6 +675,11 @@ video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id) return getTexture(actual_id); } +video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id) +{ + return getTexture(name + "^[applyfiltersformesh", id); +} + void TextureSource::processQueue() { /* @@ -685,7 +704,7 @@ void TextureSource::insertSourceImage(const std::string &name, video::IImage *im { //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl; - assert(get_current_thread_id() == m_main_thread); + sanity_check(get_current_thread_id() == m_main_thread); m_sourcecache.insert(name, img, true, m_device->getVideoDriver()); m_source_image_existence.set(name, true); @@ -696,7 +715,7 @@ void TextureSource::rebuildImagesAndTextures() JMutexAutoLock lock(m_textureinfo_cache_mutex); video::IVideoDriver* driver = m_device->getVideoDriver(); - assert(driver != 0); + sanity_check(driver); // Recreate textures for (u32 i=0; i<m_textureinfo_cache.size(); i++){ @@ -704,13 +723,14 @@ void TextureSource::rebuildImagesAndTextures() video::IImage *img = generateImage(ti->name); #ifdef __ANDROID__ img = Align2Npot2(img, driver); - assert(img->getDimension().Height == npot2(img->getDimension().Height)); - assert(img->getDimension().Width == npot2(img->getDimension().Width)); + sanity_check(img->getDimension().Height == npot2(img->getDimension().Height)); + sanity_check(img->getDimension().Width == npot2(img->getDimension().Width)); #endif // Create texture from resulting image video::ITexture *t = NULL; if (img) { t = driver->addTexture(ti->name.c_str(), img); + guiScalingCache(io::path(ti->name.c_str()), driver, img); img->drop(); } video::ITexture *t_old = ti->texture; @@ -726,7 +746,7 @@ video::ITexture* TextureSource::generateTextureFromMesh( const TextureFromMeshParams ¶ms) { video::IVideoDriver *driver = m_device->getVideoDriver(); - assert(driver); + sanity_check(driver); #ifdef __ANDROID__ const GLubyte* renderstr = glGetString(GL_RENDERER); @@ -742,9 +762,9 @@ video::ITexture* TextureSource::generateTextureFromMesh( ) { // Get a scene manager scene::ISceneManager *smgr_main = m_device->getSceneManager(); - assert(smgr_main); + sanity_check(smgr_main); scene::ISceneManager *smgr = smgr_main->createNewSceneManager(); - assert(smgr); + sanity_check(smgr); const float scaling = 0.2; @@ -831,6 +851,8 @@ video::ITexture* TextureSource::generateTextureFromMesh( rawImage->copyToScaling(inventory_image); rawImage->drop(); + guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image); + video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image); inventory_image->drop(); @@ -979,7 +1001,7 @@ video::IImage* TextureSource::generateImage(const std::string &name) video::IVideoDriver* driver = m_device->getVideoDriver(); - assert(driver); + sanity_check(driver); /* Parse out the last part of the name of the image and act @@ -988,7 +1010,7 @@ video::IImage* TextureSource::generateImage(const std::string &name) std::string last_part_of_name = name.substr(last_separator_pos + 1); - /* + /* If this name is enclosed in parentheses, generate it and blit it onto the base image */ @@ -1079,7 +1101,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, video::IImage *& baseimg) { video::IVideoDriver* driver = m_device->getVideoDriver(); - assert(driver); + sanity_check(driver); // Stuff starting with [ are special commands if (part_of_name.size() == 0 || part_of_name[0] != '[') @@ -1107,7 +1129,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, //core::dimension2d<u32> dim(2,2); core::dimension2d<u32> dim(1,1); image = driver->createImage(video::ECF_A8R8G8B8, dim); - assert(image); + sanity_check(image != NULL); /*image->setPixel(0,0, video::SColor(255,255,0,0)); image->setPixel(1,0, video::SColor(255,0,255,0)); image->setPixel(0,1, video::SColor(255,0,0,255)); @@ -1170,7 +1192,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, Adds a cracking texture N = animation frame count, P = crack progression */ - if (part_of_name.substr(0,6) == "[crack") + if (str_starts_with(part_of_name, "[crack")) { if (baseimg == NULL) { errorstream<<"generateImagePart(): baseimg == NULL " @@ -1186,28 +1208,29 @@ bool TextureSource::generateImagePart(std::string part_of_name, s32 frame_count = stoi(sf.next(":")); s32 progression = stoi(sf.next(":")); - /* - Load crack image. + if (progression >= 0) { + /* + Load crack image. - It is an image with a number of cracking stages - horizontally tiled. - */ - video::IImage *img_crack = m_sourcecache.getOrLoad( + It is an image with a number of cracking stages + horizontally tiled. + */ + video::IImage *img_crack = m_sourcecache.getOrLoad( "crack_anylength.png", m_device); - if (img_crack && progression >= 0) - { - draw_crack(img_crack, baseimg, + if (img_crack) { + draw_crack(img_crack, baseimg, use_overlay, frame_count, progression, driver); - img_crack->drop(); + img_crack->drop(); + } } } /* [combine:WxH:X,Y=filename:X,Y=filename2 Creates a bigger texture from an amount of smaller ones */ - else if (part_of_name.substr(0,8) == "[combine") + else if (str_starts_with(part_of_name, "[combine")) { Strfnd sf(part_of_name); sf.next(":"); @@ -1251,7 +1274,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, /* "[brighten" */ - else if (part_of_name.substr(0,9) == "[brighten") + else if (str_starts_with(part_of_name, "[brighten")) { if (baseimg == NULL) { errorstream<<"generateImagePart(): baseimg==NULL " @@ -1269,7 +1292,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, that the transparent parts don't look completely black when simple alpha channel is used for rendering. */ - else if (part_of_name.substr(0,8) == "[noalpha") + else if (str_starts_with(part_of_name, "[noalpha")) { if (baseimg == NULL){ errorstream<<"generateImagePart(): baseimg==NULL " @@ -1293,7 +1316,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, "[makealpha:R,G,B" Convert one color to transparent. */ - else if (part_of_name.substr(0,11) == "[makealpha:") + else if (str_starts_with(part_of_name, "[makealpha:")) { if (baseimg == NULL) { errorstream<<"generateImagePart(): baseimg == NULL " @@ -1349,7 +1372,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, The resulting transform will be equivalent to one of the eight existing ones, though (see: dihedral group). */ - else if (part_of_name.substr(0,10) == "[transform") + else if (str_starts_with(part_of_name, "[transform")) { if (baseimg == NULL) { errorstream<<"generateImagePart(): baseimg == NULL " @@ -1363,7 +1386,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, transform, baseimg->getDimension()); video::IImage *image = driver->createImage( baseimg->getColorFormat(), dim); - assert(image); + sanity_check(image != NULL); imageTransform(transform, baseimg, image); baseimg->drop(); baseimg = image; @@ -1376,7 +1399,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, Example (a grass block (not actually used in game): "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png" */ - else if (part_of_name.substr(0,14) == "[inventorycube") + else if (str_starts_with(part_of_name, "[inventorycube")) { if (baseimg != NULL){ errorstream<<"generateImagePart(): baseimg != NULL " @@ -1423,7 +1446,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, (imagename_left + "__temp__").c_str(), img_left); video::ITexture *texture_right = driver->addTexture( (imagename_right + "__temp__").c_str(), img_right); - assert(texture_top && texture_left && texture_right); + FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), ""); // Drop images img_top->drop(); @@ -1477,7 +1500,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, // Create image of render target video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim); - assert(image); + FATAL_ERROR_IF(!image, "Could not create image of render target"); // Cleanup texture driver->removeTexture(rtt); @@ -1493,7 +1516,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, [lowpart:percent:filename Adds the lower part of a texture */ - else if (part_of_name.substr(0,9) == "[lowpart:") + else if (str_starts_with(part_of_name, "[lowpart:")) { Strfnd sf(part_of_name); sf.next(":"); @@ -1529,7 +1552,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, Crops a frame of a vertical animation. N = frame count, I = frame index */ - else if (part_of_name.substr(0,15) == "[verticalframe:") + else if (str_starts_with(part_of_name, "[verticalframe:")) { Strfnd sf(part_of_name); sf.next(":"); @@ -1573,7 +1596,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, [mask:filename Applies a mask to an image */ - else if (part_of_name.substr(0,6) == "[mask:") + else if (str_starts_with(part_of_name, "[mask:")) { if (baseimg == NULL) { errorstream << "generateImage(): baseimg == NULL " @@ -1589,6 +1612,7 @@ bool TextureSource::generateImagePart(std::string part_of_name, if (img) { apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0), img->getDimension()); + img->drop(); } else { errorstream << "generateImage(): Failed to load \"" << filename << "\"."; @@ -1599,7 +1623,8 @@ bool TextureSource::generateImagePart(std::string part_of_name, Overlays image with given color color = color as ColorString */ - else if (part_of_name.substr(0,10) == "[colorize:") { + else if (str_starts_with(part_of_name, "[colorize:")) + { Strfnd sf(part_of_name); sf.next(":"); std::string color_str = sf.next(":"); @@ -1636,6 +1661,43 @@ bool TextureSource::generateImagePart(std::string part_of_name, blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio); img->drop(); } + else if (str_starts_with(part_of_name, "[applyfiltersformesh")) + { + // Apply the "clean transparent" filter, if configured. + if (g_settings->getBool("texture_clean_transparent")) + imageCleanTransparent(baseimg, 127); + + /* Upscale textures to user's requested minimum size. This is a trick to make + * filters look as good on low-res textures as on high-res ones, by making + * low-res textures BECOME high-res ones. This is helpful for worlds that + * mix high- and low-res textures, or for mods with least-common-denominator + * textures that don't have the resources to offer high-res alternatives. + */ + s32 scaleto = g_settings->getS32("texture_min_size"); + if (scaleto > 1) { + const core::dimension2d<u32> dim = baseimg->getDimension(); + + /* Calculate scaling needed to make the shortest texture dimension + * equal to the target minimum. If e.g. this is a vertical frames + * animation, the short dimension will be the real size. + */ + u32 xscale = scaleto / dim.Width; + u32 yscale = scaleto / dim.Height; + u32 scale = (xscale > yscale) ? xscale : yscale; + + // Never downscale; only scale up by 2x or more. + if (scale > 1) { + u32 w = scale * dim.Width; + u32 h = scale * dim.Height; + const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h); + video::IImage *newimg = driver->createImage( + baseimg->getColorFormat(), newdim); + baseimg->copyToScaling(newimg); + baseimg->drop(); + baseimg = newimg; + } + } + } else { errorstream << "generateImagePart(): Invalid " @@ -1893,10 +1955,10 @@ void imageTransform(u32 transform, video::IImage *src, video::IImage *dst) if (src == NULL || dst == NULL) return; - core::dimension2d<u32> srcdim = src->getDimension(); core::dimension2d<u32> dstdim = dst->getDimension(); - assert(dstdim == imageTransformDimension(transform, srcdim)); + // Pre-conditions + assert(dstdim == imageTransformDimension(transform, src->getDimension())); assert(transform <= 7); /* @@ -1935,9 +1997,8 @@ void imageTransform(u32 transform, video::IImage *src, video::IImage *dst) video::ITexture* TextureSource::getNormalTexture(const std::string &name) { - u32 id; if (isKnownSourceImage("override_normal.png")) - return getTexture("override_normal.png", &id); + return getTexture("override_normal.png"); std::string fname_base = name; std::string normal_ext = "_normal.png"; size_t pos = fname_base.find("."); @@ -1949,7 +2010,65 @@ video::ITexture* TextureSource::getNormalTexture(const std::string &name) fname_base.replace(i, 4, normal_ext); i += normal_ext.length(); } - return getTexture(fname_base, &id); + return getTexture(fname_base); } return NULL; } + +video::SColor TextureSource::getTextureAverageColor(const std::string &name) +{ + video::IVideoDriver *driver = m_device->getVideoDriver(); + video::SColor c(0, 0, 0, 0); + video::ITexture *texture = getTexture(name); + video::IImage *image = driver->createImage(texture, + core::position2d<s32>(0, 0), + texture->getOriginalSize()); + u32 total = 0; + u32 tR = 0; + u32 tG = 0; + u32 tB = 0; + core::dimension2d<u32> dim = image->getDimension(); + u16 step = 1; + if (dim.Width > 16) + step = dim.Width / 16; + for (u16 x = 0; x < dim.Width; x += step) { + for (u16 y = 0; y < dim.Width; y += step) { + c = image->getPixel(x,y); + if (c.getAlpha() > 0) { + total++; + tR += c.getRed(); + tG += c.getGreen(); + tB += c.getBlue(); + } + } + } + image->drop(); + if (total > 0) { + c.setRed(tR / total); + c.setGreen(tG / total); + c.setBlue(tB / total); + } + c.setAlpha(255); + return c; +} + + +video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present) +{ + std::string tname = "__shaderFlagsTexture"; + tname += normalmap_present ? "1" : "0"; + + if (isKnownSourceImage(tname)) { + return getTexture(tname); + } else { + video::IVideoDriver *driver = m_device->getVideoDriver(); + video::IImage *flags_image = driver->createImage( + video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1)); + sanity_check(flags_image != NULL); + video::SColor c(255, normalmap_present ? 255 : 0, 0, 0); + flags_image->setPixel(0, 0, c); + insertSourceImage(tname, flags_image); + flags_image->drop(); + return getTexture(tname); + } +} diff --git a/src/tile.h b/src/client/tile.h index ea7a9135a..7796e801d 100644 --- a/src/tile.h +++ b/src/client/tile.h @@ -28,8 +28,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "threads.h" #include <string> #include <vector> +#include "util/numeric.h" class IGameDef; +struct TileSpec; +struct TileDef; /* tile.{h,cpp}: Texture handling stuff. @@ -102,11 +105,15 @@ public: virtual video::ITexture* getTexture(u32 id)=0; virtual video::ITexture* getTexture( const std::string &name, u32 *id = NULL)=0; + virtual video::ITexture* getTextureForMesh( + const std::string &name, u32 *id = NULL) = 0; virtual IrrlichtDevice* getDevice()=0; virtual bool isKnownSourceImage(const std::string &name)=0; virtual video::ITexture* generateTextureFromMesh( const TextureFromMeshParams ¶ms)=0; virtual video::ITexture* getNormalTexture(const std::string &name)=0; + virtual video::SColor getTextureAverageColor(const std::string &name)=0; + virtual video::ITexture *getShaderFlagsTexture(bool normalmap_present)=0; }; class IWritableTextureSource : public ITextureSource @@ -128,26 +135,13 @@ public: virtual void insertSourceImage(const std::string &name, video::IImage *img)=0; virtual void rebuildImagesAndTextures()=0; virtual video::ITexture* getNormalTexture(const std::string &name)=0; + virtual video::SColor getTextureAverageColor(const std::string &name)=0; + virtual video::ITexture *getShaderFlagsTexture(bool normalmap_present)=0; }; IWritableTextureSource* createTextureSource(IrrlichtDevice *device); #ifdef __ANDROID__ -/** - * @param size get next npot2 value - * @return npot2 value - */ -inline unsigned int npot2(unsigned int size) -{ - if (size == 0) return 0; - unsigned int npot = 1; - - while ((size >>= 1) > 0) { - npot <<= 1; - } - return npot; -} - video::IImage * Align2Npot2(video::IImage * image, video::IVideoDriver* driver); #endif @@ -172,6 +166,8 @@ enum MaterialType{ // defined by extra parameters #define MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES 0x08 #define MATERIAL_FLAG_HIGHLIGHTED 0x10 +#define MATERIAL_FLAG_TILEABLE_HORIZONTAL 0x20 +#define MATERIAL_FLAG_TILEABLE_VERTICAL 0x40 /* This fully defines the looks of a tile. @@ -182,12 +178,14 @@ struct FrameSpec FrameSpec(): texture_id(0), texture(NULL), - normal_texture(NULL) + normal_texture(NULL), + flags_texture(NULL) { } u32 texture_id; video::ITexture *texture; video::ITexture *normal_texture; + video::ITexture *flags_texture; }; struct TileSpec @@ -196,6 +194,7 @@ struct TileSpec texture_id(0), texture(NULL), normal_texture(NULL), + flags_texture(NULL), alpha(255), material_type(TILE_MATERIAL_BASIC), material_flags( @@ -251,17 +250,32 @@ struct TileSpec } material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) ? true : false; + if (!(material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL)) { + material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; + } + if (!(material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL)) { + material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; + } } void applyMaterialOptionsWithShaders(video::SMaterial &material) const { material.BackfaceCulling = (material_flags & MATERIAL_FLAG_BACKFACE_CULLING) ? true : false; + if (!(material_flags & MATERIAL_FLAG_TILEABLE_HORIZONTAL)) { + material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; + material.TextureLayer[1].TextureWrapU = video::ETC_CLAMP_TO_EDGE; + } + if (!(material_flags & MATERIAL_FLAG_TILEABLE_VERTICAL)) { + material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; + material.TextureLayer[1].TextureWrapV = video::ETC_CLAMP_TO_EDGE; + } } u32 texture_id; video::ITexture *texture; video::ITexture *normal_texture; + video::ITexture *flags_texture; // Vertex alpha (when MATERIAL_ALPHA_VERTEX is used) u8 alpha; @@ -276,5 +290,4 @@ struct TileSpec u8 rotation; }; - #endif diff --git a/src/clientiface.cpp b/src/clientiface.cpp index 40d1ef811..3330e0af9 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -25,13 +25,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include "settings.h" #include "mapblock.h" -#include "connection.h" +#include "network/connection.h" #include "environment.h" #include "map.h" #include "emerge.h" #include "serverobject.h" // TODO this is used for cleanup of only -#include "main.h" // for g_settings #include "log.h" +#include "util/srp.h" const char *ClientInterface::statenames[] = { "Invalid", @@ -59,7 +59,7 @@ void RemoteClient::ResendBlockIfOnWire(v3s16 p) } } -void RemoteClient::GetNextBlocks( +void RemoteClient::GetNextBlocks ( ServerEnvironment *env, EmergeManager * emerge, float dtime, @@ -182,18 +182,15 @@ void RemoteClient::GetNextBlocks( //bool queue_is_full = false; s16 d; - for(d = d_start; d <= d_max; d++) - { + for(d = d_start; d <= d_max; d++) { /* Get the border/face dot coordinates of a "d-radiused" box */ - std::list<v3s16> list; - getFacePositions(list, d); + std::vector<v3s16> list = FacePositionCache::getFacePositions(d); - std::list<v3s16>::iterator li; - for(li=list.begin(); li!=list.end(); ++li) - { + std::vector<v3s16>::iterator li; + for(li = list.begin(); li != list.end(); ++li) { v3s16 p = *li + center; /* @@ -212,25 +209,19 @@ void RemoteClient::GetNextBlocks( max_simul_dynamic = max_simul_sends_setting; // Don't select too many blocks for sending - if(num_blocks_selected >= max_simul_dynamic) - { + if (num_blocks_selected >= max_simul_dynamic) { //queue_is_full = true; goto queue_full_break; } // Don't send blocks that are currently being transferred - if(m_blocks_sending.find(p) != m_blocks_sending.end()) + if (m_blocks_sending.find(p) != m_blocks_sending.end()) continue; /* Do not go over-limit */ - if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE) + if (blockpos_over_limit(p)) continue; // If this is true, inexistent block will be made from scratch @@ -242,7 +233,7 @@ void RemoteClient::GetNextBlocks( generate = false;*/ // Limit the send area vertically to 1/2 - if(abs(p.Y - center.Y) > full_d_max / 2) + if (abs(p.Y - center.Y) > full_d_max / 2) continue; } @@ -431,10 +422,12 @@ void RemoteClient::notifyEvent(ClientStateEvent event) //intentionally do nothing break; case CS_Created: - switch(event) - { - case CSE_Init: - m_state = CS_InitSent; + switch (event) { + case CSE_Hello: + m_state = CS_HelloSent; + break; + case CSE_InitLegacy: + m_state = CS_AwaitingInit2; break; case CSE_Disconnect: m_state = CS_Disconnecting; @@ -451,7 +444,32 @@ void RemoteClient::notifyEvent(ClientStateEvent event) case CS_Denied: /* don't do anything if in denied state */ break; - case CS_InitSent: + case CS_HelloSent: + switch(event) + { + case CSE_AuthAccept: + m_state = CS_AwaitingInit2; + if ((chosen_mech == AUTH_MECHANISM_SRP) + || (chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD)) + srp_verifier_delete((SRPVerifier *) auth_data); + chosen_mech = AUTH_MECHANISM_NONE; + break; + case CSE_Disconnect: + m_state = CS_Disconnecting; + break; + case CSE_SetDenied: + m_state = CS_Denied; + if ((chosen_mech == AUTH_MECHANISM_SRP) + || (chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD)) + srp_verifier_delete((SRPVerifier *) auth_data); + chosen_mech = AUTH_MECHANISM_NONE; + break; + default: + myerror << "HelloSent: Invalid client state transition! " << event; + throw ClientStateError(myerror.str()); + } + break; + case CS_AwaitingInit2: switch(event) { case CSE_GotInit2: @@ -518,6 +536,13 @@ void RemoteClient::notifyEvent(ClientStateEvent event) case CSE_Disconnect: m_state = CS_Disconnecting; break; + case CSE_SudoSuccess: + m_state = CS_SudoMode; + if ((chosen_mech == AUTH_MECHANISM_SRP) + || (chosen_mech == AUTH_MECHANISM_LEGACY_PASSWORD)) + srp_verifier_delete((SRPVerifier *) auth_data); + chosen_mech = AUTH_MECHANISM_NONE; + break; /* Init GotInit2 SetDefinitionsSent SetMediaSent SetDenied */ default: myerror << "Active: Invalid client state transition! " << event; @@ -525,6 +550,24 @@ void RemoteClient::notifyEvent(ClientStateEvent event) break; } break; + case CS_SudoMode: + switch(event) + { + case CSE_SetDenied: + m_state = CS_Denied; + break; + case CSE_Disconnect: + m_state = CS_Disconnecting; + break; + case CSE_SudoLeave: + m_state = CS_Active; + break; + default: + myerror << "Active: Invalid client state transition! " << event; + throw ClientStateError(myerror.str()); + break; + } + break; case CS_Disconnecting: /* we are already disconnecting */ break; @@ -563,9 +606,9 @@ ClientInterface::~ClientInterface() } } -std::list<u16> ClientInterface::getClientIDs(ClientState min_state) +std::vector<u16> ClientInterface::getClientIDs(ClientState min_state) { - std::list<u16> reply; + std::vector<u16> reply; JMutexAutoLock clientslock(m_clients_mutex); for(std::map<u16, RemoteClient*>::iterator @@ -599,20 +642,22 @@ void ClientInterface::UpdatePlayerList() { if (m_env != NULL) { - std::list<u16> clients = getClientIDs(); + std::vector<u16> clients = getClientIDs(); m_clients_names.clear(); if(!clients.empty()) infostream<<"Players:"<<std::endl; - for(std::list<u16>::iterator + + for(std::vector<u16>::iterator i = clients.begin(); - i != clients.end(); ++i) - { + i != clients.end(); ++i) { Player *player = m_env->getPlayer(*i); - if(player==NULL) + + if (player == NULL) continue; - infostream<<"* "<<player->getName()<<"\t"; + + infostream << "* " << player->getName() << "\t"; { JMutexAutoLock clientslock(m_clients_mutex); @@ -620,30 +665,29 @@ void ClientInterface::UpdatePlayerList() if(client != NULL) client->PrintInfo(infostream); } + m_clients_names.push_back(player->getName()); } } } -void ClientInterface::send(u16 peer_id,u8 channelnum, - SharedBuffer<u8> data, bool reliable) +void ClientInterface::send(u16 peer_id, u8 channelnum, + NetworkPacket* pkt, bool reliable) { - m_con->Send(peer_id, channelnum, data, reliable); + m_con->Send(peer_id, channelnum, pkt, reliable); } void ClientInterface::sendToAll(u16 channelnum, - SharedBuffer<u8> data, bool reliable) + NetworkPacket* pkt, bool reliable) { JMutexAutoLock clientslock(m_clients_mutex); for(std::map<u16, RemoteClient*>::iterator i = m_clients.begin(); - i != m_clients.end(); ++i) - { + i != m_clients.end(); ++i) { RemoteClient *client = i->second; - if (client->net_proto_version != 0) - { - m_con->Send(client->peer_id, channelnum, data, reliable); + if (client->net_proto_version != 0) { + m_con->Send(client->peer_id, channelnum, pkt, reliable); } } } diff --git a/src/clientiface.h b/src/clientiface.h index cb3dae04b..f6c4294e2 100644 --- a/src/clientiface.h +++ b/src/clientiface.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "constants.h" #include "serialization.h" // for SER_FMT_VER_INVALID #include "jthread/jmutex.h" +#include "network/networkpacket.h" #include <list> #include <vector> @@ -46,96 +47,119 @@ class EmergeManager; | Created | | | \-----------------/ - | - | -+-----------------------------+ invalid playername, password -|IN: | or denied by mod -| TOSERVER_INIT |------------------------------ -+-----------------------------+ | - | | - | Auth ok | - | | -+-----------------------------+ | -|OUT: | | -| TOCLIENT_INIT | | -+-----------------------------+ | - | | - v | - /-----------------\ | - | | | - | InitSent | | - | | | - \-----------------/ +------------------ - | | | -+-----------------------------+ +-----------------------------+ | -|IN: | |OUT: | | -| TOSERVER_INIT2 | | TOCLIENT_ACCESS_DENIED | | -+-----------------------------+ +-----------------------------+ | - | | | - v v | - /-----------------\ /-----------------\ | - | | | | | - | InitDone | | Denied | | - | | | | | - \-----------------/ \-----------------/ | - | | -+-----------------------------+ | -|OUT: | | -| TOCLIENT_MOVEMENT | | -| TOCLIENT_ITEMDEF | | -| TOCLIENT_NODEDEF | | -| TOCLIENT_ANNOUNCE_MEDIA | | -| TOCLIENT_DETACHED_INVENTORY | | -| TOCLIENT_TIME_OF_DAY | | -+-----------------------------+ | - | | - | | - | ----------------------------------- | - v | | | - /-----------------\ v | - | | +-----------------------------+ | - | DefinitionsSent | |IN: | | - | | | TOSERVER_REQUEST_MEDIA | | - \-----------------/ | TOSERVER_RECEIVED_MEDIA | | - | +-----------------------------+ | - | ^ | | - | ----------------------------------- | - | | -+-----------------------------+ | -|IN: | | -| TOSERVER_CLIENT_READY | | -+-----------------------------+ | - | async | - v mod action | -+-----------------------------+ (ban,kick) | -|OUT: | | -| TOCLIENT_MOVE_PLAYER | | -| TOCLIENT_PRIVILEGES | | -| TOCLIENT_INVENTORY_FORMSPEC | | -| UpdateCrafting | | -| TOCLIENT_INVENTORY | | -| TOCLIENT_HP (opt) | | -| TOCLIENT_BREATH | | -| TOCLIENT_DEATHSCREEN | | -+-----------------------------+ | - | | - v | - /-----------------\ | - | |------------------------------------------------------ - | Active | - | |---------------------------------- - \-----------------/ timeout | - | +-----------------------------+ - | |OUT: | - | | TOCLIENT_DISCONNECT | - | +-----------------------------+ - | | - | v -+-----------------------------+ /-----------------\ -|IN: | | | -| TOSERVER_DISCONNECT |------------------->| Disconnecting | -+-----------------------------+ | | - \-----------------/ + | depending of the incoming packet + +--------------------------------------- + v v ++-----------------------------+ +-----------------------------+ +|IN: | |IN: | +| TOSERVER_INIT_LEGACY |----- | TOSERVER_INIT | invalid playername, ++-----------------------------+ | +-----------------------------+ password (for _LEGACY), + | | | or denied by mod + | Auth ok -------------------+--------------------------------- + v v | ++-----------------------------+ +-----------------------------+ | +|OUT: | |OUT: | | +| TOCLIENT_INIT_LEGACY | | TOCLIENT_HELLO | | ++-----------------------------+ +-----------------------------+ | + | | | + | | | + v v | + /-----------------\ /-----------------\ | + | | | | | + | AwaitingInit2 |<--------- | HelloSent | | + | | | | | | + \-----------------/ | \-----------------/ | + | | | | ++-----------------------------+ | *-----------------------------* Auth fails | +|IN: | | |Authentication, depending on |-----------------+ +| TOSERVER_INIT2 | | | packet sent by client | | ++-----------------------------+ | *-----------------------------* | + | | | | + | | | Authentication | + v | | successful | + /-----------------\ | v | + | | | +-----------------------------+ | + | InitDone | | |OUT: | | + | | | | TOCLIENT_AUTH_ACCEPT | | + \-----------------/ | +-----------------------------+ | + | | | | ++-----------------------------+ --------------------- | +|OUT: | | +| TOCLIENT_MOVEMENT | | +| TOCLIENT_ITEMDEF | | +| TOCLIENT_NODEDEF | | +| TOCLIENT_ANNOUNCE_MEDIA | | +| TOCLIENT_DETACHED_INVENTORY | | +| TOCLIENT_TIME_OF_DAY | | ++-----------------------------+ | + | | + | | + | ----------------------------- | + v | | | + /-----------------\ v | + | | +-----------------------------+ | + | DefinitionsSent | |IN: | | + | | | TOSERVER_REQUEST_MEDIA | | + \-----------------/ | TOSERVER_RECEIVED_MEDIA | | + | +-----------------------------+ | + | ^ | | + | ----------------------------- | + v | ++-----------------------------+ --------------------------------+ +|IN: | | | +| TOSERVER_CLIENT_READY | v | ++-----------------------------+ +-------------------------------+ | + | |OUT: | | + v | TOCLIENT_ACCESS_DENIED_LEGAGY | | ++-----------------------------+ +-------------------------------+ | +|OUT: | | | +| TOCLIENT_MOVE_PLAYER | v | +| TOCLIENT_PRIVILEGES | /-----------------\ | +| TOCLIENT_INVENTORY_FORMSPEC | | | | +| UpdateCrafting | | Denied | | +| TOCLIENT_INVENTORY | | | | +| TOCLIENT_HP (opt) | \-----------------/ | +| TOCLIENT_BREATH | | +| TOCLIENT_DEATHSCREEN | | ++-----------------------------+ | + | | + v | + /-----------------\ async mod action (ban, kick) | + | |--------------------------------------------------------------- + ---->| Active | + | | |---------------------------------------------- + | \-----------------/ timeout v + | | | +-----------------------------+ + | | | |OUT: | + | | | | TOCLIENT_DISCONNECT | + | | | +-----------------------------+ + | | | | + | | v v + | | +-----------------------------+ /-----------------\ + | | |IN: | | | + | | | TOSERVER_DISCONNECT |------------------->| Disconnecting | + | | +-----------------------------+ | | + | | \-----------------/ + | | any auth packet which was + | | allowed in TOCLIENT_AUTH_ACCEPT + | v + | *-----------------------------* Auth +-------------------------------+ + | |Authentication, depending on | succeeds |OUT: | + | | packet sent by client |---------->| TOCLIENT_ACCEPT_SUDO_MODE | + | *-----------------------------* +-------------------------------+ + | | | + | | Auth fails /-----------------\ + | v | | + | +-------------------------------+ | SudoMode | + | |OUT: | | | + | | TOCLIENT_DENY_SUDO_MODE | \-----------------/ + | +-------------------------------+ | + | | v + | | +-----------------------------+ + | | sets password accordingly |IN: | + -------------------+-------------------------------| TOSERVER_FIRST_SRP | + +-----------------------------+ + */ namespace con { class Connection; @@ -149,19 +173,25 @@ enum ClientState CS_Disconnecting, CS_Denied, CS_Created, - CS_InitSent, + CS_AwaitingInit2, + CS_HelloSent, CS_InitDone, CS_DefinitionsSent, - CS_Active + CS_Active, + CS_SudoMode }; enum ClientStateEvent { - CSE_Init, + CSE_Hello, + CSE_AuthAccept, + CSE_InitLegacy, CSE_GotInit2, CSE_SetDenied, CSE_SetDefinitionsSent, CSE_SetClientReady, + CSE_SudoSuccess, + CSE_SudoLeave, CSE_Disconnect }; @@ -200,10 +230,26 @@ public: // u16 net_proto_version; + /* Authentication information */ + std::string enc_pwd; + bool create_player_on_auth_success; + AuthMechanism chosen_mech; + void * auth_data; + u32 allowed_auth_mechs; + u32 allowed_sudo_mechs; + + bool isSudoMechAllowed(AuthMechanism mech) + { return allowed_sudo_mechs & mech; } + bool isMechAllowed(AuthMechanism mech) + { return allowed_auth_mechs & mech; } + RemoteClient(): peer_id(PEER_ID_INEXISTENT), serialization_version(SER_FMT_VER_INVALID), net_proto_version(0), + create_player_on_auth_success(false), + chosen_mech(AUTH_MECHANISM_NONE), + auth_data(NULL), m_time_from_building(9999), m_pending_serialization_version(SER_FMT_VER_INVALID), m_state(CS_Created), @@ -216,6 +262,7 @@ public: m_version_minor(0), m_version_patch(0), m_full_version("unknown"), + m_deployed_compression(0), m_connection_time(getTime(PRECISION_SECONDS)) { } @@ -292,13 +339,15 @@ public: void setPendingSerializationVersion(u8 version) { m_pending_serialization_version = version; } + void setDeployedCompressionMode(u16 byteFlag) + { m_deployed_compression = byteFlag; } + void confirmSerializationVersion() { serialization_version = m_pending_serialization_version; } /* get uptime */ u32 uptime(); - /* set version information */ void setVersionInfo(u8 major, u8 minor, u8 patch, std::string full) { m_version_major = major; @@ -369,6 +418,8 @@ private: std::string m_full_version; + u16 m_deployed_compression; + /* time this client was created */ @@ -387,16 +438,16 @@ public: void step(float dtime); /* get list of active client id's */ - std::list<u16> getClientIDs(ClientState min_state=CS_Active); + std::vector<u16> getClientIDs(ClientState min_state=CS_Active); /* get list of client player names */ std::vector<std::string> getPlayerNames(); /* send message to client */ - void send(u16 peer_id, u8 channelnum, SharedBuffer<u8> data, bool reliable); + void send(u16 peer_id, u8 channelnum, NetworkPacket* pkt, bool reliable); /* send to all clients */ - void sendToAll(u16 channelnum, SharedBuffer<u8> data, bool reliable); + void sendToAll(u16 channelnum, NetworkPacket* pkt, bool reliable); /* delete a client */ void DeleteClient(u16 peer_id); @@ -425,9 +476,12 @@ public: /* event to update client state */ void event(u16 peer_id, ClientStateEvent event); - /* set environment */ - void setEnv(ServerEnvironment* env) - { assert(m_env == 0); m_env = env; } + /* Set environment. Do not call this function if environment is already set */ + void setEnv(ServerEnvironment *env) + { + assert(m_env == NULL); // pre-condition + m_env = env; + } static std::string state2Name(ClientState state); @@ -457,7 +511,7 @@ private: JMutex m_env_mutex; float m_print_info_timer; - + static const char *statenames[]; }; diff --git a/src/clientmap.cpp b/src/clientmap.cpp index 2db901f22..288a12135 100644 --- a/src/clientmap.cpp +++ b/src/clientmap.cpp @@ -24,12 +24,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <matrix4.h> #include "log.h" #include "mapsector.h" -#include "main.h" // dout_client, g_settings #include "nodedef.h" #include "mapblock.h" #include "profiler.h" #include "settings.h" -#include "camera.h" // CameraModes +#include "camera.h" // CameraModes #include "util/mathconstants.h" #include <algorithm> @@ -102,34 +101,6 @@ MapSector * ClientMap::emergeSector(v2s16 p2d) return sector; } -#if 0 -void ClientMap::deSerializeSector(v2s16 p2d, std::istream &is) -{ - DSTACK(__FUNCTION_NAME); - ClientMapSector *sector = NULL; - - //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out - - core::map<v2s16, MapSector*>::Node *n = m_sectors.find(p2d); - - if(n != NULL) - { - sector = (ClientMapSector*)n->getValue(); - assert(sector->getId() == MAPSECTOR_CLIENT); - } - else - { - sector = new ClientMapSector(this, p2d); - { - //JMutexAutoLock lock(m_sector_mutex); // Bulk comment-out - m_sectors.insert(p2d, sector); - } - } - - sector->deSerialize(is); -} -#endif - void ClientMap::OnRegisterSceneNode() { if(IsVisible) @@ -161,9 +132,9 @@ static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac, else is_transparent = (f.solidness != 2); if(!is_transparent){ - count++; - if(count >= needed_count) + if(count == needed_count) return true; + count++; } step *= stepfac; } @@ -247,7 +218,7 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) continue; } - std::list< MapBlock * > sectorblocks; + MapBlockVect sectorblocks; sector->getBlocks(sectorblocks); /* @@ -256,8 +227,8 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) u32 sector_blocks_drawn = 0; - std::list< MapBlock * >::iterator i; - for(i=sectorblocks.begin(); i!=sectorblocks.end(); i++) + for(MapBlockVect::iterator i = sectorblocks.begin(); + i != sectorblocks.end(); i++) { MapBlock *block = *i; @@ -391,12 +362,12 @@ void ClientMap::updateDrawList(video::IVideoDriver* driver) struct MeshBufList { video::SMaterial m; - std::list<scene::IMeshBuffer*> bufs; + std::vector<scene::IMeshBuffer*> bufs; }; struct MeshBufListList { - std::list<MeshBufList> lists; + std::vector<MeshBufList> lists; void clear() { @@ -405,7 +376,7 @@ struct MeshBufListList void add(scene::IMeshBuffer *buf) { - for(std::list<MeshBufList>::iterator i = lists.begin(); + for(std::vector<MeshBufList>::iterator i = lists.begin(); i != lists.end(); ++i){ MeshBufList &l = *i; video::SMaterial &m = buf->getMaterial(); @@ -568,7 +539,7 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) MapBlockMesh *mapBlockMesh = block->mesh; assert(mapBlockMesh); - scene::SMesh *mesh = mapBlockMesh->getMesh(); + scene::IMesh *mesh = mapBlockMesh->getMesh(); assert(mesh); u32 c = mesh->getMeshBufferCount(); @@ -595,25 +566,20 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) } } - std::list<MeshBufList> &lists = drawbufs.lists; + std::vector<MeshBufList> &lists = drawbufs.lists; int timecheck_counter = 0; - for(std::list<MeshBufList>::iterator i = lists.begin(); - i != lists.end(); ++i) - { - { - timecheck_counter++; - if(timecheck_counter > 50) - { - timecheck_counter = 0; - int time2 = time(0); - if(time2 > time1 + 4) - { - infostream<<"ClientMap::renderMap(): " - "Rendering takes ages, returning." - <<std::endl; - return; - } + for(std::vector<MeshBufList>::iterator i = lists.begin(); + i != lists.end(); ++i) { + timecheck_counter++; + if(timecheck_counter > 50) { + timecheck_counter = 0; + int time2 = time(0); + if(time2 > time1 + 4) { + infostream << "ClientMap::renderMap(): " + "Rendering takes ages, returning." + << std::endl; + return; } } @@ -621,60 +587,14 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) driver->setMaterial(list.m); - for(std::list<scene::IMeshBuffer*>::iterator j = list.bufs.begin(); - j != list.bufs.end(); ++j) - { + for(std::vector<scene::IMeshBuffer*>::iterator j = list.bufs.begin(); + j != list.bufs.end(); ++j) { scene::IMeshBuffer *buf = *j; driver->drawMeshBuffer(buf); vertex_count += buf->getVertexCount(); meshbuffer_count++; } -#if 0 - /* - Draw the faces of the block - */ - { - //JMutexAutoLock lock(block->mesh_mutex); - MapBlockMesh *mapBlockMesh = block->mesh; - assert(mapBlockMesh); - - scene::SMesh *mesh = mapBlockMesh->getMesh(); - assert(mesh); - - u32 c = mesh->getMeshBufferCount(); - bool stuff_actually_drawn = false; - for(u32 i=0; i<c; i++) - { - scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); - const video::SMaterial& material = buf->getMaterial(); - video::IMaterialRenderer* rnd = - driver->getMaterialRenderer(material.MaterialType); - bool transparent = (rnd && rnd->isTransparent()); - // Render transparent on transparent pass and likewise. - if(transparent == is_transparent_pass) - { - if(buf->getVertexCount() == 0) - errorstream<<"Block ["<<analyze_block(block) - <<"] contains an empty meshbuf"<<std::endl; - /* - This *shouldn't* hurt too much because Irrlicht - doesn't change opengl textures if the old - material has the same texture. - */ - driver->setMaterial(buf->getMaterial()); - driver->drawMeshBuffer(buf); - vertex_count += buf->getVertexCount(); - meshbuffer_count++; - stuff_actually_drawn = true; - } - } - if(stuff_actually_drawn) - blocks_had_pass_meshbuf++; - else - blocks_without_stuff++; - } -#endif } } // ScopeProfiler diff --git a/src/clientmedia.cpp b/src/clientmedia.cpp index 434eeb248..ea11ad239 100644 --- a/src/clientmedia.cpp +++ b/src/clientmedia.cpp @@ -18,20 +18,19 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "clientmedia.h" -#include "util/serialize.h" -#include "util/string.h" #include "httpfetch.h" #include "client.h" -#include "clientserver.h" #include "filecache.h" #include "filesys.h" -#include "hex.h" -#include "sha1.h" #include "debug.h" #include "log.h" #include "porting.h" #include "settings.h" -#include "main.h" +#include "network/networkprotocol.h" +#include "util/hex.h" +#include "util/serialize.h" +#include "util/sha1.h" +#include "util/string.h" static std::string getMediaCacheDir() { @@ -72,7 +71,7 @@ ClientMediaDownloader::~ClientMediaDownloader() void ClientMediaDownloader::addFile(std::string name, std::string sha1) { - assert(!m_initial_step_done); + assert(!m_initial_step_done); // pre-condition // if name was already announced, ignore the new announcement if (m_files.count(name) != 0) { @@ -107,7 +106,7 @@ void ClientMediaDownloader::addFile(std::string name, std::string sha1) void ClientMediaDownloader::addRemoteServer(std::string baseurl) { - assert(!m_initial_step_done); + assert(!m_initial_step_done); // pre-condition #ifdef USE_CURL @@ -356,11 +355,11 @@ void ClientMediaDownloader::remoteMediaReceived( m_remote_file_transfers.erase(it); } - assert(m_files.count(name) != 0); + sanity_check(m_files.count(name) != 0); FileStatus *filestatus = m_files[name]; - assert(!filestatus->received); - assert(filestatus->current_remote >= 0); + sanity_check(!filestatus->received); + sanity_check(filestatus->current_remote >= 0); RemoteServerStatus *remote = m_remotes[filestatus->current_remote]; @@ -382,6 +381,7 @@ void ClientMediaDownloader::remoteMediaReceived( s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus) { + // Pre-conditions assert(filestatus != NULL); assert(!filestatus->received); assert(filestatus->current_remote < 0); @@ -483,12 +483,12 @@ void ClientMediaDownloader::startRemoteMediaTransfers() void ClientMediaDownloader::startConventionalTransfers(Client *client) { - assert(m_httpfetch_active == 0); + assert(m_httpfetch_active == 0); // pre-condition if (m_uncached_received_count != m_uncached_count) { // Some media files have not been received yet, use the // conventional slow method (minetest protocol) to get them - std::list<std::string> file_requests; + std::vector<std::string> file_requests; for (std::map<std::string, FileStatus*>::iterator it = m_files.begin(); it != m_files.end(); ++it) { @@ -616,7 +616,7 @@ std::string ClientMediaDownloader::serializeRequiredHashSet() it = m_files.begin(); it != m_files.end(); ++it) { if (!it->second->received) { - assert(it->second->sha1.size() == 20); + FATAL_ERROR_IF(it->second->sha1.size() != 20, "Invalid SHA1 size"); os << it->second->sha1; } } diff --git a/src/clientobject.cpp b/src/clientobject.cpp index 37f693c5e..ae1be092f 100644 --- a/src/clientobject.cpp +++ b/src/clientobject.cpp @@ -39,14 +39,13 @@ ClientActiveObject::~ClientActiveObject() removeFromScene(true); } -ClientActiveObject* ClientActiveObject::create(u8 type, IGameDef *gamedef, - ClientEnvironment *env) +ClientActiveObject* ClientActiveObject::create(ActiveObjectType type, + IGameDef *gamedef, ClientEnvironment *env) { // Find factory function std::map<u16, Factory>::iterator n; n = m_types.find(type); - if(n == m_types.end()) - { + if(n == m_types.end()) { // If factory is not found, just return. dstream<<"WARNING: ClientActiveObject: No factory for type=" <<(int)type<<std::endl; diff --git a/src/clientobject.h b/src/clientobject.h index 24150628e..be24e1388 100644 --- a/src/clientobject.h +++ b/src/clientobject.h @@ -54,6 +54,7 @@ public: 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 core::aabbox3d<f32>* getSelectionBox(){return NULL;} virtual bool getCollisionBox(aabb3f *toset){return false;} @@ -86,7 +87,7 @@ public: virtual void initialize(const std::string &data){} // Create a certain type of ClientActiveObject - static ClientActiveObject* create(u8 type, IGameDef *gamedef, + static ClientActiveObject* create(ActiveObjectType type, IGameDef *gamedef, ClientEnvironment *env); // If returns true, punch will not be sent to the server diff --git a/src/clouds.cpp b/src/clouds.cpp index 10ac8f5b3..29210e2b4 100644 --- a/src/clouds.cpp +++ b/src/clouds.cpp @@ -21,10 +21,20 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "noise.h" #include "constants.h" #include "debug.h" -#include "main.h" // For g_profiler and g_settings #include "profiler.h" #include "settings.h" + +// Menu clouds are created later +class Clouds; +Clouds *g_menuclouds = NULL; +irr::scene::ISceneManager *g_menucloudsmgr = NULL; + +static void cloud_3d_setting_changed(const std::string &settingname, void *data) +{ + ((Clouds *)data)->readSettings(); +} + Clouds::Clouds( scene::ISceneNode* parent, scene::ISceneManager* mgr, @@ -47,8 +57,10 @@ Clouds::Clouds( //m_material.MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA; m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; - m_cloud_y = BS * (cloudheight ? cloudheight : - g_settings->getS16("cloud_height")); + m_passed_cloud_y = cloudheight; + readSettings(); + g_settings->registerChangedCallback("enable_3d_clouds", + &cloud_3d_setting_changed, this); m_box = core::aabbox3d<f32>(-BS*1000000,m_cloud_y-BS,-BS*1000000, BS*1000000,m_cloud_y+BS,BS*1000000); @@ -57,6 +69,8 @@ Clouds::Clouds( Clouds::~Clouds() { + g_settings->deregisterChangedCallback("enable_3d_clouds", + &cloud_3d_setting_changed, this); } void Clouds::OnRegisterSceneNode() @@ -82,26 +96,24 @@ void Clouds::render() ScopeProfiler sp(g_profiler, "Rendering of clouds, avg", SPT_AVG); - bool enable_3d = g_settings->getBool("enable_3d_clouds"); - int num_faces_to_draw = enable_3d ? 6 : 1; + int num_faces_to_draw = m_enable_3d ? 6 : 1; - m_material.setFlag(video::EMF_BACK_FACE_CULLING, enable_3d); + m_material.setFlag(video::EMF_BACK_FACE_CULLING, m_enable_3d); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); driver->setMaterial(m_material); /* - Clouds move from X+ towards X- + Clouds move from Z+ towards Z- */ - const s16 cloud_radius_i = 12; - const float cloud_size = BS*64; - const v2f cloud_speed(0, -BS*2); + const float cloud_size = BS * 64; + const v2f cloud_speed(0, -BS * 2); - const float cloud_full_radius = cloud_size * cloud_radius_i; + const float cloud_full_radius = cloud_size * m_cloud_radius_i; // Position of cloud noise origin in world coordinates - v2f world_cloud_origin_pos_f = m_time*cloud_speed; + v2f world_cloud_origin_pos_f = m_time * cloud_speed; // Position of cloud noise origin from the camera v2f cloud_origin_from_camera_f = world_cloud_origin_pos_f - m_camera_pos; // The center point of drawing in the noise @@ -160,55 +172,50 @@ void Clouds::render() // Read noise - bool *grid = new bool[cloud_radius_i*2*cloud_radius_i*2]; + bool *grid = new bool[m_cloud_radius_i * 2 * m_cloud_radius_i * 2]; - for(s16 zi=-cloud_radius_i; zi<cloud_radius_i; zi++) - for(s16 xi=-cloud_radius_i; xi<cloud_radius_i; xi++) - { - u32 i = (zi+cloud_radius_i)*cloud_radius_i*2 + xi+cloud_radius_i; - - v2s16 p_in_noise_i( - xi+center_of_drawing_in_noise_i.X, - zi+center_of_drawing_in_noise_i.Y - ); - -#if 0 - double noise = noise2d_perlin_abs( - (float)p_in_noise_i.X*cloud_size/BS/200, - (float)p_in_noise_i.Y*cloud_size/BS/200, - m_seed, 3, 0.4); - grid[i] = (noise >= 0.80); -#endif -#if 1 - double noise = noise2d_perlin( - (float)p_in_noise_i.X*cloud_size/BS/200, - (float)p_in_noise_i.Y*cloud_size/BS/200, - m_seed, 3, 0.5); - grid[i] = (noise >= 0.4); -#endif + float cloud_size_noise = cloud_size / BS / 200; + + for(s16 zi = -m_cloud_radius_i; zi < m_cloud_radius_i; zi++) { + u32 si = (zi + m_cloud_radius_i) * m_cloud_radius_i * 2 + m_cloud_radius_i; + + for (s16 xi = -m_cloud_radius_i; xi < m_cloud_radius_i; xi++) { + u32 i = si + xi; + + v2s16 p_in_noise_i( + xi + center_of_drawing_in_noise_i.X, + zi + center_of_drawing_in_noise_i.Y + ); + + double noise = noise2d_perlin( + (float)p_in_noise_i.X * cloud_size_noise, + (float)p_in_noise_i.Y * cloud_size_noise, + m_seed, 3, 0.5); + grid[i] = (noise >= 0.4); + } } #define GETINDEX(x, z, radius) (((z)+(radius))*(radius)*2 + (x)+(radius)) #define INAREA(x, z, radius) \ ((x) >= -(radius) && (x) < (radius) && (z) >= -(radius) && (z) < (radius)) - for(s16 zi0=-cloud_radius_i; zi0<cloud_radius_i; zi0++) - for(s16 xi0=-cloud_radius_i; xi0<cloud_radius_i; xi0++) + for (s16 zi0= -m_cloud_radius_i; zi0 < m_cloud_radius_i; zi0++) + for (s16 xi0= -m_cloud_radius_i; xi0 < m_cloud_radius_i; xi0++) { s16 zi = zi0; s16 xi = xi0; // Draw from front to back (needed for transparency) /*if(zi <= 0) - zi = -cloud_radius_i - zi; + zi = -m_cloud_radius_i - zi; if(xi <= 0) - xi = -cloud_radius_i - xi;*/ + xi = -m_cloud_radius_i - xi;*/ // Draw from back to front if(zi >= 0) - zi = cloud_radius_i - zi - 1; + zi = m_cloud_radius_i - zi - 1; if(xi >= 0) - xi = cloud_radius_i - xi - 1; + xi = m_cloud_radius_i - xi - 1; - u32 i = GETINDEX(xi, zi, cloud_radius_i); + u32 i = GETINDEX(xi, zi, m_cloud_radius_i); if(grid[i] == false) continue; @@ -230,8 +237,8 @@ void Clouds::render() }*/ f32 rx = cloud_size/2; - f32 ry = 8*BS; - f32 rz = cloud_size/2; + f32 ry = 8 * BS; + f32 rz = cloud_size / 2; for(int i=0; i<num_faces_to_draw; i++) { @@ -247,8 +254,8 @@ void Clouds::render() v[3].Pos.set( rx, ry,-rz); break; case 1: // back - if(INAREA(xi, zi-1, cloud_radius_i)){ - u32 j = GETINDEX(xi, zi-1, cloud_radius_i); + if (INAREA(xi, zi - 1, m_cloud_radius_i)) { + u32 j = GETINDEX(xi, zi - 1, m_cloud_radius_i); if(grid[j]) continue; } @@ -262,8 +269,8 @@ void Clouds::render() v[3].Pos.set(-rx,-ry,-rz); break; case 2: //right - if(INAREA(xi+1, zi, cloud_radius_i)){ - u32 j = GETINDEX(xi+1, zi, cloud_radius_i); + if (INAREA(xi + 1, zi, m_cloud_radius_i)) { + u32 j = GETINDEX(xi+1, zi, m_cloud_radius_i); if(grid[j]) continue; } @@ -277,8 +284,8 @@ void Clouds::render() v[3].Pos.set( rx,-ry,-rz); break; case 3: // front - if(INAREA(xi, zi+1, cloud_radius_i)){ - u32 j = GETINDEX(xi, zi+1, cloud_radius_i); + if (INAREA(xi, zi + 1, m_cloud_radius_i)) { + u32 j = GETINDEX(xi, zi + 1, m_cloud_radius_i); if(grid[j]) continue; } @@ -292,8 +299,8 @@ void Clouds::render() v[3].Pos.set( rx,-ry, rz); break; case 4: // left - if(INAREA(xi-1, zi, cloud_radius_i)){ - u32 j = GETINDEX(xi-1, zi, cloud_radius_i); + if (INAREA(xi-1, zi, m_cloud_radius_i)) { + u32 j = GETINDEX(xi-1, zi, m_cloud_radius_i); if(grid[j]) continue; } @@ -349,3 +356,11 @@ void Clouds::update(v2f camera_p, video::SColorf color) //dstream<<"m_brightness="<<m_brightness<<std::endl; } +void Clouds::readSettings() +{ + m_cloud_y = BS * (m_passed_cloud_y ? m_passed_cloud_y : + g_settings->getS16("cloud_height")); + m_cloud_radius_i = g_settings->getU16("cloud_radius"); + m_enable_3d = g_settings->getBool("enable_3d_clouds"); +} + diff --git a/src/clouds.h b/src/clouds.h index a9e58e0f0..195f48de0 100644 --- a/src/clouds.h +++ b/src/clouds.h @@ -24,6 +24,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iostream> #include "constants.h" +// Menu clouds +class Clouds; +extern Clouds *g_menuclouds; + +// Scene manager used for menu clouds +namespace irr{namespace scene{class ISceneManager;}} +extern irr::scene::ISceneManager *g_menucloudsmgr; + class Clouds : public scene::ISceneNode { public: @@ -75,10 +83,15 @@ public: BS * 1000000, m_cloud_y + BS - BS * camera_offset.Y, BS * 1000000); } + void readSettings(); + private: video::SMaterial m_material; core::aabbox3d<f32> m_box; + s16 m_passed_cloud_y; float m_cloud_y; + u16 m_cloud_radius_i; + bool m_enable_3d; video::SColorf m_color; u32 m_seed; v2f m_camera_pos; diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index e111a650d..04f368594 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -3,31 +3,27 @@ #ifndef CMAKE_CONFIG_H #define CMAKE_CONFIG_H -#define CMAKE_PROJECT_NAME "@PROJECT_NAME@" -#define CMAKE_VERSION_STRING "@VERSION_STRING@" -#define CMAKE_PRODUCT_VERSION_STRING "@VERSION_MAJOR@.@VERSION_MINOR@" -#define CMAKE_RUN_IN_PLACE @RUN_IN_PLACE@ -#define CMAKE_USE_GETTEXT @USE_GETTEXT@ -#define CMAKE_USE_CURL @USE_CURL@ -#define CMAKE_USE_SOUND @USE_SOUND@ -#define CMAKE_USE_FREETYPE @USE_FREETYPE@ -#define CMAKE_STATIC_SHAREDIR "@SHAREDIR@" -#define CMAKE_USE_LEVELDB @USE_LEVELDB@ -#define CMAKE_USE_LUAJIT @USE_LUAJIT@ -#define CMAKE_USE_REDIS @USE_REDIS@ -#define CMAKE_VERSION_MAJOR @VERSION_MAJOR@ -#define CMAKE_VERSION_MINOR @VERSION_MINOR@ -#define CMAKE_VERSION_PATCH @VERSION_PATCH@ -#define CMAKE_VERSION_PATCH_ORIG @VERSION_PATCH_ORIG@ -#define CMAKE_VERSION_EXTRA_STRING "@VERSION_EXTRA@" -#define CMAKE_HAVE_ENDIAN_H @HAVE_ENDIAN_H@ - -#ifdef NDEBUG - #define CMAKE_BUILD_TYPE "Release" -#else - #define CMAKE_BUILD_TYPE "Debug" -#endif -#define CMAKE_BUILD_INFO "BUILD_TYPE=" CMAKE_BUILD_TYPE " RUN_IN_PLACE=@RUN_IN_PLACE@ USE_GETTEXT=@USE_GETTEXT@ USE_SOUND=@USE_SOUND@ USE_CURL=@USE_CURL@ USE_FREETYPE=@USE_FREETYPE@ USE_LUAJIT=@USE_LUAJIT@ STATIC_SHAREDIR=@SHAREDIR@" +#define PROJECT_NAME "@PROJECT_NAME@" +#define PROJECT_NAME_C "@PROJECT_NAME_CAPITALIZED@" +#define VERSION_MAJOR @VERSION_MAJOR@ +#define VERSION_MINOR @VERSION_MINOR@ +#define VERSION_PATCH @VERSION_PATCH@ +#define VERSION_EXTRA "@VERSION_EXTRA@" +#define VERSION_STRING "@VERSION_STRING@" +#define PRODUCT_VERSION_STRING "@VERSION_MAJOR@.@VERSION_MINOR@" +#define STATIC_SHAREDIR "@SHAREDIR@" +#define BUILD_TYPE "@CMAKE_BUILD_TYPE@" +#cmakedefine01 RUN_IN_PLACE +#cmakedefine01 USE_GETTEXT +#cmakedefine01 USE_CURL +#cmakedefine01 USE_SOUND +#cmakedefine01 USE_FREETYPE +#cmakedefine01 USE_LEVELDB +#cmakedefine01 USE_LUAJIT +#cmakedefine01 USE_SPATIAL +#cmakedefine01 USE_SYSTEM_GMP +#cmakedefine01 USE_REDIS +#cmakedefine01 HAVE_ENDIAN_H #endif diff --git a/src/cmake_config_githash.h.in b/src/cmake_config_githash.h.in index 4d5fcd60f..c72960c9f 100644 --- a/src/cmake_config_githash.h.in +++ b/src/cmake_config_githash.h.in @@ -4,7 +4,7 @@ #ifndef CMAKE_CONFIG_GITHASH_H #define CMAKE_CONFIG_GITHASH_H -#define CMAKE_VERSION_GITHASH "@VERSION_GITHASH@" +#define VERSION_GITHASH "@VERSION_GITHASH@" #endif diff --git a/src/collision.cpp b/src/collision.cpp index 9e0c85531..6afc79ab7 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -28,7 +28,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <vector> #include <set> #include "util/timetaker.h" -#include "main.h" // g_profiler #include "profiler.h" // float error is 10 - 9.96875 = 0.03125 @@ -173,7 +172,7 @@ bool wouldCollideWithCeiling( { //TimeTaker tt("wouldCollideWithCeiling"); - assert(y_increase >= 0); + assert(y_increase >= 0); // pre-condition for(std::vector<aabb3f>::const_iterator i = staticboxes.begin(); @@ -300,16 +299,14 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, /* add object boxes to cboxes */ - std::list<ActiveObject*> objects; + std::vector<ActiveObject*> objects; #ifndef SERVER ClientEnvironment *c_env = dynamic_cast<ClientEnvironment*>(env); - if (c_env != 0) - { + if (c_env != 0) { f32 distance = speed_f.getLength(); std::vector<DistanceSortedActiveObject> clientobjects; c_env->getActiveObjects(pos_f,distance * 1.5,clientobjects); - for (size_t i=0; i < clientobjects.size(); i++) - { + for (size_t i=0; i < clientobjects.size(); i++) { if ((self == 0) || (self != clientobjects[i].obj)) { objects.push_back((ActiveObject*)clientobjects[i].obj); } @@ -319,12 +316,11 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, #endif { ServerEnvironment *s_env = dynamic_cast<ServerEnvironment*>(env); - if (s_env != 0) - { + if (s_env != 0) { f32 distance = speed_f.getLength(); - std::set<u16> s_objects = s_env->getObjectsInsideRadius(pos_f,distance * 1.5); - for (std::set<u16>::iterator iter = s_objects.begin(); iter != s_objects.end(); iter++) - { + std::vector<u16> s_objects; + s_env->getObjectsInsideRadius(s_objects, pos_f, distance * 1.5); + for (std::vector<u16>::iterator iter = s_objects.begin(); iter != s_objects.end(); iter++) { ServerActiveObject *current = s_env->getActiveObject(*iter); if ((self == 0) || (self != current)) { objects.push_back((ActiveObject*)current); @@ -333,16 +329,14 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, } } - for (std::list<ActiveObject*>::const_iterator iter = objects.begin();iter != objects.end(); ++iter) - { + for (std::vector<ActiveObject*>::const_iterator iter = objects.begin(); + iter != objects.end(); ++iter) { ActiveObject *object = *iter; - if (object != NULL) - { + if (object != NULL) { aabb3f object_collisionbox; if (object->getCollisionBox(&object_collisionbox) && - object->collideWithObjects()) - { + object->collideWithObjects()) { cboxes.push_back(object_collisionbox); is_unloaded.push_back(false); is_step_up.push_back(false); @@ -354,11 +348,11 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, } } //tt3 - assert(cboxes.size() == is_unloaded.size()); - assert(cboxes.size() == is_step_up.size()); - assert(cboxes.size() == bouncy_values.size()); - assert(cboxes.size() == node_positions.size()); - assert(cboxes.size() == is_object.size()); + assert(cboxes.size() == is_unloaded.size()); // post-condition + assert(cboxes.size() == is_step_up.size()); // post-condition + assert(cboxes.size() == bouncy_values.size()); // post-condition + assert(cboxes.size() == node_positions.size()); // post-condition + assert(cboxes.size() == is_object.size()); // post-condition /* Collision detection @@ -373,7 +367,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, //f32 d = 0.15*BS; // This should always apply, otherwise there are glitches - assert(d > pos_max_d); + assert(d > pos_max_d); // invariant int loopcount = 0; @@ -464,7 +458,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, pos_f += speed_f * nearest_dtime; dtime -= nearest_dtime; } - + bool is_collision = true; if(is_unloaded[nearest_boxindex]) is_collision = false; @@ -567,76 +561,3 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, return result; } - -#if 0 -// This doesn't seem to work and isn't used -collisionMoveResult collisionMovePrecise(Map *map, IGameDef *gamedef, - f32 pos_max_d, const aabb3f &box_0, - f32 stepheight, f32 dtime, - v3f &pos_f, v3f &speed_f, v3f &accel_f) -{ - //TimeTaker tt("collisionMovePrecise"); - ScopeProfiler sp(g_profiler, "collisionMovePrecise avg", SPT_AVG); - - collisionMoveResult final_result; - - // If there is no speed, there are no collisions - if(speed_f.getLength() == 0) - return final_result; - - // Don't allow overly huge dtime - if(dtime > 2.0) - dtime = 2.0; - - f32 dtime_downcount = dtime; - - u32 loopcount = 0; - do - { - loopcount++; - - // Maximum time increment (for collision detection etc) - // time = distance / speed - f32 dtime_max_increment = 1.0; - if(speed_f.getLength() != 0) - dtime_max_increment = pos_max_d / speed_f.getLength(); - - // Maximum time increment is 10ms or lower - if(dtime_max_increment > 0.01) - dtime_max_increment = 0.01; - - f32 dtime_part; - if(dtime_downcount > dtime_max_increment) - { - dtime_part = dtime_max_increment; - dtime_downcount -= dtime_part; - } - else - { - dtime_part = dtime_downcount; - /* - Setting this to 0 (no -=dtime_part) disables an infinite loop - when dtime_part is so small that dtime_downcount -= dtime_part - does nothing - */ - dtime_downcount = 0; - } - - collisionMoveResult result = collisionMoveSimple(map, gamedef, - pos_max_d, box_0, stepheight, dtime_part, - pos_f, speed_f, accel_f); - - if(result.touching_ground) - final_result.touching_ground = true; - if(result.collides) - final_result.collides = true; - if(result.collides_xz) - final_result.collides_xz = true; - if(result.standing_on_unloaded) - final_result.standing_on_unloaded = true; - } - while(dtime_downcount > 0.001); - - return final_result; -} -#endif diff --git a/src/collision.h b/src/collision.h index 32086aae3..fc4187eda 100644 --- a/src/collision.h +++ b/src/collision.h @@ -75,15 +75,6 @@ collisionMoveResult collisionMoveSimple(Environment *env,IGameDef *gamedef, v3f &accel_f,ActiveObject* self=0, bool collideWithObjects=true); -#if 0 -// This doesn't seem to work and isn't used -// Moves using as many iterations as needed -collisionMoveResult collisionMovePrecise(Map *map, IGameDef *gamedef, - f32 pos_max_d, const aabb3f &box_0, - f32 stepheight, f32 dtime, - v3f &pos_f, v3f &speed_f, v3f &accel_f); -#endif - // Helper function: // 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 diff --git a/src/config.h b/src/config.h index b07aa5d22..0955ea8f5 100644 --- a/src/config.h +++ b/src/config.h @@ -6,82 +6,37 @@ #ifndef CONFIG_H #define CONFIG_H -#define PROJECT_NAME "Minetest" -#define RUN_IN_PLACE 0 -#define STATIC_SHAREDIR "" +#define STRINGIFY(x) #x +#define STR(x) STRINGIFY(x) -#define USE_GETTEXT 0 -#ifndef USE_SOUND - #define USE_SOUND 0 -#endif - -#ifndef USE_CURL - #define USE_CURL 0 -#endif - -#ifndef USE_FREETYPE - #define USE_FREETYPE 0 -#endif - -#ifndef USE_LEVELDB - #define USE_LEVELDB 0 -#endif - -#ifndef USE_LUAJIT - #define USE_LUAJIT 0 -#endif - -#ifndef USE_REDIS - #define USE_REDIS 0 -#endif - -#define HAVE_ENDIAN_H 0 - -#ifdef USE_CMAKE_CONFIG_H +#if defined USE_CMAKE_CONFIG_H #include "cmake_config.h" - #undef PROJECT_NAME - #define PROJECT_NAME CMAKE_PROJECT_NAME - #undef RUN_IN_PLACE - #define RUN_IN_PLACE CMAKE_RUN_IN_PLACE - #undef USE_GETTEXT - #define USE_GETTEXT CMAKE_USE_GETTEXT - #undef USE_SOUND - #define USE_SOUND CMAKE_USE_SOUND - #undef USE_CURL - #define USE_CURL CMAKE_USE_CURL - #undef USE_FREETYPE - #define USE_FREETYPE CMAKE_USE_FREETYPE - #undef STATIC_SHAREDIR - #define STATIC_SHAREDIR CMAKE_STATIC_SHAREDIR - #undef USE_LEVELDB - #define USE_LEVELDB CMAKE_USE_LEVELDB - #undef USE_LUAJIT - #define USE_LUAJIT CMAKE_USE_LUAJIT - #undef USE_REDIS - #define USE_REDIS CMAKE_USE_REDIS - #undef VERSION_MAJOR - #define VERSION_MAJOR CMAKE_VERSION_MAJOR - #undef VERSION_MINOR - #define VERSION_MINOR CMAKE_VERSION_MINOR - #undef VERSION_PATCH - #define VERSION_PATCH CMAKE_VERSION_PATCH - #undef VERSION_PATCH_ORIG - #define VERSION_PATCH_ORIG CMAKE_VERSION_PATCH_ORIG - #undef VERSION_STRING - #define VERSION_STRING CMAKE_VERSION_STRING - #undef PRODUCT_VERSION_STRING - #define PRODUCT_VERSION_STRING CMAKE_PRODUCT_VERSION_STRING - #undef VERSION_EXTRA_STRING - #define VERSION_EXTRA_STRING CMAKE_VERSION_EXTRA_STRING - #undef HAVE_ENDIAN_H - #define HAVE_ENDIAN_H CMAKE_HAVE_ENDIAN_H -#endif - -#ifdef __ANDROID__ +#elif defined (__ANDROID__) || defined (ANDROID) + #define PROJECT_NAME "minetest" + #define PROJECT_NAME_C "Minetest" + #define STATIC_SHAREDIR "" #include "android_version.h" - #define VERSION_STRING CMAKE_VERSION_STRING -#endif + #ifdef NDEBUG + #define BUILD_TYPE "Release" + #else + #define BUILD_TYPE "Debug" + #endif +#else + #ifdef NDEBUG + #define BUILD_TYPE "Release" + #else + #define BUILD_TYPE "Debug" + #endif +#endif + +#define BUILD_INFO "BUILD_TYPE=" BUILD_TYPE \ + " RUN_IN_PLACE=" STR(RUN_IN_PLACE) \ + " USE_GETTEXT=" STR(USE_GETTEXT) \ + " USE_SOUND=" STR(USE_SOUND) \ + " USE_CURL=" STR(USE_CURL) \ + " USE_FREETYPE=" STR(USE_FREETYPE) \ + " USE_LUAJIT=" STR(USE_LUAJIT) \ + " STATIC_SHAREDIR=" STR(STATIC_SHAREDIR) #endif - diff --git a/src/constants.h b/src/constants.h index d7163bf68..b606fc4fa 100644 --- a/src/constants.h +++ b/src/constants.h @@ -64,7 +64,8 @@ with this program; if not, write to the Free Software Foundation, Inc., // The absolute working limit is (2^15 - viewing_range). // I really don't want to make every algorithm to check if it's going near // the limit or not, so this is lower. -#define MAP_GENERATION_LIMIT (31000) +// This is the maximum value the setting map_generation_limit can be +#define MAX_MAP_GENERATION_LIMIT (31000) // Size of node in floating-point units // The original idea behind this is to disallow plain casts between @@ -97,6 +98,12 @@ with this program; if not, write to the Free Software Foundation, Inc., // TODO: Use case-insensitive player names instead of this hack. #define PLAYER_FILE_ALTERNATE_TRIES 1000 +// For screenshots a serial number is appended to the filename + datetimestamp +// if filename + datetimestamp is not unique. +// This is the maximum number of attempts to try and add a serial to the end of +// the file attempting to ensure a unique filename +#define SCREENSHOT_MAX_SERIAL_TRIES 1000 + /* GUI related things */ @@ -110,4 +117,3 @@ with this program; if not, write to the Free Software Foundation, Inc., #define DEFAULT_FONT_SIZE (10) #endif - diff --git a/src/content_abm.cpp b/src/content_abm.cpp index 1ee41b2ec..8694ef981 100644 --- a/src/content_abm.cpp +++ b/src/content_abm.cpp @@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_sao.h" #include "settings.h" #include "mapblock.h" // For getNodeBlockPos -#include "main.h" // for g_settings #include "map.h" #include "scripting_game.h" #include "log.h" diff --git a/src/content_cao.cpp b/src/content_cao.cpp index 6d41b2749..0293b7983 100644 --- a/src/content_cao.cpp +++ b/src/content_cao.cpp @@ -27,14 +27,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" // For IntervalLimiter #include "util/serialize.h" #include "util/mathconstants.h" -#include "tile.h" +#include "client/tile.h" #include "environment.h" #include "collision.h" #include "settings.h" #include "serialization.h" // For decompressZlib #include "gamedef.h" #include "clientobject.h" -#include "content_object.h" #include "mesh.h" #include "itemdef.h" #include "tool.h" @@ -43,7 +42,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "localplayer.h" #include "map.h" -#include "main.h" // g_settings #include "camera.h" // CameraModes #include "wieldmesh.h" #include "log.h" @@ -144,12 +142,12 @@ class TestCAO : public ClientActiveObject public: TestCAO(IGameDef *gamedef, ClientEnvironment *env); virtual ~TestCAO(); - - u8 getType() const + + ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_TEST; } - + static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env); void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, @@ -194,9 +192,9 @@ void TestCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, { if(m_node != NULL) return; - + //video::IVideoDriver* driver = smgr->getVideoDriver(); - + scene::SMesh *mesh = new scene::SMesh(); scene::IMeshBuffer *buf = new scene::SMeshBuffer(); video::SColor c(255,255,255,255); @@ -212,7 +210,7 @@ void TestCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, // Set material buf->getMaterial().setFlag(video::EMF_LIGHTING, false); buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); - buf->getMaterial().setTexture(0, tsrc->getTexture("rat.png")); + buf->getMaterial().setTexture(0, tsrc->getTextureForMesh("rat.png")); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); buf->getMaterial().setFlag(video::EMF_FOG_ENABLE, true); buf->getMaterial().MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; @@ -288,12 +286,12 @@ class ItemCAO : public ClientActiveObject public: ItemCAO(IGameDef *gamedef, ClientEnvironment *env); virtual ~ItemCAO(); - - u8 getType() const + + ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_ITEM; } - + static ClientActiveObject* create(IGameDef *gamedef, ClientEnvironment *env); void addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, @@ -310,12 +308,12 @@ public: void processMessage(const std::string &data); void initialize(const std::string &data); - + core::aabbox3d<f32>* getSelectionBox() {return &m_selection_box;} v3f getPosition() {return m_position;} - + std::string infoText() {return m_infotext;} @@ -359,9 +357,9 @@ void ItemCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, { if(m_node != NULL) return; - + //video::IVideoDriver* driver = smgr->getVideoDriver(); - + scene::SMesh *mesh = new scene::SMesh(); scene::IMeshBuffer *buf = new scene::SMeshBuffer(); video::SColor c(255,255,255,255); @@ -471,7 +469,7 @@ void ItemCAO::updateTexture() <<": error deSerializing itemstring \"" <<m_itemstring<<std::endl; } - + // Set meshbuffer texture m_node->getMaterial(0).setTexture(0, texture); } @@ -516,7 +514,7 @@ void ItemCAO::processMessage(const std::string &data) void ItemCAO::initialize(const std::string &data) { infostream<<"ItemCAO: Got init data"<<std::endl; - + { std::istringstream is(data, std::ios::binary); // version @@ -529,7 +527,7 @@ void ItemCAO::initialize(const std::string &data) // itemstring m_itemstring = deSerializeString(is); } - + updateNodePos(); updateInfoText(); } @@ -545,7 +543,6 @@ GenericCAO::GenericCAO(IGameDef *gamedef, ClientEnvironment *env): // m_is_player(false), m_is_local_player(false), - m_id(0), // m_smgr(NULL), m_irr(NULL), @@ -554,6 +551,7 @@ GenericCAO::GenericCAO(IGameDef *gamedef, ClientEnvironment *env): m_animated_meshnode(NULL), m_wield_meshnode(NULL), m_spritenode(NULL), + m_nametag_color(video::SColor(255, 255, 255, 255)), m_textnode(NULL), m_position(v3f(0,10*BS,0)), m_velocity(v3f(0,0,0)), @@ -567,6 +565,7 @@ GenericCAO::GenericCAO(IGameDef *gamedef, ClientEnvironment *env): m_animation_range(v2s32(0,0)), m_animation_speed(15), m_animation_blend(0), + m_animation_loop(true), m_bone_position(std::map<std::string, core::vector2d<v3f> >()), m_attachment_bone(""), m_attachment_position(v3f(0,0,0)), @@ -728,6 +727,16 @@ scene::IBillboardSceneNode* GenericCAO::getSpriteSceneNode() return m_spritenode; } +void GenericCAO::setChildrenVisible(bool toset) +{ + for (std::vector<u16>::size_type i = 0; i < m_children.size(); i++) { + GenericCAO *obj = m_env->getGenericCAO(m_children[i]); + if (obj) { + obj->setVisible(toset); + } + } +} + void GenericCAO::setAttachments() { updateAttachments(); @@ -737,7 +746,7 @@ ClientActiveObject* GenericCAO::getParent() { ClientActiveObject *obj = NULL; - u16 attached_id = m_env->m_attachements[getId()]; + u16 attached_id = m_env->attachement_parent_ids[getId()]; if ((attached_id != 0) && (attached_id != getId())) { @@ -749,18 +758,17 @@ ClientActiveObject* GenericCAO::getParent() void GenericCAO::removeFromScene(bool permanent) { // Should be true when removing the object permanently and false when refreshing (eg: updating visuals) - if((m_env != NULL) && (permanent)) + if((m_env != NULL) && (permanent)) { - for(std::vector<u16>::iterator ci = m_children.begin(); - ci != m_children.end(); ci++) - { - if (m_env->m_attachements[*ci] == getId()) { - m_env->m_attachements[*ci] = 0; + for (std::vector<u16>::size_type i = 0; i < m_children.size(); i++) { + u16 ci = m_children[i]; + if (m_env->attachement_parent_ids[ci] == getId()) { + m_env->attachement_parent_ids[ci] = 0; } } - m_env->m_attachements[getId()] = 0; - + m_env->attachement_parent_ids[getId()] = 0; + LocalPlayer* player = m_env->getLocalPlayer(); if (this == player->parent) { player->parent = NULL; @@ -823,7 +831,7 @@ void GenericCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, NULL, v2f(1, 1), v3f(0,0,0), -1); m_spritenode->grab(); m_spritenode->setMaterialTexture(0, - tsrc->getTexture("unknown_node.png")); + tsrc->getTextureForMesh("unknown_node.png")); m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); @@ -899,7 +907,7 @@ void GenericCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, m_meshnode = smgr->addMeshSceneNode(mesh, NULL); m_meshnode->grab(); mesh->drop(); - + m_meshnode->setScale(v3f(m_prop.visual_size.X, m_prop.visual_size.Y, m_prop.visual_size.X)); @@ -962,9 +970,9 @@ void GenericCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, if (node && m_is_player && !m_is_local_player) { // Add a text node for showing the name gui::IGUIEnvironment* gui = irr->getGUIEnvironment(); - std::wstring wname = narrow_to_wide(m_name); - m_textnode = smgr->addTextSceneNode(gui->getBuiltInFont(), - wname.c_str(), video::SColor(255,255,255,255), node); + std::wstring wname = utf8_to_wide(m_name); + m_textnode = smgr->addTextSceneNode(gui->getSkin()->getFont(), + wname.c_str(), m_nametag_color, node); m_textnode->grab(); m_textnode->setPosition(v3f(0, BS*1.1, 0)); } @@ -977,19 +985,37 @@ void GenericCAO::addToScene(scene::ISceneManager *smgr, ITextureSource *tsrc, void GenericCAO::updateLight(u8 light_at_pos) { + // Don't update light of attached one + if (getParent() != NULL) { + return; + } + + updateLightNoCheck(light_at_pos); + + // Update light of all children + for (std::vector<u16>::size_type i = 0; i < m_children.size(); i++) { + ClientActiveObject *obj = m_env->getActiveObject(m_children[i]); + if (obj) { + obj->updateLightNoCheck(light_at_pos); + } + } +} + +void GenericCAO::updateLightNoCheck(u8 light_at_pos) +{ u8 li = decode_light(light_at_pos); - if(li != m_last_light) - { + if (li != m_last_light) { m_last_light = li; video::SColor color(255,li,li,li); - if(m_meshnode) + if (m_meshnode) { setMeshColor(m_meshnode->getMesh(), color); - if(m_animated_meshnode) + } else if (m_animated_meshnode) { setMeshColor(m_animated_meshnode->getMesh(), color); - if(m_wield_meshnode) + } else if (m_wield_meshnode) { m_wield_meshnode->setColor(color); - if(m_spritenode) + } else if (m_spritenode) { m_spritenode->setColor(color); + } } } @@ -1015,7 +1041,7 @@ void GenericCAO::updateNodePos() } } } - + void GenericCAO::step(float dtime, ClientEnvironment *env) { // Handel model of local player instantly to prevent lags @@ -1101,7 +1127,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) for(std::vector<u16>::iterator ci = m_children.begin(); ci != m_children.end();) { - if (m_env->m_attachements[*ci] != getId()) { + if (m_env->attachement_parent_ids[*ci] != getId()) { ci = m_children.erase(ci); continue; } @@ -1118,11 +1144,9 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) addToScene(m_smgr, m_gamedef->tsrc(), m_irr); // Attachments, part 2: Now that the parent has been refreshed, put its attachments back - for(std::vector<u16>::iterator ci = m_children.begin(); - ci != m_children.end(); ci++) - { + for (std::vector<u16>::size_type i = 0; i < m_children.size(); i++) { // Get the object of the child - ClientActiveObject *obj = m_env->getActiveObject(*ci); + ClientActiveObject *obj = m_env->getActiveObject(m_children[i]); if (obj) obj->setAttachments(); } @@ -1300,7 +1324,7 @@ void GenericCAO::updateTextures(const std::string &mod) texturestring = m_prop.textures[0]; texturestring += mod; m_spritenode->setMaterialTexture(0, - tsrc->getTexture(texturestring)); + tsrc->getTextureForMesh(texturestring)); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest @@ -1328,7 +1352,7 @@ void GenericCAO::updateTextures(const std::string &mod) if(texturestring == "") continue; // Empty texture string means don't modify that material texturestring += mod; - video::ITexture* texture = tsrc->getTexture(texturestring); + video::ITexture* texture = tsrc->getTextureForMesh(texturestring); if(!texture) { errorstream<<"GenericCAO::updateTextures(): Could not load texture "<<texturestring<<std::endl; @@ -1377,7 +1401,7 @@ void GenericCAO::updateTextures(const std::string &mod) material.setFlag(video::EMF_LIGHTING, false); material.setFlag(video::EMF_BILINEAR_FILTER, false); material.setTexture(0, - tsrc->getTexture(texturestring)); + tsrc->getTextureForMesh(texturestring)); material.getTextureMatrix(0).makeIdentity(); // This allows setting per-material colors. However, until a real lighting @@ -1405,7 +1429,7 @@ void GenericCAO::updateTextures(const std::string &mod) tname += mod; scene::IMeshBuffer *buf = mesh->getMeshBuffer(0); buf->getMaterial().setTexture(0, - tsrc->getTexture(tname)); + tsrc->getTextureForMesh(tname)); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest @@ -1430,7 +1454,7 @@ void GenericCAO::updateTextures(const std::string &mod) tname += mod; scene::IMeshBuffer *buf = mesh->getMeshBuffer(1); buf->getMaterial().setTexture(0, - tsrc->getTexture(tname)); + tsrc->getTextureForMesh(tname)); // This allows setting per-material colors. However, until a real lighting // system is added, the code below will have no effect. Once MineTest @@ -1460,9 +1484,18 @@ void GenericCAO::updateAnimation() { if(m_animated_meshnode == NULL) return; - m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y); - m_animated_meshnode->setAnimationSpeed(m_animation_speed); + + if (m_animated_meshnode->getStartFrame() != m_animation_range.X || + m_animated_meshnode->getEndFrame() != m_animation_range.Y) + m_animated_meshnode->setFrameLoop(m_animation_range.X, m_animation_range.Y); + if (m_animated_meshnode->getAnimationSpeed() != m_animation_speed) + m_animated_meshnode->setAnimationSpeed(m_animation_speed); m_animated_meshnode->setTransitionTime(m_animation_blend); +// Requires Irrlicht 1.8 or greater +#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR > 1 + if (m_animated_meshnode->getLoopMode() != m_animation_loop) + m_animated_meshnode->setLoopMode(m_animation_loop); +#endif } void GenericCAO::updateBonePosition() @@ -1486,20 +1519,11 @@ void GenericCAO::updateBonePosition() } } } - + void GenericCAO::updateAttachments() { - // 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 always be hidden - m_is_visible = !m_attached_to_local; - } - - if(getParent() == NULL || m_attached_to_local) // Detach or don't attach - { + if (getParent() == NULL) { // Detach or don't attach scene::ISceneNode *node = getSceneNode(); if (node) { v3f old_position = node->getAbsolutePosition(); @@ -1637,6 +1661,8 @@ void GenericCAO::processMessage(const std::string &data) m_animation_range = v2s32((s32)range.X, (s32)range.Y); m_animation_speed = readF1000(is); m_animation_blend = readF1000(is); + // these are sent inverted so we get true when the server sends nothing + m_animation_loop = !readU8(is); updateAnimation(); } else { LocalPlayer *player = m_env->getLocalPlayer(); @@ -1645,6 +1671,8 @@ void GenericCAO::processMessage(const std::string &data) m_animation_range = v2s32((s32)range.X, (s32)range.Y); m_animation_speed = readF1000(is); m_animation_blend = readF1000(is); + // these are sent inverted so we get true when the server sends nothing + m_animation_loop = !readU8(is); } // update animation only if local animations present // and received animation is unknown (except idle animation) @@ -1668,14 +1696,31 @@ void GenericCAO::processMessage(const std::string &data) m_bone_position[bone] = core::vector2d<v3f>(position, rotation); updateBonePosition(); - } - else if(cmd == GENERIC_CMD_SET_ATTACHMENT) { - m_env->m_attachements[getId()] = readS16(is); - m_children.push_back(m_env->m_attachements[getId()]); + } else if (cmd == GENERIC_CMD_ATTACH_TO) { + u16 parentID = readS16(is); + u16 oldparent = m_env->attachement_parent_ids[getId()]; + if (oldparent) { + m_children.erase(std::remove(m_children.begin(), m_children.end(), + getId()), m_children.end()); + } + m_env->attachement_parent_ids[getId()] = parentID; + GenericCAO *parentobj = m_env->getGenericCAO(parentID); + + if (parentobj) { + parentobj->m_children.push_back(getId()); + } + m_attachment_bone = deSerializeString(is); m_attachment_position = readV3F1000(is); m_attachment_rotation = readV3F1000(is); + // 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 + m_is_visible = !m_attached_to_local; + } + updateAttachments(); } else if(cmd == GENERIC_CMD_PUNCHED) { @@ -1716,13 +1761,26 @@ void GenericCAO::processMessage(const std::string &data) int rating = readS16(is); m_armor_groups[name] = rating; } + } else if (cmd == GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) { + readU8(is); // version + m_nametag_color = readARGB8(is); + if (m_textnode != NULL) { + m_textnode->setTextColor(m_nametag_color); + + // Enforce hiding nametag, + // because if freetype is enabled, a grey + // shadow can remain. + m_textnode->setVisible(m_nametag_color.getAlpha() > 0); + } } } - + +/* \pre punchitem != NULL + */ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem, float time_from_last_punch) { - assert(punchitem); + assert(punchitem); // pre-condition const ToolCapabilities *toolcap = &punchitem->getToolCapabilities(m_gamedef->idef()); PunchDamageResult result = getPunchDamage( diff --git a/src/content_cao.h b/src/content_cao.h index 69e2e54a2..299d6c73e 100644 --- a/src/content_cao.h +++ b/src/content_cao.h @@ -22,7 +22,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <map> #include "irrlichttypes_extrabloated.h" -#include "content_object.h" #include "clientobject.h" #include "object_properties.h" #include "itemgroup.h" @@ -61,7 +60,6 @@ private: std::string m_name; bool m_is_player; bool m_is_local_player; - int m_id; // Property-ish things ObjectProperties m_prop; // @@ -72,6 +70,7 @@ private: scene::IAnimatedMeshSceneNode *m_animated_meshnode; WieldMeshSceneNode *m_wield_meshnode; scene::IBillboardSceneNode *m_spritenode; + video::SColor m_nametag_color; scene::ITextSceneNode* m_textnode; v3f m_position; v3f m_velocity; @@ -87,6 +86,7 @@ private: v2s32 m_animation_range; int m_animation_speed; int m_animation_blend; + bool m_animation_loop; std::map<std::string, core::vector2d<v3f> > m_bone_position; // stores position and rotation for each bone name std::string m_attachment_bone; v3f m_attachment_position; @@ -115,7 +115,7 @@ public: return new GenericCAO(gamedef, env); } - inline u8 getType() const + inline ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_GENERIC; } @@ -162,6 +162,8 @@ public: m_is_visible = toset; } + void setChildrenVisible(bool toset); + void setAttachments(); void removeFromScene(bool permanent); @@ -176,6 +178,8 @@ public: void updateLight(u8 light_at_pos); + void updateLightNoCheck(u8 light_at_pos); + v3s16 getLightPosition(); void updateNodePos(); diff --git a/src/content_cso.cpp b/src/content_cso.cpp index 4779b20d1..0790024fc 100644 --- a/src/content_cso.cpp +++ b/src/content_cso.cpp @@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_cso.h" #include <IBillboardSceneNode.h> -#include "tile.h" +#include "client/tile.h" #include "environment.h" #include "gamedef.h" #include "log.h" @@ -50,7 +50,7 @@ public: m_spritenode = smgr->addBillboardSceneNode( NULL, v2f(1,1), pos, -1); m_spritenode->setMaterialTexture(0, - env->getGameDef()->tsrc()->getTexture("smoke_puff.png")); + env->getGameDef()->tsrc()->getTextureForMesh("smoke_puff.png")); m_spritenode->setMaterialFlag(video::EMF_LIGHTING, false); m_spritenode->setMaterialFlag(video::EMF_BILINEAR_FILTER, false); //m_spritenode->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF); diff --git a/src/content_mapblock.cpp b/src/content_mapblock.cpp index 9af3be789..8fa041312 100644 --- a/src/content_mapblock.cpp +++ b/src/content_mapblock.cpp @@ -20,11 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_mapblock.h" #include "util/numeric.h" #include "util/directiontables.h" -#include "main.h" // For g_settings #include "mapblock_mesh.h" // For MapBlock_LightColor() and MeshCollector #include "settings.h" #include "nodedef.h" -#include "tile.h" +#include "client/tile.h" #include "mesh.h" #include <IMeshManipulator.h> #include "gamedef.h" @@ -45,18 +44,16 @@ with this program; if not, write to the Free Software Foundation, Inc., // (compatible with ContentFeatures). If you specified 0,0,1,1 // for each face, that would be the same as passing NULL. void makeCuboid(MeshCollector *collector, const aabb3f &box, - TileSpec *tiles, int tilecount, - video::SColor &c, const f32* txc) + TileSpec *tiles, int tilecount, video::SColor &c, const f32* txc) { - assert(tilecount >= 1 && tilecount <= 6); + assert(tilecount >= 1 && tilecount <= 6); // pre-condition v3f min = box.MinEdge; v3f max = box.MaxEdge; - if(txc == NULL) - { + if(txc == NULL) { static const f32 txc_default[24] = { 0,0,1,1, 0,0,1,1, @@ -160,14 +157,16 @@ void makeCuboid(MeshCollector *collector, const aabb3f &box, } u16 indices[] = {0,1,2,2,3,0}; // Add to mesh collector - for(s32 j=0; j<24; j+=4) - { - int tileindex = MYMIN(j/4, tilecount-1); - collector->append(tiles[tileindex], - vertices+j, 4, indices, 6); + for (s32 j = 0; j < 24; j += 4) { + int tileindex = MYMIN(j / 4, tilecount - 1); + collector->append(tiles[tileindex], vertices + j, 4, indices, 6); } } +/* + TODO: Fix alpha blending for special nodes + Currently only the last element rendered is blended correct +*/ void mapblock_mesh_generate_special(MeshMakeData *data, MeshCollector &collector) { @@ -191,54 +190,6 @@ void mapblock_mesh_generate_special(MeshMakeData *data, v3s16 blockpos_nodes = data->m_blockpos*MAP_BLOCKSIZE; - // Create selection mesh - v3s16 p = data->m_highlighted_pos_relative; - if (data->m_show_hud && - (p.X >= 0) && (p.X < MAP_BLOCKSIZE) && - (p.Y >= 0) && (p.Y < MAP_BLOCKSIZE) && - (p.Z >= 0) && (p.Z < MAP_BLOCKSIZE)) { - - MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); - if(n.getContent() != CONTENT_AIR) { - // Get selection mesh light level - static const v3s16 dirs[7] = { - v3s16( 0, 0, 0), - v3s16( 0, 1, 0), - v3s16( 0,-1, 0), - v3s16( 1, 0, 0), - v3s16(-1, 0, 0), - v3s16( 0, 0, 1), - v3s16( 0, 0,-1) - }; - - u16 l = 0; - u16 l1 = 0; - for (u8 i = 0; i < 7; i++) { - MapNode n1 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dirs[i]); - l1 = getInteriorLight(n1, -4, nodedef); - if (l1 > l) - l = l1; - } - video::SColor c = MapBlock_LightColor(255, l, 0); - data->m_highlight_mesh_color = c; - std::vector<aabb3f> boxes = n.getSelectionBoxes(nodedef); - TileSpec h_tile; - h_tile.material_flags |= MATERIAL_FLAG_HIGHLIGHTED; - h_tile.texture = tsrc->getTexture("halo.png",&h_tile.texture_id); - v3f pos = intToFloat(p, BS); - f32 d = 0.05 * BS; - for(std::vector<aabb3f>::iterator - i = boxes.begin(); - i != boxes.end(); i++) - { - aabb3f box = *i; - box.MinEdge += v3f(-d, -d, -d) + pos; - box.MaxEdge += v3f(d, d, d) + pos; - makeCuboid(&collector, box, &h_tile, 1, c, NULL); - } - } - } - for(s16 z = 0; z < MAP_BLOCKSIZE; z++) for(s16 y = 0; y < MAP_BLOCKSIZE; y++) for(s16 x = 0; x < MAP_BLOCKSIZE; x++) @@ -254,8 +205,8 @@ void mapblock_mesh_generate_special(MeshMakeData *data, switch(f.drawtype){ default: - infostream<<"Got "<<f.drawtype<<std::endl; - assert(0); + infostream << "Got " << f.drawtype << std::endl; + FATAL_ERROR("Unknown drawtype"); break; case NDT_AIRLIKE: break; @@ -802,7 +753,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data, break;} case NDT_GLASSLIKE_FRAMED_OPTIONAL: // This is always pre-converted to something else - assert(0); + FATAL_ERROR("NDT_GLASSLIKE_FRAMED_OPTIONAL not pre-converted as expected"); break; case NDT_GLASSLIKE_FRAMED: { @@ -1054,7 +1005,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data, break;} case NDT_ALLFACES_OPTIONAL: // This is always pre-converted to something else - assert(0); + FATAL_ERROR("NDT_ALLFACES_OPTIONAL not pre-converted"); break; case NDT_TORCHLIKE: { @@ -1446,109 +1397,42 @@ void mapblock_mesh_generate_special(MeshMakeData *data, break;} case NDT_RAILLIKE: { - bool is_rail_x [] = { false, false }; /* x-1, x+1 */ - bool is_rail_z [] = { false, false }; /* z-1, z+1 */ - - bool is_rail_z_minus_y [] = { false, false }; /* z-1, z+1; y-1 */ - bool is_rail_x_minus_y [] = { false, false }; /* x-1, z+1; y-1 */ - bool is_rail_z_plus_y [] = { false, false }; /* z-1, z+1; y+1 */ - bool is_rail_x_plus_y [] = { false, false }; /* x-1, x+1; y+1 */ - - MapNode n_minus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1,y,z)); - MapNode n_plus_x = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1,y,z)); - MapNode n_minus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z-1)); - MapNode n_plus_z = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x,y,z+1)); - MapNode n_plus_x_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1, y+1, z)); - MapNode n_plus_x_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x+1, y-1, z)); - MapNode n_minus_x_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1, y+1, z)); - MapNode n_minus_x_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x-1, y-1, z)); - MapNode n_plus_z_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y+1, z+1)); - MapNode n_minus_z_plus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y+1, z-1)); - MapNode n_plus_z_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y-1, z+1)); - MapNode n_minus_z_minus_y = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y-1, z-1)); + bool is_rail_x[6]; /* (-1,-1,0) X (1,-1,0) (-1,0,0) X (1,0,0) (-1,1,0) X (1,1,0) */ + bool is_rail_z[6]; content_t thiscontent = n.getContent(); std::string groupname = "connect_to_raillike"; // name of the group that enables connecting to raillike nodes of different kind - bool self_connect_to_raillike = ((ItemGroupList) nodedef->get(n).groups)[groupname] != 0; - - if ((nodedef->get(n_minus_x).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_minus_x).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_minus_x.getContent() == thiscontent) - is_rail_x[0] = true; - - if ((nodedef->get(n_minus_x_minus_y).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_minus_x_minus_y).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_minus_x_minus_y.getContent() == thiscontent) - is_rail_x_minus_y[0] = true; - - if ((nodedef->get(n_minus_x_plus_y).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_minus_x_plus_y).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_minus_x_plus_y.getContent() == thiscontent) - is_rail_x_plus_y[0] = true; - - if ((nodedef->get(n_plus_x).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_plus_x).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_plus_x.getContent() == thiscontent) - is_rail_x[1] = true; - - if ((nodedef->get(n_plus_x_minus_y).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_plus_x_minus_y).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_plus_x_minus_y.getContent() == thiscontent) - is_rail_x_minus_y[1] = true; - - if ((nodedef->get(n_plus_x_plus_y).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_plus_x_plus_y).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_plus_x_plus_y.getContent() == thiscontent) - is_rail_x_plus_y[1] = true; - - if ((nodedef->get(n_minus_z).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_minus_z).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_minus_z.getContent() == thiscontent) - is_rail_z[0] = true; - - if ((nodedef->get(n_minus_z_minus_y).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_minus_z_minus_y).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_minus_z_minus_y.getContent() == thiscontent) - is_rail_z_minus_y[0] = true; - - if ((nodedef->get(n_minus_z_plus_y).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_minus_z_plus_y).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_minus_z_plus_y.getContent() == thiscontent) - is_rail_z_plus_y[0] = true; - - if ((nodedef->get(n_plus_z).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_plus_z).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_plus_z.getContent() == thiscontent) - is_rail_z[1] = true; - - if ((nodedef->get(n_plus_z_minus_y).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_plus_z_minus_y).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_plus_z_minus_y.getContent() == thiscontent) - is_rail_z_minus_y[1] = true; - - if ((nodedef->get(n_plus_z_plus_y).drawtype == NDT_RAILLIKE - && ((ItemGroupList) nodedef->get(n_plus_z_plus_y).groups)[groupname] != 0 - && self_connect_to_raillike) - || n_plus_z_plus_y.getContent() == thiscontent) - is_rail_z_plus_y[1] = true; - - bool is_rail_x_all[] = {false, false}; - bool is_rail_z_all[] = {false, false}; - is_rail_x_all[0]=is_rail_x[0] || is_rail_x_minus_y[0] || is_rail_x_plus_y[0]; - is_rail_x_all[1]=is_rail_x[1] || is_rail_x_minus_y[1] || is_rail_x_plus_y[1]; - is_rail_z_all[0]=is_rail_z[0] || is_rail_z_minus_y[0] || is_rail_z_plus_y[0]; - is_rail_z_all[1]=is_rail_z[1] || is_rail_z_minus_y[1] || is_rail_z_plus_y[1]; + int self_group = ((ItemGroupList) nodedef->get(n).groups)[groupname]; + + u8 index = 0; + for (s8 y0 = -1; y0 <= 1; y0++) { + // Prevent from indexing never used coordinates + for (s8 xz = -1; xz <= 1; xz++) { + if (xz == 0) + continue; + MapNode n_xy = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x + xz, y + y0, z)); + MapNode n_zy = data->m_vmanip.getNodeNoEx(blockpos_nodes + v3s16(x, y + y0, z + xz)); + ContentFeatures def_xy = nodedef->get(n_xy); + ContentFeatures def_zy = nodedef->get(n_zy); + + // Check if current node would connect with the rail + is_rail_x[index] = ((def_xy.drawtype == NDT_RAILLIKE + && ((ItemGroupList) def_xy.groups)[groupname] == self_group) + || n_xy.getContent() == thiscontent); + + is_rail_z[index] = ((def_zy.drawtype == NDT_RAILLIKE + && ((ItemGroupList) def_zy.groups)[groupname] == self_group) + || n_zy.getContent() == thiscontent); + index++; + } + } + + bool is_rail_x_all[2]; // [0] = negative x, [1] = positive x coordinate from the current node position + bool is_rail_z_all[2]; + is_rail_x_all[0] = is_rail_x[0] || is_rail_x[2] || is_rail_x[4]; + is_rail_x_all[1] = is_rail_x[1] || is_rail_x[3] || is_rail_x[5]; + is_rail_z_all[0] = is_rail_z[0] || is_rail_z[2] || is_rail_z[4]; + is_rail_z_all[1] = is_rail_z[1] || is_rail_z[3] || is_rail_z[5]; // reasonable default, flat straight unrotated rail bool is_straight = true; @@ -1557,13 +1441,10 @@ void mapblock_mesh_generate_special(MeshMakeData *data, u8 tileindex = 0; // check for sloped rail - if (is_rail_x_plus_y[0] || is_rail_x_plus_y[1] || is_rail_z_plus_y[0] || is_rail_z_plus_y[1]) - { - adjacencies = 5; //5 means sloped + if (is_rail_x[4] || is_rail_x[5] || is_rail_z[4] || is_rail_z[5]) { + adjacencies = 5; // 5 means sloped is_straight = true; // sloped is always straight - } - else - { + } else { // is really straight, rails on both sides is_straight = (is_rail_x_all[0] && is_rail_x_all[1]) || (is_rail_z_all[0] && is_rail_z_all[1]); adjacencies = is_rail_x_all[0] + is_rail_x_all[1] + is_rail_z_all[0] + is_rail_z_all[1]; @@ -1571,44 +1452,44 @@ void mapblock_mesh_generate_special(MeshMakeData *data, switch (adjacencies) { case 1: - if(is_rail_x_all[0] || is_rail_x_all[1]) + if (is_rail_x_all[0] || is_rail_x_all[1]) angle = 90; break; case 2: - if(!is_straight) + if (!is_straight) tileindex = 1; // curved - if(is_rail_x_all[0] && is_rail_x_all[1]) + if (is_rail_x_all[0] && is_rail_x_all[1]) angle = 90; - if(is_rail_z_all[0] && is_rail_z_all[1]){ - if (is_rail_z_plus_y[0]) + if (is_rail_z_all[0] && is_rail_z_all[1]) { + if (is_rail_z[4]) angle = 180; } - else if(is_rail_x_all[0] && is_rail_z_all[0]) + else if (is_rail_x_all[0] && is_rail_z_all[0]) angle = 270; - else if(is_rail_x_all[0] && is_rail_z_all[1]) + else if (is_rail_x_all[0] && is_rail_z_all[1]) angle = 180; - else if(is_rail_x_all[1] && is_rail_z_all[1]) + else if (is_rail_x_all[1] && is_rail_z_all[1]) angle = 90; break; case 3: // here is where the potential to 'switch' a junction is, but not implemented at present tileindex = 2; // t-junction if(!is_rail_x_all[1]) - angle=180; + angle = 180; if(!is_rail_z_all[0]) - angle=90; + angle = 90; if(!is_rail_z_all[1]) - angle=270; + angle = 270; break; case 4: tileindex = 3; // crossing break; case 5: //sloped - if(is_rail_z_plus_y[0]) + if (is_rail_z[4]) angle = 180; - if(is_rail_x_plus_y[0]) + if (is_rail_x[4]) angle = 90; - if(is_rail_x_plus_y[1]) + if (is_rail_x[5]) angle = -90; break; default: @@ -1626,7 +1507,7 @@ void mapblock_mesh_generate_special(MeshMakeData *data, float s = BS/2; short g = -1; - if (is_rail_x_plus_y[0] || is_rail_x_plus_y[1] || is_rail_z_plus_y[0] || is_rail_z_plus_y[1]) + if (is_rail_x[4] || is_rail_x[5] || is_rail_z[4] || is_rail_z[5]) g = 1; //Object is at a slope video::S3DVertex vertices[4] = @@ -1766,5 +1647,55 @@ void mapblock_mesh_generate_special(MeshMakeData *data, break;} } } + + /* + Caused by incorrect alpha blending, selection mesh needs to be created as + last element to ensure it gets blended correct over nodes with alpha channel + */ + // Create selection mesh + v3s16 p = data->m_highlighted_pos_relative; + if (data->m_show_hud && + (p.X >= 0) && (p.X < MAP_BLOCKSIZE) && + (p.Y >= 0) && (p.Y < MAP_BLOCKSIZE) && + (p.Z >= 0) && (p.Z < MAP_BLOCKSIZE)) { + + MapNode n = data->m_vmanip.getNodeNoEx(blockpos_nodes + p); + if(n.getContent() != CONTENT_AIR) { + // Get selection mesh light level + static const v3s16 dirs[7] = { + v3s16( 0, 0, 0), + v3s16( 0, 1, 0), + v3s16( 0,-1, 0), + v3s16( 1, 0, 0), + v3s16(-1, 0, 0), + v3s16( 0, 0, 1), + v3s16( 0, 0,-1) + }; + + u16 l = 0; + u16 l1 = 0; + for (u8 i = 0; i < 7; i++) { + MapNode n1 = data->m_vmanip.getNodeNoEx(blockpos_nodes + p + dirs[i]); + l1 = getInteriorLight(n1, -4, nodedef); + if (l1 > l) + l = l1; + } + video::SColor c = MapBlock_LightColor(255, l, 0); + data->m_highlight_mesh_color = c; + std::vector<aabb3f> boxes = n.getSelectionBoxes(nodedef); + TileSpec h_tile; + h_tile.material_flags |= MATERIAL_FLAG_HIGHLIGHTED; + h_tile.texture = tsrc->getTextureForMesh("halo.png",&h_tile.texture_id); + v3f pos = intToFloat(p, BS); + f32 d = 0.05 * BS; + for (std::vector<aabb3f>::iterator i = boxes.begin(); + i != boxes.end(); i++) { + aabb3f box = *i; + box.MinEdge += v3f(-d, -d, -d) + pos; + box.MaxEdge += v3f(d, d, d) + pos; + makeCuboid(&collector, box, &h_tile, 1, c, NULL); + } + } + } } diff --git a/src/content_mapnode.cpp b/src/content_mapnode.cpp index 44d0b8e38..3a4a4652a 100644 --- a/src/content_mapnode.cpp +++ b/src/content_mapnode.cpp @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapnode.h" #include "nodedef.h" #include "nameidmapping.h" -#include <map> +#include "util/string.h" /* Legacy node content type IDs @@ -218,14 +218,13 @@ public: } std::string get(const std::string &old) { - std::map<std::string, std::string>::const_iterator i; - i = old_to_new.find(old); - if(i == old_to_new.end()) + StringMap::const_iterator it = old_to_new.find(old); + if (it == old_to_new.end()) return ""; - return i->second; + return it->second; } private: - std::map<std::string, std::string> old_to_new; + StringMap old_to_new; }; NewNameGetter newnamegetter; @@ -234,16 +233,3 @@ std::string content_mapnode_get_new_name(const std::string &oldname) { return newnamegetter.get(oldname); } - -content_t legacy_get_id(const std::string &oldname, INodeDefManager *ndef) -{ - std::string newname = content_mapnode_get_new_name(oldname); - if(newname == "") - return CONTENT_IGNORE; - content_t id; - bool found = ndef->getId(newname, id); - if(!found) - return CONTENT_IGNORE; - return id; -} - diff --git a/src/content_mapnode.h b/src/content_mapnode.h index 5c9c0b66d..5d68afe59 100644 --- a/src/content_mapnode.h +++ b/src/content_mapnode.h @@ -37,8 +37,5 @@ void content_mapnode_get_name_id_mapping(NameIdMapping *nimap); // Convert "CONTENT_STONE"-style names to dynamic ids std::string content_mapnode_get_new_name(const std::string &oldname); class INodeDefManager; -content_t legacy_get_id(const std::string &oldname, INodeDefManager *ndef); -#define LEGN(ndef, oldname) legacy_get_id(oldname, ndef) #endif - diff --git a/src/content_sao.cpp b/src/content_sao.cpp index d4b3079ec..add1726fc 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -23,12 +23,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "collision.h" #include "environment.h" #include "settings.h" -#include "main.h" // For g_profiler -#include "profiler.h" #include "serialization.h" // For compressZlib #include "tool.h" // For ToolCapabilities #include "gamedef.h" #include "player.h" +#include "server.h" #include "scripting_game.h" #include "genericobject.h" #include "log.h" @@ -36,54 +35,6 @@ with this program; if not, write to the Free Software Foundation, Inc., std::map<u16, ServerActiveObject::Factory> ServerActiveObject::m_types; /* - DummyLoadSAO -*/ - -class DummyLoadSAO : public ServerActiveObject -{ -public: - DummyLoadSAO(ServerEnvironment *env, v3f pos, u8 type): - ServerActiveObject(env, pos) - { - ServerActiveObject::registerType(type, create); - } - // Pretend to be the test object (to fool the client) - u8 getType() const - { return ACTIVEOBJECT_TYPE_TEST; } - // And never save to disk - bool isStaticAllowed() const - { return false; } - - static ServerActiveObject* create(ServerEnvironment *env, v3f pos, - const std::string &data) - { - return new DummyLoadSAO(env, pos, 0); - } - - void step(float dtime, bool send_recommended) - { - m_removed = true; - infostream<<"DummyLoadSAO step"<<std::endl; - } - - bool getCollisionBox(aabb3f *toset) { - return false; - } - - bool collideWithObjects() { - return false; - } - -private: -}; - -// Prototype (registers item for deserialization) -DummyLoadSAO proto1_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_RAT); -DummyLoadSAO proto2_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_OERKKI1); -DummyLoadSAO proto3_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_FIREFLY); -DummyLoadSAO proto4_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_MOBV2); - -/* TestSAO */ @@ -97,7 +48,7 @@ public: { ServerActiveObject::registerType(getType(), create); } - u8 getType() const + ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_TEST; } static ServerActiveObject* create(ServerEnvironment *env, v3f pos, @@ -138,7 +89,7 @@ public: data += itos(m_base_position.Z); ActiveObjectMessage aom(getId(), false, data); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } } @@ -159,203 +110,6 @@ private: TestSAO proto_TestSAO(NULL, v3f(0,0,0)); /* - ItemSAO - - DEPRECATED: New dropped items are implemented in Lua; see - builtin/item_entity.lua. -*/ - -class ItemSAO : public ServerActiveObject -{ -public: - u8 getType() const - { return ACTIVEOBJECT_TYPE_ITEM; } - - float getMinimumSavedMovement() - { return 0.1*BS; } - - static ServerActiveObject* create(ServerEnvironment *env, v3f pos, - const std::string &data) - { - std::istringstream is(data, std::ios::binary); - char buf[1]; - // read version - is.read(buf, 1); - u8 version = buf[0]; - // check if version is supported - if(version != 0) - return NULL; - std::string itemstring = deSerializeString(is); - infostream<<"create(): Creating item \"" - <<itemstring<<"\""<<std::endl; - return new ItemSAO(env, pos, itemstring); - } - - ItemSAO(ServerEnvironment *env, v3f pos, - const std::string &itemstring): - ServerActiveObject(env, pos), - m_itemstring(itemstring), - m_itemstring_changed(false), - m_speed_f(0,0,0), - m_last_sent_position(0,0,0) - { - ServerActiveObject::registerType(getType(), create); - } - - void step(float dtime, bool send_recommended) - { - ScopeProfiler sp2(g_profiler, "step avg", SPT_AVG); - - assert(m_env); - - const float interval = 0.2; - if(m_move_interval.step(dtime, interval)==false) - return; - dtime = interval; - - core::aabbox3d<f32> box(-BS/3.,0.0,-BS/3., BS/3.,BS*2./3.,BS/3.); - collisionMoveResult moveresult; - // Apply gravity - m_speed_f += v3f(0, -dtime*9.81*BS, 0); - // Maximum movement without glitches - f32 pos_max_d = BS*0.25; - // Limit speed - if(m_speed_f.getLength()*dtime > pos_max_d) - m_speed_f *= pos_max_d / (m_speed_f.getLength()*dtime); - v3f pos_f = getBasePosition(); - v3f accel_f = v3f(0,0,0); - f32 stepheight = 0; - moveresult = collisionMoveSimple(m_env,m_env->getGameDef(), - pos_max_d, box, stepheight, dtime, - pos_f, m_speed_f, accel_f); - - if(send_recommended == false) - return; - - if(pos_f.getDistanceFrom(m_last_sent_position) > 0.05*BS) - { - setBasePosition(pos_f); - m_last_sent_position = pos_f; - - std::ostringstream os(std::ios::binary); - // command (0 = update position) - writeU8(os, 0); - // pos - writeV3F1000(os, m_base_position); - // create message and add to list - ActiveObjectMessage aom(getId(), false, os.str()); - m_messages_out.push_back(aom); - } - if(m_itemstring_changed) - { - m_itemstring_changed = false; - - std::ostringstream os(std::ios::binary); - // command (1 = update itemstring) - writeU8(os, 1); - // itemstring - os<<serializeString(m_itemstring); - // create message and add to list - ActiveObjectMessage aom(getId(), false, os.str()); - m_messages_out.push_back(aom); - } - } - - std::string getClientInitializationData(u16 protocol_version) - { - std::ostringstream os(std::ios::binary); - // version - writeU8(os, 0); - // pos - writeV3F1000(os, m_base_position); - // itemstring - os<<serializeString(m_itemstring); - return os.str(); - } - - std::string getStaticData() - { - infostream<<__FUNCTION_NAME<<std::endl; - std::ostringstream os(std::ios::binary); - // version - writeU8(os, 0); - // itemstring - os<<serializeString(m_itemstring); - return os.str(); - } - - ItemStack createItemStack() - { - try{ - IItemDefManager *idef = m_env->getGameDef()->idef(); - ItemStack item; - item.deSerialize(m_itemstring, idef); - infostream<<__FUNCTION_NAME<<": m_itemstring=\""<<m_itemstring - <<"\" -> item=\""<<item.getItemString()<<"\"" - <<std::endl; - return item; - } - catch(SerializationError &e) - { - infostream<<__FUNCTION_NAME<<": serialization error: " - <<"m_itemstring=\""<<m_itemstring<<"\""<<std::endl; - return ItemStack(); - } - } - - int punch(v3f dir, - const ToolCapabilities *toolcap, - ServerActiveObject *puncher, - float time_from_last_punch) - { - // Take item into inventory - ItemStack item = createItemStack(); - Inventory *inv = puncher->getInventory(); - if(inv != NULL) - { - std::string wieldlist = puncher->getWieldList(); - ItemStack leftover = inv->addItem(wieldlist, item); - puncher->setInventoryModified(); - if(leftover.empty()) - { - m_removed = true; - } - else - { - m_itemstring = leftover.getItemString(); - m_itemstring_changed = true; - } - } - - return 0; - } - - bool getCollisionBox(aabb3f *toset) { - return false; - } - - bool collideWithObjects() { - return false; - } - -private: - std::string m_itemstring; - bool m_itemstring_changed; - v3f m_speed_f; - v3f m_last_sent_position; - IntervalLimiter m_move_interval; -}; - -// Prototype (registers item for deserialization) -ItemSAO proto_ItemSAO(NULL, v3f(0,0,0), ""); - -ServerActiveObject* createItemSAO(ServerEnvironment *env, v3f pos, - const std::string &itemstring) -{ - return new ItemSAO(env, pos, itemstring); -} - -/* LuaEntitySAO */ @@ -381,6 +135,7 @@ LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, m_armor_groups_sent(false), m_animation_speed(0), m_animation_blend(0), + m_animation_loop(true), m_animation_sent(false), m_bone_position_sent(false), m_attachment_parent_id(0), @@ -477,7 +232,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) std::string str = getPropertyPacket(); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } // If attached, check that our parent is still there. If it isn't, detach. @@ -564,15 +319,16 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) m_armor_groups); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } if(m_animation_sent == false){ m_animation_sent = true; - std::string str = gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend); + std::string str = gob_cmd_update_animation( + m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } if(m_bone_position_sent == false){ @@ -581,7 +337,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } } @@ -590,7 +346,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } } @@ -611,7 +367,8 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) writeU8(os, 4 + m_bone_position.size()); // number of messages stuffed in here os<<serializeLongString(getPropertyPacket()); // message 1 os<<serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 - os<<serializeLongString(gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend)); // 3 + os<<serializeLongString(gob_cmd_update_animation( + m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3 for(std::map<std::string, core::vector2d<v3f> >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ os<<serializeLongString(gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y)); // m_bone_position.size } @@ -664,19 +421,19 @@ int LuaEntitySAO::punch(v3f dir, ServerActiveObject *puncher, float time_from_last_punch) { - if(!m_registered){ + if (!m_registered){ // Delete unknown LuaEntities when punched m_removed = true; return 0; } // It's best that attachments cannot be punched - if(isAttached()) + if (isAttached()) return 0; ItemStack *punchitem = NULL; ItemStack punchitem_static; - if(puncher){ + if (puncher) { punchitem_static = puncher->getWieldedItem(); punchitem = &punchitem_static; } @@ -687,31 +444,26 @@ int LuaEntitySAO::punch(v3f dir, punchitem, time_from_last_punch); - if(result.did_punch) - { + if (result.did_punch) { setHP(getHP() - result.damage); + if (result.damage > 0) { + std::string punchername = puncher ? puncher->getDescription() : "nil"; - std::string punchername = "nil"; - - if ( puncher != 0 ) - punchername = puncher->getDescription(); - - actionstream<<getDescription()<<" punched by " - <<punchername<<", damage "<<result.damage - <<" hp, health now "<<getHP()<<" hp"<<std::endl; - - { - std::string str = gob_cmd_punched(result.damage, getHP()); - // create message and add to list - ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + actionstream << getDescription() << " punched by " + << punchername << ", damage " << result.damage + << " hp, health now " << getHP() << " hp" << std::endl; } - if(getHP() == 0) - m_removed = true; + std::string str = gob_cmd_punched(result.damage, getHP()); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); } + if (getHP() == 0) + m_removed = true; + m_env->getScriptIface()->luaentity_Punch(m_id, puncher, time_from_last_punch, toolcap, dir); @@ -720,10 +472,10 @@ int LuaEntitySAO::punch(v3f dir, void LuaEntitySAO::rightClick(ServerActiveObject *clicker) { - if(!m_registered) + if (!m_registered) return; // It's best that attachments cannot be clicked - if(isAttached()) + if (isAttached()) return; m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker); } @@ -778,21 +530,41 @@ void LuaEntitySAO::setArmorGroups(const ItemGroupList &armor_groups) m_armor_groups_sent = false; } -void LuaEntitySAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend) +ItemGroupList LuaEntitySAO::getArmorGroups() +{ + return m_armor_groups; +} + +void LuaEntitySAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend, bool frame_loop) { m_animation_range = frame_range; m_animation_speed = frame_speed; m_animation_blend = frame_blend; + m_animation_loop = frame_loop; m_animation_sent = false; } -void LuaEntitySAO::setBonePosition(std::string bone, v3f position, v3f rotation) +void LuaEntitySAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, bool *frame_loop) +{ + *frame_range = m_animation_range; + *frame_speed = m_animation_speed; + *frame_blend = m_animation_blend; + *frame_loop = m_animation_loop; +} + +void LuaEntitySAO::setBonePosition(const std::string &bone, v3f position, v3f rotation) { m_bone_position[bone] = core::vector2d<v3f>(position, rotation); m_bone_position_sent = false; } -void LuaEntitySAO::setAttachment(int parent_id, std::string bone, v3f position, v3f rotation) +void LuaEntitySAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation) +{ + *position = m_bone_position[bone].X; + *rotation = m_bone_position[bone].Y; +} + +void LuaEntitySAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation) { // Attachments need to be handled on both the server and client. // If we just attach on the server, we can only copy the position of the parent. Attachments @@ -809,6 +581,30 @@ void LuaEntitySAO::setAttachment(int parent_id, std::string bone, v3f position, m_attachment_sent = false; } +void LuaEntitySAO::getAttachment(int *parent_id, std::string *bone, v3f *position, + v3f *rotation) +{ + *parent_id = m_attachment_parent_id; + *bone = m_attachment_bone; + *position = m_attachment_position; + *rotation = m_attachment_rotation; +} + +void LuaEntitySAO::addAttachmentChild(int child_id) +{ + m_attachment_child_ids.insert(child_id); +} + +void LuaEntitySAO::removeAttachmentChild(int child_id) +{ + m_attachment_child_ids.erase(child_id); +} + +std::set<int> LuaEntitySAO::getAttachmentChildIds() +{ + return m_attachment_child_ids; +} + ObjectProperties* LuaEntitySAO::accessObjectProperties() { return &m_prop; @@ -854,7 +650,7 @@ void LuaEntitySAO::setTextureMod(const std::string &mod) std::string str = gob_cmd_set_texture_mod(mod); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength, @@ -868,7 +664,7 @@ void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength, ); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } std::string LuaEntitySAO::getName() @@ -908,7 +704,7 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) ); // create message and add to list ActiveObjectMessage aom(getId(), false, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } bool LuaEntitySAO::getCollisionBox(aabb3f *toset) { @@ -956,16 +752,14 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, m_is_singleplayer(is_singleplayer), m_animation_speed(0), m_animation_blend(0), + m_animation_loop(true), m_animation_sent(false), m_bone_position_sent(false), m_attachment_parent_id(0), m_attachment_sent(false), + m_nametag_color(video::SColor(255, 255, 255, 255)), + m_nametag_sent(false), // public - m_moved(false), - m_inventory_not_sent(false), - m_hp_not_sent(false), - m_breath_not_sent(false), - m_wielded_item_not_sent(false), m_physics_override_speed(1), m_physics_override_jump(1), m_physics_override_gravity(1), @@ -973,8 +767,8 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, m_physics_override_sneak_glitch(true), m_physics_override_sent(false) { - assert(m_player); - assert(m_peer_id != 0); + assert(m_player); // pre-condition + assert(m_peer_id != 0); // pre-condition setBasePosition(m_player->getPosition()); m_inventory = &m_player->inventory; m_armor_groups["fleshy"] = 100; @@ -1051,10 +845,11 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) writeF1000(os, m_player->getYaw()); writeS16(os, getHP()); - writeU8(os, 5 + m_bone_position.size()); // number of messages stuffed in here + writeU8(os, 6 + m_bone_position.size()); // number of messages stuffed in here os<<serializeLongString(getPropertyPacket()); // message 1 os<<serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 - os<<serializeLongString(gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend)); // 3 + os<<serializeLongString(gob_cmd_update_animation( + m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3 for(std::map<std::string, core::vector2d<v3f> >::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ os<<serializeLongString(gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y)); // m_bone_position.size } @@ -1062,6 +857,7 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) os<<serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed, m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak, m_physics_override_sneak_glitch)); // 5 + os << serializeLongString(gob_cmd_update_nametag_attributes(m_nametag_color)); // 6 } else { @@ -1082,7 +878,7 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version) std::string PlayerSAO::getStaticData() { - assert(0); + FATAL_ERROR("Deprecated function (?)"); return ""; } @@ -1105,7 +901,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) std::string str = getPropertyPacket(); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } // If attached, check that our parent is still there. If it isn't, detach. @@ -1116,7 +912,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_attachment_position = v3f(0,0,0); m_attachment_rotation = v3f(0,0,0); m_player->setPosition(m_last_good_position); - m_moved = true; + ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); } //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl; @@ -1168,22 +964,16 @@ void PlayerSAO::step(float dtime, bool send_recommended) ); // create message and add to list ActiveObjectMessage aom(getId(), false, str); - m_messages_out.push_back(aom); - } - - if(m_wielded_item_not_sent) - { - m_wielded_item_not_sent = false; - // GenericCAO has no special way to show this + m_messages_out.push(aom); } - if(m_armor_groups_sent == false){ + if(m_armor_groups_sent == false) { m_armor_groups_sent = true; std::string str = gob_cmd_update_armor_groups( m_armor_groups); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } if(m_physics_override_sent == false){ @@ -1193,15 +983,16 @@ void PlayerSAO::step(float dtime, bool send_recommended) m_physics_override_sneak, m_physics_override_sneak_glitch); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } if(m_animation_sent == false){ m_animation_sent = true; - std::string str = gob_cmd_update_animation(m_animation_range, m_animation_speed, m_animation_blend); + std::string str = gob_cmd_update_animation( + m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } if(m_bone_position_sent == false){ @@ -1210,7 +1001,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } } @@ -1219,7 +1010,15 @@ void PlayerSAO::step(float dtime, bool send_recommended) std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); + } + + if (m_nametag_sent == false) { + m_nametag_sent = true; + std::string str = gob_cmd_update_nametag_attributes(m_nametag_color); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); } } @@ -1237,8 +1036,7 @@ void PlayerSAO::setPos(v3f pos) m_player->setPosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; - // Force position change on client - m_moved = true; + ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); } void PlayerSAO::moveTo(v3f pos, bool continuous) @@ -1248,22 +1046,19 @@ void PlayerSAO::moveTo(v3f pos, bool continuous) m_player->setPosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; - // Force position change on client - m_moved = true; + ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); } void PlayerSAO::setYaw(float yaw) { m_player->setYaw(yaw); - // Force change on client - m_moved = true; + ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); } void PlayerSAO::setPitch(float pitch) { m_player->setPitch(pitch); - // Force change on client - m_moved = true; + ((Server*)m_env->getGameDef())->SendMovePlayer(m_peer_id); } int PlayerSAO::punch(v3f dir, @@ -1272,19 +1067,19 @@ int PlayerSAO::punch(v3f dir, float time_from_last_punch) { // It's best that attachments cannot be punched - if(isAttached()) + if (isAttached()) return 0; - if(!toolcap) + if (!toolcap) return 0; // No effect if PvP disabled - if(g_settings->getBool("enable_pvp") == false){ - if(puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER){ + if (g_settings->getBool("enable_pvp") == false) { + if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { std::string str = gob_cmd_punched(0, getHP()); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); return 0; } } @@ -1294,14 +1089,35 @@ int PlayerSAO::punch(v3f dir, std::string punchername = "nil"; - if ( puncher != 0 ) + if (puncher != 0) punchername = puncher->getDescription(); - actionstream<<"Player "<<m_player->getName()<<" punched by " - <<punchername<<", damage "<<hitparams.hp - <<" HP"<<std::endl; + PlayerSAO *playersao = m_player->getPlayerSAO(); - setHP(getHP() - hitparams.hp); + bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao, + puncher, time_from_last_punch, toolcap, dir, + hitparams.hp); + + if (!damage_handled) { + setHP(getHP() - hitparams.hp); + } else { // override client prediction + if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + std::string str = gob_cmd_punched(0, getHP()); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } + } + + + 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; return hitparams.wear; } @@ -1326,27 +1142,28 @@ void PlayerSAO::setHP(s16 hp) { s16 oldhp = m_player->hp; - if(hp < 0) + s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, + hp - oldhp); + if (hp_change == 0) + return; + hp = oldhp + hp_change; + + if (hp < 0) hp = 0; - else if(hp > PLAYER_MAX_HP) + else if (hp > PLAYER_MAX_HP) hp = PLAYER_MAX_HP; - if(hp < oldhp && g_settings->getBool("enable_damage") == false) - { - m_hp_not_sent = true; // fix wrong prediction on client + if(hp < oldhp && g_settings->getBool("enable_damage") == false) { return; } m_player->hp = hp; - if(hp != oldhp) { - m_hp_not_sent = true; - if(oldhp > hp) - m_damage += oldhp - hp; - } + if (oldhp > hp) + m_damage += (oldhp - hp); // Update properties on death - if((hp == 0) != (oldhp == 0)) + if ((hp == 0) != (oldhp == 0)) m_properties_sent = false; } @@ -1366,23 +1183,43 @@ void PlayerSAO::setArmorGroups(const ItemGroupList &armor_groups) m_armor_groups_sent = false; } -void PlayerSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend) +ItemGroupList PlayerSAO::getArmorGroups() +{ + return m_armor_groups; +} + +void PlayerSAO::setAnimation(v2f frame_range, float frame_speed, float frame_blend, bool frame_loop) { // store these so they can be updated to clients m_animation_range = frame_range; m_animation_speed = frame_speed; m_animation_blend = frame_blend; + m_animation_loop = frame_loop; m_animation_sent = false; } -void PlayerSAO::setBonePosition(std::string bone, v3f position, v3f rotation) +void PlayerSAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, bool *frame_loop) +{ + *frame_range = m_animation_range; + *frame_speed = m_animation_speed; + *frame_blend = m_animation_blend; + *frame_loop = m_animation_loop; +} + +void PlayerSAO::setBonePosition(const std::string &bone, v3f position, v3f rotation) { // store these so they can be updated to clients m_bone_position[bone] = core::vector2d<v3f>(position, rotation); m_bone_position_sent = false; } -void PlayerSAO::setAttachment(int parent_id, std::string bone, v3f position, v3f rotation) +void PlayerSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation) +{ + *position = m_bone_position[bone].X; + *rotation = m_bone_position[bone].Y; +} + +void PlayerSAO::setAttachment(int parent_id, const std::string &bone, v3f position, v3f rotation) { // Attachments need to be handled on both the server and client. // If we just attach on the server, we can only copy the position of the parent. Attachments @@ -1399,6 +1236,30 @@ void PlayerSAO::setAttachment(int parent_id, std::string bone, v3f position, v3f m_attachment_sent = false; } +void PlayerSAO::getAttachment(int *parent_id, std::string *bone, v3f *position, + v3f *rotation) +{ + *parent_id = m_attachment_parent_id; + *bone = m_attachment_bone; + *position = m_attachment_position; + *rotation = m_attachment_rotation; +} + +void PlayerSAO::addAttachmentChild(int child_id) +{ + m_attachment_child_ids.insert(child_id); +} + +void PlayerSAO::removeAttachmentChild(int child_id) +{ + m_attachment_child_ids.erase(child_id); +} + +std::set<int> PlayerSAO::getAttachmentChildIds() +{ + return m_attachment_child_ids; +} + ObjectProperties* PlayerSAO::accessObjectProperties() { return &m_prop; @@ -1409,6 +1270,17 @@ void PlayerSAO::notifyObjectPropertiesModified() m_properties_sent = false; } +void PlayerSAO::setNametagColor(video::SColor color) +{ + m_nametag_color = color; + m_nametag_sent = false; +} + +video::SColor PlayerSAO::getNametagColor() +{ + return m_nametag_color; +} + Inventory* PlayerSAO::getInventory() { return m_inventory; @@ -1425,11 +1297,6 @@ InventoryLocation PlayerSAO::getInventoryLocation() const return loc; } -void PlayerSAO::setInventoryModified() -{ - m_inventory_not_sent = true; -} - std::string PlayerSAO::getWieldList() const { return "main"; @@ -1442,10 +1309,8 @@ int PlayerSAO::getWieldIndex() const void PlayerSAO::setWieldIndex(int i) { - if(i != m_wield_index) - { + if(i != m_wield_index) { m_wield_index = i; - m_wielded_item_not_sent = true; } } @@ -1512,7 +1377,6 @@ bool PlayerSAO::checkMovementCheat() <<" moved too fast; resetting position" <<std::endl; m_player->setPosition(m_last_good_position); - m_moved = true; cheated = true; } } diff --git a/src/content_sao.h b/src/content_sao.h index 38baeab3a..1f0a68cd8 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -21,14 +21,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #define CONTENT_SAO_HEADER #include "serverobject.h" -#include "content_object.h" #include "itemgroup.h" #include "player.h" #include "object_properties.h" -ServerActiveObject* createItemSAO(ServerEnvironment *env, v3f pos, - const std::string &itemstring); - /* LuaEntitySAO needs some internals exposed. */ @@ -39,9 +35,9 @@ public: LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &name, const std::string &state); ~LuaEntitySAO(); - u8 getType() const + ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_LUAENTITY; } - u8 getSendType() const + ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; } virtual void addedToEnvironment(u32 dtime_s); static ServerActiveObject* create(ServerEnvironment *env, v3f pos, @@ -62,9 +58,16 @@ public: void setHP(s16 hp); s16 getHP() const; void setArmorGroups(const ItemGroupList &armor_groups); - void setAnimation(v2f frame_range, float frame_speed, float frame_blend); - void setBonePosition(std::string bone, v3f position, v3f rotation); - void setAttachment(int parent_id, std::string bone, v3f position, v3f rotation); + ItemGroupList getArmorGroups(); + 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 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 addAttachmentChild(int child_id); + void removeAttachmentChild(int child_id); + std::set<int> getAttachmentChildIds(); ObjectProperties* accessObjectProperties(); void notifyObjectPropertiesModified(); /* LuaEntitySAO-specific */ @@ -106,12 +109,14 @@ private: v2f m_animation_range; float m_animation_speed; float m_animation_blend; + bool m_animation_loop; bool m_animation_sent; std::map<std::string, core::vector2d<v3f> > m_bone_position; bool m_bone_position_sent; int m_attachment_parent_id; + std::set<int> m_attachment_child_ids; std::string m_attachment_bone; v3f m_attachment_position; v3f m_attachment_rotation; @@ -158,9 +163,9 @@ public: PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, const std::set<std::string> &privs, bool is_singleplayer); ~PlayerSAO(); - u8 getType() const + ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_PLAYER; } - u8 getSendType() const + ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; } std::string getDescription(); @@ -196,11 +201,20 @@ public: u16 getBreath() const; void setBreath(u16 breath); void setArmorGroups(const ItemGroupList &armor_groups); - void setAnimation(v2f frame_range, float frame_speed, float frame_blend); - void setBonePosition(std::string bone, v3f position, v3f rotation); - void setAttachment(int parent_id, std::string bone, v3f position, v3f rotation); + ItemGroupList getArmorGroups(); + 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 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 addAttachmentChild(int child_id); + void removeAttachmentChild(int child_id); + std::set<int> getAttachmentChildIds(); ObjectProperties* accessObjectProperties(); void notifyObjectPropertiesModified(); + void setNametagColor(video::SColor color); + video::SColor getNametagColor(); /* Inventory interface @@ -209,7 +223,6 @@ public: Inventory* getInventory(); const Inventory* getInventory() const; InventoryLocation getInventoryLocation() const; - void setInventoryModified(); std::string getWieldList() const; int getWieldIndex() const; void setWieldIndex(int i); @@ -307,25 +320,23 @@ private: v2f m_animation_range; float m_animation_speed; float m_animation_blend; + bool m_animation_loop; bool m_animation_sent; std::map<std::string, core::vector2d<v3f> > m_bone_position; // Stores position and rotation for each bone name bool m_bone_position_sent; int m_attachment_parent_id; + std::set<int> m_attachment_child_ids; std::string m_attachment_bone; v3f m_attachment_position; v3f m_attachment_rotation; bool m_attachment_sent; -public: - // Some flags used by Server - bool m_moved; - bool m_inventory_not_sent; - bool m_hp_not_sent; - bool m_breath_not_sent; - bool m_wielded_item_not_sent; + video::SColor m_nametag_color; + bool m_nametag_sent; +public: float m_physics_override_speed; float m_physics_override_jump; float m_physics_override_gravity; diff --git a/src/convert_json.cpp b/src/convert_json.cpp index cea089623..e03508e21 100644 --- a/src/convert_json.cpp +++ b/src/convert_json.cpp @@ -25,7 +25,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mods.h" #include "config.h" #include "log.h" -#include "main.h" // for g_settings #include "settings.h" #include "httpfetch.h" #include "porting.h" @@ -357,18 +356,10 @@ ModStoreModDetails readModStoreModDetails(Json::Value& details) { } //value - if (details["rating"].asString().size()) { - - std::string id_raw = details["rating"].asString(); - char* endptr = 0; - float numbervalue = strtof(id_raw.c_str(),&endptr); - - if ((id_raw != "") && (*endptr == 0)) { - retval.rating = numbervalue; - } - } - else { - retval.rating = 0.0; + if (details["value"].isInt()) { + retval.rating = details["value"].asInt(); + } else { + retval.rating = 0; } //depends diff --git a/src/craftdef.cpp b/src/craftdef.cpp index afc41303f..409481e64 100644 --- a/src/craftdef.cpp +++ b/src/craftdef.cpp @@ -27,31 +27,71 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gamedef.h" #include "inventory.h" #include "util/serialize.h" +#include "util/string.h" +#include "util/numeric.h" #include "strfnd.h" #include "exceptions.h" +inline bool isGroupRecipeStr(const std::string &rec_name) +{ + return str_starts_with(rec_name, std::string("group:")); +} + +inline u64 getHashForString(const std::string &recipe_str) +{ + /*errorstream << "Hashing craft string \"" << recipe_str << '"';*/ + return murmur_hash_64_ua(recipe_str.data(), recipe_str.length(), 0xdeadbeef); +} + +static u64 getHashForGrid(CraftHashType type, const std::vector<std::string> &grid_names) +{ + switch (type) { + case CRAFT_HASH_TYPE_ITEM_NAMES: { + std::ostringstream os; + bool is_first = true; + for (size_t i = 0; i < grid_names.size(); i++) { + if (grid_names[i] != "") { + os << (is_first ? "" : "\n") << grid_names[i]; + is_first = false; + } + } + return getHashForString(os.str()); + } case CRAFT_HASH_TYPE_COUNT: { + u64 cnt = 0; + for (size_t i = 0; i < grid_names.size(); i++) + if (grid_names[i] != "") + cnt++; + return cnt; + } case CRAFT_HASH_TYPE_UNHASHED: + return 0; + } + // invalid CraftHashType + assert(false); + return 0; +} + // Check if input matches recipe // Takes recipe groups into account static bool inputItemMatchesRecipe(const std::string &inp_name, const std::string &rec_name, IItemDefManager *idef) { // Exact name - if(inp_name == rec_name) + if (inp_name == rec_name) return true; // Group - if(rec_name.substr(0,6) == "group:" && idef->isKnown(inp_name)){ + if (isGroupRecipeStr(rec_name) && idef->isKnown(inp_name)) { const struct ItemDefinition &def = idef->get(inp_name); Strfnd f(rec_name.substr(6)); bool all_groups_match = true; - do{ + do { std::string check_group = f.next(","); - if(itemgroup_get(def.groups, check_group) == 0){ + if (itemgroup_get(def.groups, check_group) == 0) { all_groups_match = false; break; } - }while(!f.atend()); - if(all_groups_match) + } while (!f.atend()); + if (all_groups_match) return true; } @@ -72,11 +112,9 @@ static std::vector<std::string> craftGetItemNames( const std::vector<std::string> &itemstrings, IGameDef *gamedef) { std::vector<std::string> result; - for(std::vector<std::string>::const_iterator - i = itemstrings.begin(); - i != itemstrings.end(); i++) - { - result.push_back(craftGetItemName(*i, gamedef)); + for (std::vector<std::string>::size_type i = 0; + i < itemstrings.size(); i++) { + result.push_back(craftGetItemName(itemstrings[i], gamedef)); } return result; } @@ -86,11 +124,9 @@ static std::vector<std::string> craftGetItemNames( const std::vector<ItemStack> &items, IGameDef *gamedef) { std::vector<std::string> result; - for(std::vector<ItemStack>::const_iterator - i = items.begin(); - i != items.end(); i++) - { - result.push_back(i->name); + for (std::vector<ItemStack>::size_type i = 0; + i < items.size(); i++) { + result.push_back(items[i].name); } return result; } @@ -100,11 +136,10 @@ static std::vector<ItemStack> craftGetItems( const std::vector<std::string> &items, IGameDef *gamedef) { std::vector<ItemStack> result; - for(std::vector<std::string>::const_iterator - i = items.begin(); - i != items.end(); i++) - { - result.push_back(ItemStack(std::string(*i),(u16)1,(u16)0,"",gamedef->getItemDefManager())); + for (std::vector<std::string>::size_type i = 0; + i < items.size(); i++) { + result.push_back(ItemStack(std::string(items[i]), (u16)1, + (u16)0, "", gamedef->getItemDefManager())); } return result; } @@ -118,32 +153,26 @@ static bool craftGetBounds(const std::vector<std::string> &items, unsigned int w bool success = false; unsigned int x = 0; unsigned int y = 0; - for(std::vector<std::string>::const_iterator - i = items.begin(); - i != items.end(); i++) - { - if(*i != "") // Is this an actual item? - { - if(!success) - { + for (std::vector<std::string>::size_type i = 0; + i < items.size(); i++) { + // Is this an actual item? + if (items[i] != "") { + if (!success) { // This is the first nonempty item min_x = max_x = x; min_y = max_y = y; success = true; - } - else - { - if(x < min_x) min_x = x; - if(x > max_x) max_x = x; - if(y < min_y) min_y = y; - if(y > max_y) max_y = y; + } else { + if (x < min_x) min_x = x; + if (x > max_x) max_x = x; + if (y < min_y) min_y = y; + if (y > max_y) max_y = y; } } // Step coordinate x++; - if(x == width) - { + if (x == width) { x = 0; y++; } @@ -154,12 +183,10 @@ static bool craftGetBounds(const std::vector<std::string> &items, unsigned int w // Removes 1 from each item stack static void craftDecrementInput(CraftInput &input, IGameDef *gamedef) { - for(std::vector<ItemStack>::iterator - i = input.items.begin(); - i != input.items.end(); i++) - { - if(i->count != 0) - i->remove(1); + for (std::vector<ItemStack>::size_type i = 0; + i < input.items.size(); i++) { + if (input.items[i].count != 0) + input.items[i].remove(1); } } @@ -167,11 +194,11 @@ static void craftDecrementInput(CraftInput &input, IGameDef *gamedef) // Example: if replacements contains the pair ("bucket:bucket_water", "bucket:bucket_empty"), // a water bucket will not be removed but replaced by an empty bucket. static void craftDecrementOrReplaceInput(CraftInput &input, + std::vector<ItemStack> &output_replacements, const CraftReplacements &replacements, IGameDef *gamedef) { - if(replacements.pairs.empty()) - { + if (replacements.pairs.empty()) { craftDecrementInput(input, gamedef); return; } @@ -179,37 +206,33 @@ static void craftDecrementOrReplaceInput(CraftInput &input, // Make a copy of the replacements pair list std::vector<std::pair<std::string, std::string> > pairs = replacements.pairs; - for(std::vector<ItemStack>::iterator - i = input.items.begin(); - i != input.items.end(); i++) - { - if(i->count == 1) - { - // Find an appropriate replacement - bool found_replacement = false; - for(std::vector<std::pair<std::string, std::string> >::iterator - j = pairs.begin(); - j != pairs.end(); j++) - { - ItemStack from_item; - from_item.deSerialize(j->first, gamedef->idef()); - if(i->name == from_item.name) - { - i->deSerialize(j->second, gamedef->idef()); + for (std::vector<ItemStack>::size_type i = 0; + i < input.items.size(); i++) { + ItemStack &item = input.items[i]; + // Find an appropriate replacement + bool found_replacement = false; + for (std::vector<std::pair<std::string, std::string> >::iterator + j = pairs.begin(); + j != pairs.end(); ++j) { + if (item.name == craftGetItemName(j->first, gamedef)) { + if (item.count == 1) { + item.deSerialize(j->second, gamedef->idef()); found_replacement = true; pairs.erase(j); break; + } else { + ItemStack rep; + rep.deSerialize(j->second, gamedef->idef()); + item.remove(1); + found_replacement = true; + output_replacements.push_back(rep); + break; } } - // No replacement was found, simply decrement count to zero - if(!found_replacement) - i->remove(1); - } - else if(i->count >= 2) - { - // Ignore replacements for items with count >= 2 - i->remove(1); } + // No replacement was found, simply decrement count by one + if (!found_replacement && item.count > 0) + item.remove(1); } } @@ -218,24 +241,19 @@ static std::string craftDumpMatrix(const std::vector<std::string> &items, unsigned int width) { std::ostringstream os(std::ios::binary); - os<<"{ "; + os << "{ "; unsigned int x = 0; - for(std::vector<std::string>::const_iterator - i = items.begin(); - i != items.end(); i++, x++) - { - if(x == width) - { - os<<"; "; + for(std::vector<std::string>::size_type i = 0; + i < items.size(); i++, x++) { + if (x == width) { + os << "; "; x = 0; + } else if (x != 0) { + os << ","; } - else if(x != 0) - { - os<<","; - } - os<<"\""<<(*i)<<"\""; + os << '"' << items[i] << '"'; } - os<<" }"; + os << " }"; return os.str(); } @@ -244,24 +262,19 @@ std::string craftDumpMatrix(const std::vector<ItemStack> &items, unsigned int width) { std::ostringstream os(std::ios::binary); - os<<"{ "; + os << "{ "; unsigned int x = 0; - for(std::vector<ItemStack>::const_iterator - i = items.begin(); - i != items.end(); i++, x++) - { - if(x == width) - { - os<<"; "; + for (std::vector<ItemStack>::size_type i = 0; + i < items.size(); i++, x++) { + if (x == width) { + os << "; "; x = 0; + } else if (x != 0) { + os << ","; } - else if(x != 0) - { - os<<","; - } - os<<"\""<<(i->getItemString())<<"\""; + os << '"' << (items[i].getItemString()) << '"'; } - os<<" }"; + os << " }"; return os.str(); } @@ -273,7 +286,8 @@ std::string craftDumpMatrix(const std::vector<ItemStack> &items, std::string CraftInput::dump() const { std::ostringstream os(std::ios::binary); - os<<"(method="<<((int)method)<<", items="<<craftDumpMatrix(items, width)<<")"; + os << "(method=" << ((int)method) << ", items=" + << craftDumpMatrix(items, width) << ")"; return os.str(); } @@ -284,7 +298,7 @@ std::string CraftInput::dump() const std::string CraftOutput::dump() const { std::ostringstream os(std::ios::binary); - os<<"(item=\""<<item<<"\", time="<<time<<")"; + os << "(item=\"" << item << "\", time=" << time << ")"; return os.str(); } @@ -297,86 +311,18 @@ std::string CraftReplacements::dump() const std::ostringstream os(std::ios::binary); os<<"{"; const char *sep = ""; - for(std::vector<std::pair<std::string, std::string> >::const_iterator - i = pairs.begin(); - i != pairs.end(); i++) - { - os<<sep<<"\""<<(i->first)<<"\"=>\""<<(i->second)<<"\""; + for (std::vector<std::pair<std::string, std::string> >::size_type i = 0; + i < pairs.size(); i++) { + const std::pair<std::string, std::string> &repl_p = pairs[i]; + os << sep + << '"' << (repl_p.first) + << "\"=>\"" << (repl_p.second) << '"'; sep = ","; } - os<<"}"; + os << "}"; return os.str(); } -void CraftReplacements::serialize(std::ostream &os) const -{ - writeU16(os, pairs.size()); - for(u32 i=0; i<pairs.size(); i++) - { - os<<serializeString(pairs[i].first); - os<<serializeString(pairs[i].second); - } -} - -void CraftReplacements::deSerialize(std::istream &is) -{ - pairs.clear(); - u32 count = readU16(is); - for(u32 i=0; i<count; i++) - { - std::string first = deSerializeString(is); - std::string second = deSerializeString(is); - pairs.push_back(std::make_pair(first, second)); - } -} - -/* - CraftDefinition -*/ - -void CraftDefinition::serialize(std::ostream &os) const -{ - writeU8(os, 1); // version - os<<serializeString(getName()); - serializeBody(os); -} - -CraftDefinition* CraftDefinition::deSerialize(std::istream &is) -{ - int version = readU8(is); - if(version != 1) throw SerializationError( - "unsupported CraftDefinition version"); - std::string name = deSerializeString(is); - CraftDefinition *def = NULL; - if(name == "shaped") - { - def = new CraftDefinitionShaped; - } - else if(name == "shapeless") - { - def = new CraftDefinitionShapeless; - } - else if(name == "toolrepair") - { - def = new CraftDefinitionToolRepair; - } - else if(name == "cooking") - { - def = new CraftDefinitionCooking; - } - else if(name == "fuel") - { - def = new CraftDefinitionFuel; - } - else - { - infostream<<"Unknown CraftDefinition name=\""<<name<<"\""<<std::endl; - throw SerializationError("Unknown CraftDefinition name"); - } - def->deSerializeBody(is, version); - return def; -} - /* CraftDefinitionShaped */ @@ -388,57 +334,64 @@ std::string CraftDefinitionShaped::getName() const bool CraftDefinitionShaped::check(const CraftInput &input, IGameDef *gamedef) const { - if(input.method != CRAFT_METHOD_NORMAL) + if (input.method != CRAFT_METHOD_NORMAL) return false; // Get input item matrix std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef); unsigned int inp_width = input.width; - if(inp_width == 0) + if (inp_width == 0) return false; - while(inp_names.size() % inp_width != 0) + while (inp_names.size() % inp_width != 0) inp_names.push_back(""); // Get input bounds - unsigned int inp_min_x=0, inp_max_x=0, inp_min_y=0, inp_max_y=0; - if(!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x, inp_min_y, inp_max_y)) + unsigned int inp_min_x = 0, inp_max_x = 0, inp_min_y = 0, inp_max_y = 0; + if (!craftGetBounds(inp_names, inp_width, inp_min_x, inp_max_x, + inp_min_y, inp_max_y)) return false; // it was empty + std::vector<std::string> rec_names; + if (hash_inited) + rec_names = recipe_names; + else + rec_names = craftGetItemNames(recipe, gamedef); + // Get recipe item matrix - std::vector<std::string> rec_names = craftGetItemNames(recipe, gamedef); unsigned int rec_width = width; - if(rec_width == 0) + if (rec_width == 0) return false; - while(rec_names.size() % rec_width != 0) + while (rec_names.size() % rec_width != 0) rec_names.push_back(""); // Get recipe bounds unsigned int rec_min_x=0, rec_max_x=0, rec_min_y=0, rec_max_y=0; - if(!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x, rec_min_y, rec_max_y)) + if (!craftGetBounds(rec_names, rec_width, rec_min_x, rec_max_x, + rec_min_y, rec_max_y)) return false; // it was empty // Different sizes? - if(inp_max_x - inp_min_x != rec_max_x - rec_min_x) - return false; - if(inp_max_y - inp_min_y != rec_max_y - rec_min_y) + if (inp_max_x - inp_min_x != rec_max_x - rec_min_x || + inp_max_y - inp_min_y != rec_max_y - rec_min_y) return false; // Verify that all item names in the bounding box are equal unsigned int w = inp_max_x - inp_min_x + 1; unsigned int h = inp_max_y - inp_min_y + 1; - for(unsigned int y=0; y<h; y++) - for(unsigned int x=0; x<w; x++) - { - unsigned int inp_x = inp_min_x + x; - unsigned int inp_y = inp_min_y + y; - unsigned int rec_x = rec_min_x + x; - unsigned int rec_y = rec_min_y + y; - - if(!inputItemMatchesRecipe( - inp_names[inp_y * inp_width + inp_x], - rec_names[rec_y * rec_width + rec_x], gamedef->idef()) - ){ - return false; + + for (unsigned int y=0; y < h; y++) { + unsigned int inp_y = (inp_min_y + y) * inp_width; + unsigned int rec_y = (rec_min_y + y) * rec_width; + + for (unsigned int x=0; x < w; x++) { + unsigned int inp_x = inp_min_x + x; + unsigned int rec_x = rec_min_x + x; + + if (!inputItemMatchesRecipe( + inp_names[inp_y + inp_x], + rec_names[rec_y + rec_x], gamedef->idef())) { + return false; + } } } @@ -455,41 +408,54 @@ CraftInput CraftDefinitionShaped::getInput(const CraftOutput &output, IGameDef * return CraftInput(CRAFT_METHOD_NORMAL,width,craftGetItems(recipe,gamedef)); } -void CraftDefinitionShaped::decrementInput(CraftInput &input, IGameDef *gamedef) const +void CraftDefinitionShaped::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements, + IGameDef *gamedef) const { - craftDecrementOrReplaceInput(input, replacements, gamedef); + craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef); } -std::string CraftDefinitionShaped::dump() const +CraftHashType CraftDefinitionShaped::getHashType() const { - std::ostringstream os(std::ios::binary); - os<<"(shaped, output=\""<<output - <<"\", recipe="<<craftDumpMatrix(recipe, width) - <<", replacements="<<replacements.dump()<<")"; - return os.str(); + assert(hash_inited); // Pre-condition + bool has_group = false; + for (size_t i = 0; i < recipe_names.size(); i++) { + if (isGroupRecipeStr(recipe_names[i])) { + has_group = true; + break; + } + } + if (has_group) + return CRAFT_HASH_TYPE_COUNT; + else + return CRAFT_HASH_TYPE_ITEM_NAMES; } -void CraftDefinitionShaped::serializeBody(std::ostream &os) const +u64 CraftDefinitionShaped::getHash(CraftHashType type) const { - os<<serializeString(output); - writeU16(os, width); - writeU16(os, recipe.size()); - for(u32 i=0; i<recipe.size(); i++) - os<<serializeString(recipe[i]); - replacements.serialize(os); + assert(hash_inited); // Pre-condition + assert((type == CRAFT_HASH_TYPE_ITEM_NAMES) + || (type == CRAFT_HASH_TYPE_COUNT)); // Pre-condition + + std::vector<std::string> rec_names = recipe_names; + std::sort(rec_names.begin(), rec_names.end()); + return getHashForGrid(type, rec_names); } -void CraftDefinitionShaped::deSerializeBody(std::istream &is, int version) +void CraftDefinitionShaped::initHash(IGameDef *gamedef) { - if(version != 1) throw SerializationError( - "unsupported CraftDefinitionShaped version"); - output = deSerializeString(is); - width = readU16(is); - recipe.clear(); - u32 count = readU16(is); - for(u32 i=0; i<count; i++) - recipe.push_back(deSerializeString(is)); - replacements.deSerialize(is); + if (hash_inited) + return; + hash_inited = true; + recipe_names = craftGetItemNames(recipe, gamedef); +} + +std::string CraftDefinitionShaped::dump() const +{ + std::ostringstream os(std::ios::binary); + os << "(shaped, output=\"" << output + << "\", recipe=" << craftDumpMatrix(recipe, width) + << ", replacements=" << replacements.dump() << ")"; + return os.str(); } /* @@ -503,48 +469,53 @@ std::string CraftDefinitionShapeless::getName() const bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef) const { - if(input.method != CRAFT_METHOD_NORMAL) + if (input.method != CRAFT_METHOD_NORMAL) return false; - + // Filter empty items out of input std::vector<std::string> input_filtered; - for(std::vector<ItemStack>::const_iterator - i = input.items.begin(); - i != input.items.end(); i++) - { - if(i->name != "") - input_filtered.push_back(i->name); + for (std::vector<ItemStack>::size_type i = 0; + i < input.items.size(); i++) { + const ItemStack &item = input.items[i]; + if (item.name != "") + input_filtered.push_back(item.name); } // If there is a wrong number of items in input, no match - if(input_filtered.size() != recipe.size()){ + if (input_filtered.size() != recipe.size()) { /*dstream<<"Number of input items ("<<input_filtered.size() <<") does not match recipe size ("<<recipe.size()<<") " <<"of recipe with output="<<output<<std::endl;*/ return false; } - // Try with all permutations of the recipe - std::vector<std::string> recipe_copy = craftGetItemNames(recipe, gamedef); - // Start from the lexicographically first permutation (=sorted) - std::sort(recipe_copy.begin(), recipe_copy.end()); - //while(std::prev_permutation(recipe_copy.begin(), recipe_copy.end())){} - do{ + std::vector<std::string> recipe_copy; + if (hash_inited) + recipe_copy = recipe_names; + else { + recipe_copy = craftGetItemNames(recipe, gamedef); + std::sort(recipe_copy.begin(), recipe_copy.end()); + } + + // Try with all permutations of the recipe, + // start from the lexicographically first permutation (=sorted), + // recipe_names is pre-sorted + do { // If all items match, the recipe matches bool all_match = true; //dstream<<"Testing recipe (output="<<output<<"):"; - for(size_t i=0; i<recipe.size(); i++){ + for (size_t i=0; i<recipe.size(); i++) { //dstream<<" ("<<input_filtered[i]<<" == "<<recipe_copy[i]<<")"; - if(!inputItemMatchesRecipe(input_filtered[i], recipe_copy[i], - gamedef->idef())){ + if (!inputItemMatchesRecipe(input_filtered[i], recipe_copy[i], + gamedef->idef())) { all_match = false; break; } } //dstream<<" -> match="<<all_match<<std::endl; - if(all_match) + if (all_match) return true; - }while(std::next_permutation(recipe_copy.begin(), recipe_copy.end())); + } while (std::next_permutation(recipe_copy.begin(), recipe_copy.end())); return false; } @@ -556,42 +527,55 @@ CraftOutput CraftDefinitionShapeless::getOutput(const CraftInput &input, IGameDe CraftInput CraftDefinitionShapeless::getInput(const CraftOutput &output, IGameDef *gamedef) const { - return CraftInput(CRAFT_METHOD_NORMAL,0,craftGetItems(recipe,gamedef)); + return CraftInput(CRAFT_METHOD_NORMAL, 0, craftGetItems(recipe, gamedef)); } -void CraftDefinitionShapeless::decrementInput(CraftInput &input, IGameDef *gamedef) const +void CraftDefinitionShapeless::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements, + IGameDef *gamedef) const { - craftDecrementOrReplaceInput(input, replacements, gamedef); + craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef); } -std::string CraftDefinitionShapeless::dump() const +CraftHashType CraftDefinitionShapeless::getHashType() const { - std::ostringstream os(std::ios::binary); - os<<"(shapeless, output=\""<<output - <<"\", recipe="<<craftDumpMatrix(recipe, recipe.size()) - <<", replacements="<<replacements.dump()<<")"; - return os.str(); + assert(hash_inited); // Pre-condition + bool has_group = false; + for (size_t i = 0; i < recipe_names.size(); i++) { + if (isGroupRecipeStr(recipe_names[i])) { + has_group = true; + break; + } + } + if (has_group) + return CRAFT_HASH_TYPE_COUNT; + else + return CRAFT_HASH_TYPE_ITEM_NAMES; +} + +u64 CraftDefinitionShapeless::getHash(CraftHashType type) const +{ + assert(hash_inited); // Pre-condition + assert(type == CRAFT_HASH_TYPE_ITEM_NAMES + || type == CRAFT_HASH_TYPE_COUNT); // Pre-condition + return getHashForGrid(type, recipe_names); } -void CraftDefinitionShapeless::serializeBody(std::ostream &os) const +void CraftDefinitionShapeless::initHash(IGameDef *gamedef) { - os<<serializeString(output); - writeU16(os, recipe.size()); - for(u32 i=0; i<recipe.size(); i++) - os<<serializeString(recipe[i]); - replacements.serialize(os); + if (hash_inited) + return; + hash_inited = true; + recipe_names = craftGetItemNames(recipe, gamedef); + std::sort(recipe_names.begin(), recipe_names.end()); } -void CraftDefinitionShapeless::deSerializeBody(std::istream &is, int version) +std::string CraftDefinitionShapeless::dump() const { - if(version != 1) throw SerializationError( - "unsupported CraftDefinitionShapeless version"); - output = deSerializeString(is); - recipe.clear(); - u32 count = readU16(is); - for(u32 i=0; i<count; i++) - recipe.push_back(deSerializeString(is)); - replacements.deSerialize(is); + std::ostringstream os(std::ios::binary); + os << "(shapeless, output=\"" << output + << "\", recipe=" << craftDumpMatrix(recipe, recipe.size()) + << ", replacements=" << replacements.dump() << ")"; + return os.str(); } /* @@ -605,10 +589,9 @@ static ItemStack craftToolRepair( IGameDef *gamedef) { IItemDefManager *idef = gamedef->idef(); - if(item1.count != 1 || item2.count != 1 || item1.name != item2.name + if (item1.count != 1 || item2.count != 1 || item1.name != item2.name || idef->get(item1.name).type != ITEM_TOOL - || idef->get(item2.name).type != ITEM_TOOL) - { + || idef->get(item2.name).type != ITEM_TOOL) { // Failure return ItemStack(); } @@ -617,9 +600,9 @@ static ItemStack craftToolRepair( s32 item2_uses = 65536 - (u32) item2.wear; s32 new_uses = item1_uses + item2_uses; s32 new_wear = 65536 - new_uses + floor(additional_wear * 65536 + 0.5); - if(new_wear >= 65536) + if (new_wear >= 65536) return ItemStack(); - if(new_wear < 0) + if (new_wear < 0) new_wear = 0; ItemStack repaired = item1; @@ -634,21 +617,19 @@ std::string CraftDefinitionToolRepair::getName() const bool CraftDefinitionToolRepair::check(const CraftInput &input, IGameDef *gamedef) const { - if(input.method != CRAFT_METHOD_NORMAL) + if (input.method != CRAFT_METHOD_NORMAL) return false; ItemStack item1; ItemStack item2; - for(std::vector<ItemStack>::const_iterator - i = input.items.begin(); - i != input.items.end(); i++) - { - if(!i->empty()) - { - if(item1.empty()) - item1 = *i; - else if(item2.empty()) - item2 = *i; + for (std::vector<ItemStack>::size_type i = 0; + i < input.items.size(); i++) { + const ItemStack &item = input.items[i]; + if (!item.empty()) { + if (item1.empty()) + item1 = item; + else if (item2.empty()) + item2 = item; else return false; } @@ -661,16 +642,14 @@ CraftOutput CraftDefinitionToolRepair::getOutput(const CraftInput &input, IGameD { ItemStack item1; ItemStack item2; - for(std::vector<ItemStack>::const_iterator - i = input.items.begin(); - i != input.items.end(); i++) - { - if(!i->empty()) - { - if(item1.empty()) - item1 = *i; - else if(item2.empty()) - item2 = *i; + for (std::vector<ItemStack>::size_type i = 0; + i < input.items.size(); i++) { + const ItemStack &item = input.items[i]; + if (!item.empty()) { + if (item1.empty()) + item1 = item; + else if (item2.empty()) + item2 = item; } } ItemStack repaired = craftToolRepair(item1, item2, additional_wear, gamedef); @@ -681,10 +660,11 @@ CraftInput CraftDefinitionToolRepair::getInput(const CraftOutput &output, IGameD { std::vector<ItemStack> stack; stack.push_back(ItemStack()); - return CraftInput(CRAFT_METHOD_COOKING,additional_wear,stack); + return CraftInput(CRAFT_METHOD_COOKING, additional_wear, stack); } -void CraftDefinitionToolRepair::decrementInput(CraftInput &input, IGameDef *gamedef) const +void CraftDefinitionToolRepair::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements, + IGameDef *gamedef) const { craftDecrementInput(input, gamedef); } @@ -692,22 +672,10 @@ void CraftDefinitionToolRepair::decrementInput(CraftInput &input, IGameDef *game std::string CraftDefinitionToolRepair::dump() const { std::ostringstream os(std::ios::binary); - os<<"(toolrepair, additional_wear="<<additional_wear<<")"; + os << "(toolrepair, additional_wear=" << additional_wear << ")"; return os.str(); } -void CraftDefinitionToolRepair::serializeBody(std::ostream &os) const -{ - writeF1000(os, additional_wear); -} - -void CraftDefinitionToolRepair::deSerializeBody(std::istream &is, int version) -{ - if(version != 1) throw SerializationError( - "unsupported CraftDefinitionToolRepair version"); - additional_wear = readF1000(is); -} - /* CraftDefinitionCooking */ @@ -719,27 +687,26 @@ std::string CraftDefinitionCooking::getName() const bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) const { - if(input.method != CRAFT_METHOD_COOKING) + if (input.method != CRAFT_METHOD_COOKING) return false; // Filter empty items out of input std::vector<std::string> input_filtered; - for(std::vector<ItemStack>::const_iterator - i = input.items.begin(); - i != input.items.end(); i++) - { - if(i->name != "") - input_filtered.push_back(i->name); + for (std::vector<ItemStack>::size_type i = 0; + i < input.items.size(); i++) { + const std::string &name = input.items[i].name; + if (name != "") + input_filtered.push_back(name); } // If there is a wrong number of items in input, no match - if(input_filtered.size() != 1){ + if (input_filtered.size() != 1) { /*dstream<<"Number of input items ("<<input_filtered.size() <<") does not match recipe size (1) " <<"of cooking recipe with output="<<output<<std::endl;*/ return false; } - + // Check the single input item return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef()); } @@ -756,37 +723,49 @@ CraftInput CraftDefinitionCooking::getInput(const CraftOutput &output, IGameDef return CraftInput(CRAFT_METHOD_COOKING,cooktime,craftGetItems(rec,gamedef)); } -void CraftDefinitionCooking::decrementInput(CraftInput &input, IGameDef *gamedef) const +void CraftDefinitionCooking::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements, + IGameDef *gamedef) const { - craftDecrementOrReplaceInput(input, replacements, gamedef); + craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef); } -std::string CraftDefinitionCooking::dump() const +CraftHashType CraftDefinitionCooking::getHashType() const { - std::ostringstream os(std::ios::binary); - os<<"(cooking, output=\""<<output - <<"\", recipe=\""<<recipe - <<"\", cooktime="<<cooktime<<")" - <<", replacements="<<replacements.dump()<<")"; - return os.str(); + if (isGroupRecipeStr(recipe_name)) + return CRAFT_HASH_TYPE_COUNT; + else + return CRAFT_HASH_TYPE_ITEM_NAMES; +} + +u64 CraftDefinitionCooking::getHash(CraftHashType type) const +{ + if (type == CRAFT_HASH_TYPE_ITEM_NAMES) { + return getHashForString(recipe_name); + } else if (type == CRAFT_HASH_TYPE_COUNT) { + return 1; + } else { + //illegal hash type for this CraftDefinition (pre-condition) + assert(false); + return 0; + } } -void CraftDefinitionCooking::serializeBody(std::ostream &os) const +void CraftDefinitionCooking::initHash(IGameDef *gamedef) { - os<<serializeString(output); - os<<serializeString(recipe); - writeF1000(os, cooktime); - replacements.serialize(os); + if (hash_inited) + return; + hash_inited = true; + recipe_name = craftGetItemName(recipe, gamedef); } -void CraftDefinitionCooking::deSerializeBody(std::istream &is, int version) +std::string CraftDefinitionCooking::dump() const { - if(version != 1) throw SerializationError( - "unsupported CraftDefinitionCooking version"); - output = deSerializeString(is); - recipe = deSerializeString(is); - cooktime = readF1000(is); - replacements.deSerialize(is); + std::ostringstream os(std::ios::binary); + os << "(cooking, output=\"" << output + << "\", recipe=\"" << recipe + << "\", cooktime=" << cooktime << ")" + << ", replacements=" << replacements.dump() << ")"; + return os.str(); } /* @@ -800,27 +779,26 @@ std::string CraftDefinitionFuel::getName() const bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) const { - if(input.method != CRAFT_METHOD_FUEL) + if (input.method != CRAFT_METHOD_FUEL) return false; // Filter empty items out of input std::vector<std::string> input_filtered; - for(std::vector<ItemStack>::const_iterator - i = input.items.begin(); - i != input.items.end(); i++) - { - if(i->name != "") - input_filtered.push_back(i->name); + for (std::vector<ItemStack>::size_type i = 0; + i < input.items.size(); i++) { + const std::string &name = input.items[i].name; + if (name != "") + input_filtered.push_back(name); } // If there is a wrong number of items in input, no match - if(input_filtered.size() != 1){ + if (input_filtered.size() != 1) { /*dstream<<"Number of input items ("<<input_filtered.size() <<") does not match recipe size (1) " <<"of fuel recipe with burntime="<<burntime<<std::endl;*/ return false; } - + // Check the single input item return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef()); } @@ -837,34 +815,47 @@ CraftInput CraftDefinitionFuel::getInput(const CraftOutput &output, IGameDef *ga return CraftInput(CRAFT_METHOD_COOKING,(int)burntime,craftGetItems(rec,gamedef)); } -void CraftDefinitionFuel::decrementInput(CraftInput &input, IGameDef *gamedef) const +void CraftDefinitionFuel::decrementInput(CraftInput &input, std::vector<ItemStack> &output_replacements, + IGameDef *gamedef) const { - craftDecrementOrReplaceInput(input, replacements, gamedef); + craftDecrementOrReplaceInput(input, output_replacements, replacements, gamedef); } -std::string CraftDefinitionFuel::dump() const +CraftHashType CraftDefinitionFuel::getHashType() const { - std::ostringstream os(std::ios::binary); - os<<"(fuel, recipe=\""<<recipe - <<"\", burntime="<<burntime<<")" - <<", replacements="<<replacements.dump()<<")"; - return os.str(); + if (isGroupRecipeStr(recipe_name)) + return CRAFT_HASH_TYPE_COUNT; + else + return CRAFT_HASH_TYPE_ITEM_NAMES; } -void CraftDefinitionFuel::serializeBody(std::ostream &os) const +u64 CraftDefinitionFuel::getHash(CraftHashType type) const { - os<<serializeString(recipe); - writeF1000(os, burntime); - replacements.serialize(os); + if (type == CRAFT_HASH_TYPE_ITEM_NAMES) { + return getHashForString(recipe_name); + } else if (type == CRAFT_HASH_TYPE_COUNT) { + return 1; + } else { + //illegal hash type for this CraftDefinition (pre-condition) + assert(false); + return 0; + } } -void CraftDefinitionFuel::deSerializeBody(std::istream &is, int version) +void CraftDefinitionFuel::initHash(IGameDef *gamedef) { - if(version != 1) throw SerializationError( - "unsupported CraftDefinitionFuel version"); - recipe = deSerializeString(is); - burntime = readF1000(is); - replacements.deSerialize(is); + if (hash_inited) + return; + hash_inited = true; + recipe_name = craftGetItemName(recipe, gamedef); +} +std::string CraftDefinitionFuel::dump() const +{ + std::ostringstream os(std::ios::binary); + os << "(fuel, recipe=\"" << recipe + << "\", burntime=" << burntime << ")" + << ", replacements=" << replacements.dump() << ")"; + return os.str(); } /* @@ -874,203 +865,171 @@ void CraftDefinitionFuel::deSerializeBody(std::istream &is, int version) class CCraftDefManager: public IWritableCraftDefManager { public: + CCraftDefManager() + { + m_craft_defs.resize(craft_hash_type_max + 1); + } + virtual ~CCraftDefManager() { clear(); } + virtual bool getCraftResult(CraftInput &input, CraftOutput &output, - bool decrementInput, IGameDef *gamedef) const + 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(std::vector<ItemStack>::const_iterator - i = input.items.begin(); - i != input.items.end(); i++) - { - if(!i->empty()) - { + for (std::vector<ItemStack>::size_type i = 0; + i < input.items.size(); i++) { + if (!input.items[i].empty()) { all_empty = false; break; } } - if(all_empty) + if (all_empty) return false; - // Walk crafting definitions from back to front, so that later - // definitions can override earlier ones. - for(std::vector<CraftDefinition*>::const_reverse_iterator - i = m_craft_definitions.rbegin(); - i != m_craft_definitions.rend(); i++) - { - CraftDefinition *def = *i; + std::vector<std::string> input_names; + input_names = craftGetItemNames(input.items, gamedef); + std::sort(input_names.begin(), input_names.end()); - /*infostream<<"Checking "<<input.dump()<<std::endl - <<" against "<<def->dump()<<std::endl;*/ + // Try hash types with increasing collision rate, and return if found. + for (int type = 0; type <= craft_hash_type_max; type++) { + u64 hash = getHashForGrid((CraftHashType) type, input_names); - try { - if(def->check(input, gamedef)) - { - // Get output, then decrement input (if requested) - output = def->getOutput(input, gamedef); - if(decrementInput) - def->decrementInput(input, gamedef); - return true; - } - } - catch(SerializationError &e) - { - errorstream<<"getCraftResult: ERROR: " - <<"Serialization error in recipe " - <<def->dump()<<std::endl; - // then go on with the next craft definition - } - } - return false; - } - virtual bool getCraftRecipe(CraftInput &input, CraftOutput &output, - IGameDef *gamedef) const - { - CraftOutput tmpout; - tmpout.item = ""; - tmpout.time = 0; + /*errorstream << "Checking type " << type << " with hash " << hash << std::endl;*/ - // If output item is empty, abort. - if(output.item.empty()) - return false; + // We'd like to do "const [...] hash_collisions = m_craft_defs[type][hash];" + // but that doesn't compile for some reason. This does. + std::map<u64, std::vector<CraftDefinition*> >::const_iterator + col_iter = (m_craft_defs[type]).find(hash); + + if (col_iter == (m_craft_defs[type]).end()) + continue; - // Walk crafting definitions from back to front, so that later - // definitions can override earlier ones. - for(std::vector<CraftDefinition*>::const_reverse_iterator - i = m_craft_definitions.rbegin(); - i != m_craft_definitions.rend(); i++) - { - CraftDefinition *def = *i; - - /*infostream<<"Checking "<<input.dump()<<std::endl - <<" against "<<def->dump()<<std::endl;*/ - - try { - tmpout = def->getOutput(input, gamedef); - if((tmpout.item.substr(0,output.item.length()) == output.item) && - ((tmpout.item[output.item.length()] == 0) || - (tmpout.item[output.item.length()] == ' '))) - { + const std::vector<CraftDefinition*> &hash_collisions = col_iter->second; + // Walk crafting definitions from back to front, so that later + // definitions can override earlier ones. + for (std::vector<CraftDefinition*>::size_type + i = hash_collisions.size(); i > 0; i--) { + CraftDefinition *def = hash_collisions[i - 1]; + + /*errorstream << "Checking " << input.dump() << std::endl + << " against " << def->dump() << std::endl;*/ + + if (def->check(input, gamedef)) { // Get output, then decrement input (if requested) - input = def->getInput(output, gamedef); + output = def->getOutput(input, gamedef); + if (decrementInput) + def->decrementInput(input, output_replacement, gamedef); + /*errorstream << "Check RETURNS TRUE" << std::endl;*/ return true; } } - catch(SerializationError &e) - { - errorstream<<"getCraftResult: ERROR: " - <<"Serialization error in recipe " - <<def->dump()<<std::endl; - // then go on with the next craft definition - } } return false; } + virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output, - IGameDef *gamedef) const + IGameDef *gamedef, unsigned limit=0) const { - std::vector<CraftDefinition*> recipes_list; - CraftInput input; - CraftOutput tmpout; - tmpout.item = ""; - tmpout.time = 0; - - for(std::vector<CraftDefinition*>::const_reverse_iterator - i = m_craft_definitions.rbegin(); - i != m_craft_definitions.rend(); i++) - { - CraftDefinition *def = *i; - - /*infostream<<"Checking "<<input.dump()<<std::endl - <<" against "<<def->dump()<<std::endl;*/ - - try { - tmpout = def->getOutput(input, gamedef); - if(tmpout.item.substr(0,output.item.length()) == output.item) - { - // Get output, then decrement input (if requested) - input = def->getInput(output, gamedef); - recipes_list.push_back(*i); - } - } - catch(SerializationError &e) - { - errorstream<<"getCraftResult: ERROR: " - <<"Serialization error in recipe " - <<def->dump()<<std::endl; - // then go on with the next craft definition - } + std::vector<CraftDefinition*> recipes; + + std::map<std::string, std::vector<CraftDefinition*> >::const_iterator + vec_iter = m_output_craft_definitions.find(output.item); + + if (vec_iter == m_output_craft_definitions.end()) + return recipes; + + const std::vector<CraftDefinition*> &vec = vec_iter->second; + + recipes.reserve(limit ? MYMIN(limit, vec.size()) : vec.size()); + + for (std::vector<CraftDefinition*>::size_type i = vec.size(); + i > 0; i--) { + CraftDefinition *def = vec[i - 1]; + if (limit && recipes.size() >= limit) + break; + recipes.push_back(def); } - return recipes_list; + + return recipes; } virtual std::string dump() const { std::ostringstream os(std::ios::binary); - os<<"Crafting definitions:\n"; - for(std::vector<CraftDefinition*>::const_iterator - i = m_craft_definitions.begin(); - i != m_craft_definitions.end(); i++) - { - os<<(*i)->dump()<<"\n"; + os << "Crafting definitions:\n"; + for (int type = 0; type <= craft_hash_type_max; type++) { + for (std::map<u64, std::vector<CraftDefinition*> >::const_iterator + it = (m_craft_defs[type]).begin(); + it != (m_craft_defs[type]).end(); it++) { + for (std::vector<CraftDefinition*>::size_type i = 0; + i < it->second.size(); i++) { + os << "type " << type + << " hash " << it->first + << " def " << it->second[i]->dump() + << "\n"; + } + } } return os.str(); } - virtual void registerCraft(CraftDefinition *def) + virtual void registerCraft(CraftDefinition *def, IGameDef *gamedef) { - verbosestream<<"registerCraft: registering craft definition: " - <<def->dump()<<std::endl; - m_craft_definitions.push_back(def); + verbosestream << "registerCraft: registering craft definition: " + << def->dump() << std::endl; + m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0].push_back(def); + + CraftInput input; + std::string output_name = craftGetItemName( + def->getOutput(input, gamedef).item, gamedef); + m_output_craft_definitions[output_name].push_back(def); } virtual void clear() { - for(std::vector<CraftDefinition*>::iterator - i = m_craft_definitions.begin(); - i != m_craft_definitions.end(); i++){ - delete *i; - } - m_craft_definitions.clear(); - } - virtual void serialize(std::ostream &os) const - { - writeU8(os, 0); // version - u16 count = m_craft_definitions.size(); - writeU16(os, count); - for(std::vector<CraftDefinition*>::const_iterator - i = m_craft_definitions.begin(); - i != m_craft_definitions.end(); i++){ - CraftDefinition *def = *i; - // Serialize wrapped in a string - std::ostringstream tmp_os(std::ios::binary); - def->serialize(tmp_os); - os<<serializeString(tmp_os.str()); + for (int type = 0; type <= craft_hash_type_max; type++) { + for (std::map<u64, std::vector<CraftDefinition*> >::iterator + it = m_craft_defs[type].begin(); + it != m_craft_defs[type].end(); it++) { + for (std::vector<CraftDefinition*>::iterator + iit = it->second.begin(); + iit != it->second.end(); ++iit) { + delete *iit; + } + it->second.clear(); + } + m_craft_defs[type].clear(); } + m_output_craft_definitions.clear(); } - virtual void deSerialize(std::istream &is) + virtual void initHashes(IGameDef *gamedef) { - // Clear everything - clear(); - // Deserialize - int version = readU8(is); - if(version != 0) throw SerializationError( - "unsupported CraftDefManager version"); - u16 count = readU16(is); - for(u16 i=0; i<count; i++){ - // Deserialize a string and grab a CraftDefinition from it - std::istringstream tmp_is(deSerializeString(is), std::ios::binary); - CraftDefinition *def = CraftDefinition::deSerialize(tmp_is); - // Register - registerCraft(def); + // Move the CraftDefs from the unhashed layer into layers higher up. + std::vector<CraftDefinition *> &unhashed = + m_craft_defs[(int) CRAFT_HASH_TYPE_UNHASHED][0]; + for (std::vector<CraftDefinition*>::size_type i = 0; + i < unhashed.size(); i++) { + CraftDefinition *def = unhashed[i]; + + // Initialize and get the definition's hash + def->initHash(gamedef); + CraftHashType type = def->getHashType(); + u64 hash = def->getHash(type); + + // Enter the definition + m_craft_defs[type][hash].push_back(def); } + unhashed.clear(); } private: - std::vector<CraftDefinition*> m_craft_definitions; + //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; }; IWritableCraftDefManager* createCraftDefManager() diff --git a/src/craftdef.h b/src/craftdef.h index 14dc53003..cebb2d7ae 100644 --- a/src/craftdef.h +++ b/src/craftdef.h @@ -44,6 +44,28 @@ enum CraftMethod }; /* + The type a hash can be. The earlier a type is mentioned in this enum, + the earlier it is tried at crafting, and the less likely is a collision. + Changing order causes changes in behaviour, so know what you do. + */ +enum CraftHashType +{ + // Hashes the normalized names of the recipe's elements. + // Only recipes without group usage can be found here, + // because groups can't be guessed efficiently. + CRAFT_HASH_TYPE_ITEM_NAMES, + + // Counts the non-empty slots. + CRAFT_HASH_TYPE_COUNT, + + // This layer both spares an extra variable, and helps to retain (albeit rarely used) functionality. Maps to 0. + // Before hashes are "initialized", all hashes reside here, after initialisation, none are. + CRAFT_HASH_TYPE_UNHASHED + +}; +const int craft_hash_type_max = (int) CRAFT_HASH_TYPE_UNHASHED; + +/* Input: The contents of the crafting slots, arranged in matrix form */ struct CraftInput @@ -106,8 +128,6 @@ struct CraftReplacements pairs(pairs_) {} std::string dump() const; - void serialize(std::ostream &os) const; - void deSerialize(std::istream &is); }; /* @@ -119,9 +139,6 @@ public: CraftDefinition(){} virtual ~CraftDefinition(){} - void serialize(std::ostream &os) const; - static CraftDefinition* deSerialize(std::istream &is); - // Returns type of crafting definition virtual std::string getName() const=0; @@ -133,13 +150,16 @@ public: // the inverse of the above virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const=0; // Decreases count of every input item - virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const=0; + virtual void decrementInput(CraftInput &input, + std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const=0; - virtual std::string dump() const=0; + virtual CraftHashType getHashType() const = 0; + 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; -protected: - virtual void serializeBody(std::ostream &os) const=0; - virtual void deSerializeBody(std::istream &is, int version)=0; + virtual std::string dump() const=0; }; /* @@ -152,14 +172,15 @@ class CraftDefinitionShaped: public CraftDefinition { public: CraftDefinitionShaped(): - output(""), width(1), recipe(), replacements() + output(""), width(1), recipe(), hash_inited(false), replacements() {} 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_) + output(output_), width(width_), recipe(recipe_), + hash_inited(false), replacements(replacements_) {} virtual ~CraftDefinitionShaped(){} @@ -167,13 +188,15 @@ public: virtual bool check(const CraftInput &input, IGameDef *gamedef) const; virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; - virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const; + virtual void decrementInput(CraftInput &input, + std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const; - virtual std::string dump() const; + virtual CraftHashType getHashType() const; + virtual u64 getHash(CraftHashType type) const; -protected: - virtual void serializeBody(std::ostream &os) const; - virtual void deSerializeBody(std::istream &is, int version); + virtual void initHash(IGameDef *gamedef); + + virtual std::string dump() const; private: // Output itemstring @@ -182,6 +205,10 @@ private: unsigned int width; // Recipe matrix (itemstrings) std::vector<std::string> recipe; + // Recipe matrix (item names) + std::vector<std::string> recipe_names; + // bool indicating if initHash has been called already + bool hash_inited; // Replacement items for decrementInput() CraftReplacements replacements; }; @@ -195,13 +222,14 @@ class CraftDefinitionShapeless: public CraftDefinition { public: CraftDefinitionShapeless(): - output(""), recipe(), replacements() + output(""), recipe(), hash_inited(false), replacements() {} CraftDefinitionShapeless( const std::string &output_, const std::vector<std::string> &recipe_, const CraftReplacements &replacements_): - output(output_), recipe(recipe_), replacements(replacements_) + output(output_), recipe(recipe_), + hash_inited(false), replacements(replacements_) {} virtual ~CraftDefinitionShapeless(){} @@ -209,19 +237,25 @@ public: virtual bool check(const CraftInput &input, IGameDef *gamedef) const; virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; - virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const; + virtual void decrementInput(CraftInput &input, + std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const; - virtual std::string dump() const; + virtual CraftHashType getHashType() const; + virtual u64 getHash(CraftHashType type) const; -protected: - virtual void serializeBody(std::ostream &os) const; - virtual void deSerializeBody(std::istream &is, int version); + virtual void initHash(IGameDef *gamedef); + + virtual std::string dump() const; private: // Output itemstring std::string output; // Recipe list (itemstrings) std::vector<std::string> recipe; + // Recipe list (item names) + std::vector<std::string> recipe_names; + // bool indicating if initHash has been called already + bool hash_inited; // Replacement items for decrementInput() CraftReplacements replacements; }; @@ -247,13 +281,15 @@ public: virtual bool check(const CraftInput &input, IGameDef *gamedef) const; virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; - virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const; + virtual void decrementInput(CraftInput &input, + std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const; - virtual std::string dump() const; + virtual CraftHashType getHashType() const { return CRAFT_HASH_TYPE_COUNT; } + virtual u64 getHash(CraftHashType type) const { return 2; } + + virtual void initHash(IGameDef *gamedef) {} -protected: - virtual void serializeBody(std::ostream &os) const; - virtual void deSerializeBody(std::istream &is, int version); + virtual std::string dump() const; private: // This is a constant that is added to the wear of the result. @@ -272,14 +308,15 @@ class CraftDefinitionCooking: public CraftDefinition { public: CraftDefinitionCooking(): - output(""), recipe(""), cooktime() + output(""), recipe(""), hash_inited(false), cooktime() {} CraftDefinitionCooking( const std::string &output_, const std::string &recipe_, float cooktime_, const CraftReplacements &replacements_): - output(output_), recipe(recipe_), cooktime(cooktime_), replacements(replacements_) + output(output_), recipe(recipe_), hash_inited(false), + cooktime(cooktime_), replacements(replacements_) {} virtual ~CraftDefinitionCooking(){} @@ -287,19 +324,25 @@ public: virtual bool check(const CraftInput &input, IGameDef *gamedef) const; virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; - virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const; + virtual void decrementInput(CraftInput &input, + std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const; - virtual std::string dump() const; + virtual CraftHashType getHashType() const; + virtual u64 getHash(CraftHashType type) const; -protected: - virtual void serializeBody(std::ostream &os) const; - virtual void deSerializeBody(std::istream &is, int version); + virtual void initHash(IGameDef *gamedef); + + virtual std::string dump() const; private: // Output itemstring std::string output; // Recipe itemstring std::string recipe; + // Recipe item name + std::string recipe_name; + // bool indicating if initHash has been called already + bool hash_inited; // Time in seconds float cooktime; // Replacement items for decrementInput() @@ -314,12 +357,12 @@ class CraftDefinitionFuel: public CraftDefinition { public: CraftDefinitionFuel(): - recipe(""), burntime() + recipe(""), hash_inited(false), burntime() {} CraftDefinitionFuel(std::string recipe_, float burntime_, const CraftReplacements &replacements_): - recipe(recipe_), burntime(burntime_), replacements(replacements_) + recipe(recipe_), hash_inited(false), burntime(burntime_), replacements(replacements_) {} virtual ~CraftDefinitionFuel(){} @@ -327,17 +370,23 @@ public: virtual bool check(const CraftInput &input, IGameDef *gamedef) const; virtual CraftOutput getOutput(const CraftInput &input, IGameDef *gamedef) const; virtual CraftInput getInput(const CraftOutput &output, IGameDef *gamedef) const; - virtual void decrementInput(CraftInput &input, IGameDef *gamedef) const; + virtual void decrementInput(CraftInput &input, + std::vector<ItemStack> &output_replacements, IGameDef *gamedef) const; - virtual std::string dump() const; + virtual CraftHashType getHashType() const; + virtual u64 getHash(CraftHashType type) const; + + virtual void initHash(IGameDef *gamedef); -protected: - virtual void serializeBody(std::ostream &os) const; - virtual void deSerializeBody(std::istream &is, int version); + virtual std::string dump() const; private: // Recipe itemstring std::string recipe; + // Recipe item name + std::string recipe_name; + // bool indicating if initHash has been called already + bool hash_inited; // Time in seconds float burntime; // Replacement items for decrementInput() @@ -355,16 +404,13 @@ public: // The main crafting function virtual bool getCraftResult(CraftInput &input, CraftOutput &output, + std::vector<ItemStack> &output_replacements, bool decrementInput, IGameDef *gamedef) const=0; - virtual bool getCraftRecipe(CraftInput &input, CraftOutput &output, - IGameDef *gamedef) const=0; virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output, - IGameDef *gamedef) const=0; - + IGameDef *gamedef, unsigned limit=0) const=0; + // Print crafting recipes for debugging virtual std::string dump() const=0; - - virtual void serialize(std::ostream &os) const=0; }; class IWritableCraftDefManager : public ICraftDefManager @@ -375,23 +421,23 @@ public: // The main crafting function virtual bool getCraftResult(CraftInput &input, CraftOutput &output, + std::vector<ItemStack> &output_replacements, bool decrementInput, IGameDef *gamedef) const=0; - virtual bool getCraftRecipe(CraftInput &input, CraftOutput &output, - IGameDef *gamedef) const=0; - virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output, - IGameDef *gamedef) const=0; + virtual std::vector<CraftDefinition*> getCraftRecipes(CraftOutput &output, + IGameDef *gamedef, unsigned limit=0) const=0; // Print crafting recipes for debugging virtual std::string dump() const=0; // Add a crafting definition. // After calling this, the pointer belongs to the manager. - virtual void registerCraft(CraftDefinition *def)=0; + virtual void registerCraft(CraftDefinition *def, IGameDef *gamedef) = 0; + // Delete all crafting definitions virtual void clear()=0; - virtual void serialize(std::ostream &os) const=0; - virtual void deSerialize(std::istream &is)=0; + // To be called after all mods are loaded, so that we catch all aliases + virtual void initHashes(IGameDef *gamedef) = 0; }; IWritableCraftDefManager* createCraftDefManager(); diff --git a/src/database-dummy.cpp b/src/database-dummy.cpp index 271d9c975..2e5de5ed1 100644 --- a/src/database-dummy.cpp +++ b/src/database-dummy.cpp @@ -18,64 +18,38 @@ with this program; if not, write to the Free Software Foundation, Inc., */ /* -Dummy "database" class +Dummy database class */ - #include "database-dummy.h" -#include "map.h" -#include "mapsector.h" -#include "mapblock.h" -#include "serialization.h" -#include "main.h" -#include "settings.h" -#include "log.h" - -Database_Dummy::Database_Dummy(ServerMap *map) -{ - srvmap = map; -} - -int Database_Dummy::Initialized(void) -{ - return 1; -} - -void Database_Dummy::beginSave() {} -void Database_Dummy::endSave() {} -bool Database_Dummy::saveBlock(v3s16 blockpos, std::string &data) +bool Database_Dummy::saveBlock(const v3s16 &pos, const std::string &data) { - m_database[getBlockAsInteger(blockpos)] = data; + m_database[getBlockAsInteger(pos)] = data; return true; } -std::string Database_Dummy::loadBlock(v3s16 blockpos) +std::string Database_Dummy::loadBlock(const v3s16 &pos) { - if (m_database.count(getBlockAsInteger(blockpos))) - return m_database[getBlockAsInteger(blockpos)]; - else + s64 i = getBlockAsInteger(pos); + std::map<s64, std::string>::iterator it = m_database.find(i); + if (it == m_database.end()) return ""; + return it->second; } -bool Database_Dummy::deleteBlock(v3s16 blockpos) +bool Database_Dummy::deleteBlock(const v3s16 &pos) { - m_database.erase(getBlockAsInteger(blockpos)); + m_database.erase(getBlockAsInteger(pos)); return true; } -void Database_Dummy::listAllLoadableBlocks(std::list<v3s16> &dst) +void Database_Dummy::listAllLoadableBlocks(std::vector<v3s16> &dst) { - for(std::map<u64, std::string>::iterator x = m_database.begin(); x != m_database.end(); ++x) - { - v3s16 p = getIntegerAsBlock(x->first); - //dstream<<"block_i="<<block_i<<" p="<<PP(p)<<std::endl; - dst.push_back(p); + for (std::map<s64, std::string>::const_iterator x = m_database.begin(); + x != m_database.end(); ++x) { + dst.push_back(getIntegerAsBlock(x->first)); } } -Database_Dummy::~Database_Dummy() -{ - m_database.clear(); -} diff --git a/src/database-dummy.h b/src/database-dummy.h index a1535937d..0cf56928e 100644 --- a/src/database-dummy.h +++ b/src/database-dummy.h @@ -25,22 +25,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database.h" #include "irrlichttypes.h" -class ServerMap; - class Database_Dummy : public Database { public: - Database_Dummy(ServerMap *map); - virtual void beginSave(); - virtual void endSave(); - virtual bool saveBlock(v3s16 blockpos, std::string &data); - virtual std::string loadBlock(v3s16 blockpos); - virtual bool deleteBlock(v3s16 blockpos); - virtual void listAllLoadableBlocks(std::list<v3s16> &dst); - virtual int Initialized(void); - ~Database_Dummy(); + virtual bool saveBlock(const v3s16 &pos, const std::string &data); + virtual std::string loadBlock(const v3s16 &pos); + virtual bool deleteBlock(const v3s16 &pos); + virtual void listAllLoadableBlocks(std::vector<v3s16> &dst); + private: - ServerMap *srvmap; - std::map<u64, std::string> m_database; + std::map<s64, std::string> m_database; }; + #endif + diff --git a/src/database-leveldb.cpp b/src/database-leveldb.cpp index de510e533..e895354a4 100644 --- a/src/database-leveldb.cpp +++ b/src/database-leveldb.cpp @@ -22,57 +22,54 @@ with this program; if not, write to the Free Software Foundation, Inc., #if USE_LEVELDB #include "database-leveldb.h" -#include "leveldb/db.h" -#include "map.h" -#include "mapsector.h" -#include "mapblock.h" -#include "serialization.h" -#include "main.h" -#include "settings.h" #include "log.h" #include "filesys.h" +#include "exceptions.h" +#include "util/string.h" + +#include "leveldb/db.h" + #define ENSURE_STATUS_OK(s) \ if (!(s).ok()) { \ - throw FileNotGoodException(std::string("LevelDB error: ") + (s).ToString()); \ + throw FileNotGoodException(std::string("LevelDB error: ") + \ + (s).ToString()); \ } -Database_LevelDB::Database_LevelDB(ServerMap *map, std::string savedir) + +Database_LevelDB::Database_LevelDB(const std::string &savedir) { leveldb::Options options; options.create_if_missing = true; - leveldb::Status status = leveldb::DB::Open(options, savedir + DIR_DELIM + "map.db", &m_database); + leveldb::Status status = leveldb::DB::Open(options, + savedir + DIR_DELIM + "map.db", &m_database); ENSURE_STATUS_OK(status); - srvmap = map; } -int Database_LevelDB::Initialized(void) +Database_LevelDB::~Database_LevelDB() { - return 1; + delete m_database; } -void Database_LevelDB::beginSave() {} -void Database_LevelDB::endSave() {} - -bool Database_LevelDB::saveBlock(v3s16 blockpos, std::string &data) +bool Database_LevelDB::saveBlock(const v3s16 &pos, const std::string &data) { leveldb::Status status = m_database->Put(leveldb::WriteOptions(), - i64tos(getBlockAsInteger(blockpos)), data); + i64tos(getBlockAsInteger(pos)), data); if (!status.ok()) { errorstream << "WARNING: saveBlock: LevelDB error saving block " - << PP(blockpos) << ": " << status.ToString() << std::endl; + << PP(pos) << ": " << status.ToString() << std::endl; return false; } return true; } -std::string Database_LevelDB::loadBlock(v3s16 blockpos) +std::string Database_LevelDB::loadBlock(const v3s16 &pos) { std::string datastr; leveldb::Status status = m_database->Get(leveldb::ReadOptions(), - i64tos(getBlockAsInteger(blockpos)), &datastr); + i64tos(getBlockAsInteger(pos)), &datastr); if(status.ok()) return datastr; @@ -80,20 +77,20 @@ std::string Database_LevelDB::loadBlock(v3s16 blockpos) return ""; } -bool Database_LevelDB::deleteBlock(v3s16 blockpos) +bool Database_LevelDB::deleteBlock(const v3s16 &pos) { leveldb::Status status = m_database->Delete(leveldb::WriteOptions(), - i64tos(getBlockAsInteger(blockpos))); + i64tos(getBlockAsInteger(pos))); if (!status.ok()) { errorstream << "WARNING: deleteBlock: LevelDB error deleting block " - << PP(blockpos) << ": " << status.ToString() << std::endl; + << PP(pos) << ": " << status.ToString() << std::endl; return false; } return true; } -void Database_LevelDB::listAllLoadableBlocks(std::list<v3s16> &dst) +void Database_LevelDB::listAllLoadableBlocks(std::vector<v3s16> &dst) { leveldb::Iterator* it = m_database->NewIterator(leveldb::ReadOptions()); for (it->SeekToFirst(); it->Valid(); it->Next()) { @@ -103,8 +100,5 @@ void Database_LevelDB::listAllLoadableBlocks(std::list<v3s16> &dst) delete it; } -Database_LevelDB::~Database_LevelDB() -{ - delete m_database; -} -#endif +#endif // USE_LEVELDB + diff --git a/src/database-leveldb.h b/src/database-leveldb.h index c195260da..4afe2fdc7 100644 --- a/src/database-leveldb.h +++ b/src/database-leveldb.h @@ -28,23 +28,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "leveldb/db.h" #include <string> -class ServerMap; - class Database_LevelDB : public Database { public: - Database_LevelDB(ServerMap *map, std::string savedir); - virtual void beginSave(); - virtual void endSave(); - virtual bool saveBlock(v3s16 blockpos, std::string &data); - virtual std::string loadBlock(v3s16 blockpos); - virtual bool deleteBlock(v3s16 blockpos); - virtual void listAllLoadableBlocks(std::list<v3s16> &dst); - virtual int Initialized(void); + Database_LevelDB(const std::string &savedir); ~Database_LevelDB(); + + virtual bool saveBlock(const v3s16 &pos, const std::string &data); + virtual std::string loadBlock(const v3s16 &pos); + virtual bool deleteBlock(const v3s16 &pos); + virtual void listAllLoadableBlocks(std::vector<v3s16> &dst); + private: - ServerMap *srvmap; - leveldb::DB* m_database; + leveldb::DB *m_database; }; + +#endif // USE_LEVELDB + #endif -#endif + diff --git a/src/database-redis.cpp b/src/database-redis.cpp index b086f899d..cc4e5bade 100644 --- a/src/database-redis.cpp +++ b/src/database-redis.cpp @@ -20,85 +20,79 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "config.h" #if USE_REDIS -/* - Redis databases -*/ - #include "database-redis.h" -#include <hiredis.h> -#include "map.h" -#include "mapsector.h" -#include "mapblock.h" -#include "serialization.h" -#include "main.h" #include "settings.h" #include "log.h" -#include "filesys.h" +#include "exceptions.h" +#include "util/string.h" + +#include <hiredis.h> +#include <cassert> -Database_Redis::Database_Redis(ServerMap *map, std::string savedir) +Database_Redis::Database_Redis(Settings &conf) { - Settings conf; - conf.readConfigFile((std::string(savedir) + DIR_DELIM + "world.mt").c_str()); std::string tmp; try { - tmp = conf.get("redis_address"); - hash = conf.get("redis_hash"); - } catch(SettingNotFoundException e) { - throw SettingNotFoundException("Set redis_address and redis_hash in world.mt to use the redis backend"); + tmp = conf.get("redis_address"); + hash = conf.get("redis_hash"); + } catch (SettingNotFoundException) { + throw SettingNotFoundException("Set redis_address and " + "redis_hash in world.mt to use the redis backend"); } const char *addr = tmp.c_str(); int port = conf.exists("redis_port") ? conf.getU16("redis_port") : 6379; ctx = redisConnect(addr, port); - if(!ctx) + if (!ctx) { throw FileNotGoodException("Cannot allocate redis context"); - else if(ctx->err) { + } else if (ctx->err) { std::string err = std::string("Connection error: ") + ctx->errstr; redisFree(ctx); throw FileNotGoodException(err); } - srvmap = map; } -int Database_Redis::Initialized(void) +Database_Redis::~Database_Redis() { - return 1; + redisFree(ctx); } void Database_Redis::beginSave() { - redisReply *reply; - reply = (redisReply*) redisCommand(ctx, "MULTI"); - if(!reply) - throw FileNotGoodException(std::string("redis command 'MULTI' failed: ") + ctx->errstr); + redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "MULTI")); + if (!reply) { + throw FileNotGoodException(std::string( + "Redis command 'MULTI' failed: ") + ctx->errstr); + } freeReplyObject(reply); } void Database_Redis::endSave() { - redisReply *reply; - reply = (redisReply*) redisCommand(ctx, "EXEC"); - if(!reply) - throw FileNotGoodException(std::string("redis command 'EXEC' failed: ") + ctx->errstr); + redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "EXEC")); + if (!reply) { + throw FileNotGoodException(std::string( + "Redis command 'EXEC' failed: ") + ctx->errstr); + } freeReplyObject(reply); } -bool Database_Redis::saveBlock(v3s16 blockpos, std::string &data) +bool Database_Redis::saveBlock(const v3s16 &pos, const std::string &data) { - std::string tmp = i64tos(getBlockAsInteger(blockpos)); + std::string tmp = i64tos(getBlockAsInteger(pos)); - redisReply *reply = (redisReply *)redisCommand(ctx, "HSET %s %s %b", - hash.c_str(), tmp.c_str(), data.c_str(), data.size()); + redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HSET %s %s %b", + hash.c_str(), tmp.c_str(), data.c_str(), data.size())); if (!reply) { errorstream << "WARNING: saveBlock: redis command 'HSET' failed on " - "block " << PP(blockpos) << ": " << ctx->errstr << std::endl; + "block " << PP(pos) << ": " << ctx->errstr << std::endl; freeReplyObject(reply); return false; } if (reply->type == REDIS_REPLY_ERROR) { - errorstream << "WARNING: saveBlock: saving block " << PP(blockpos) - << "failed" << std::endl; + errorstream << "WARNING: saveBlock: saving block " << PP(pos) + << " failed: " << reply->str << std::endl; freeReplyObject(reply); return false; } @@ -107,38 +101,43 @@ bool Database_Redis::saveBlock(v3s16 blockpos, std::string &data) return true; } -std::string Database_Redis::loadBlock(v3s16 blockpos) +std::string Database_Redis::loadBlock(const v3s16 &pos) { - std::string tmp = i64tos(getBlockAsInteger(blockpos)); - redisReply *reply; - reply = (redisReply*) redisCommand(ctx, "HGET %s %s", hash.c_str(), tmp.c_str()); - - if(!reply) - throw FileNotGoodException(std::string("redis command 'HGET %s %s' failed: ") + ctx->errstr); - if(reply->type != REDIS_REPLY_STRING) - return ""; - - std::string str(reply->str, reply->len); - freeReplyObject(reply); // std::string copies the memory so this won't cause any problems - return str; -} - -bool Database_Redis::deleteBlock(v3s16 blockpos) -{ - std::string tmp = i64tos(getBlockAsInteger(blockpos)); + std::string tmp = i64tos(getBlockAsInteger(pos)); + redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, + "HGET %s %s", hash.c_str(), tmp.c_str())); - redisReply *reply = (redisReply *)redisCommand(ctx, "HDEL %s %s", - hash.c_str(), tmp.c_str()); if (!reply) { - errorstream << "WARNING: deleteBlock: redis command 'HDEL' failed on " - "block " << PP(blockpos) << ": " << ctx->errstr << std::endl; + throw FileNotGoodException(std::string( + "Redis command 'HGET %s %s' failed: ") + ctx->errstr); + } + switch (reply->type) { + case REDIS_REPLY_STRING: { + std::string str(reply->str, reply->len); + // std::string copies the memory so this won't cause any problems freeReplyObject(reply); - return false; + return str; + } + case REDIS_REPLY_ERROR: + errorstream << "WARNING: loadBlock: loading block " << PP(pos) + << " failed: " << reply->str << std::endl; } + freeReplyObject(reply); + return ""; +} - if (reply->type == REDIS_REPLY_ERROR) { - errorstream << "WARNING: deleteBlock: deleting block " << PP(blockpos) - << "failed" << std::endl; +bool Database_Redis::deleteBlock(const v3s16 &pos) +{ + std::string tmp = i64tos(getBlockAsInteger(pos)); + + redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, + "HDEL %s %s", hash.c_str(), tmp.c_str())); + if (!reply) { + throw FileNotGoodException(std::string( + "Redis command 'HDEL %s %s' failed: ") + ctx->errstr); + } else if (reply->type == REDIS_REPLY_ERROR) { + errorstream << "WARNING: deleteBlock: deleting block " << PP(pos) + << " failed: " << reply->str << std::endl; freeReplyObject(reply); return false; } @@ -147,24 +146,25 @@ bool Database_Redis::deleteBlock(v3s16 blockpos) return true; } -void Database_Redis::listAllLoadableBlocks(std::list<v3s16> &dst) +void Database_Redis::listAllLoadableBlocks(std::vector<v3s16> &dst) { - redisReply *reply; - reply = (redisReply*) redisCommand(ctx, "HKEYS %s", hash.c_str()); - if(!reply) - throw FileNotGoodException(std::string("redis command 'HKEYS %s' failed: ") + ctx->errstr); - if(reply->type != REDIS_REPLY_ARRAY) - throw FileNotGoodException("Failed to get keys from database"); - for(size_t i = 0; i < reply->elements; i++) - { - assert(reply->element[i]->type == REDIS_REPLY_STRING); - dst.push_back(getIntegerAsBlock(stoi64(reply->element[i]->str))); + redisReply *reply = static_cast<redisReply *>(redisCommand(ctx, "HKEYS %s", hash.c_str())); + if (!reply) { + throw FileNotGoodException(std::string( + "Redis command 'HKEYS %s' failed: ") + ctx->errstr); + } + switch (reply->type) { + case REDIS_REPLY_ARRAY: + for (size_t i = 0; i < reply->elements; i++) { + assert(reply->element[i]->type == REDIS_REPLY_STRING); + dst.push_back(getIntegerAsBlock(stoi64(reply->element[i]->str))); + } + case REDIS_REPLY_ERROR: + throw FileNotGoodException(std::string( + "Failed to get keys from database: ") + reply->str); } freeReplyObject(reply); } -Database_Redis::~Database_Redis() -{ - redisFree(ctx); -} -#endif +#endif // USE_REDIS + diff --git a/src/database-redis.h b/src/database-redis.h index 34b90fa59..45e702c83 100644 --- a/src/database-redis.h +++ b/src/database-redis.h @@ -28,24 +28,28 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <hiredis.h> #include <string> -class ServerMap; +class Settings; class Database_Redis : public Database { public: - Database_Redis(ServerMap *map, std::string savedir); + Database_Redis(Settings &conf); + ~Database_Redis(); + virtual void beginSave(); virtual void endSave(); - virtual bool saveBlock(v3s16 blockpos, std::string &data); - virtual std::string loadBlock(v3s16 blockpos); - virtual bool deleteBlock(v3s16 blockpos); - virtual void listAllLoadableBlocks(std::list<v3s16> &dst); - virtual int Initialized(void); - ~Database_Redis(); + + virtual bool saveBlock(const v3s16 &pos, const std::string &data); + virtual std::string loadBlock(const v3s16 &pos); + virtual bool deleteBlock(const v3s16 &pos); + virtual void listAllLoadableBlocks(std::vector<v3s16> &dst); + private: - ServerMap *srvmap; redisContext *ctx; std::string hash; }; + +#endif // USE_REDIS + #endif -#endif + diff --git a/src/database-sqlite3.cpp b/src/database-sqlite3.cpp index 0679da97d..84b1a7122 100644 --- a/src/database-sqlite3.cpp +++ b/src/database-sqlite3.cpp @@ -18,297 +18,231 @@ with this program; if not, write to the Free Software Foundation, Inc., */ /* - SQLite format specification: - - Initially only replaces sectors/ and sectors2/ - - If map.sqlite does not exist in the save dir - or the block was not found in the database - the map will try to load from sectors folder. - In either case, map.sqlite will be created - and all future saves will save there. - - Structure of map.sqlite: - Tables: - blocks - (PK) INT pos - BLOB data +SQLite format specification: + blocks: + (PK) INT id + BLOB data */ #include "database-sqlite3.h" -#include "map.h" -#include "mapsector.h" -#include "mapblock.h" -#include "serialization.h" -#include "main.h" -#include "settings.h" #include "log.h" #include "filesys.h" +#include "exceptions.h" +#include "settings.h" +#include "util/string.h" -Database_SQLite3::Database_SQLite3(ServerMap *map, std::string savedir) -{ - m_database = NULL; - m_database_read = NULL; - m_database_write = NULL; - m_database_list = NULL; - m_database_delete = NULL; - m_savedir = savedir; - srvmap = map; -} +#include <cassert> + + +#define SQLRES(s, r) \ + if ((s) != (r)) { \ + throw FileNotGoodException(std::string(\ + "SQLite3 database error (" \ + __FILE__ ":" TOSTRING(__LINE__) \ + "): ") +\ + sqlite3_errmsg(m_database)); \ + } +#define SQLOK(s) SQLRES(s, SQLITE_OK) -int Database_SQLite3::Initialized(void) +#define PREPARE_STATEMENT(name, query) \ + SQLOK(sqlite3_prepare_v2(m_database, query, -1, &m_stmt_##name, NULL)) + +#define FINALIZE_STATEMENT(statement) \ + if (sqlite3_finalize(statement) != SQLITE_OK) { \ + throw FileNotGoodException(std::string( \ + "SQLite3: Failed to finalize " #statement ": ") + \ + sqlite3_errmsg(m_database)); \ + } + + +Database_SQLite3::Database_SQLite3(const std::string &savedir) : + m_initialized(false), + m_savedir(savedir), + m_database(NULL), + m_stmt_read(NULL), + m_stmt_write(NULL), + m_stmt_list(NULL), + m_stmt_delete(NULL), + m_stmt_begin(NULL), + m_stmt_end(NULL) { - return m_database ? 1 : 0; } void Database_SQLite3::beginSave() { verifyDatabase(); - if(sqlite3_exec(m_database, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK) - errorstream<<"WARNING: beginSave() failed, saving might be slow."; + SQLRES(sqlite3_step(m_stmt_begin), SQLITE_DONE); + sqlite3_reset(m_stmt_begin); } void Database_SQLite3::endSave() { verifyDatabase(); - if(sqlite3_exec(m_database, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK) - errorstream<<"WARNING: endSave() failed, map might not have saved."; + SQLRES(sqlite3_step(m_stmt_end), SQLITE_DONE); + sqlite3_reset(m_stmt_end); } -void Database_SQLite3::createDirs(std::string path) +void Database_SQLite3::openDatabase() { - if(fs::CreateAllDirs(path) == false) - { - infostream<<DTIME<<"Database_SQLite3: Failed to create directory " - <<"\""<<path<<"\""<<std::endl; - throw BaseException("Database_SQLite3 failed to create directory"); - } -} + if (m_database) return; -void Database_SQLite3::verifyDatabase() { - if(m_database) - return; - - std::string dbp = m_savedir + DIR_DELIM "map.sqlite"; - bool needs_create = false; - int d; + std::string dbp = m_savedir + DIR_DELIM + "map.sqlite"; // Open the database connection - createDirs(m_savedir); // ? + if (!fs::CreateAllDirs(m_savedir)) { + infostream << "Database_SQLite3: Failed to create directory \"" + << m_savedir << "\"" << std::endl; + throw FileNotGoodException("Failed to create database " + "save directory"); + } - if(!fs::PathExists(dbp)) - needs_create = true; + bool needs_create = !fs::PathExists(dbp); - d = sqlite3_open_v2(dbp.c_str(), &m_database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); - if(d != SQLITE_OK) { - errorstream<<"SQLite3 database failed to open: "<<sqlite3_errmsg(m_database)<<std::endl; + if (sqlite3_open_v2(dbp.c_str(), &m_database, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + NULL) != SQLITE_OK) { + errorstream << "SQLite3 database failed to open: " + << sqlite3_errmsg(m_database) << std::endl; throw FileNotGoodException("Cannot open database file"); } - if(needs_create) + if (needs_create) { createDatabase(); + } - std::string querystr = std::string("PRAGMA synchronous = ") + std::string query_str = std::string("PRAGMA synchronous = ") + itos(g_settings->getU16("sqlite_synchronous")); - d = sqlite3_exec(m_database, querystr.c_str(), NULL, NULL, NULL); - if(d != SQLITE_OK) { - errorstream<<"Database pragma set failed: " - <<sqlite3_errmsg(m_database)<<std::endl; - throw FileNotGoodException("Cannot set pragma"); - } + SQLOK(sqlite3_exec(m_database, query_str.c_str(), NULL, NULL, NULL)); +} - d = sqlite3_prepare(m_database, "SELECT `data` FROM `blocks` WHERE `pos`=? LIMIT 1", -1, &m_database_read, NULL); - if(d != SQLITE_OK) { - errorstream<<"SQLite3 read statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl; - throw FileNotGoodException("Cannot prepare read statement"); - } +void Database_SQLite3::verifyDatabase() +{ + if (m_initialized) return; + + openDatabase(); + + PREPARE_STATEMENT(begin, "BEGIN"); + PREPARE_STATEMENT(end, "COMMIT"); + PREPARE_STATEMENT(read, "SELECT `data` FROM `blocks` WHERE `pos` = ? LIMIT 1"); #ifdef __ANDROID__ - d = sqlite3_prepare(m_database, "INSERT INTO `blocks` VALUES(?, ?);", -1, &m_database_write, NULL); + PREPARE_STATEMENT(write, "INSERT INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); #else - d = sqlite3_prepare(m_database, "REPLACE INTO `blocks` VALUES(?, ?);", -1, &m_database_write, NULL); + PREPARE_STATEMENT(write, "REPLACE INTO `blocks` (`pos`, `data`) VALUES (?, ?)"); #endif - if(d != SQLITE_OK) { - errorstream<<"SQLite3 write statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl; - throw FileNotGoodException("Cannot prepare write statement"); - } + PREPARE_STATEMENT(delete, "DELETE FROM `blocks` WHERE `pos` = ?"); + PREPARE_STATEMENT(list, "SELECT `pos` FROM `blocks`"); - d = sqlite3_prepare(m_database, "DELETE FROM `blocks` WHERE `pos`=?;", -1, &m_database_delete, NULL); - if(d != SQLITE_OK) { - infostream<<"WARNING: SQLite3 database delete statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl; - throw FileNotGoodException("Cannot prepare delete statement"); - } + m_initialized = true; - d = sqlite3_prepare(m_database, "SELECT `pos` FROM `blocks`", -1, &m_database_list, NULL); - if(d != SQLITE_OK) { - infostream<<"SQLite3 list statment failed to prepare: "<<sqlite3_errmsg(m_database)<<std::endl; - throw FileNotGoodException("Cannot prepare read statement"); - } + verbosestream << "ServerMap: SQLite3 database opened." << std::endl; +} - infostream<<"ServerMap: SQLite3 database opened"<<std::endl; +inline void Database_SQLite3::bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index) +{ + SQLOK(sqlite3_bind_int64(stmt, index, getBlockAsInteger(pos))); } -bool Database_SQLite3::deleteBlock(v3s16 blockpos) +bool Database_SQLite3::deleteBlock(const v3s16 &pos) { verifyDatabase(); - if (sqlite3_bind_int64(m_database_delete, 1, - getBlockAsInteger(blockpos)) != SQLITE_OK) { - errorstream << "WARNING: Could not bind block position for delete: " - << sqlite3_errmsg(m_database) << std::endl; - } + bindPos(m_stmt_delete, pos); - if (sqlite3_step(m_database_delete) != SQLITE_DONE) { + bool good = sqlite3_step(m_stmt_delete) == SQLITE_DONE; + sqlite3_reset(m_stmt_delete); + + if (!good) { errorstream << "WARNING: deleteBlock: Block failed to delete " - << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl; - sqlite3_reset(m_database_delete); - return false; + << PP(pos) << ": " << sqlite3_errmsg(m_database) << std::endl; } - - sqlite3_reset(m_database_delete); - return true; + return good; } -bool Database_SQLite3::saveBlock(v3s16 blockpos, std::string &data) +bool Database_SQLite3::saveBlock(const v3s16 &pos, const std::string &data) { verifyDatabase(); - s64 bkey = getBlockAsInteger(blockpos); - #ifdef __ANDROID__ /** - * Note: For some unknown reason sqlite3 fails to REPLACE blocks on android, - * deleting them and inserting first works. + * Note: For some unknown reason SQLite3 fails to REPLACE blocks on Android, + * deleting them and then inserting works. */ - if (sqlite3_bind_int64(m_database_read, 1, bkey) != SQLITE_OK) { - infostream << "WARNING: Could not bind block position for load: " - << sqlite3_errmsg(m_database)<<std::endl; - } + bindPos(m_stmt_read, pos); - int step_result = sqlite3_step(m_database_read); - sqlite3_reset(m_database_read); - - if (step_result == SQLITE_ROW) { - if (sqlite3_bind_int64(m_database_delete, 1, bkey) != SQLITE_OK) { - infostream << "WARNING: Could not bind block position for delete: " - << sqlite3_errmsg(m_database)<<std::endl; - } - - if (sqlite3_step(m_database_delete) != SQLITE_DONE) { - errorstream << "WARNING: saveBlock: Block failed to delete " - << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl; - return false; - } - sqlite3_reset(m_database_delete); + if (sqlite3_step(m_stmt_read) == SQLITE_ROW) { + deleteBlock(pos); } + sqlite3_reset(m_stmt_read); #endif - if (sqlite3_bind_int64(m_database_write, 1, bkey) != SQLITE_OK) { - errorstream << "WARNING: saveBlock: Block position failed to bind: " - << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl; - sqlite3_reset(m_database_write); - return false; - } - - if (sqlite3_bind_blob(m_database_write, 2, (void *)data.c_str(), - data.size(), NULL) != SQLITE_OK) { - errorstream << "WARNING: saveBlock: Block data failed to bind: " - << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl; - sqlite3_reset(m_database_write); - return false; - } + bindPos(m_stmt_write, pos); + SQLOK(sqlite3_bind_blob(m_stmt_write, 2, data.data(), data.size(), NULL)); - if (sqlite3_step(m_database_write) != SQLITE_DONE) { - errorstream << "WARNING: saveBlock: Block failed to save " - << PP(blockpos) << ": " << sqlite3_errmsg(m_database) << std::endl; - sqlite3_reset(m_database_write); - return false; - } - - sqlite3_reset(m_database_write); + SQLRES(sqlite3_step(m_stmt_write), SQLITE_DONE) + sqlite3_reset(m_stmt_write); return true; } -std::string Database_SQLite3::loadBlock(v3s16 blockpos) +std::string Database_SQLite3::loadBlock(const v3s16 &pos) { verifyDatabase(); - if (sqlite3_bind_int64(m_database_read, 1, getBlockAsInteger(blockpos)) != SQLITE_OK) { - errorstream << "Could not bind block position for load: " - << sqlite3_errmsg(m_database)<<std::endl; - } - - if (sqlite3_step(m_database_read) == SQLITE_ROW) { - const char *data = (const char *) sqlite3_column_blob(m_database_read, 0); - size_t len = sqlite3_column_bytes(m_database_read, 0); + bindPos(m_stmt_read, pos); - std::string s = ""; - if(data) - s = std::string(data, len); + if (sqlite3_step(m_stmt_read) != SQLITE_ROW) { + sqlite3_reset(m_stmt_read); + return ""; + } + const char *data = (const char *) sqlite3_column_blob(m_stmt_read, 0); + size_t len = sqlite3_column_bytes(m_stmt_read, 0); - sqlite3_step(m_database_read); - // We should never get more than 1 row, so ok to reset - sqlite3_reset(m_database_read); + std::string s; + if (data) + s = std::string(data, len); - return s; - } + sqlite3_step(m_stmt_read); + // We should never get more than 1 row, so ok to reset + sqlite3_reset(m_stmt_read); - sqlite3_reset(m_database_read); - return ""; + return s; } void Database_SQLite3::createDatabase() { - int e; - assert(m_database); - e = sqlite3_exec(m_database, - "CREATE TABLE IF NOT EXISTS `blocks` (" - "`pos` INT NOT NULL PRIMARY KEY," - "`data` BLOB" - ");" - , NULL, NULL, NULL); - if(e != SQLITE_OK) - throw FileNotGoodException("Could not create sqlite3 database structure"); - else - infostream<<"ServerMap: SQLite3 database structure was created"; - + assert(m_database); // Pre-condition + SQLOK(sqlite3_exec(m_database, + "CREATE TABLE IF NOT EXISTS `blocks` (\n" + " `pos` INT PRIMARY KEY,\n" + " `data` BLOB\n" + ");\n", + NULL, NULL, NULL)); } -void Database_SQLite3::listAllLoadableBlocks(std::list<v3s16> &dst) +void Database_SQLite3::listAllLoadableBlocks(std::vector<v3s16> &dst) { verifyDatabase(); - while(sqlite3_step(m_database_list) == SQLITE_ROW) - { - sqlite3_int64 block_i = sqlite3_column_int64(m_database_list, 0); - v3s16 p = getIntegerAsBlock(block_i); - //dstream<<"block_i="<<block_i<<" p="<<PP(p)<<std::endl; - dst.push_back(p); + while (sqlite3_step(m_stmt_list) == SQLITE_ROW) { + dst.push_back(getIntegerAsBlock(sqlite3_column_int64(m_stmt_list, 0))); } + sqlite3_reset(m_stmt_list); } - -#define FINALIZE_STATEMENT(statement) \ - if ( statement ) \ - rc = sqlite3_finalize(statement); \ - if ( rc != SQLITE_OK ) \ - errorstream << "Database_SQLite3::~Database_SQLite3():" \ - << "Failed to finalize: " << #statement << ": rc=" << rc << std::endl; - Database_SQLite3::~Database_SQLite3() { - int rc = SQLITE_OK; - - FINALIZE_STATEMENT(m_database_read) - FINALIZE_STATEMENT(m_database_write) - FINALIZE_STATEMENT(m_database_list) - FINALIZE_STATEMENT(m_database_delete) - - if(m_database) - rc = sqlite3_close(m_database); - - if (rc != SQLITE_OK) { + FINALIZE_STATEMENT(m_stmt_read) + FINALIZE_STATEMENT(m_stmt_write) + FINALIZE_STATEMENT(m_stmt_list) + FINALIZE_STATEMENT(m_stmt_begin) + FINALIZE_STATEMENT(m_stmt_end) + FINALIZE_STATEMENT(m_stmt_delete) + + if (sqlite3_close(m_database) != SQLITE_OK) { errorstream << "Database_SQLite3::~Database_SQLite3(): " - << "Failed to close database: rc=" << rc << std::endl; + << "Failed to close database: " + << sqlite3_errmsg(m_database) << std::endl; } } + diff --git a/src/database-sqlite3.h b/src/database-sqlite3.h index 5035c67c8..a775742be 100644 --- a/src/database-sqlite3.h +++ b/src/database-sqlite3.h @@ -27,35 +27,43 @@ extern "C" { #include "sqlite3.h" } -class ServerMap; - class Database_SQLite3 : public Database { public: - Database_SQLite3(ServerMap *map, std::string savedir); + Database_SQLite3(const std::string &savedir); + virtual void beginSave(); virtual void endSave(); - virtual bool saveBlock(v3s16 blockpos, std::string &data); - virtual std::string loadBlock(v3s16 blockpos); - virtual bool deleteBlock(v3s16 blockpos); - virtual void listAllLoadableBlocks(std::list<v3s16> &dst); - virtual int Initialized(void); + virtual bool saveBlock(const v3s16 &pos, const std::string &data); + virtual std::string loadBlock(const v3s16 &pos); + virtual bool deleteBlock(const v3s16 &pos); + virtual void listAllLoadableBlocks(std::vector<v3s16> &dst); + virtual bool initialized() const { return m_initialized; } ~Database_SQLite3(); -private: - ServerMap *srvmap; - std::string m_savedir; - sqlite3 *m_database; - sqlite3_stmt *m_database_read; - sqlite3_stmt *m_database_write; - sqlite3_stmt *m_database_delete; - sqlite3_stmt *m_database_list; +private: + // Open the database + void openDatabase(); // Create the database structure void createDatabase(); - // Verify we can read/write to the database + // Open and initialize the database if needed void verifyDatabase(); - void createDirs(std::string path); + + void bindPos(sqlite3_stmt *stmt, const v3s16 &pos, int index=1); + + bool m_initialized; + + std::string m_savedir; + + sqlite3 *m_database; + sqlite3_stmt *m_stmt_read; + sqlite3_stmt *m_stmt_write; + sqlite3_stmt *m_stmt_list; + sqlite3_stmt *m_stmt_delete; + sqlite3_stmt *m_stmt_begin; + sqlite3_stmt *m_stmt_end; }; #endif + diff --git a/src/database.cpp b/src/database.cpp index 26f6992fc..262d475ec 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -48,7 +48,7 @@ static inline s64 pythonmodulo(s64 i, s16 mod) } -s64 Database::getBlockAsInteger(const v3s16 pos) const +s64 Database::getBlockAsInteger(const v3s16 &pos) { return (u64) pos.Z * 0x1000000 + (u64) pos.Y * 0x1000 + @@ -56,7 +56,7 @@ s64 Database::getBlockAsInteger(const v3s16 pos) const } -v3s16 Database::getIntegerAsBlock(s64 i) const +v3s16 Database::getIntegerAsBlock(s64 i) { v3s16 pos; pos.X = unsigned_to_signed(pythonmodulo(i, 4096), 2048); diff --git a/src/database.h b/src/database.h index f04c4aa50..cee7b6fd9 100644 --- a/src/database.h +++ b/src/database.h @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef DATABASE_HEADER #define DATABASE_HEADER -#include <list> +#include <vector> #include <string> #include "irr_v3d.h" #include "irrlichttypes.h" @@ -32,16 +32,22 @@ with this program; if not, write to the Free Software Foundation, Inc., class Database { public: - virtual void beginSave() = 0; - virtual void endSave() = 0; - - virtual bool saveBlock(v3s16 blockpos, std::string &data) = 0; - virtual std::string loadBlock(v3s16 blockpos) = 0; - virtual bool deleteBlock(v3s16 blockpos) = 0; - s64 getBlockAsInteger(const v3s16 pos) const; - v3s16 getIntegerAsBlock(s64 i) const; - virtual void listAllLoadableBlocks(std::list<v3s16> &dst) = 0; - virtual int Initialized(void)=0; - virtual ~Database() {}; + virtual ~Database() {} + + virtual void beginSave() {} + virtual void endSave() {} + + virtual bool saveBlock(const v3s16 &pos, const std::string &data) = 0; + virtual std::string loadBlock(const v3s16 &pos) = 0; + virtual bool deleteBlock(const v3s16 &pos) = 0; + + static s64 getBlockAsInteger(const v3s16 &pos); + static v3s16 getIntegerAsBlock(s64 i); + + virtual void listAllLoadableBlocks(std::vector<v3s16> &dst) = 0; + + virtual bool initialized() const { return true; } }; + #endif + diff --git a/src/debug.cpp b/src/debug.cpp index bd970a8e4..ae2ffadc3 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -133,11 +133,11 @@ Nullstream dummyout; Assert */ -void assert_fail(const char *assertion, const char *file, +void sanity_check_fn(const char *assertion, const char *file, unsigned int line, const char *function) { DEBUGPRINT("\nIn thread %lx:\n" - "%s:%u: %s: Assertion '%s' failed.\n", + "%s:%u: %s: An engine assumption '%s' failed.\n", (unsigned long)get_current_thread_id(), file, line, function, assertion); @@ -149,6 +149,22 @@ void assert_fail(const char *assertion, const char *file, abort(); } +void fatal_error_fn(const char *msg, const char *file, + unsigned int line, const char *function) +{ + DEBUGPRINT("\nIn thread %lx:\n" + "%s:%u: %s: A fatal error occurred: %s\n", + (unsigned long)get_current_thread_id(), + file, line, function, msg); + + debug_stacks_print(); + + if(g_debugstreams[1]) + fclose(g_debugstreams[1]); + + abort(); +} + /* DebugStack */ @@ -369,9 +385,11 @@ long WINAPI Win32ExceptionHandler(struct _EXCEPTION_POINTERS *pExceptInfo) MINIDUMP_USER_STREAM_INFORMATION mdusi; MINIDUMP_USER_STREAM mdus; bool minidump_created = false; - std::string version_str("Minetest "); - std::string dumpfile = porting::path_user + DIR_DELIM "minetest.dmp"; + std::string dumpfile = porting::path_user + DIR_DELIM PROJECT_NAME ".dmp"; + + std::string version_str(PROJECT_NAME " "); + version_str += g_version_hash; HANDLE hFile = CreateFileA(dumpfile.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); @@ -385,8 +403,6 @@ long WINAPI Win32ExceptionHandler(struct _EXCEPTION_POINTERS *pExceptInfo) mdei.ExceptionPointers = pExceptInfo; mdei.ThreadId = GetCurrentThreadId(); - version_str += minetest_version_hash; - mdus.Type = CommentStreamA; mdus.BufferSize = version_str.size(); mdus.Buffer = (PVOID)version_str.c_str(); diff --git a/src/debug.h b/src/debug.h index 1027fde69..9684aa2df 100644 --- a/src/debug.h +++ b/src/debug.h @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iostream> #include <exception> +#include <assert.h> #include "gettime.h" #if (defined(WIN32) || defined(_WIN32_WCE)) @@ -72,28 +73,38 @@ extern std::ostream dstream; extern std::ostream dstream_no_stderr; extern Nullstream dummyout; -/* - Include assert.h and immediately undef assert so that it can't override - our assert later on. leveldb/slice.h is a notable offender. -*/ -#include <assert.h> -#undef assert +/* Abort program execution immediately + */ +__NORETURN extern void fatal_error_fn( + const char *msg, const char *file, + unsigned int line, const char *function); + +#define FATAL_ERROR(msg) \ + fatal_error_fn((msg), __FILE__, __LINE__, __FUNCTION_NAME) + +#define FATAL_ERROR_IF(expr, msg) \ + ((expr) \ + ? fatal_error_fn((msg), __FILE__, __LINE__, __FUNCTION_NAME) \ + : (void)(0)) /* - Assert + sanity_check() + Equivalent to assert() but persists in Release builds (i.e. when NDEBUG is + defined) */ -__NORETURN extern void assert_fail( +__NORETURN extern void sanity_check_fn( const char *assertion, const char *file, unsigned int line, const char *function); -#define ASSERT(expr)\ - ((expr)\ - ? (void)(0)\ - : assert_fail(#expr, __FILE__, __LINE__, __FUNCTION_NAME)) +#define SANITY_CHECK(expr) \ + ((expr) \ + ? (void)(0) \ + : sanity_check_fn(#expr, __FILE__, __LINE__, __FUNCTION_NAME)) + +#define sanity_check(expr) SANITY_CHECK(expr) -#define assert(expr) ASSERT(expr) void debug_set_exception_handler(); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6504c5155..f0b02b2d9 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -43,11 +43,13 @@ void set_default_settings(Settings *settings) settings->setDefault("keymap_special1", "KEY_KEY_E"); settings->setDefault("keymap_chat", "KEY_KEY_T"); settings->setDefault("keymap_cmd", "/"); + settings->setDefault("keymap_minimap", "KEY_F9"); settings->setDefault("keymap_console", "KEY_F10"); settings->setDefault("keymap_rangeselect", "KEY_KEY_R"); settings->setDefault("keymap_freemove", "KEY_KEY_K"); settings->setDefault("keymap_fastmove", "KEY_KEY_J"); settings->setDefault("keymap_noclip", "KEY_KEY_H"); + settings->setDefault("keymap_cinematic", "KEY_F8"); settings->setDefault("keymap_screenshot", "KEY_F12"); settings->setDefault("keymap_toggle_hud", "KEY_F1"); settings->setDefault("keymap_toggle_chat", "KEY_F2"); @@ -56,7 +58,7 @@ void set_default_settings(Settings *settings) #if DEBUG "KEY_F4"); #else - "none"); + ""); #endif settings->setDefault("keymap_toggle_debug", "KEY_F5"); settings->setDefault("keymap_toggle_profiler", "KEY_F6"); @@ -92,6 +94,7 @@ void set_default_settings(Settings *settings) // A bit more than the server will send around the player, to make fog blend well settings->setDefault("viewing_range_nodes_max", "240"); settings->setDefault("viewing_range_nodes_min", "35"); + settings->setDefault("map_generation_limit", "31000"); settings->setDefault("screenW", "800"); settings->setDefault("screenH", "600"); settings->setDefault("fullscreen", "false"); @@ -101,11 +104,12 @@ void set_default_settings(Settings *settings) settings->setDefault("address", ""); settings->setDefault("random_input", "false"); settings->setDefault("client_unload_unused_data_timeout", "600"); + settings->setDefault("client_mapblock_limit", "5000"); settings->setDefault("enable_fog", "true"); settings->setDefault("fov", "72"); settings->setDefault("view_bobbing", "true"); settings->setDefault("new_style_water", "false"); - settings->setDefault("new_style_leaves", "true"); + settings->setDefault("leaves_style", "fancy"); settings->setDefault("connected_glass", "false"); settings->setDefault("smooth_lighting", "true"); settings->setDefault("display_gamma", "1.8"); @@ -115,6 +119,9 @@ void set_default_settings(Settings *settings) settings->setDefault("free_move", "false"); settings->setDefault("noclip", "false"); settings->setDefault("continuous_forward", "false"); + settings->setDefault("cinematic", "false"); + settings->setDefault("camera_smoothing", "0"); + settings->setDefault("cinematic_camera_smoothing", "0.7"); settings->setDefault("fast_move", "false"); settings->setDefault("invert_mouse", "false"); settings->setDefault("enable_clouds", "true"); @@ -123,6 +130,7 @@ void set_default_settings(Settings *settings) settings->setDefault("fall_bobbing_amount", "0.0"); settings->setDefault("enable_3d_clouds", "true"); settings->setDefault("cloud_height", "120"); + settings->setDefault("cloud_radius", "12"); settings->setDefault("menu_clouds", "true"); settings->setDefault("opaque_water", "false"); settings->setDefault("console_color", "(0,0,0)"); @@ -133,6 +141,8 @@ void set_default_settings(Settings *settings) settings->setDefault("crosshair_alpha", "255"); settings->setDefault("hud_scaling", "1.0"); settings->setDefault("gui_scaling", "1.0"); + settings->setDefault("gui_scaling_filter", "false"); + settings->setDefault("gui_scaling_filter_txr2img", "true"); settings->setDefault("mouse_sensitivity", "0.2"); settings->setDefault("enable_sound", "true"); settings->setDefault("sound_volume", "0.8"); @@ -145,14 +155,18 @@ void set_default_settings(Settings *settings) settings->setDefault("anisotropic_filter", "false"); settings->setDefault("bilinear_filter", "false"); settings->setDefault("trilinear_filter", "false"); + settings->setDefault("texture_clean_transparent", "false"); + settings->setDefault("texture_min_size", "64"); settings->setDefault("preload_item_visuals", "false"); settings->setDefault("enable_bumpmapping", "false"); settings->setDefault("enable_parallax_occlusion", "false"); settings->setDefault("generate_normalmaps", "false"); settings->setDefault("normalmaps_strength", "0.6"); settings->setDefault("normalmaps_smooth", "1"); - settings->setDefault("parallax_occlusion_scale", "0.06"); - settings->setDefault("parallax_occlusion_bias", "0.03"); + settings->setDefault("parallax_occlusion_mode", "1"); + settings->setDefault("parallax_occlusion_iterations", "4"); + settings->setDefault("parallax_occlusion_scale", "0.08"); + settings->setDefault("parallax_occlusion_bias", "0.04"); settings->setDefault("enable_waving_water", "false"); settings->setDefault("water_wave_height", "1.0"); settings->setDefault("water_wave_length", "20.0"); @@ -163,7 +177,11 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_shaders", "true"); settings->setDefault("repeat_rightclick_time", "0.25"); settings->setDefault("enable_particles", "true"); - settings->setDefault("enable_mesh_cache", "true"); + settings->setDefault("enable_mesh_cache", "false"); + + settings->setDefault("enable_minimap", "true"); + settings->setDefault("minimap_shape_round", "true"); + settings->setDefault("minimap_double_scan_height", "true"); settings->setDefault("curl_timeout", "5000"); settings->setDefault("curl_parallel_limit", "8"); @@ -236,6 +254,10 @@ void set_default_settings(Settings *settings) settings->setDefault("deprecated_lua_api_handling", "log"); #endif + settings->setDefault("kick_msg_shutdown", "Server shutting down."); + 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("profiler_print_interval", "0"); settings->setDefault("enable_mapgen_debug_info", "false"); settings->setDefault("active_object_send_range_blocks", "3"); @@ -249,7 +271,6 @@ void set_default_settings(Settings *settings) settings->setDefault("max_clearobjects_extra_loaded_blocks", "4096"); settings->setDefault("time_send_interval", "5"); settings->setDefault("time_speed", "72"); - settings->setDefault("year_days", "30"); settings->setDefault("server_unload_unused_data_timeout", "29"); settings->setDefault("max_objects_per_block", "49"); settings->setDefault("server_map_save_interval", "5.3"); @@ -263,6 +284,8 @@ void set_default_settings(Settings *settings) settings->setDefault("emergequeue_limit_diskonly", "32"); settings->setDefault("emergequeue_limit_generate", "32"); settings->setDefault("num_emerge_threads", "1"); + settings->setDefault("secure.enable_security", "false"); + settings->setDefault("secure.trusted_mods", ""); // physics stuff settings->setDefault("movement_acceleration_default", "3"); @@ -287,7 +310,9 @@ void set_default_settings(Settings *settings) settings->setDefault("mg_name", "v6"); settings->setDefault("water_level", "1"); settings->setDefault("chunksize", "5"); - settings->setDefault("mg_flags", ""); + settings->setDefault("mg_flags", "dungeons"); + settings->setDefault("mgv6_spflags", "jungles, snowbiomes"); + settings->setDefault("enable_floating_dungeons", "true"); // IPv6 settings->setDefault("enable_ipv6", "true"); @@ -312,7 +337,7 @@ void set_default_settings(Settings *settings) settings->setDefault("enable_particles", "false"); settings->setDefault("video_driver", "ogles1"); settings->setDefault("touchtarget", "true"); - settings->setDefault("TMPFolder","/sdcard/Minetest/tmp/"); + settings->setDefault("TMPFolder","/sdcard/" PROJECT_NAME_C "/tmp/"); settings->setDefault("touchscreen_threshold","20"); settings->setDefault("smooth_lighting", "false"); settings->setDefault("max_simultaneous_block_sends_per_client", "3"); diff --git a/src/drawscene.cpp b/src/drawscene.cpp index b089e71e6..509f341d5 100644 --- a/src/drawscene.cpp +++ b/src/drawscene.cpp @@ -18,12 +18,12 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "drawscene.h" -#include "main.h" // for g_settings #include "settings.h" #include "clouds.h" #include "clientmap.h" #include "util/timetaker.h" #include "fontengine.h" +#include "guiscalingfilter.h" typedef enum { LEFT = -1, @@ -324,19 +324,19 @@ void draw_sidebyside_3d_mode(Camera& camera, bool show_hud, //makeColorKeyTexture mirrors texture so we do it twice to get it right again driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0)); - driver->draw2DImage(left_image, + draw2DImageFilterScaled(driver, left_image, irr::core::rect<s32>(0, 0, screensize.X/2, screensize.Y), irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false); - driver->draw2DImage(hudtexture, + draw2DImageFilterScaled(driver, hudtexture, irr::core::rect<s32>(0, 0, screensize.X/2, screensize.Y), irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true); - driver->draw2DImage(right_image, + draw2DImageFilterScaled(driver, right_image, irr::core::rect<s32>(screensize.X/2, 0, screensize.X, screensize.Y), irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false); - driver->draw2DImage(hudtexture, + draw2DImageFilterScaled(driver, hudtexture, irr::core::rect<s32>(screensize.X/2, 0, screensize.X, screensize.Y), irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true); @@ -380,19 +380,19 @@ void draw_top_bottom_3d_mode(Camera& camera, bool show_hud, //makeColorKeyTexture mirrors texture so we do it twice to get it right again driver->makeColorKeyTexture(hudtexture, irr::video::SColor(255, 0, 0, 0)); - driver->draw2DImage(left_image, + draw2DImageFilterScaled(driver, left_image, irr::core::rect<s32>(0, 0, screensize.X, screensize.Y/2), irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false); - driver->draw2DImage(hudtexture, + draw2DImageFilterScaled(driver, hudtexture, irr::core::rect<s32>(0, 0, screensize.X, screensize.Y/2), irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true); - driver->draw2DImage(right_image, + draw2DImageFilterScaled(driver, right_image, irr::core::rect<s32>(0, screensize.Y/2, screensize.X, screensize.Y), irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, false); - driver->draw2DImage(hudtexture, + draw2DImageFilterScaled(driver, hudtexture, irr::core::rect<s32>(0, screensize.Y/2, screensize.X, screensize.Y), irr::core::rect<s32>(0, 0, screensize.X, screensize.Y), 0, 0, true); @@ -416,10 +416,11 @@ void draw_plain(Camera& camera, bool show_hud, Hud& hud, camera.drawWieldedTool(); } -void draw_scene(video::IVideoDriver* driver, scene::ISceneManager* smgr, - Camera& camera, Client& client, LocalPlayer* player, Hud& hud, - gui::IGUIEnvironment* guienv, std::vector<aabb3f> hilightboxes, - const v2u32& screensize, video::SColor skycolor, bool show_hud) +void draw_scene(video::IVideoDriver *driver, scene::ISceneManager *smgr, + Camera &camera, Client& client, LocalPlayer *player, Hud &hud, + Mapper &mapper, gui::IGUIEnvironment *guienv, + std::vector<aabb3f> hilightboxes, const v2u32 &screensize, + video::SColor skycolor, bool show_hud, bool show_minimap) { TimeTaker timer("smgr"); @@ -484,6 +485,8 @@ void draw_scene(video::IVideoDriver* driver, scene::ISceneManager* smgr, hud.drawCrosshair(); hud.drawHotbar(client.getPlayerItem()); hud.drawLuaElements(camera.getOffset()); + if (show_minimap) + mapper.drawMinimap(); } guienv->drawAll(); diff --git a/src/drawscene.h b/src/drawscene.h index 3268bcbf2..0630f2970 100644 --- a/src/drawscene.h +++ b/src/drawscene.h @@ -22,16 +22,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "camera.h" #include "hud.h" +#include "minimap.h" #include "irrlichttypes_extrabloated.h" -void draw_load_screen(const std::wstring &text, IrrlichtDevice* device, - gui::IGUIEnvironment* guienv, float dtime=0, int percent=0, - bool clouds=true); +void draw_load_screen(const std::wstring &text, IrrlichtDevice *device, + gui::IGUIEnvironment *guienv, float dtime = 0, int percent = 0, + bool clouds = true); -void draw_scene(video::IVideoDriver* driver, scene::ISceneManager* smgr, - Camera& camera, Client& client, LocalPlayer* player, Hud& hud, - gui::IGUIEnvironment* guienv, std::vector<aabb3f> hilightboxes, - const v2u32& screensize, video::SColor skycolor, bool show_hud); +void draw_scene(video::IVideoDriver *driver, scene::ISceneManager *smgr, + Camera &camera, Client &client, LocalPlayer *player, Hud &hud, + Mapper &mapper, gui::IGUIEnvironment *guienv, + std::vector<aabb3f> hilightboxes, const v2u32 &screensize, + video::SColor skycolor, bool show_hud, bool show_minimap); #endif /* DRAWSCENE_H_ */ diff --git a/src/dungeongen.cpp b/src/dungeongen.cpp index eb452a196..8ce64e1e1 100644 --- a/src/dungeongen.cpp +++ b/src/dungeongen.cpp @@ -26,8 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "nodedef.h" #include "profiler.h" -#include "settings.h" // For g_settings -#include "main.h" // For g_profiler +#include "settings.h" //#define DGEN_USE_TORCHES @@ -39,7 +38,8 @@ NoiseParams nparams_dungeon_density(0.0, 1.0, v3f(2.5, 2.5, 2.5), 0, 2, 1.4, 2.0 /////////////////////////////////////////////////////////////////////////////// -DungeonGen::DungeonGen(Mapgen *mapgen, DungeonParams *dparams) { +DungeonGen::DungeonGen(Mapgen *mapgen, DungeonParams *dparams) +{ this->mg = mapgen; this->vm = mapgen->vm; @@ -56,10 +56,10 @@ DungeonGen::DungeonGen(Mapgen *mapgen, DungeonParams *dparams) { dp.c_stair = mg->ndef->getId("mapgen_stair_cobble"); dp.diagonal_dirs = false; - dp.mossratio = 3.0; - dp.holesize = v3s16(1, 2, 1); - dp.roomsize = v3s16(0,0,0); - dp.notifytype = GENNOTIFY_DUNGEON; + dp.mossratio = 3.0; + dp.holesize = v3s16(1, 2, 1); + dp.roomsize = v3s16(0, 0, 0); + dp.notifytype = GENNOTIFY_DUNGEON; dp.np_rarity = nparams_dungeon_rarity; dp.np_wetness = nparams_dungeon_wetness; @@ -68,7 +68,8 @@ DungeonGen::DungeonGen(Mapgen *mapgen, DungeonParams *dparams) { } -void DungeonGen::generate(u32 bseed, v3s16 nmin, v3s16 nmax) { +void DungeonGen::generate(u32 bseed, v3s16 nmin, v3s16 nmax) +{ //TimeTaker t("gen dungeons"); if (NoisePerlin3D(&dp.np_rarity, nmin.X, nmin.Y, nmin.Z, mg->seed) < 0.2) return; @@ -79,14 +80,17 @@ void DungeonGen::generate(u32 bseed, v3s16 nmin, v3s16 nmax) { // Dungeon generator doesn't modify places which have this set vm->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE); - // Set all air and water to be untouchable to make dungeons open - // to caves and open air + bool no_float = !g_settings->getBool("enable_floating_dungeons"); + + // Set all air and water (and optionally ignore) to be untouchable + // to make dungeons open to caves and open air 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); for (s16 x = nmin.X; x <= nmax.X; x++) { content_t c = vm->m_data[i].getContent(); - if (c == CONTENT_AIR || c == dp.c_water) + if (c == CONTENT_AIR || c == dp.c_water || + (no_float && c == CONTENT_IGNORE)) vm->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE; i++; } @@ -94,7 +98,7 @@ void DungeonGen::generate(u32 bseed, v3s16 nmin, v3s16 nmax) { } // Add it - makeDungeon(v3s16(1,1,1) * MAP_BLOCKSIZE); + makeDungeon(v3s16(1, 1, 1) * MAP_BLOCKSIZE); // Convert some cobble to mossy cobble if (dp.mossratio != 0.0) { @@ -127,20 +131,19 @@ void DungeonGen::makeDungeon(v3s16 start_padding) Find place for first room */ bool fits = false; - for (u32 i = 0; i < 100 && !fits; i++) - { + for (u32 i = 0; i < 100 && !fits; i++) { bool is_large_room = ((random.next() & 3) == 1); roomsize = is_large_room ? - v3s16(random.range(8, 16),random.range(8, 16),random.range(8, 16)) : - v3s16(random.range(4, 8),random.range(4, 6),random.range(4, 8)); + v3s16(random.range(8, 16), random.range(8, 16), random.range(8, 16)) : + v3s16(random.range(4, 8), random.range(4, 6), random.range(4, 8)); roomsize += dp.roomsize; // start_padding is used to disallow starting the generation of // a dungeon in a neighboring generation chunk roomplace = vm->m_area.MinEdge + start_padding + v3s16( - random.range(0,areasize.X-roomsize.X-1-start_padding.X), - random.range(0,areasize.Y-roomsize.Y-1-start_padding.Y), - random.range(0,areasize.Z-roomsize.Z-1-start_padding.Z)); + random.range(0, areasize.X - roomsize.X - 1 - start_padding.X), + random.range(0, areasize.Y - roomsize.Y - 1 - start_padding.Y), + random.range(0, areasize.Z - roomsize.Z - 1 - start_padding.Z)); /* Check that we're not putting the room to an unknown place, @@ -149,12 +152,11 @@ void DungeonGen::makeDungeon(v3s16 start_padding) fits = true; for (s16 z = 1; z < roomsize.Z - 1; z++) for (s16 y = 1; y < roomsize.Y - 1; y++) - for (s16 x = 1; x < roomsize.X - 1; x++) - { + for (s16 x = 1; x < roomsize.X - 1; x++) { v3s16 p = roomplace + v3s16(x, y, z); u32 vi = vm->m_area.index(p); if ((vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_INSIDE) || - vm->m_data[vi].getContent() == CONTENT_IGNORE) { + vm->m_data[vi].getContent() == CONTENT_IGNORE) { fits = false; break; } @@ -172,8 +174,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(2, 16); - for (u32 i = 0; i < room_count; i++) - { + for (u32 i = 0; i < room_count; i++) { // Make a room to the determined place makeRoom(roomsize, roomplace); @@ -211,7 +212,7 @@ void DungeonGen::makeDungeon(v3s16 start_padding) if (!findPlaceForDoor(doorplace, doordir)) return; - if (random.range(0,1) == 0) + if (random.range(0, 1) == 0) // Make the door makeDoor(doorplace, doordir); else @@ -224,7 +225,7 @@ void DungeonGen::makeDungeon(v3s16 start_padding) makeCorridor(doorplace, doordir, corridor_end, corridor_end_dir); // Find a place for a random sized room - roomsize = v3s16(random.range(4,8),random.range(4,6),random.range(4,8)); + roomsize = v3s16(random.range(4, 8), random.range(4, 6), random.range(4, 8)); roomsize += dp.roomsize; m_pos = corridor_end; @@ -232,7 +233,7 @@ void DungeonGen::makeDungeon(v3s16 start_padding) if (!findPlaceForRoomDoor(roomsize, doorplace, doordir, roomplace)) return; - if (random.range(0,1) == 0) + if (random.range(0, 1) == 0) // Make the door makeDoor(doorplace, doordir); else @@ -250,11 +251,10 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) // Make +-X walls for (s16 z = 0; z < roomsize.Z; z++) - for (s16 y = 0; y < roomsize.Y; y++) - { + for (s16 y = 0; y < roomsize.Y; y++) { { v3s16 p = roomplace + v3s16(0, y, z); - if (vm->m_area.contains(p) == false) + if (!vm->m_area.contains(p)) continue; u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) @@ -263,7 +263,7 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) } { v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z); - if (vm->m_area.contains(p) == false) + if (!vm->m_area.contains(p)) continue; u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) @@ -274,11 +274,10 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) // Make +-Z walls for (s16 x = 0; x < roomsize.X; x++) - for (s16 y = 0; y < roomsize.Y; y++) - { + for (s16 y = 0; y < roomsize.Y; y++) { { v3s16 p = roomplace + v3s16(x, y, 0); - if (vm->m_area.contains(p) == false) + if (!vm->m_area.contains(p)) continue; u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) @@ -287,7 +286,7 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) } { v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1); - if (vm->m_area.contains(p) == false) + if (!vm->m_area.contains(p)) continue; u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) @@ -298,11 +297,10 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) // Make +-Y walls (floor and ceiling) for (s16 z = 0; z < roomsize.Z; z++) - for (s16 x = 0; x < roomsize.X; x++) - { + for (s16 x = 0; x < roomsize.X; x++) { { v3s16 p = roomplace + v3s16(x, 0, z); - if (vm->m_area.contains(p) == false) + if (!vm->m_area.contains(p)) continue; u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) @@ -311,7 +309,7 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) } { v3s16 p = roomplace + v3s16(x,roomsize. Y - 1, z); - if (vm->m_area.contains(p) == false) + if (!vm->m_area.contains(p)) continue; u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) @@ -323,41 +321,39 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) // Fill with air for (s16 z = 1; z < roomsize.Z - 1; z++) for (s16 y = 1; y < roomsize.Y - 1; y++) - for (s16 x = 1; x < roomsize.X - 1; x++) - { + for (s16 x = 1; x < roomsize.X - 1; x++) { v3s16 p = roomplace + v3s16(x, y, z); - if (vm->m_area.contains(p) == false) + if (!vm->m_area.contains(p)) continue; u32 vi = vm->m_area.index(p); vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE; - vm->m_data[vi] = n_air; + vm->m_data[vi] = n_air; } } void DungeonGen::makeFill(v3s16 place, v3s16 size, - u8 avoid_flags, MapNode n, u8 or_flags) + u8 avoid_flags, MapNode n, u8 or_flags) { for (s16 z = 0; z < size.Z; z++) for (s16 y = 0; y < size.Y; y++) - for (s16 x = 0; x < size.X; x++) - { + for (s16 x = 0; x < size.X; x++) { v3s16 p = place + v3s16(x, y, z); - if (vm->m_area.contains(p) == false) + if (!vm->m_area.contains(p)) continue; u32 vi = vm->m_area.index(p); if (vm->m_flags[vi] & avoid_flags) continue; vm->m_flags[vi] |= or_flags; - vm->m_data[vi] = n; + vm->m_data[vi] = n; } } void DungeonGen::makeHole(v3s16 place) { - makeFill(place, dp.holesize, 0, - MapNode(CONTENT_AIR), VMANIP_FLAG_DUNGEON_INSIDE); + makeFill(place, dp.holesize, 0, MapNode(CONTENT_AIR), + VMANIP_FLAG_DUNGEON_INSIDE); } @@ -372,8 +368,8 @@ void DungeonGen::makeDoor(v3s16 doorplace, v3s16 doordir) } -void DungeonGen::makeCorridor(v3s16 doorplace, - v3s16 doordir, v3s16 &result_place, v3s16 &result_dir) +void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, + v3s16 &result_place, v3s16 &result_dir) { makeHole(doorplace); v3s16 p0 = doorplace; @@ -396,22 +392,26 @@ void DungeonGen::makeCorridor(v3s16 doorplace, if (partcount != 0) p.Y += make_stairs; - if (vm->m_area.contains(p) == true && - vm->m_area.contains(p + v3s16(0, 1, 0)) == true) { + if (vm->m_area.contains(p) && vm->m_area.contains(p + v3s16(0, 1, 0))) { if (make_stairs) { - makeFill(p + v3s16(-1, -1, -1), dp.holesize + v3s16(2, 3, 2), - VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(dp.c_cobble), 0); + makeFill(p + v3s16(-1, -1, -1), + dp.holesize + v3s16(2, 3, 2), + VMANIP_FLAG_DUNGEON_UNTOUCHABLE, + MapNode(dp.c_cobble), + 0); makeHole(p); makeHole(p - dir); - // TODO: fix stairs code so it works 100% (quite difficult) + // TODO: fix stairs code so it works 100% + // (quite difficult) // exclude stairs from the bottom step // exclude stairs from diagonal steps if (((dir.X ^ dir.Z) & 1) && - (((make_stairs == 1) && i != 0) || - ((make_stairs == -1) && i != length - 1))) { - // rotate face 180 deg if making stairs backwards + (((make_stairs == 1) && i != 0) || + ((make_stairs == -1) && i != length - 1))) { + // rotate face 180 deg if + // making stairs backwards int facedir = dir_to_facedir(dir * make_stairs); u32 vi = vm->m_area.index(p.X - dir.X, p.Y - 1, p.Z - dir.Z); @@ -423,8 +423,11 @@ void DungeonGen::makeCorridor(v3s16 doorplace, vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); } } else { - makeFill(p + v3s16(-1, -1, -1), dp.holesize + v3s16(2, 2, 2), - VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(dp.c_cobble), 0); + makeFill(p + v3s16(-1, -1, -1), + dp.holesize + v3s16(2, 2, 2), + VMANIP_FLAG_DUNGEON_UNTOUCHABLE, + MapNode(dp.c_cobble), + 0); makeHole(p); } @@ -444,7 +447,7 @@ void DungeonGen::makeCorridor(v3s16 doorplace, dir = random_turn(random, dir); - partlength = random.range(1,length); + partlength = random.range(1, length); make_stairs = 0; if (random.next() % 2 == 0 && partlength >= 3) @@ -458,20 +461,15 @@ void DungeonGen::makeCorridor(v3s16 doorplace, bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) { - for (u32 i = 0; i < 100; i++) - { + for (u32 i = 0; i < 100; i++) { v3s16 p = m_pos + m_dir; v3s16 p1 = p + v3s16(0, 1, 0); - if (vm->m_area.contains(p) == false - || vm->m_area.contains(p1) == false - || i % 4 == 0) - { + if (!vm->m_area.contains(p) || !vm->m_area.contains(p1) || i % 4 == 0) { randomizeDir(); continue; } - if (vm->getNodeNoExNoEmerge(p).getContent() == dp.c_cobble - && vm->getNodeNoExNoEmerge(p1).getContent() == dp.c_cobble) - { + if (vm->getNodeNoExNoEmerge(p).getContent() == dp.c_cobble && + vm->getNodeNoExNoEmerge(p1).getContent() == dp.c_cobble) { // Found wall, this is a good place! result_place = p; result_dir = m_dir; @@ -483,19 +481,25 @@ bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) Determine where to move next */ // Jump one up if the actual space is there - if (vm->getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent() == dp.c_cobble - && vm->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() == CONTENT_AIR - && vm->getNodeNoExNoEmerge(p+v3s16(0,2,0)).getContent() == CONTENT_AIR) + if (vm->getNodeNoExNoEmerge(p + + v3s16(0, 0, 0)).getContent() == dp.c_cobble && + vm->getNodeNoExNoEmerge(p + + v3s16(0, 1, 0)).getContent() == CONTENT_AIR && + vm->getNodeNoExNoEmerge(p + + v3s16(0, 2, 0)).getContent() == CONTENT_AIR) p += v3s16(0,1,0); // Jump one down if the actual space is there - if (vm->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() == dp.c_cobble - && vm->getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent() == CONTENT_AIR - && vm->getNodeNoExNoEmerge(p+v3s16(0,-1,0)).getContent() == CONTENT_AIR) - p += v3s16(0,-1,0); + if (vm->getNodeNoExNoEmerge(p + + v3s16(0, 1, 0)).getContent() == dp.c_cobble && + vm->getNodeNoExNoEmerge(p + + v3s16(0, 0, 0)).getContent() == CONTENT_AIR && + vm->getNodeNoExNoEmerge(p + + v3s16(0, -1, 0)).getContent() == CONTENT_AIR) + p += v3s16(0, -1, 0); // Check if walking is now possible - if (vm->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR - || vm->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() != CONTENT_AIR) - { + if (vm->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR || + vm->getNodeNoExNoEmerge(p + + v3s16(0, 1, 0)).getContent() != CONTENT_AIR) { // Cannot continue walking here randomizeDir(); continue; @@ -508,10 +512,9 @@ bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, - v3s16 &result_doordir, v3s16 &result_roomplace) + v3s16 &result_doordir, v3s16 &result_roomplace) { - for (s16 trycount = 0; trycount < 30; trycount++) - { + for (s16 trycount = 0; trycount < 30; trycount++) { v3s16 doorplace; v3s16 doordir; bool r = findPlaceForDoor(doorplace, doordir); @@ -522,16 +525,16 @@ bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, #if 1 if (doordir == v3s16(1, 0, 0)) // X+ roomplace = doorplace + - v3s16(0, -1, random.range(-roomsize.Z + 2, -2)); + v3s16(0, -1, random.range(-roomsize.Z + 2, -2)); if (doordir == v3s16(-1, 0, 0)) // X- roomplace = doorplace + - v3s16(-roomsize.X + 1, -1, random.range(-roomsize.Z + 2, -2)); + v3s16(-roomsize.X + 1, -1, random.range(-roomsize.Z + 2, -2)); if (doordir == v3s16(0, 0, 1)) // Z+ roomplace = doorplace + - v3s16(random.range(-roomsize.X + 2, -2), -1, 0); + v3s16(random.range(-roomsize.X + 2, -2), -1, 0); if (doordir == v3s16(0, 0, -1)) // Z- roomplace = doorplace + - v3s16(random.range(-roomsize.X + 2, -2), -1, -roomsize.Z + 1); + v3s16(random.range(-roomsize.X + 2, -2), -1, -roomsize.Z + 1); #endif #if 0 if (doordir == v3s16(1, 0, 0)) // X+ @@ -548,23 +551,18 @@ bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, bool fits = true; for (s16 z = 1; z < roomsize.Z - 1; z++) for (s16 y = 1; y < roomsize.Y - 1; y++) - for (s16 x = 1; x < roomsize.X - 1; x++) - { + for (s16 x = 1; x < roomsize.X - 1; x++) { v3s16 p = roomplace + v3s16(x, y, z); - if (vm->m_area.contains(p) == false) - { + if (!vm->m_area.contains(p)) { fits = false; break; } - if (vm->m_flags[vm->m_area.index(p)] - & VMANIP_FLAG_DUNGEON_INSIDE) - { + if (vm->m_flags[vm->m_area.index(p)] & VMANIP_FLAG_DUNGEON_INSIDE) { fits = false; break; } } - if(fits == false) - { + if (fits == false) { // Find new place continue; } @@ -602,15 +600,12 @@ v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs) v3s16 turn_xz(v3s16 olddir, int t) { v3s16 dir; - if (t == 0) - { + if (t == 0) { // Turn right dir.X = olddir.Z; dir.Z = -olddir.X; dir.Y = olddir.Y; - } - else - { + } else { // Turn left dir.X = -olddir.Z; dir.Z = olddir.X; @@ -625,10 +620,8 @@ v3s16 random_turn(PseudoRandom &random, v3s16 olddir) int turn = random.range(0, 2); v3s16 dir; if (turn == 0) - { // Go straight dir = olddir; - } else if (turn == 1) // Turn right dir = turn_xz(olddir, 0); @@ -639,7 +632,8 @@ v3s16 random_turn(PseudoRandom &random, v3s16 olddir) } -int dir_to_facedir(v3s16 d) { +int dir_to_facedir(v3s16 d) +{ if (abs(d.X) > abs(d.Z)) return d.X < 0 ? 3 : 1; else diff --git a/src/emerge.cpp b/src/emerge.cpp index a697bcb07..d6bda731a 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -28,7 +28,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "environment.h" #include "util/container.h" #include "util/thread.h" -#include "main.h" #include "constants.h" #include "voxel.h" #include "config.h" @@ -166,7 +165,7 @@ EmergeManager::~EmergeManager() void EmergeManager::loadMapgenParams() { - loadParamsFromSettings(g_settings); + params.load(*g_settings); } @@ -344,9 +343,9 @@ Mapgen *EmergeManager::createMapgen(const std::string &mgname, int mgid, MapgenSpecificParams *EmergeManager::createMapgenParams(const std::string &mgname) { u32 i; - for (i = 0; i != ARRLEN(reg_mapgens) && mgname != reg_mapgens[i].name; i++); + for (i = 0; i < ARRLEN(reg_mapgens) && mgname != reg_mapgens[i].name; i++); if (i == ARRLEN(reg_mapgens)) { - errorstream << "EmergeManager; mapgen " << mgname << + errorstream << "EmergeManager: Mapgen " << mgname << " not registered" << std::endl; return NULL; } @@ -356,56 +355,6 @@ MapgenSpecificParams *EmergeManager::createMapgenParams(const std::string &mgnam } -void EmergeManager::loadParamsFromSettings(Settings *settings) -{ - std::string seed_str; - const char *setname = (settings == g_settings) ? "fixed_map_seed" : "seed"; - - if (!settings->getNoEx("seed", seed_str)) { - g_settings->getNoEx(setname, seed_str); - } - if (!seed_str.empty()) { - params.seed = read_seed(seed_str.c_str()); - } else { - params.seed = - ((u64)(myrand() & 0xffff) << 0) | - ((u64)(myrand() & 0xffff) << 16) | - ((u64)(myrand() & 0xffff) << 32) | - ((u64)(myrand() & 0xffff) << 48); - } - - settings->getNoEx("mg_name", params.mg_name); - settings->getS16NoEx("water_level", params.water_level); - settings->getS16NoEx("chunksize", params.chunksize); - settings->getFlagStrNoEx("mg_flags", params.flags, flagdesc_mapgen); - settings->getNoiseParams("mg_biome_np_heat", params.np_biome_heat); - settings->getNoiseParams("mg_biome_np_humidity", params.np_biome_humidity); - - delete params.sparams; - params.sparams = createMapgenParams(params.mg_name); - - if (params.sparams) { - params.sparams->readParams(g_settings); - params.sparams->readParams(settings); - } -} - - -void EmergeManager::saveParamsToSettings(Settings *settings) -{ - settings->set("mg_name", params.mg_name); - settings->setU64("seed", params.seed); - settings->setS16("water_level", params.water_level); - settings->setS16("chunksize", params.chunksize); - settings->setFlagStr("mg_flags", params.flags, flagdesc_mapgen, (u32)-1); - settings->setNoiseParams("mg_biome_np_heat", params.np_biome_heat); - settings->setNoiseParams("mg_biome_np_humidity", params.np_biome_humidity); - - if (params.sparams) - params.sparams->writeParams(settings); -} - - ////////////////////////////// Emerge Thread ////////////////////////////////// bool EmergeThread::popBlockEmerge(v3s16 *pos, u8 *flags) @@ -545,8 +494,8 @@ void *EmergeThread::Thread() try { // takes about 90ms with -O1 on an e3-1230v2 m_server->getScriptIface()->environment_OnGenerated( minp, maxp, mapgen->blockseed); - } catch(LuaError &e) { - m_server->setAsyncFatalError(e.what()); + } catch (LuaError &e) { + m_server->setAsyncFatalError("Lua: " + std::string(e.what())); } EMERGE_DBG_OUT("ended up with: " << analyze_block(block)); @@ -569,20 +518,22 @@ void *EmergeThread::Thread() } catch (VersionMismatchException &e) { std::ostringstream err; - err << "World data version mismatch in MapBlock "<<PP(last_tried_pos)<<std::endl; - err << "----"<<std::endl; - err << "\""<<e.what()<<"\""<<std::endl; - err << "See debug.txt."<<std::endl; - err << "World probably saved by a newer version of Minetest."<<std::endl; + err << "World data version mismatch in MapBlock " << PP(last_tried_pos) << std::endl + << "----" << std::endl + << "\"" << e.what() << "\"" << std::endl + << "See debug.txt." << std::endl + << "World probably saved by a newer version of " PROJECT_NAME_C "." + << std::endl; m_server->setAsyncFatalError(err.str()); } catch (SerializationError &e) { std::ostringstream err; - err << "Invalid data in MapBlock "<<PP(last_tried_pos)<<std::endl; - err << "----"<<std::endl; - err << "\""<<e.what()<<"\""<<std::endl; - err << "See debug.txt."<<std::endl; - err << "You can ignore this using [ignore_world_load_errors = true]."<<std::endl; + err << "Invalid data in MapBlock " << PP(last_tried_pos) << std::endl + << "----" << std::endl + << "\"" << e.what() << "\"" << std::endl + << "See debug.txt." << std::endl + << "You can ignore this using [ignore_world_load_errors = true]." + << std::endl; m_server->setAsyncFatalError(err.str()); } diff --git a/src/emerge.h b/src/emerge.h index 8bcc96ee0..1653199ec 100644 --- a/src/emerge.h +++ b/src/emerge.h @@ -101,19 +101,16 @@ public: ~EmergeManager(); void loadMapgenParams(); + static MapgenSpecificParams *createMapgenParams(const std::string &mgname); void initMapgens(); Mapgen *getCurrentMapgen(); Mapgen *createMapgen(const std::string &mgname, int mgid, MapgenParams *mgparams); - MapgenSpecificParams *createMapgenParams(const std::string &mgname); static void getMapgenNames(std::list<const char *> &mgnames); void startThreads(); void stopThreads(); bool enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate); - void loadParamsFromSettings(Settings *settings); - void saveParamsToSettings(Settings *settings); - //mapgen helper methods Biome *getBiomeAtPoint(v3s16 p); int getGroundLevelAtPoint(v2s16 p); diff --git a/src/environment.cpp b/src/environment.cpp index e1f79803b..dbbfc6f1f 100644 --- a/src/environment.cpp +++ b/src/environment.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 <fstream> #include "environment.h" #include "filesys.h" #include "porting.h" @@ -31,7 +32,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "scripting_game.h" #include "nodedef.h" #include "nodemetadata.h" -#include "main.h" // For g_settings, g_profiler #include "gamedef.h" #ifndef SERVER #include "clientmap.h" @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapblock_mesh.h" #include "event.h" #endif +#include "server.h" #include "daynightratio.h" #include "map.h" #include "emerge.h" @@ -61,9 +62,8 @@ Environment::Environment(): Environment::~Environment() { // Deallocate players - for(std::list<Player*>::iterator i = m_players.begin(); - i != m_players.end(); ++i) - { + for(std::vector<Player*>::iterator i = m_players.begin(); + i != m_players.end(); ++i) { delete (*i); } } @@ -78,9 +78,9 @@ void Environment::addPlayer(Player *player) */ // If peer id is non-zero, it has to be unique. if(player->peer_id != 0) - assert(getPlayer(player->peer_id) == NULL); + FATAL_ERROR_IF(getPlayer(player->peer_id) != NULL, "Peer id not unique"); // Name has to be unique. - assert(getPlayer(player->getName()) == NULL); + FATAL_ERROR_IF(getPlayer(player->getName()) != NULL, "Player name not unique"); // Add. m_players.push_back(player); } @@ -89,7 +89,7 @@ void Environment::removePlayer(u16 peer_id) { DSTACK(__FUNCTION_NAME); - for(std::list<Player*>::iterator i = m_players.begin(); + for(std::vector<Player*>::iterator i = m_players.begin(); i != m_players.end();) { Player *player = *i; @@ -104,7 +104,7 @@ void Environment::removePlayer(u16 peer_id) void Environment::removePlayer(const char *name) { - for (std::list<Player*>::iterator it = m_players.begin(); + for (std::vector<Player*>::iterator it = m_players.begin(); it != m_players.end(); ++it) { if (strcmp((*it)->getName(), name) == 0) { delete *it; @@ -116,9 +116,8 @@ void Environment::removePlayer(const char *name) Player * Environment::getPlayer(u16 peer_id) { - for(std::list<Player*>::iterator i = m_players.begin(); - i != m_players.end(); ++i) - { + for(std::vector<Player*>::iterator i = m_players.begin(); + i != m_players.end(); ++i) { Player *player = *i; if(player->peer_id == peer_id) return player; @@ -128,9 +127,8 @@ Player * Environment::getPlayer(u16 peer_id) Player * Environment::getPlayer(const char *name) { - for(std::list<Player*>::iterator i = m_players.begin(); - i != m_players.end(); ++i) - { + for(std::vector<Player*>::iterator i = m_players.begin(); + i != m_players.end(); ++i) { Player *player = *i; if(strcmp(player->getName(), name) == 0) return player; @@ -140,15 +138,13 @@ Player * Environment::getPlayer(const char *name) Player * Environment::getRandomConnectedPlayer() { - std::list<Player*> connected_players = getPlayers(true); + std::vector<Player*> connected_players = getPlayers(true); u32 chosen_one = myrand() % connected_players.size(); u32 j = 0; - for(std::list<Player*>::iterator + for(std::vector<Player*>::iterator i = connected_players.begin(); - i != connected_players.end(); ++i) - { - if(j == chosen_one) - { + i != connected_players.end(); ++i) { + if(j == chosen_one) { Player *player = *i; return player; } @@ -159,17 +155,15 @@ Player * Environment::getRandomConnectedPlayer() Player * Environment::getNearestConnectedPlayer(v3f pos) { - std::list<Player*> connected_players = getPlayers(true); + std::vector<Player*> connected_players = getPlayers(true); f32 nearest_d = 0; Player *nearest_player = NULL; - for(std::list<Player*>::iterator + for(std::vector<Player*>::iterator i = connected_players.begin(); - i != connected_players.end(); ++i) - { + i != connected_players.end(); ++i) { Player *player = *i; f32 d = player->getPosition().getDistanceFrom(pos); - if(d < nearest_d || nearest_player == NULL) - { + if(d < nearest_d || nearest_player == NULL) { nearest_d = d; nearest_player = player; } @@ -177,22 +171,20 @@ Player * Environment::getNearestConnectedPlayer(v3f pos) return nearest_player; } -std::list<Player*> Environment::getPlayers() +std::vector<Player*> Environment::getPlayers() { return m_players; } -std::list<Player*> Environment::getPlayers(bool ignore_disconnected) +std::vector<Player*> Environment::getPlayers(bool ignore_disconnected) { - std::list<Player*> newlist; - for(std::list<Player*>::iterator + std::vector<Player*> newlist; + for(std::vector<Player*>::iterator i = m_players.begin(); - i != m_players.end(); ++i) - { + i != m_players.end(); ++i) { Player *player = *i; - - if(ignore_disconnected) - { + + if(ignore_disconnected) { // Ignore disconnected players if(player->peer_id == 0) continue; @@ -212,25 +204,43 @@ u32 Environment::getDayNightRatio() void Environment::setTimeOfDaySpeed(float speed) { - JMutexAutoLock(this->m_lock); + JMutexAutoLock(this->m_timeofday_lock); m_time_of_day_speed = speed; } float Environment::getTimeOfDaySpeed() { - JMutexAutoLock(this->m_lock); + JMutexAutoLock(this->m_timeofday_lock); float retval = m_time_of_day_speed; return retval; } +void Environment::setTimeOfDay(u32 time) +{ + JMutexAutoLock(this->m_time_lock); + m_time_of_day = time; + m_time_of_day_f = (float)time / 24000.0; +} + +u32 Environment::getTimeOfDay() +{ + JMutexAutoLock(this->m_time_lock); + u32 retval = m_time_of_day; + return retval; +} + +float Environment::getTimeOfDayF() +{ + JMutexAutoLock(this->m_time_lock); + float retval = m_time_of_day_f; + return retval; +} + void Environment::stepTimeOfDay(float dtime) { - float day_speed = 0; - { - JMutexAutoLock(this->m_lock); - day_speed = m_time_of_day_speed; - } - + // getTimeOfDaySpeed lock the value we need to prevent MT problems + float day_speed = getTimeOfDaySpeed(); + m_time_counter += dtime; f32 speed = day_speed * 24000./(24.*3600); u32 units = (u32)(m_time_counter*speed); @@ -287,7 +297,7 @@ void fillRadiusBlock(v3s16 p0, s16 r, std::set<v3s16> &list) } } -void ActiveBlockList::update(std::list<v3s16> &active_positions, +void ActiveBlockList::update(std::vector<v3s16> &active_positions, s16 radius, std::set<v3s16> &blocks_removed, std::set<v3s16> &blocks_added) @@ -296,7 +306,7 @@ void ActiveBlockList::update(std::list<v3s16> &active_positions, Create the new list */ std::set<v3s16> newlist = m_forceloaded_list; - for(std::list<v3s16>::iterator i = active_positions.begin(); + for(std::vector<v3s16>::iterator i = active_positions.begin(); i != active_positions.end(); ++i) { fillRadiusBlock(*i, radius, newlist); @@ -373,7 +383,7 @@ ServerEnvironment::~ServerEnvironment() m_map->drop(); // Delete ActiveBlockModifiers - for(std::list<ABMWithState>::iterator + for(std::vector<ABMWithState>::iterator i = m_abms.begin(); i != m_abms.end(); ++i){ delete i->abm; } @@ -395,8 +405,8 @@ bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 //calculate normalized direction vector v3f normalized_vector = v3f((pos2.X - pos1.X)/distance, - (pos2.Y - pos1.Y)/distance, - (pos2.Z - pos1.Z)/distance); + (pos2.Y - pos1.Y)/distance, + (pos2.Z - pos1.Z)/distance); //find out if there's a node on path between pos1 and pos2 for (float i = 1; i < distance; i += stepsize) { @@ -416,12 +426,24 @@ bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 return true; } +void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, + const std::string &str_reason, bool reconnect) +{ + for (std::vector<Player*>::iterator it = m_players.begin(); + it != m_players.end(); + ++it) { + ((Server*)m_gamedef)->DenyAccessVerCompliant((*it)->peer_id, + (*it)->protocol_version, (AccessDeniedCode)reason, + str_reason, reconnect); + } +} + void ServerEnvironment::saveLoadedPlayers() { std::string players_path = m_path_world + DIR_DELIM "players"; fs::CreateDir(players_path); - for (std::list<Player*>::iterator it = m_players.begin(); + for (std::vector<Player*>::iterator it = m_players.begin(); it != m_players.end(); ++it) { RemotePlayer *player = static_cast<RemotePlayer*>(*it); @@ -444,41 +466,43 @@ void ServerEnvironment::savePlayer(const std::string &playername) Player *ServerEnvironment::loadPlayer(const std::string &playername) { - std::string players_path = m_path_world + DIR_DELIM "players" DIR_DELIM; - - RemotePlayer *player = static_cast<RemotePlayer*>(getPlayer(playername.c_str())); bool newplayer = false; bool found = false; + std::string players_path = m_path_world + DIR_DELIM "players" DIR_DELIM; + std::string path = players_path + playername; + + RemotePlayer *player = static_cast<RemotePlayer *>(getPlayer(playername.c_str())); if (!player) { - player = new RemotePlayer(m_gamedef, playername.c_str()); + player = new RemotePlayer(m_gamedef, ""); newplayer = true; } - RemotePlayer testplayer(m_gamedef, ""); - std::string path = players_path + playername; for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) { - // Open file and deserialize + //// Open file and deserialize std::ifstream is(path.c_str(), std::ios_base::binary); - if (!is.good()) { - return NULL; - } - testplayer.deSerialize(is, path); + if (!is.good()) + continue; + player->deSerialize(is, path); is.close(); - if (testplayer.getName() == playername) { - *player = testplayer; + + if (player->getName() == playername) { found = true; break; } + path = players_path + playername + itos(i); } + if (!found) { infostream << "Player file for player " << playername << " not found" << std::endl; + if (newplayer) + delete player; return NULL; } - if (newplayer) { + + if (newplayer) addPlayer(player); - } player->setModified(false); return player; } @@ -549,9 +573,9 @@ class ABMHandler { private: ServerEnvironment *m_env; - std::map<content_t, std::list<ActiveABM> > m_aabms; + std::map<content_t, std::vector<ActiveABM> > m_aabms; public: - ABMHandler(std::list<ABMWithState> &abms, + ABMHandler(std::vector<ABMWithState> &abms, float dtime_s, ServerEnvironment *env, bool use_timers): m_env(env) @@ -559,8 +583,8 @@ public: if(dtime_s < 0.001) return; INodeDefManager *ndef = env->getGameDef()->ndef(); - for(std::list<ABMWithState>::iterator - i = abms.begin(); i != abms.end(); ++i){ + for(std::vector<ABMWithState>::iterator + i = abms.begin(); i != abms.end(); ++i) { ActiveBlockModifier *abm = i->abm; float trigger_interval = abm->getTriggerInterval(); if(trigger_interval < 0.001) @@ -604,10 +628,10 @@ public: k != ids.end(); k++) { content_t c = *k; - std::map<content_t, std::list<ActiveABM> >::iterator j; + std::map<content_t, std::vector<ActiveABM> >::iterator j; j = m_aabms.find(c); if(j == m_aabms.end()){ - std::list<ActiveABM> aabmlist; + std::vector<ActiveABM> aabmlist; m_aabms[c] = aabmlist; j = m_aabms.find(c); } @@ -664,14 +688,13 @@ public: content_t c = n.getContent(); v3s16 p = p0 + block->getPosRelative(); - std::map<content_t, std::list<ActiveABM> >::iterator j; + std::map<content_t, std::vector<ActiveABM> >::iterator j; j = m_aabms.find(c); if(j == m_aabms.end()) continue; - for(std::list<ActiveABM>::iterator - i = j->second.begin(); i != j->second.end(); i++) - { + for(std::vector<ActiveABM>::iterator + i = j->second.begin(); i != j->second.end(); i++) { if(myrand() % i->chance != 0) continue; @@ -738,7 +761,7 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime) /*infostream<<"ServerEnvironment::activateBlock(): block is " <<dtime_s<<" seconds old."<<std::endl;*/ - + // Activate stored objects activateObjects(block, dtime_s); @@ -830,9 +853,8 @@ bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n) return true; } -std::set<u16> ServerEnvironment::getObjectsInsideRadius(v3f pos, float radius) +void ServerEnvironment::getObjectsInsideRadius(std::vector<u16> &objects, v3f pos, float radius) { - std::set<u16> objects; for(std::map<u16, ServerActiveObject*>::iterator i = m_active_objects.begin(); i != m_active_objects.end(); ++i) @@ -842,20 +864,18 @@ std::set<u16> ServerEnvironment::getObjectsInsideRadius(v3f pos, float radius) v3f objectpos = obj->getBasePosition(); if(objectpos.getDistanceFrom(pos) > radius) continue; - objects.insert(id); + objects.push_back(id); } - return objects; } void ServerEnvironment::clearAllObjects() { infostream<<"ServerEnvironment::clearAllObjects(): " <<"Removing all active objects"<<std::endl; - std::list<u16> objects_to_remove; + std::vector<u16> objects_to_remove; for(std::map<u16, ServerActiveObject*>::iterator i = m_active_objects.begin(); - i != m_active_objects.end(); ++i) - { + i != m_active_objects.end(); ++i) { ServerActiveObject* obj = i->second; if(obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) continue; @@ -866,7 +886,7 @@ void ServerEnvironment::clearAllObjects() if(block){ block->m_static_objects.remove(id); block->raiseModified(MOD_STATE_WRITE_NEEDED, - "clearAllObjects"); + MOD_REASON_CLEAR_ALL_OBJECTS); obj->m_static_exists = false; } } @@ -888,15 +908,15 @@ void ServerEnvironment::clearAllObjects() // Id to be removed from m_active_objects objects_to_remove.push_back(id); } + // Remove references from m_active_objects - for(std::list<u16>::iterator i = objects_to_remove.begin(); - i != objects_to_remove.end(); ++i) - { + for(std::vector<u16>::iterator i = objects_to_remove.begin(); + i != objects_to_remove.end(); ++i) { m_active_objects.erase(*i); } // Get list of loaded blocks - std::list<v3s16> loaded_blocks; + std::vector<v3s16> loaded_blocks; infostream<<"ServerEnvironment::clearAllObjects(): " <<"Listing all loaded blocks"<<std::endl; m_map->listAllLoadedBlocks(loaded_blocks); @@ -905,7 +925,7 @@ void ServerEnvironment::clearAllObjects() <<loaded_blocks.size()<<std::endl; // Get list of loadable blocks - std::list<v3s16> loadable_blocks; + std::vector<v3s16> loadable_blocks; infostream<<"ServerEnvironment::clearAllObjects(): " <<"Listing all loadable blocks"<<std::endl; m_map->listAllLoadableBlocks(loadable_blocks); @@ -915,12 +935,11 @@ void ServerEnvironment::clearAllObjects() <<", now clearing"<<std::endl; // Grab a reference on each loaded block to avoid unloading it - for(std::list<v3s16>::iterator i = loaded_blocks.begin(); - i != loaded_blocks.end(); ++i) - { + for(std::vector<v3s16>::iterator i = loaded_blocks.begin(); + i != loaded_blocks.end(); ++i) { v3s16 p = *i; MapBlock *block = m_map->getBlockNoCreateNoEx(p); - assert(block); + assert(block != NULL); block->refGrab(); } @@ -931,9 +950,8 @@ void ServerEnvironment::clearAllObjects() u32 num_blocks_checked = 0; u32 num_blocks_cleared = 0; u32 num_objs_cleared = 0; - for(std::list<v3s16>::iterator i = loadable_blocks.begin(); - i != loadable_blocks.end(); ++i) - { + for(std::vector<v3s16>::iterator i = loadable_blocks.begin(); + i != loadable_blocks.end(); ++i) { v3s16 p = *i; MapBlock *block = m_map->emergeBlock(p, false); if(!block){ @@ -947,7 +965,7 @@ void ServerEnvironment::clearAllObjects() block->m_static_objects.m_stored.clear(); block->m_static_objects.m_active.clear(); block->raiseModified(MOD_STATE_WRITE_NEEDED, - "clearAllObjects"); + MOD_REASON_CLEAR_ALL_OBJECTS); num_objs_cleared += num_stored + num_active; num_blocks_cleared++; } @@ -969,9 +987,8 @@ void ServerEnvironment::clearAllObjects() m_map->unloadUnreferencedBlocks(); // Drop references that were added above - for(std::list<v3s16>::iterator i = loaded_blocks.begin(); - i != loaded_blocks.end(); ++i) - { + for(std::vector<v3s16>::iterator i = loaded_blocks.begin(); + i != loaded_blocks.end(); ++i) { v3s16 p = *i; MapBlock *block = m_map->getBlockNoCreateNoEx(p); assert(block); @@ -986,7 +1003,7 @@ void ServerEnvironment::clearAllObjects() void ServerEnvironment::step(float dtime) { DSTACK(__FUNCTION_NAME); - + //TimeTaker timer("ServerEnv step"); /* Step time of day */ @@ -1006,21 +1023,21 @@ void ServerEnvironment::step(float dtime) m_game_time += inc_i; m_game_time_fraction_counter -= (float)inc_i; } - + /* Handle players */ { ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG); - for(std::list<Player*>::iterator i = m_players.begin(); + for(std::vector<Player*>::iterator i = m_players.begin(); i != m_players.end(); ++i) { Player *player = *i; - + // Ignore disconnected players if(player->peer_id == 0) continue; - + // Move player->move(dtime, this, 100*BS); } @@ -1035,20 +1052,20 @@ void ServerEnvironment::step(float dtime) /* Get player block positions */ - std::list<v3s16> players_blockpos; - for(std::list<Player*>::iterator + std::vector<v3s16> players_blockpos; + for(std::vector<Player*>::iterator i = m_players.begin(); - i != m_players.end(); ++i) - { + i != m_players.end(); ++i) { Player *player = *i; // Ignore disconnected players if(player->peer_id == 0) continue; + v3s16 blockpos = getNodeBlockPos( floatToInt(player->getPosition(), BS)); players_blockpos.push_back(blockpos); } - + /* Update list of active blocks, collecting changes */ @@ -1064,7 +1081,7 @@ void ServerEnvironment::step(float dtime) // Convert active objects that are no more in active blocks to static deactivateFarObjects(false); - + for(std::set<v3s16>::iterator i = blocks_removed.begin(); i != blocks_removed.end(); ++i) @@ -1073,11 +1090,11 @@ void ServerEnvironment::step(float dtime) /* infostream<<"Server: Block " << PP(p) << " became inactive"<<std::endl; */ - + MapBlock *block = m_map->getBlockNoCreateNoEx(p); if(block==NULL) continue; - + // Set current time as timestamp (and let it set ChangedFlag) block->setTimestamp(m_game_time); } @@ -1110,7 +1127,7 @@ void ServerEnvironment::step(float dtime) if(m_active_blocks_nodemetadata_interval.step(dtime, 1.0)) { ScopeProfiler sp(g_profiler, "SEnv: mess in act. blocks avg /1s", SPT_AVG); - + float dtime = 1.0; for(std::set<v3s16>::iterator @@ -1118,7 +1135,7 @@ void ServerEnvironment::step(float dtime) i != m_active_blocks.m_list.end(); ++i) { v3s16 p = *i; - + /*infostream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z <<") being handled"<<std::endl;*/ @@ -1128,14 +1145,14 @@ void ServerEnvironment::step(float dtime) // Reset block usage timer block->resetUsageTimer(); - + // Set current time as timestamp block->setTimestampNoChangedFlag(m_game_time); // If time has changed much from the one on disk, // set block to be saved when it is unloaded if(block->getTimestamp() > block->getDiskTimestamp() + 60) block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD, - "Timestamp older than 60s (step)"); + MOD_REASON_BLOCK_EXPIRED); // Run node timers std::map<v3s16, NodeTimer> elapsed_timers = @@ -1153,7 +1170,7 @@ void ServerEnvironment::step(float dtime) } } } - + const float abm_interval = 1.0; if(m_active_block_modifier_interval.step(dtime, abm_interval)) do{ // breakable @@ -1164,7 +1181,7 @@ void ServerEnvironment::step(float dtime) } ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg /1s", SPT_AVG); TimeTaker timer("modify in active blocks"); - + // Initialize handling of ActiveBlockModifiers ABMHandler abmhandler(m_abms, abm_interval, this, true); @@ -1173,14 +1190,14 @@ void ServerEnvironment::step(float dtime) i != m_active_blocks.m_list.end(); ++i) { v3s16 p = *i; - + /*infostream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z <<") being handled"<<std::endl;*/ MapBlock *block = m_map->getBlockNoCreateNoEx(p); - if(block==NULL) + if(block == NULL) continue; - + // Set current time as timestamp block->setTimestampNoChangedFlag(m_game_time); @@ -1197,7 +1214,7 @@ void ServerEnvironment::step(float dtime) m_active_block_interval_overload_skip = (time_ms / max_time_ms) + 1; } }while(0); - + /* Step script environment (run global on_step()) */ @@ -1211,7 +1228,7 @@ void ServerEnvironment::step(float dtime) //TimeTaker timer("Step active objects"); g_profiler->avg("SEnv: num of objects", m_active_objects.size()); - + // This helps the objects to send data at the same time bool send_recommended = false; m_send_recommended_timer += dtime; @@ -1234,12 +1251,13 @@ void ServerEnvironment::step(float dtime) // Read messages from object while(!obj->m_messages_out.empty()) { - m_active_object_messages.push_back( - obj->m_messages_out.pop_front()); + m_active_object_messages.push( + obj->m_messages_out.front()); + obj->m_messages_out.pop(); } } } - + /* Manage active objects */ @@ -1282,7 +1300,7 @@ u16 getFreeServerActiveObjectId( last_used_id ++; if(isFreeServerActiveObjectId(last_used_id, objects)) return last_used_id; - + if(last_used_id == startid) return 0; } @@ -1290,57 +1308,12 @@ u16 getFreeServerActiveObjectId( u16 ServerEnvironment::addActiveObject(ServerActiveObject *object) { - assert(object); + assert(object); // Pre-condition m_added_objects++; u16 id = addActiveObjectRaw(object, true, 0); return id; } -#if 0 -bool ServerEnvironment::addActiveObjectAsStatic(ServerActiveObject *obj) -{ - assert(obj); - - v3f objectpos = obj->getBasePosition(); - - // The block in which the object resides in - v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS)); - - /* - Update the static data - */ - - // Create new static object - std::string staticdata = obj->getStaticData(); - StaticObject s_obj(obj->getType(), objectpos, staticdata); - // Add to the block where the object is located in - v3s16 blockpos = getNodeBlockPos(floatToInt(objectpos, BS)); - // Get or generate the block - MapBlock *block = m_map->emergeBlock(blockpos); - - bool succeeded = false; - - if(block) - { - block->m_static_objects.insert(0, s_obj); - block->raiseModified(MOD_STATE_WRITE_AT_UNLOAD, - "addActiveObjectAsStatic"); - succeeded = true; - } - else{ - infostream<<"ServerEnvironment::addActiveObjectAsStatic: " - <<"Could not find or generate " - <<"a block for storing static object"<<std::endl; - succeeded = false; - } - - if(obj->environmentDeletes()) - delete obj; - - return succeeded; -} -#endif - /* Finds out what new objects have been added to inside a radius around a position @@ -1438,7 +1411,7 @@ void ServerEnvironment::getRemovedActiveObjects(v3s16 pos, s16 radius, removed_objects.insert(id); continue; } - + f32 distance_f = object->getBasePosition().getDistanceFrom(pos_f); if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { if (distance_f <= player_radius_f || player_radius_f == 0) @@ -1451,13 +1424,40 @@ void ServerEnvironment::getRemovedActiveObjects(v3s16 pos, s16 radius, } } +void ServerEnvironment::setStaticForActiveObjectsInBlock( + v3s16 blockpos, bool static_exists, v3s16 static_block) +{ + MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos); + if (!block) + return; + + for (std::map<u16, StaticObject>::iterator + so_it = block->m_static_objects.m_active.begin(); + so_it != block->m_static_objects.m_active.end(); ++so_it) { + // Get the ServerActiveObject counterpart to this StaticObject + std::map<u16, ServerActiveObject *>::iterator ao_it; + ao_it = m_active_objects.find(so_it->first); + if (ao_it == m_active_objects.end()) { + // If this ever happens, there must be some kind of nasty bug. + errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): " + "Object from MapBlock::m_static_objects::m_active not found " + "in m_active_objects"; + continue; + } + + ServerActiveObject *sao = ao_it->second; + sao->m_static_exists = static_exists; + sao->m_static_block = static_block; + } +} + ActiveObjectMessage ServerEnvironment::getActiveObjectMessage() { if(m_active_object_messages.empty()) return ActiveObjectMessage(0); - + ActiveObjectMessage message = m_active_object_messages.front(); - m_active_object_messages.pop_front(); + m_active_object_messages.pop(); return message; } @@ -1468,7 +1468,7 @@ ActiveObjectMessage ServerEnvironment::getActiveObjectMessage() u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, bool set_changed, u32 dtime_s) { - assert(object); + assert(object); // Pre-condition if(object->getId() == 0){ u16 new_id = getFreeServerActiveObjectId(m_active_objects); if(new_id == 0) @@ -1495,19 +1495,19 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, } /*infostream<<"ServerEnvironment::addActiveObjectRaw(): " <<"added (id="<<object->getId()<<")"<<std::endl;*/ - + m_active_objects[object->getId()] = object; - + verbosestream<<"ServerEnvironment::addActiveObjectRaw(): " <<"Added id="<<object->getId()<<"; there are now " <<m_active_objects.size()<<" active objects." <<std::endl; - + // Register reference in scripting api (must be done before post-init) m_script->addObjectReference(object); // Post-initialize object object->addedToEnvironment(dtime_s); - + // Add static data to block if(object->isStaticAllowed()) { @@ -1525,7 +1525,7 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, if(set_changed) block->raiseModified(MOD_STATE_WRITE_NEEDED, - "addActiveObjectRaw"); + MOD_REASON_ADD_ACTIVE_OBJECT_RAW); } else { v3s16 p = floatToInt(objectpos, BS); errorstream<<"ServerEnvironment::addActiveObjectRaw(): " @@ -1533,7 +1533,7 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, <<" statically (pos="<<PP(p)<<")"<<std::endl; } } - + return object->getId(); } @@ -1542,11 +1542,10 @@ u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object, */ void ServerEnvironment::removeRemovedObjects() { - std::list<u16> objects_to_remove; + std::vector<u16> objects_to_remove; for(std::map<u16, ServerActiveObject*>::iterator i = m_active_objects.begin(); - i != m_active_objects.end(); ++i) - { + i != m_active_objects.end(); ++i) { u16 id = i->first; ServerActiveObject* obj = i->second; // This shouldn't happen but check it @@ -1575,7 +1574,7 @@ void ServerEnvironment::removeRemovedObjects() if (block) { block->m_static_objects.remove(id); block->raiseModified(MOD_STATE_WRITE_NEEDED, - "removeRemovedObjects/remove"); + MOD_REASON_REMOVE_OBJECTS_REMOVE); obj->m_static_exists = false; } else { infostream<<"Failed to emerge block from which an object to " @@ -1600,7 +1599,7 @@ void ServerEnvironment::removeRemovedObjects() block->m_static_objects.m_stored.push_back(i->second); block->m_static_objects.m_active.erase(id); block->raiseModified(MOD_STATE_WRITE_NEEDED, - "removeRemovedObjects/deactivate"); + MOD_REASON_REMOVE_OBJECTS_DEACTIVATE); } } else { infostream<<"Failed to emerge block from which an object to " @@ -1616,13 +1615,13 @@ void ServerEnvironment::removeRemovedObjects() // Delete if(obj->environmentDeletes()) delete obj; + // Id to be removed from m_active_objects objects_to_remove.push_back(id); } // Remove references from m_active_objects - for(std::list<u16>::iterator i = objects_to_remove.begin(); - i != objects_to_remove.end(); ++i) - { + for(std::vector<u16>::iterator i = objects_to_remove.begin(); + i != objects_to_remove.end(); ++i) { m_active_objects.erase(*i); } } @@ -1666,17 +1665,19 @@ static void print_hexdump(std::ostream &o, const std::string &data) */ void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s) { - if(block==NULL) + if(block == NULL) return; + // Ignore if no stored objects (to not set changed flag) if(block->m_static_objects.m_stored.empty()) return; + verbosestream<<"ServerEnvironment::activateObjects(): " <<"activating objects of block "<<PP(block->getPos()) <<" ("<<block->m_static_objects.m_stored.size() <<" objects)"<<std::endl; bool large_amount = (block->m_static_objects.m_stored.size() > g_settings->getU16("max_objects_per_block")); - if(large_amount){ + if (large_amount) { errorstream<<"suspiciously large amount of objects detected: " <<block->m_static_objects.m_stored.size()<<" in " <<PP(block->getPos()) @@ -1684,32 +1685,28 @@ void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s) // Clear stored list block->m_static_objects.m_stored.clear(); block->raiseModified(MOD_STATE_WRITE_NEEDED, - "stored list cleared in activateObjects due to " - "large amount of objects"); + MOD_REASON_TOO_MANY_OBJECTS); return; } // Activate stored objects - std::list<StaticObject> new_stored; - for(std::list<StaticObject>::iterator + std::vector<StaticObject> new_stored; + for (std::vector<StaticObject>::iterator i = block->m_static_objects.m_stored.begin(); - i != block->m_static_objects.m_stored.end(); ++i) - { - /*infostream<<"Server: Creating an active object from " - <<"static data"<<std::endl;*/ + i != block->m_static_objects.m_stored.end(); ++i) { StaticObject &s_obj = *i; + // Create an active object from the data ServerActiveObject *obj = ServerActiveObject::create - (s_obj.type, this, 0, s_obj.pos, s_obj.data); + ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data); // If couldn't create object, store static data back. - if(obj==NULL) - { + if(obj == NULL) { errorstream<<"ServerEnvironment::activateObjects(): " <<"failed to create active object from static object " <<"in block "<<PP(s_obj.pos/BS) <<" type="<<(int)s_obj.type<<" data:"<<std::endl; print_hexdump(verbosestream, s_obj.data); - + new_stored.push_back(s_obj); continue; } @@ -1722,10 +1719,9 @@ void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s) // Clear stored list block->m_static_objects.m_stored.clear(); // Add leftover failed stuff to stored list - for(std::list<StaticObject>::iterator + for(std::vector<StaticObject>::iterator i = new_stored.begin(); - i != new_stored.end(); ++i) - { + i != new_stored.end(); ++i) { StaticObject &s_obj = *i; block->m_static_objects.m_stored.push_back(s_obj); } @@ -1765,14 +1761,13 @@ void ServerEnvironment::activateObjects(MapBlock *block, u32 dtime_s) */ void ServerEnvironment::deactivateFarObjects(bool force_delete) { - std::list<u16> objects_to_remove; + std::vector<u16> objects_to_remove; for(std::map<u16, ServerActiveObject*>::iterator i = m_active_objects.begin(); - i != m_active_objects.end(); ++i) - { + i != m_active_objects.end(); ++i) { ServerActiveObject* obj = i->second; assert(obj); - + // Do not deactivate if static data creation not allowed if(!force_delete && !obj->isStaticAllowed()) continue; @@ -1811,7 +1806,7 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete) block->m_static_objects.insert(id, s_obj); obj->m_static_block = blockpos_o; block->raiseModified(MOD_STATE_WRITE_NEEDED, - "deactivateFarObjects: Static data moved in"); + MOD_REASON_STATIC_DATA_ADDED); // Delete from block where object was located block = m_map->emergeBlock(old_static_block, false); @@ -1824,7 +1819,7 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete) } block->m_static_objects.remove(id); block->raiseModified(MOD_STATE_WRITE_NEEDED, - "deactivateFarObjects: Static data moved out"); + MOD_REASON_STATIC_DATA_REMOVED); continue; } @@ -1848,36 +1843,38 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete) // Create new static object std::string staticdata_new = obj->getStaticData(); StaticObject s_obj(obj->getType(), objectpos, staticdata_new); - + bool stays_in_same_block = false; bool data_changed = true; - if(obj->m_static_exists){ - if(obj->m_static_block == blockpos_o) + if (obj->m_static_exists) { + if (obj->m_static_block == blockpos_o) stays_in_same_block = true; MapBlock *block = m_map->emergeBlock(obj->m_static_block, false); - - std::map<u16, StaticObject>::iterator n = + + if (block) { + std::map<u16, StaticObject>::iterator n = block->m_static_objects.m_active.find(id); - if(n != block->m_static_objects.m_active.end()){ - StaticObject static_old = n->second; + if (n != block->m_static_objects.m_active.end()) { + StaticObject static_old = n->second; - float save_movem = obj->getMinimumSavedMovement(); + float save_movem = obj->getMinimumSavedMovement(); - if(static_old.data == staticdata_new && - (static_old.pos - objectpos).getLength() < save_movem) - data_changed = false; - } else { - errorstream<<"ServerEnvironment::deactivateFarObjects(): " + if (static_old.data == staticdata_new && + (static_old.pos - objectpos).getLength() < save_movem) + data_changed = false; + } else { + errorstream<<"ServerEnvironment::deactivateFarObjects(): " <<"id="<<id<<" m_static_exists=true but " <<"static data doesn't actually exist in " <<PP(obj->m_static_block)<<std::endl; + } } } bool shall_be_written = (!stays_in_same_block || data_changed); - + // Delete old static object if(obj->m_static_exists) { @@ -1889,8 +1886,7 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete) // Only mark block as modified if data changed considerably if(shall_be_written) block->raiseModified(MOD_STATE_WRITE_NEEDED, - "deactivateFarObjects: Static data " - "changed considerably"); + MOD_REASON_STATIC_DATA_CHANGED); } } @@ -1932,13 +1928,12 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete) // Store static data u16 store_id = pending_delete ? id : 0; block->m_static_objects.insert(store_id, s_obj); - + // Only mark block as modified if data changed considerably if(shall_be_written) block->raiseModified(MOD_STATE_WRITE_NEEDED, - "deactivateFarObjects: Static data " - "changed considerably"); - + MOD_REASON_STATIC_DATA_CHANGED); + obj->m_static_exists = true; obj->m_static_block = block->getPos(); } @@ -1968,7 +1963,7 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete) obj->m_pending_deactivation = true; continue; } - + verbosestream<<"ServerEnvironment::deactivateFarObjects(): " <<"object id="<<id<<" is not known by clients" <<"; deleting"<<std::endl; @@ -1986,14 +1981,12 @@ void ServerEnvironment::deactivateFarObjects(bool force_delete) } // Remove references from m_active_objects - for(std::list<u16>::iterator i = objects_to_remove.begin(); - i != objects_to_remove.end(); ++i) - { + for(std::vector<u16>::iterator i = objects_to_remove.begin(); + i != objects_to_remove.end(); ++i) { m_active_objects.erase(*i); } } - #ifndef SERVER #include "clientsimpleobject.h" @@ -2012,7 +2005,7 @@ ClientEnvironment::ClientEnvironment(ClientMap *map, scene::ISceneManager *smgr, m_irr(irr) { char zero = 0; - memset(m_attachements, zero, sizeof(m_attachements)); + memset(attachement_parent_ids, zero, sizeof(attachement_parent_ids)); } ClientEnvironment::~ClientEnvironment() @@ -2025,9 +2018,8 @@ ClientEnvironment::~ClientEnvironment() delete i->second; } - for(std::list<ClientSimpleObject*>::iterator - i = m_simple_objects.begin(); i != m_simple_objects.end(); ++i) - { + for(std::vector<ClientSimpleObject*>::iterator + i = m_simple_objects.begin(); i != m_simple_objects.end(); ++i) { delete *i; } @@ -2052,16 +2044,16 @@ void ClientEnvironment::addPlayer(Player *player) It is a failure if player is local and there already is a local player */ - assert(!(player->isLocal() == true && getLocalPlayer() != NULL)); + FATAL_ERROR_IF(player->isLocal() == true && getLocalPlayer() != NULL, + "Player is local but there is already a local player"); Environment::addPlayer(player); } LocalPlayer * ClientEnvironment::getLocalPlayer() { - for(std::list<Player*>::iterator i = m_players.begin(); - i != m_players.end(); ++i) - { + for(std::vector<Player*>::iterator i = m_players.begin(); + i != m_players.end(); ++i) { Player *player = *i; if(player->isLocal()) return (LocalPlayer*)player; @@ -2084,15 +2076,15 @@ void ClientEnvironment::step(float dtime) LocalPlayer *lplayer = getLocalPlayer(); assert(lplayer); // collision info queue - std::list<CollisionInfo> player_collisions; - + std::vector<CollisionInfo> player_collisions; + /* Get the speed the player is going */ bool is_climbing = lplayer->is_climbing; - + f32 player_speed = lplayer->getSpeed().getLength(); - + /* Maximum position increment */ @@ -2104,15 +2096,15 @@ void ClientEnvironment::step(float dtime) f32 dtime_max_increment = 1; if(player_speed > 0.001) dtime_max_increment = position_max_increment / player_speed; - + // Maximum time increment is 10ms or lower if(dtime_max_increment > 0.01) dtime_max_increment = 0.01; - + // Don't allow overly huge dtime if(dtime > 0.5) dtime = 0.5; - + f32 dtime_downcount = dtime; /* @@ -2140,11 +2132,11 @@ void ClientEnvironment::step(float dtime) */ dtime_downcount = 0; } - + /* Handle local player */ - + { // Apply physics if(free_move == false && is_climbing == false) @@ -2170,18 +2162,9 @@ void ClientEnvironment::step(float dtime) if(dl > lplayer->movement_liquid_fluidity_smooth) dl = lplayer->movement_liquid_fluidity_smooth; dl *= (lplayer->liquid_viscosity * viscosity_factor) + (1 - viscosity_factor); - + v3f d = d_wanted.normalize() * dl; speed += d; - -#if 0 // old code - if(speed.X > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.X -= lplayer->movement_liquid_fluidity_smooth; - if(speed.X < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.X += lplayer->movement_liquid_fluidity_smooth; - if(speed.Y > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.Y -= lplayer->movement_liquid_fluidity_smooth; - if(speed.Y < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.Y += lplayer->movement_liquid_fluidity_smooth; - if(speed.Z > lplayer->movement_liquid_fluidity + lplayer->movement_liquid_fluidity_smooth) speed.Z -= lplayer->movement_liquid_fluidity_smooth; - if(speed.Z < -lplayer->movement_liquid_fluidity - lplayer->movement_liquid_fluidity_smooth) speed.Z += lplayer->movement_liquid_fluidity_smooth; -#endif } lplayer->setSpeed(speed); @@ -2196,13 +2179,11 @@ void ClientEnvironment::step(float dtime) } } while(dtime_downcount > 0.001); - + //std::cout<<"Looped "<<loopcount<<" times."<<std::endl; - - for(std::list<CollisionInfo>::iterator - i = player_collisions.begin(); - i != player_collisions.end(); ++i) - { + + for(std::vector<CollisionInfo>::iterator i = player_collisions.begin(); + i != player_collisions.end(); ++i) { CollisionInfo &info = *i; v3f speed_diff = info.new_speed - info.old_speed;; // Handle only fall damage @@ -2235,14 +2216,14 @@ void ClientEnvironment::step(float dtime) } } } - + /* A quick draft of lava damage */ if(m_lava_hurt_interval.step(dtime, 1.0)) { v3f pf = lplayer->getPosition(); - + // Feet, middle and head v3s16 p1 = floatToInt(pf + v3f(0, BS*0.1, 0), BS); MapNode n1 = m_map->getNodeNoEx(p1); @@ -2258,7 +2239,7 @@ void ClientEnvironment::step(float dtime) m_gamedef->ndef()->get(n2).damage_per_second); damage_per_second = MYMAX(damage_per_second, m_gamedef->ndef()->get(n3).damage_per_second); - + if(damage_per_second != 0) { damageLocalPlayer(damage_per_second, true); @@ -2317,16 +2298,14 @@ void ClientEnvironment::step(float dtime) /* Stuff that can be done in an arbitarily large dtime */ - for(std::list<Player*>::iterator i = m_players.begin(); - i != m_players.end(); ++i) - { + for(std::vector<Player*>::iterator i = m_players.begin(); + i != m_players.end(); ++i) { Player *player = *i; - + /* Handle non-local players */ - if(player->isLocal() == false) - { + if(player->isLocal() == false) { // Move player->move(dtime, this, 100*BS); @@ -2354,7 +2333,7 @@ void ClientEnvironment::step(float dtime) /* Step active objects and update lighting of them */ - + g_profiler->avg("CEnv: num of objects", m_active_objects.size()); bool update_lighting = m_active_object_light_update_interval.step(dtime, 0.21); for(std::map<u16, ClientActiveObject*>::iterator @@ -2387,25 +2366,36 @@ void ClientEnvironment::step(float dtime) Step and handle simple objects */ g_profiler->avg("CEnv: num of simple objects", m_simple_objects.size()); - for(std::list<ClientSimpleObject*>::iterator - i = m_simple_objects.begin(); i != m_simple_objects.end();) - { - ClientSimpleObject *simple = *i; - std::list<ClientSimpleObject*>::iterator cur = i; - ++i; + for(std::vector<ClientSimpleObject*>::iterator + i = m_simple_objects.begin(); i != m_simple_objects.end();) { + std::vector<ClientSimpleObject*>::iterator cur = i; + ClientSimpleObject *simple = *cur; + simple->step(dtime); - if(simple->m_to_be_removed){ + if(simple->m_to_be_removed) { delete simple; - m_simple_objects.erase(cur); + i = m_simple_objects.erase(cur); + } + else { + ++i; } } } - + void ClientEnvironment::addSimpleObject(ClientSimpleObject *simple) { m_simple_objects.push_back(simple); } +GenericCAO* ClientEnvironment::getGenericCAO(u16 id) +{ + ClientActiveObject *obj = getActiveObject(id); + if (obj && obj->getType() == ACTIVEOBJECT_TYPE_GENERIC) + return (GenericCAO*) obj; + else + return NULL; +} + ClientActiveObject* ClientEnvironment::getActiveObject(u16 id) { std::map<u16, ClientActiveObject*>::iterator n; @@ -2435,7 +2425,7 @@ u16 getFreeClientActiveObjectId( last_used_id ++; if(isFreeClientActiveObjectId(last_used_id, objects)) return last_used_id; - + if(last_used_id == startid) return 0; } @@ -2443,7 +2433,7 @@ u16 getFreeClientActiveObjectId( u16 ClientEnvironment::addActiveObject(ClientActiveObject *object) { - assert(object); + assert(object); // Pre-condition if(object->getId() == 0) { u16 new_id = getFreeClientActiveObjectId(m_active_objects); @@ -2488,7 +2478,7 @@ void ClientEnvironment::addActiveObject(u16 id, u8 type, const std::string &init_data) { ClientActiveObject* obj = - ClientActiveObject::create(type, m_gamedef, this); + ClientActiveObject::create((ActiveObjectType) type, m_gamedef, this); if(obj == NULL) { infostream<<"ClientEnvironment::addActiveObject(): " @@ -2496,7 +2486,7 @@ void ClientEnvironment::addActiveObject(u16 id, u8 type, <<std::endl; return; } - + obj->setId(id); try @@ -2532,28 +2522,23 @@ void ClientEnvironment::removeActiveObject(u16 id) m_active_objects.erase(id); } -void ClientEnvironment::processActiveObjectMessage(u16 id, - const std::string &data) +void ClientEnvironment::processActiveObjectMessage(u16 id, const std::string &data) { - ClientActiveObject* obj = getActiveObject(id); - if(obj == NULL) - { - infostream<<"ClientEnvironment::processActiveObjectMessage():" - <<" got message for id="<<id<<", which doesn't exist." - <<std::endl; + ClientActiveObject *obj = getActiveObject(id); + if (obj == NULL) { + infostream << "ClientEnvironment::processActiveObjectMessage():" + << " got message for id=" << id << ", which doesn't exist." + << std::endl; return; } - try - { + + try { obj->processMessage(data); - } - catch(SerializationError &e) - { + } catch (SerializationError &e) { errorstream<<"ClientEnvironment::processActiveObjectMessage():" - <<" id="<<id<<" type="<<obj->getType() - <<" SerializationError in processMessage()," - <<" message="<<serializeJsonString(data) - <<std::endl; + << " id=" << id << " type=" << obj->getType() + << " SerializationError in processMessage(): " << e.what() + << std::endl; } } @@ -2565,11 +2550,9 @@ void ClientEnvironment::damageLocalPlayer(u8 damage, bool handle_hp) { LocalPlayer *lplayer = getLocalPlayer(); assert(lplayer); - - if(handle_hp){ - if (lplayer->hp == 0) // Don't damage a dead player - return; - if(lplayer->hp > damage) + + if (handle_hp) { + if (lplayer->hp > damage) lplayer->hp -= damage; else lplayer->hp = 0; @@ -2593,7 +2576,7 @@ void ClientEnvironment::updateLocalPlayerBreath(u16 breath) /* Client likes to call these */ - + void ClientEnvironment::getActiveObjects(v3f origin, f32 max_d, std::vector<DistanceSortedActiveObject> &dest) { diff --git a/src/environment.h b/src/environment.h index 13c0c0efe..c70694316 100644 --- a/src/environment.h +++ b/src/environment.h @@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <set> #include <list> +#include <queue> #include <map> #include "irr_v3d.h" #include "activeobject.h" @@ -39,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapnode.h" #include "mapblock.h" #include "jthread/jmutex.h" +#include "network/networkprotocol.h" // for AccessDeniedCode class ServerEnvironment; class ActiveBlockModifier; @@ -75,28 +77,19 @@ public: Player * getPlayer(const char *name); Player * getRandomConnectedPlayer(); Player * getNearestConnectedPlayer(v3f pos); - std::list<Player*> getPlayers(); - std::list<Player*> getPlayers(bool ignore_disconnected); + std::vector<Player*> getPlayers(); + std::vector<Player*> getPlayers(bool ignore_disconnected); u32 getDayNightRatio(); // 0-23999 - virtual void setTimeOfDay(u32 time) - { - m_time_of_day = time; - m_time_of_day_f = (float)time / 24000.0; - } - - u32 getTimeOfDay() - { return m_time_of_day; } - - float getTimeOfDayF() - { return m_time_of_day_f; } + virtual void setTimeOfDay(u32 time); + u32 getTimeOfDay(); + float getTimeOfDayF(); void stepTimeOfDay(float dtime); void setTimeOfDaySpeed(float speed); - float getTimeOfDaySpeed(); void setDayNightRatioOverride(bool enable, u32 value) @@ -110,7 +103,7 @@ public: protected: // peer_ids in here should be unique, except that there may be many 0s - std::list<Player*> m_players; + std::vector<Player*> m_players; // Time of day in milli-hours (0-23999); determines day and night u32 m_time_of_day; // Time of day in 0...1 @@ -134,7 +127,8 @@ protected: bool m_cache_enable_shaders; private: - JMutex m_lock; + JMutex m_timeofday_lock; + JMutex m_time_lock; }; @@ -182,7 +176,7 @@ struct ABMWithState class ActiveBlockList { public: - void update(std::list<v3s16> &active_positions, + void update(std::vector<v3s16> &active_positions, s16 radius, std::set<v3s16> &blocks_removed, std::set<v3s16> &blocks_added); @@ -228,6 +222,8 @@ public: float getSendRecommendedInterval() { return m_recommended_send_interval; } + void kickAllPlayers(AccessDeniedCode reason, + const std::string &str_reason, bool reconnect); // Save players void saveLoadedPlayers(); void savePlayer(const std::string &playername); @@ -313,7 +309,7 @@ public: bool swapNode(v3s16 p, const MapNode &n); // Find all active objects inside a radius around a point - std::set<u16> getObjectsInsideRadius(v3f pos, float radius); + void getObjectsInsideRadius(std::vector<u16> &objects, v3f pos, float radius); // Clear all objects, loading and going through every MapBlock void clearAllObjects(); @@ -331,6 +327,11 @@ public: std::set<v3s16>* getForceloadedBlocks() { return &m_active_blocks.m_forceloaded_list; }; + // Sets the static object status all the active objects in the specified block + // This is only really needed for deleting blocks from the map + void setStaticForActiveObjectsInBlock(v3s16 blockpos, + bool static_exists, v3s16 static_block=v3s16(0,0,0)); + private: /* @@ -386,7 +387,7 @@ private: // Active object list std::map<u16, ServerActiveObject*> m_active_objects; // Outgoing network message buffer for active objects - std::list<ActiveObjectMessage> m_active_object_messages; + std::queue<ActiveObjectMessage> m_active_object_messages; // Some timers float m_send_recommended_timer; IntervalLimiter m_object_management_interval; @@ -401,7 +402,7 @@ private: u32 m_game_time; // A helper variable for incrementing the latter float m_game_time_fraction_counter; - std::list<ABMWithState> m_abms; + std::vector<ABMWithState> m_abms; // An interval for generally sending object positions and stuff float m_recommended_send_interval; // Estimate for general maximum lag as determined by server. @@ -412,6 +413,8 @@ private: #ifndef SERVER #include "clientobject.h" +#include "content_cao.h" + class ClientSimpleObject; /* @@ -474,6 +477,7 @@ public: ActiveObjects */ + GenericCAO* getGenericCAO(u16 id); ClientActiveObject* getActiveObject(u16 id); /* @@ -509,7 +513,7 @@ public: // Get event from queue. CEE_NONE is returned if queue is empty. ClientEnvEvent getClientEvent(); - u16 m_attachements[USHRT_MAX]; + u16 attachement_parent_ids[USHRT_MAX + 1]; std::list<std::string> getPlayerNames() { return m_player_names; } @@ -529,7 +533,7 @@ private: IGameDef *m_gamedef; IrrlichtDevice *m_irr; std::map<u16, ClientActiveObject*> m_active_objects; - std::list<ClientSimpleObject*> m_simple_objects; + std::vector<ClientSimpleObject*> m_simple_objects; std::list<ClientEnvEvent> m_client_event_queue; IntervalLimiter m_active_object_light_update_interval; IntervalLimiter m_lava_hurt_interval; diff --git a/src/exceptions.h b/src/exceptions.h index 5b716c2ca..6bf832828 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -70,6 +70,11 @@ public: SerializationError(const std::string &s): BaseException(s) {} }; +class PacketError : public BaseException { +public: + PacketError(const std::string &s): BaseException(s) {} +}; + class LoadError : public BaseException { public: LoadError(const std::string &s): BaseException(s) {} @@ -115,6 +120,11 @@ public: ClientStateError(std::string s): BaseException(s) {} }; +class PrngException : public BaseException { +public: + PrngException(std::string s): BaseException(s) {} +}; + /* Some "old-style" interrupts: */ diff --git a/src/filecache.cpp b/src/filecache.cpp index 33677cc83..f1694d8d5 100644 --- a/src/filecache.cpp +++ b/src/filecache.cpp @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filecache.h" -#include "clientserver.h" +#include "network/networkprotocol.h" #include "log.h" #include "filesys.h" #include <string> diff --git a/src/filesys.cpp b/src/filesys.cpp index 784715617..4cefdb807 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -34,8 +34,9 @@ namespace fs #define _WIN32_WINNT 0x0501 #include <windows.h> +#include <shlwapi.h> -std::vector<DirListNode> GetDirListing(std::string pathstring) +std::vector<DirListNode> GetDirListing(const std::string &pathstring) { std::vector<DirListNode> listing; @@ -44,7 +45,7 @@ std::vector<DirListNode> GetDirListing(std::string pathstring) DWORD dwError; std::string dirSpec = pathstring + "\\*"; - + // Find the first file in the directory. hFind = FindFirstFile(dirSpec.c_str(), &FindFileData); @@ -86,7 +87,7 @@ std::vector<DirListNode> GetDirListing(std::string pathstring) return listing; } -bool CreateDir(std::string path) +bool CreateDir(const std::string &path) { bool r = CreateDirectory(path.c_str(), NULL); if(r == true) @@ -96,12 +97,17 @@ bool CreateDir(std::string path) return false; } -bool PathExists(std::string path) +bool PathExists(const std::string &path) { return (GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES); } -bool IsDir(std::string path) +bool IsPathAbsolute(const std::string &path) +{ + return !PathIsRelative(path.c_str()); +} + +bool IsDir(const std::string &path) { DWORD attr = GetFileAttributes(path.c_str()); return (attr != INVALID_FILE_ATTRIBUTES && @@ -113,7 +119,7 @@ bool IsDirDelimiter(char c) return c == '/' || c == '\\'; } -bool RecursiveDelete(std::string path) +bool RecursiveDelete(const std::string &path) { infostream<<"Recursively deleting \""<<path<<"\""<<std::endl; @@ -136,7 +142,7 @@ bool RecursiveDelete(std::string path) infostream<<"RecursiveDelete: Deleting content of directory " <<path<<std::endl; std::vector<DirListNode> content = GetDirListing(path); - for(int i=0; i<content.size(); i++){ + for(size_t i=0; i<content.size(); i++){ const DirListNode &n = content[i]; std::string fullpath = path + DIR_DELIM + n.name; bool did = RecursiveDelete(fullpath); @@ -158,7 +164,7 @@ bool RecursiveDelete(std::string path) return true; } -bool DeleteSingleFileOrEmptyDirectory(std::string path) +bool DeleteSingleFileOrEmptyDirectory(const std::string &path) { DWORD attr = GetFileAttributes(path.c_str()); bool is_directory = (attr != INVALID_FILE_ATTRIBUTES && @@ -177,7 +183,7 @@ bool DeleteSingleFileOrEmptyDirectory(std::string path) std::string TempPath() { - DWORD bufsize = GetTempPath(0, ""); + DWORD bufsize = GetTempPath(0, NULL); if(bufsize == 0){ errorstream<<"GetTempPath failed, error = "<<GetLastError()<<std::endl; return ""; @@ -199,7 +205,7 @@ std::string TempPath() #include <sys/wait.h> #include <unistd.h> -std::vector<DirListNode> GetDirListing(std::string pathstring) +std::vector<DirListNode> GetDirListing(const std::string &pathstring) { std::vector<DirListNode> listing; @@ -252,7 +258,7 @@ std::vector<DirListNode> GetDirListing(std::string pathstring) return listing; } -bool CreateDir(std::string path) +bool CreateDir(const std::string &path) { int r = mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); if(r == 0) @@ -268,13 +274,18 @@ bool CreateDir(std::string path) } } -bool PathExists(std::string path) +bool PathExists(const std::string &path) { struct stat st; return (stat(path.c_str(),&st) == 0); } -bool IsDir(std::string path) +bool IsPathAbsolute(const std::string &path) +{ + return path[0] == '/'; +} + +bool IsDir(const std::string &path) { struct stat statbuf; if(stat(path.c_str(), &statbuf)) @@ -287,16 +298,16 @@ bool IsDirDelimiter(char c) return c == '/'; } -bool RecursiveDelete(std::string path) +bool RecursiveDelete(const std::string &path) { /* Execute the 'rm' command directly, by fork() and execve() */ - + infostream<<"Removing \""<<path<<"\""<<std::endl; //return false; - + pid_t child_pid = fork(); if(child_pid == 0) @@ -314,9 +325,9 @@ bool RecursiveDelete(std::string path) verbosestream<<"Executing '"<<argv[0]<<"' '"<<argv[1]<<"' '" <<argv[2]<<"'"<<std::endl; - + execv(argv[0], argv); - + // Execv shouldn't return. Failed. _exit(1); } @@ -333,7 +344,7 @@ bool RecursiveDelete(std::string path) } } -bool DeleteSingleFileOrEmptyDirectory(std::string path) +bool DeleteSingleFileOrEmptyDirectory(const std::string &path) { if(IsDir(path)){ bool did = (rmdir(path.c_str()) == 0); @@ -370,7 +381,7 @@ std::string TempPath() #endif -void GetRecursiveSubPaths(std::string path, std::vector<std::string> &dst) +void GetRecursiveSubPaths(const std::string &path, std::vector<std::string> &dst) { std::vector<DirListNode> content = GetDirListing(path); for(unsigned int i=0; i<content.size(); i++){ @@ -398,7 +409,7 @@ bool DeletePaths(const std::vector<std::string> &paths) return success; } -bool RecursiveDeleteContent(std::string path) +bool RecursiveDeleteContent(const std::string &path) { infostream<<"Removing content of \""<<path<<"\""<<std::endl; std::vector<DirListNode> list = GetDirListing(path); @@ -417,7 +428,7 @@ bool RecursiveDeleteContent(std::string path) return true; } -bool CreateAllDirs(std::string path) +bool CreateAllDirs(const std::string &path) { std::vector<std::string> tocreate; @@ -435,7 +446,7 @@ bool CreateAllDirs(std::string path) return true; } -bool CopyFileContents(std::string source, std::string target) +bool CopyFileContents(const std::string &source, const std::string &target) { FILE *sourcefile = fopen(source.c_str(), "rb"); if(sourcefile == NULL){ @@ -489,7 +500,7 @@ bool CopyFileContents(std::string source, std::string target) return retval; } -bool CopyDir(std::string source, std::string target) +bool CopyDir(const std::string &source, const std::string &target) { if(PathExists(source)){ if(!PathExists(target)){ @@ -519,7 +530,7 @@ bool CopyDir(std::string source, std::string target) } } -bool PathStartsWith(std::string path, std::string prefix) +bool PathStartsWith(const std::string &path, const std::string &prefix) { size_t pathsize = path.size(); size_t pathpos = 0; @@ -569,7 +580,7 @@ bool PathStartsWith(std::string path, std::string prefix) } } -std::string RemoveLastPathComponent(std::string path, +std::string RemoveLastPathComponent(const std::string &path, std::string *removed, int count) { if(removed) @@ -651,6 +662,25 @@ std::string RemoveRelativePathComponents(std::string path) return path.substr(0, pos); } +std::string AbsolutePath(const std::string &path) +{ +#ifdef _WIN32 + char *abs_path = _fullpath(NULL, path.c_str(), MAX_PATH); +#else + char *abs_path = realpath(path.c_str(), NULL); +#endif + if (!abs_path) return ""; + std::string abs_path_str(abs_path); + free(abs_path); + return abs_path_str; +} + +const char *GetFilenameFromPath(const char *path) +{ + const char *filename = strrchr(path, DIR_DELIM_CHAR); + return filename ? filename + 1 : path; +} + bool safeWriteToFile(const std::string &path, const std::string &content) { std::string tmp_file = path + ".~mt"; diff --git a/src/filesys.h b/src/filesys.h index 1b3659afe..19fcbb673 100644 --- a/src/filesys.h +++ b/src/filesys.h @@ -26,9 +26,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifdef _WIN32 // WINDOWS #define DIR_DELIM "\\" +#define DIR_DELIM_CHAR '\\' #define FILESYS_CASE_INSENSITIVE 1 #else // POSIX #define DIR_DELIM "/" +#define DIR_DELIM_CHAR '/' #define FILESYS_CASE_INSENSITIVE 0 #endif @@ -40,22 +42,25 @@ struct DirListNode std::string name; bool dir; }; -std::vector<DirListNode> GetDirListing(std::string path); + +std::vector<DirListNode> GetDirListing(const std::string &path); // Returns true if already exists -bool CreateDir(std::string path); +bool CreateDir(const std::string &path); + +bool PathExists(const std::string &path); -bool PathExists(std::string path); +bool IsPathAbsolute(const std::string &path); -bool IsDir(std::string path); +bool IsDir(const std::string &path); bool IsDirDelimiter(char c); // Only pass full paths to this one. True on success. // NOTE: The WIN32 version returns always true. -bool RecursiveDelete(std::string path); +bool RecursiveDelete(const std::string &path); -bool DeleteSingleFileOrEmptyDirectory(std::string path); +bool DeleteSingleFileOrEmptyDirectory(const std::string &path); // Returns path to temp directory, can return "" on error std::string TempPath(); @@ -63,34 +68,34 @@ std::string TempPath(); /* Multiplatform */ // The path itself not included -void GetRecursiveSubPaths(std::string path, std::vector<std::string> &dst); +void GetRecursiveSubPaths(const std::string &path, std::vector<std::string> &dst); // Tries to delete all, returns false if any failed bool DeletePaths(const std::vector<std::string> &paths); // Only pass full paths to this one. True on success. -bool RecursiveDeleteContent(std::string path); +bool RecursiveDeleteContent(const std::string &path); // Create all directories on the given path that don't already exist. -bool CreateAllDirs(std::string path); +bool CreateAllDirs(const std::string &path); // Copy a regular file -bool CopyFileContents(std::string source, std::string target); +bool CopyFileContents(const std::string &source, const std::string &target); // Copy directory and all subdirectories // Omits files and subdirectories that start with a period -bool CopyDir(std::string source, std::string target); +bool CopyDir(const std::string &source, const std::string &target); // Check if one path is prefix of another // For example, "/tmp" is a prefix of "/tmp" and "/tmp/file" but not "/tmp2" // Ignores case differences and '/' vs. '\\' on Windows -bool PathStartsWith(std::string path, std::string prefix); +bool PathStartsWith(const std::string &path, const std::string &prefix); // Remove last path component and the dir delimiter before and/or after it, // returns "" if there is only one path component. // removed: If non-NULL, receives the removed component(s). // count: Number of components to remove -std::string RemoveLastPathComponent(std::string path, +std::string RemoveLastPathComponent(const std::string &path, std::string *removed = NULL, int count = 1); // Remove "." and ".." path components and for every ".." removed, remove @@ -98,9 +103,17 @@ std::string RemoveLastPathComponent(std::string path, // this does not resolve symlinks and check for existence of directories. std::string RemoveRelativePathComponents(std::string path); +// Returns the absolute path for the passed path, with "." and ".." path +// components and symlinks removed. Returns "" on error. +std::string AbsolutePath(const std::string &path); + +// Returns the filename from a path or the entire path if no directory +// delimiter is found. +const char *GetFilenameFromPath(const char *path); + bool safeWriteToFile(const std::string &path, const std::string &content); -}//fs +} // namespace fs #endif diff --git a/src/fontengine.cpp b/src/fontengine.cpp index 3b82a3c47..f881701e0 100644 --- a/src/fontengine.cpp +++ b/src/fontengine.cpp @@ -18,7 +18,6 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "fontengine.h" #include "log.h" -#include "main.h" #include "config.h" #include "porting.h" #include "constants.h" @@ -36,7 +35,8 @@ with this program; if not, write to the Free Software Foundation, Inc., FontEngine* g_fontengine = NULL; /** callback to be used on change of font size setting */ -static void font_setting_changed(const std::string, void *userdata) { +static void font_setting_changed(const std::string &name, void *userdata) +{ g_fontengine->readSettings(); } @@ -55,9 +55,9 @@ FontEngine::FontEngine(Settings* main_settings, gui::IGUIEnvironment* env) : m_default_size[i] = (FontMode) FONT_SIZE_UNSPECIFIED; } - assert(m_settings != NULL); - assert(m_env != NULL); - assert(m_env->getSkin() != NULL); + assert(m_settings != NULL); // pre-condition + assert(m_env != NULL); // pre-condition + assert(m_env->getSkin() != NULL); // pre-condition m_currentMode = FM_Simple; @@ -172,7 +172,7 @@ unsigned int FontEngine::getTextHeight(unsigned int font_size, FontMode mode) if (font == NULL) { font = m_env->getSkin()->getFont(); } - assert(font != NULL); + FATAL_ERROR_IF(font == NULL, "Could not get skin font"); return font->getDimension(L"Some unimportant example String").Height; } @@ -187,7 +187,7 @@ unsigned int FontEngine::getTextWidth(const std::wstring& text, if (font == NULL) { font = m_env->getSkin()->getFont(); } - assert(font != NULL); + FATAL_ERROR_IF(font == NULL, "Could not get font"); return font->getDimension(text.c_str()).Width; } @@ -202,7 +202,7 @@ unsigned int FontEngine::getLineHeight(unsigned int font_size, FontMode mode) if (font == NULL) { font = m_env->getSkin()->getFont(); } - assert(font != NULL); + FATAL_ERROR_IF(font == NULL, "Could not get font"); return font->getDimension(L"Some unimportant example String").Height + font->getKerningHeight(); @@ -255,7 +255,7 @@ void FontEngine::updateSkin() // If we did fail to create a font our own make irrlicht find a default one font = m_env->getSkin()->getFont(); - assert(font); + FATAL_ERROR_IF(font == NULL, "Could not create/get font"); u32 text_height = font->getDimension(L"Hello, world!").Height; infostream << "text_height=" << text_height << std::endl; @@ -355,7 +355,7 @@ void FontEngine::initFont(unsigned int basesize, FontMode mode) /** initialize a font without freetype */ void FontEngine::initSimpleFont(unsigned int basesize, FontMode mode) { - assert((mode == FM_Simple) || (mode == FM_SimpleMono)); + assert(mode == FM_Simple || mode == FM_SimpleMono); // pre-condition std::string font_path = ""; if (mode == FM_Simple) { diff --git a/src/fontengine.h b/src/fontengine.h index 9edde05f8..9f7266775 100644 --- a/src/fontengine.h +++ b/src/fontengine.h @@ -59,7 +59,7 @@ public: unsigned int font_size=FONT_SIZE_UNSPECIFIED, FontMode mode=FM_Unspecified) { - return getTextWidth(narrow_to_wide(text)); + return getTextWidth(utf8_to_wide(text)); } /** get text width if a text for a specific font */ diff --git a/src/game.cpp b/src/game.cpp index 7fb99ef3e..11e868a80 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -18,64 +18,60 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "game.h" -#include "irrlichttypes_extrabloated.h" -#include <IGUICheckBox.h> -#include <IGUIEditBox.h> -#include <IGUIButton.h> -#include <IGUIStaticText.h> -#include <IGUIFont.h> -#include <IMaterialRendererServices.h> -#include "IMeshCache.h" + +#include <iomanip> +#include "camera.h" #include "client.h" -#include "server.h" -#include "guiPasswordChange.h" -#include "guiVolumeChange.h" -#include "guiKeyChangeMenu.h" -#include "guiFormSpecMenu.h" -#include "tool.h" -#include "guiChatConsole.h" -#include "config.h" -#include "version.h" +#include "client/tile.h" // For TextureSource +#include "clientmap.h" #include "clouds.h" -#include "particles.h" -#include "camera.h" -#include "mapblock.h" -#include "settings.h" -#include "profiler.h" -#include "mainmenumanager.h" -#include "gettext.h" +#include "config.h" +#include "content_cao.h" +#include "drawscene.h" +#include "event_manager.h" +#include "fontengine.h" +#include "itemdef.h" #include "log.h" #include "filesys.h" -// Needed for determining pointing to nodes -#include "nodedef.h" -#include "nodemetadata.h" -#include "main.h" // For g_settings -#include "itemdef.h" -#include "tile.h" // For TextureSource -#include "shader.h" // For ShaderSource +#include "gettext.h" +#include "guiChatConsole.h" +#include "guiFormSpecMenu.h" +#include "guiKeyChangeMenu.h" +#include "guiPasswordChange.h" +#include "guiVolumeChange.h" +#include "hud.h" #include "logoutputbuffer.h" -#include "subgame.h" +#include "mainmenumanager.h" +#include "mapblock.h" +#include "nodedef.h" // Needed for determining pointing to nodes +#include "nodemetadata.h" +#include "particles.h" +#include "profiler.h" #include "quicktune_shortcutter.h" -#include "clientmap.h" -#include "hud.h" +#include "server.h" +#include "settings.h" +#include "shader.h" // For ShaderSource #include "sky.h" +#include "subgame.h" +#include "tool.h" +#include "util/directiontables.h" +#include "util/pointedthing.h" +#include "version.h" +#include "minimap.h" + #include "sound.h" + #if USE_SOUND -#include "sound_openal.h" + #include "sound_openal.h" #endif -#include "event_manager.h" -#include <iomanip> -#include <list> -#include "util/directiontables.h" -#include "util/pointedthing.h" -#include "drawscene.h" -#include "content_cao.h" -#include "fontengine.h" #ifdef HAVE_TOUCHSCREENGUI -#include "touchscreengui.h" + #include "touchscreengui.h" #endif +extern Settings *g_settings; +extern Profiler *g_profiler; + /* Text input system */ @@ -89,14 +85,14 @@ struct TextDestNodeMetadata : public TextDest { // This is deprecated I guess? -celeron55 void gotText(std::wstring text) { - std::string ntext = wide_to_narrow(text); + std::string ntext = wide_to_utf8(text); infostream << "Submitting 'text' field of node at (" << m_p.X << "," << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; - std::map<std::string, std::string> fields; + StringMap fields; fields["text"] = ntext; m_client->sendNodemetaFields(m_p, "", fields); } - void gotText(std::map<std::string, std::string> fields) + void gotText(const StringMap &fields) { m_client->sendNodemetaFields(m_p, "", fields); } @@ -116,7 +112,7 @@ struct TextDestPlayerInventory : public TextDest { m_client = client; m_formname = formname; } - void gotText(std::map<std::string, std::string> fields) + void gotText(const StringMap &fields) { m_client->sendInventoryFields(m_formname, fields); } @@ -143,7 +139,7 @@ struct LocalFormspecHandler : public TextDest { errorstream << "LocalFormspecHandler::gotText old style message received" << std::endl; } - void gotText(std::map<std::string, std::string> fields) + void gotText(const StringMap &fields) { if (m_formname == "MT_PAUSE_MENU") { if (fields.find("btn_sound") != fields.end()) { @@ -185,9 +181,9 @@ struct LocalFormspecHandler : public TextDest { if ((fields.find("btn_send") != fields.end()) || (fields.find("quit") != fields.end())) { - if (fields.find("f_text") != fields.end()) { - m_client->typeChatMessage(narrow_to_wide(fields["f_text"])); - } + StringMap::const_iterator it = fields.find("f_text"); + if (it != fields.end()) + m_client->typeChatMessage(utf8_to_wide(it->second)); return; } @@ -215,12 +211,14 @@ struct LocalFormspecHandler : public TextDest { return; } - errorstream << "LocalFormspecHandler::gotText unhandled >" << m_formname << "< event" << std::endl; - int i = 0; + errorstream << "LocalFormspecHandler::gotText unhandled >" + << m_formname << "< event" << std::endl; - for (std::map<std::string, std::string>::iterator iter = fields.begin(); - iter != fields.end(); iter++) { - errorstream << "\t" << i << ": " << iter->first << "=" << iter->second << std::endl; + int i = 0; + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) { + errorstream << "\t" << i << ": " << it->first + << "=" << it->second << std::endl; i++; } } @@ -447,7 +445,7 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, FontEngine *fe, std::ostringstream os(std::ios_base::binary); g_profiler->printPage(os, show_profiler, show_profiler_max); - std::wstring text = narrow_to_wide(os.str()); + std::wstring text = utf8_to_wide(os.str()); guitext_profiler->setText(text.c_str()); guitext_profiler->setVisible(true); @@ -492,7 +490,7 @@ private: color(color) {} }; - std::list<Piece> m_log; + std::vector<Piece> m_log; public: u32 m_log_max_size; @@ -515,7 +513,7 @@ public: { std::map<std::string, Meta> m_meta; - for (std::list<Piece>::const_iterator k = m_log.begin(); + for (std::vector<Piece>::const_iterator k = m_log.begin(); k != m_log.end(); k++) { const Piece &piece = *k; @@ -565,16 +563,6 @@ public: s32 graphh = 50; s32 textx = x_left + m_log_max_size + 15; s32 textx2 = textx + 200 - 15; - - // Draw background - /*{ - u32 num_graphs = m_meta.size(); - core::rect<s32> rect(x_left, y_bottom - num_graphs*graphh, - textx2, y_bottom); - video::SColor bgcolor(120,0,0,0); - driver->draw2DRectangle(bgcolor, rect, NULL); - }*/ - s32 meta_i = 0; for (std::map<std::string, Meta>::const_iterator i = m_meta.begin(); @@ -594,16 +582,16 @@ public: s32 texth = 15; char buf[10]; snprintf(buf, 10, "%.3g", show_max); - font->draw(narrow_to_wide(buf).c_str(), + font->draw(utf8_to_wide(buf).c_str(), core::rect<s32>(textx, y - graphh, textx2, y - graphh + texth), meta.color); snprintf(buf, 10, "%.3g", show_min); - font->draw(narrow_to_wide(buf).c_str(), + font->draw(utf8_to_wide(buf).c_str(), core::rect<s32>(textx, y - texth, textx2, y), meta.color); - font->draw(narrow_to_wide(id).c_str(), + font->draw(utf8_to_wide(id).c_str(), core::rect<s32>(textx, y - graphh / 2 - texth / 2, textx2, y - graphh / 2 + texth / 2), meta.color); @@ -613,7 +601,7 @@ public: float lastscaledvalue = 0.0; bool lastscaledvalue_exists = false; - for (std::list<Piece>::const_iterator j = m_log.begin(); + for (std::vector<Piece>::const_iterator j = m_log.begin(); j != m_log.end(); j++) { const Piece &piece = *j; float value = 0; @@ -818,7 +806,7 @@ public: m_fogEnabled = g_settings->getBool("enable_fog"); } - static void SettingsCallback(const std::string name, void *userdata) + static void SettingsCallback(const std::string &name, void *userdata) { reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name); } @@ -879,6 +867,9 @@ public: services->setPixelShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3); services->setVertexShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3); + v3f minimap_yaw_vec = m_client->getMapper()->getYawVec(); + services->setPixelShaderConstant("yawVec", (irr::f32 *)&minimap_yaw_vec, 3); + // Uniform sampler layers int layer0 = 0; int layer1 = 1; @@ -887,11 +878,11 @@ public: #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) services->setPixelShaderConstant("baseTexture" , (irr::f32 *)&layer0, 1); services->setPixelShaderConstant("normalTexture" , (irr::f32 *)&layer1, 1); - services->setPixelShaderConstant("useNormalmap" , (irr::f32 *)&layer2, 1); + services->setPixelShaderConstant("textureFlags" , (irr::f32 *)&layer2, 1); #else services->setPixelShaderConstant("baseTexture" , (irr::s32 *)&layer0, 1); services->setPixelShaderConstant("normalTexture" , (irr::s32 *)&layer1, 1); - services->setPixelShaderConstant("useNormalmap" , (irr::s32 *)&layer2, 1); + services->setPixelShaderConstant("textureFlags" , (irr::s32 *)&layer2, 1); #endif } }; @@ -1058,7 +1049,7 @@ static void show_chat_menu(GUIFormSpecMenu **cur_formspec, FORMSPEC_VERSION_STRING SIZE_TAG "field[3,2.35;6,0.5;f_text;;" + text + "]" - "button_exit[4,3;3,0.5;btn_send;" + wide_to_narrow(wstrgettext("Proceed")) + "]" + "button_exit[4,3;3,0.5;btn_send;" + wide_to_utf8(wstrgettext("Proceed")) + "]" ; /* Create menu */ @@ -1098,7 +1089,7 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec, bool singleplayermode) { #ifdef __ANDROID__ - std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n" + std::string control_text = wide_to_utf8(wstrgettext("Default Controls:\n" "No menu visible:\n" "- single tap: button activate\n" "- double tap: place/use\n" @@ -1112,7 +1103,7 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec, " --> place single item to slot\n" )); #else - std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n" + std::string control_text = wide_to_utf8(wstrgettext("Default Controls:\n" "- WASD: move\n" "- Space: jump/climb\n" "- Shift: sneak/go down\n" @@ -1131,26 +1122,26 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec, os << FORMSPEC_VERSION_STRING << SIZE_TAG << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" - << wide_to_narrow(wstrgettext("Continue")) << "]"; + << wide_to_utf8(wstrgettext("Continue")) << "]"; if (!singleplayermode) { os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" - << wide_to_narrow(wstrgettext("Change Password")) << "]"; + << wide_to_utf8(wstrgettext("Change Password")) << "]"; } - + #ifndef __ANDROID__ os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" - << wide_to_narrow(wstrgettext("Sound Volume")) << "]"; + << wide_to_utf8(wstrgettext("Sound Volume")) << "]"; os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" - << wide_to_narrow(wstrgettext("Change Keys")) << "]"; + << wide_to_utf8(wstrgettext("Change Keys")) << "]"; #endif os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" - << wide_to_narrow(wstrgettext("Exit to Menu")) << "]"; + << wide_to_utf8(wstrgettext("Exit to Menu")) << "]"; os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" - << wide_to_narrow(wstrgettext("Exit to OS")) << "]" + << wide_to_utf8(wstrgettext("Exit to OS")) << "]" << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" - << "textarea[0.4,0.25;3.5,6;;" << "Minetest\n" - << minetest_build_info << "\n" + << "textarea[0.4,0.25;3.5,6;;" << PROJECT_NAME_C "\n" + << g_build_info << "\n" << "path_user = " << wrap_rows(porting::path_user, 20) << "\n;]"; @@ -1161,7 +1152,8 @@ static void show_pause_menu(GUIFormSpecMenu **cur_formspec, LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); - (*cur_formspec)->setFocus(L"btn_continue"); + std::string con("btn_continue"); + (*cur_formspec)->setFocus(con); (*cur_formspec)->doPause = true; } @@ -1175,7 +1167,7 @@ static void updateChat(Client &client, f32 dtime, bool show_debug, // Get new messages from error log buffer while (!chat_log_error_buf.empty()) { - chat_backend.addMessage(L"", narrow_to_wide(chat_log_error_buf.get())); + chat_backend.addMessage(L"", utf8_to_wide(chat_log_error_buf.get())); } // Get new messages from client @@ -1250,9 +1242,11 @@ struct KeyCache { KEYMAP_ID_CHAT, KEYMAP_ID_CMD, KEYMAP_ID_CONSOLE, + KEYMAP_ID_MINIMAP, KEYMAP_ID_FREEMOVE, KEYMAP_ID_FASTMOVE, KEYMAP_ID_NOCLIP, + KEYMAP_ID_CINEMATIC, KEYMAP_ID_SCREENSHOT, KEYMAP_ID_TOGGLE_HUD, KEYMAP_ID_TOGGLE_CHAT, @@ -1298,9 +1292,11 @@ void KeyCache::populate() key[KEYMAP_ID_CHAT] = getKeySetting("keymap_chat"); key[KEYMAP_ID_CMD] = getKeySetting("keymap_cmd"); key[KEYMAP_ID_CONSOLE] = getKeySetting("keymap_console"); + key[KEYMAP_ID_MINIMAP] = getKeySetting("keymap_minimap"); key[KEYMAP_ID_FREEMOVE] = getKeySetting("keymap_freemove"); key[KEYMAP_ID_FASTMOVE] = getKeySetting("keymap_fastmove"); key[KEYMAP_ID_NOCLIP] = getKeySetting("keymap_noclip"); + key[KEYMAP_ID_CINEMATIC] = getKeySetting("keymap_cinematic"); key[KEYMAP_ID_SCREENSHOT] = getKeySetting("keymap_screenshot"); key[KEYMAP_ID_TOGGLE_HUD] = getKeySetting("keymap_toggle_hud"); key[KEYMAP_ID_TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat"); @@ -1402,6 +1398,7 @@ struct VolatileRunFlags { bool invert_mouse; bool show_chat; bool show_hud; + bool show_minimap; bool force_fog_off; bool show_debug; bool show_profiler_graph; @@ -1420,8 +1417,7 @@ struct VolatileRunFlags { * hides most of the stuff in this class (nothing in this class is required * by any other file) but exposes the public methods/data only. */ -class Game -{ +class Game { public: Game(); ~Game(); @@ -1436,7 +1432,8 @@ public: // If address is "", local server is used and address is updated std::string *address, u16 port, - std::wstring *error_message, + std::string &error_message, + bool *reconnect, ChatBackend *chat_backend, const SubgameSpec &gamespec, // Used for local game bool simple_singleplayer_mode); @@ -1458,9 +1455,8 @@ protected: // Client creation bool createClient(const std::string &playername, - const std::string &password, std::string *address, u16 port, - std::wstring *error_message); - bool initGui(std::wstring *error_message); + const std::string &password, std::string *address, u16 port); + bool initGui(); // Client connection bool connectToServer(const std::string &playername, @@ -1470,17 +1466,17 @@ protected: // Main loop - void updateInteractTimers(GameRunData *args, f32 dtime); + void updateInteractTimers(GameRunData *runData, f32 dtime); bool checkConnection(); bool handleCallbacks(); void processQueues(); - void updateProfilers(const GameRunData &run_data, const RunStats &stats, + void updateProfilers(const GameRunData &runData, 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 processUserInput(VolatileRunFlags *flags, GameRunData *interact_args, + void processUserInput(VolatileRunFlags *flags, GameRunData *runData, f32 dtime); void processKeyboardInput(VolatileRunFlags *flags, float *statustext_time, @@ -1497,9 +1493,12 @@ protected: void toggleFreeMoveAlt(float *statustext_time, float *jump_timer); void toggleFast(float *statustext_time); void toggleNoClip(float *statustext_time); + void toggleCinematic(float *statustext_time); void toggleChat(float *statustext_time, bool *flag); void toggleHud(float *statustext_time, bool *flag); + void toggleMinimap(float *statustext_time, bool *flag, bool show_hud, + bool shift_pressed); void toggleFog(float *statustext_time, bool *flag); void toggleDebug(float *statustext_time, bool *show_debug, bool *show_profiler_graph); @@ -1546,6 +1545,9 @@ protected: void showOverlayMessage(const wchar_t *msg, float dtime, int percent, bool draw_clouds = true); + static void settingChangedCallback(const std::string &setting_name, void *data); + void readSettings(); + private: InputHandler *input; @@ -1578,6 +1580,7 @@ private: Sky *sky; // Free using ->Drop() Inventory *local_inventory; Hud *hud; + Mapper *mapper; /* 'cache' This class does take ownership/responsibily for cleaning up etc of any of @@ -1587,7 +1590,8 @@ private: video::IVideoDriver *driver; scene::ISceneManager *smgr; bool *kill; - std::wstring *error_message; + std::string *error_message; + bool *reconnect_requested; IGameDef *gamedef; // Convenience (same as *client) scene::ISceneNode *skybox; @@ -1615,10 +1619,7 @@ private: IntervalLimiter profiler_interval; - /* TODO: Add a callback function so these can be updated when a setting - * changes. At this point in time it doesn't matter (e.g. /set - * is documented to change server settings only) - * + /* * TODO: Local caching of settings is not optimal and should at some stage * be updated to use a global settings object for getting thse values * (as opposed to the this local caching). This can be addressed in @@ -1631,6 +1632,11 @@ private: bool m_cache_enable_fog; f32 m_cache_mouse_sensitivity; f32 m_repeat_right_click_time; + +#ifdef __ANDROID__ + bool m_cache_hold_aux1; +#endif + }; Game::Game() : @@ -1653,17 +1659,30 @@ Game::Game() : clouds(NULL), sky(NULL), local_inventory(NULL), - hud(NULL) + hud(NULL), + mapper(NULL) { - m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); - m_cache_enable_node_highlighting = g_settings->getBool("enable_node_highlighting"); - m_cache_enable_clouds = g_settings->getBool("enable_clouds"); - m_cache_enable_particles = g_settings->getBool("enable_particles"); - m_cache_enable_fog = g_settings->getBool("enable_fog"); - m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity"); - m_repeat_right_click_time = g_settings->getFloat("repeat_rightclick_time"); + g_settings->registerChangedCallback("doubletap_jump", + &settingChangedCallback, this); + g_settings->registerChangedCallback("enable_node_highlighting", + &settingChangedCallback, this); + g_settings->registerChangedCallback("enable_clouds", + &settingChangedCallback, this); + g_settings->registerChangedCallback("enable_particles", + &settingChangedCallback, this); + g_settings->registerChangedCallback("enable_fog", + &settingChangedCallback, this); + g_settings->registerChangedCallback("mouse_sensitivity", + &settingChangedCallback, this); + g_settings->registerChangedCallback("repeat_rightclick_time", + &settingChangedCallback, this); + + readSettings(); + +#ifdef __ANDROID__ + m_cache_hold_aux1 = false; // This is initialised properly later +#endif - m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0); } @@ -1693,6 +1712,21 @@ Game::~Game() delete draw_control; extendedResourceCleanup(); + + g_settings->deregisterChangedCallback("doubletap_jump", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("enable_node_highlighting", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("enable_clouds", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("enable_particles", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("enable_fog", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("mouse_sensitivity", + &settingChangedCallback, this); + g_settings->deregisterChangedCallback("repeat_rightclick_time", + &settingChangedCallback, this); } bool Game::startup(bool *kill, @@ -1704,18 +1738,20 @@ bool Game::startup(bool *kill, const std::string &password, std::string *address, // can change if simple_singleplayer_mode u16 port, - std::wstring *error_message, + std::string &error_message, + bool *reconnect, ChatBackend *chat_backend, const SubgameSpec &gamespec, bool simple_singleplayer_mode) { // "cache" - this->device = device; - this->kill = kill; - this->error_message = error_message; - this->random_input = random_input; - this->input = input; - this->chat_backend = chat_backend; + this->device = device; + this->kill = kill; + this->error_message = &error_message; + this->reconnect_requested = reconnect; + this->random_input = random_input; + this->input = input; + this->chat_backend = chat_backend; this->simple_singleplayer_mode = simple_singleplayer_mode; driver = device->getVideoDriver(); @@ -1726,7 +1762,7 @@ bool Game::startup(bool *kill, if (!init(map_dir, address, port, gamespec)) return false; - if (!createClient(playername, password, address, port, error_message)) + if (!createClient(playername, password, address, port)) return false; return true; @@ -1737,6 +1773,7 @@ void Game::run() { ProfilerGraph graph; RunStats stats = { 0 }; + CameraOrientation cam_view_target = { 0 }; CameraOrientation cam_view = { 0 }; GameRunData runData = { 0 }; FpsControl draw_times = { 0 }; @@ -1749,6 +1786,7 @@ void Game::run() flags.show_chat = true; flags.show_hud = true; + flags.show_minimap = g_settings->getBool("enable_minimap"); flags.show_debug = g_settings->getBool("show_debug"); flags.invert_mouse = g_settings->getBool("invert_mouse"); flags.first_loop_after_window_activation = true; @@ -1769,6 +1807,11 @@ void Game::run() set_light_table(g_settings->getFloat("display_gamma")); +#ifdef __ANDROID__ + m_cache_hold_aux1 = g_settings->getBool("fast_move") + && client->checkPrivilege("fast"); +#endif + while (device->run() && !(*kill || g_gamecallback->shutdown_requested)) { /* Must be called immediately after a device->run() call because it @@ -1792,10 +1835,20 @@ void Game::run() updateProfilers(runData, stats, draw_times, dtime); processUserInput(&flags, &runData, dtime); // Update camera before player movement to avoid camera lag of one frame - updateCameraDirection(&cam_view, &flags); + updateCameraDirection(&cam_view_target, &flags); + float cam_smoothing = 0; + if (g_settings->getBool("cinematic")) + cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing"); + else + cam_smoothing = 1 - g_settings->getFloat("camera_smoothing"); + cam_smoothing = rangelim(cam_smoothing, 0.01f, 1.0f); + cam_view.camera_yaw += (cam_view_target.camera_yaw - + cam_view.camera_yaw) * cam_smoothing; + cam_view.camera_pitch += (cam_view_target.camera_pitch - + cam_view.camera_pitch) * cam_smoothing; updatePlayerControl(cam_view); step(&dtime); - processClientEvents(&cam_view, &runData.damage_flash); + processClientEvents(&cam_view_target, &runData.damage_flash); updateCamera(&flags, draw_times.busy_time, dtime, runData.time_from_last_punch); updateSound(dtime); @@ -1848,10 +1901,11 @@ void Game::shutdown() } - +/****************************************************************************/ /**************************************************************************** Startup ****************************************************************************/ +/****************************************************************************/ bool Game::init( const std::string &map_dir, @@ -1934,10 +1988,10 @@ bool Game::createSingleplayerServer(const std::string map_dir, } if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { - *error_message = L"Unable to listen on " + - narrow_to_wide(bind_addr.serializeString()) + - L" because IPv6 is disabled"; - errorstream << wide_to_narrow(*error_message) << std::endl; + *error_message = "Unable to listen on " + + bind_addr.serializeString() + + " because IPv6 is disabled"; + errorstream << *error_message << std::endl; return false; } @@ -1950,8 +2004,7 @@ bool Game::createSingleplayerServer(const std::string map_dir, } bool Game::createClient(const std::string &playername, - const std::string &password, std::string *address, u16 port, - std::wstring *error_message) + const std::string &password, std::string *address, u16 port) { showOverlayMessage(wgettext("Creating client..."), 0, 10); @@ -1966,25 +2019,25 @@ bool Game::createClient(const std::string &playername, return false; if (!could_connect) { - if (*error_message == L"" && !connect_aborted) { + if (error_message->empty() && !connect_aborted) { // Should not happen if error messages are set properly - *error_message = L"Connection failed for unknown reason"; - errorstream << wide_to_narrow(*error_message) << std::endl; + *error_message = "Connection failed for unknown reason"; + errorstream << *error_message << std::endl; } return false; } if (!getServerContent(&connect_aborted)) { - if (*error_message == L"" && !connect_aborted) { + if (error_message->empty() && !connect_aborted) { // Should not happen if error messages are set properly - *error_message = L"Connection failed for unknown reason"; - errorstream << wide_to_narrow(*error_message) << std::endl; + *error_message = "Connection failed for unknown reason"; + errorstream << *error_message << std::endl; } return false; } // Update cached textures, meshes and materials - client->afterContentReceived(device, g_fontengine->getFont()); + client->afterContentReceived(device); /* Camera */ @@ -1997,9 +2050,8 @@ bool Game::createClient(const std::string &playername, if (m_cache_enable_clouds) { clouds = new Clouds(smgr->getRootSceneNode(), smgr, -1, time(0)); if (!clouds) { - *error_message = L"Memory allocation error"; - *error_message += narrow_to_wide(" (clouds)"); - errorstream << wide_to_narrow(*error_message) << std::endl; + *error_message = "Memory allocation error (clouds)"; + errorstream << *error_message << std::endl; return false; } } @@ -2012,9 +2064,8 @@ bool Game::createClient(const std::string &playername, local_inventory = new Inventory(itemdef_manager); if (!(sky && local_inventory)) { - *error_message = L"Memory allocation error"; - *error_message += narrow_to_wide(" (sky or local inventory)"); - errorstream << wide_to_narrow(*error_message) << std::endl; + *error_message = "Memory allocation error (sky or local inventory)"; + errorstream << *error_message << std::endl; return false; } @@ -2028,14 +2079,15 @@ bool Game::createClient(const std::string &playername, crack_animation_length = 5; } - if (!initGui(error_message)) + if (!initGui()) return false; /* Set window caption */ - core::stringw str = L"Minetest ["; + std::wstring str = utf8_to_wide(PROJECT_NAME_C); + str += L" ["; str += driver->getName(); - str += "]"; + str += L"]"; device->setWindowCaption(str.c_str()); LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -2045,19 +2097,22 @@ bool Game::createClient(const std::string &playername, hud = new Hud(driver, smgr, guienv, gamedef, player, local_inventory); if (!hud) { - *error_message = L"Memory error: could not create HUD"; - errorstream << wide_to_narrow(*error_message) << std::endl; + *error_message = "Memory error: could not create HUD"; + errorstream << *error_message << std::endl; return false; } + mapper = client->getMapper(); + mapper->setMinimapMode(MINIMAP_MODE_OFF); + return true; } -bool Game::initGui(std::wstring *error_message) +bool Game::initGui() { // First line of debug text guitext = guienv->addStaticText( - L"Minetest", + utf8_to_wide(PROJECT_NAME_C).c_str(), core::rect<s32>(0, 0, 0, 0), false, false, guiroot); @@ -2094,8 +2149,8 @@ bool Game::initGui(std::wstring *error_message) gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, chat_backend, client); if (!gui_chat_console) { - *error_message = L"Could not allocate memory for chat console"; - errorstream << wide_to_narrow(*error_message) << std::endl; + *error_message = "Could not allocate memory for chat console"; + errorstream << *error_message << std::endl; return false; } @@ -2145,16 +2200,16 @@ bool Game::connectToServer(const std::string &playername, local_server_mode = true; } } catch (ResolveError &e) { - *error_message = L"Couldn't resolve address: " + narrow_to_wide(e.what()); - errorstream << wide_to_narrow(*error_message) << std::endl; + *error_message = std::string("Couldn't resolve address: ") + e.what(); + errorstream << *error_message << std::endl; return false; } if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) { - *error_message = L"Unable to connect to " + - narrow_to_wide(connect_address.serializeString()) + - L" because IPv6 is disabled"; - errorstream << wide_to_narrow(*error_message) << std::endl; + *error_message = "Unable to connect to " + + connect_address.serializeString() + + " because IPv6 is disabled"; + errorstream << *error_message << std::endl; return false; } @@ -2184,7 +2239,10 @@ bool Game::connectToServer(const std::string &playername, input->clear(); FpsControl fps_control = { 0 }; - f32 dtime; // in seconds + f32 dtime; + f32 wait_time = 0; // in seconds + + fps_control.last_time = device->getTimer()->getTime(); while (device->run()) { @@ -2204,9 +2262,10 @@ bool Game::connectToServer(const std::string &playername, // Break conditions if (client->accessDenied()) { - *error_message = L"Access denied. Reason: " + *error_message = "Access denied. Reason: " + client->accessDeniedReason(); - errorstream << wide_to_narrow(*error_message) << std::endl; + *reconnect_requested = client->reconnectRequested(); + errorstream << *error_message << std::endl; break; } @@ -2216,6 +2275,14 @@ bool Game::connectToServer(const std::string &playername, break; } + wait_time += dtime; + // Only time out if we aren't waiting for the server we started + if ((*address != "") && (wait_time > 10)) { + *error_message = "Connection timed out."; + errorstream << *error_message << std::endl; + break; + } + // Update status showOverlayMessage(wgettext("Connecting to server..."), dtime, 20); } @@ -2235,6 +2302,8 @@ bool Game::getServerContent(bool *aborted) FpsControl fps_control = { 0 }; f32 dtime; // in seconds + fps_control.last_time = device->getTimer()->getTime(); + while (device->run()) { limitFps(&fps_control, &dtime); @@ -2252,16 +2321,12 @@ bool Game::getServerContent(bool *aborted) } // Error conditions - if (client->accessDenied()) { - *error_message = L"Access denied. Reason: " - + client->accessDeniedReason(); - errorstream << wide_to_narrow(*error_message) << std::endl; + if (!checkConnection()) return false; - } if (client->getState() < LC_Init) { - *error_message = L"Client disconnected"; - errorstream << wide_to_narrow(*error_message) << std::endl; + *error_message = "Client disconnected"; + errorstream << *error_message << std::endl; return false; } @@ -2292,18 +2357,18 @@ bool Game::getServerContent(bool *aborted) if ((USE_CURL == 0) || (!g_settings->getBool("enable_remote_media_server"))) { float cur = client->getCurRate(); - std::string cur_unit = gettext(" KB/s"); + std::string cur_unit = gettext("KiB/s"); if (cur > 900) { cur /= 1024.0; - cur_unit = gettext(" MB/s"); + cur_unit = gettext("MiB/s"); } - message << " ( " << cur << cur_unit << " )"; + message << " (" << cur << ' ' << cur_unit << ")"; } progress = 30 + client->mediaReceiveProgress() * 35 + 0.5; - draw_load_screen(narrow_to_wide(message.str()), device, + draw_load_screen(utf8_to_wide(message.str()), device, guienv, dtime, progress); } } @@ -2312,20 +2377,21 @@ bool Game::getServerContent(bool *aborted) } - +/****************************************************************************/ /**************************************************************************** Run ****************************************************************************/ +/****************************************************************************/ -inline void Game::updateInteractTimers(GameRunData *args, f32 dtime) +inline void Game::updateInteractTimers(GameRunData *runData, f32 dtime) { - if (args->nodig_delay_timer >= 0) - args->nodig_delay_timer -= dtime; + if (runData->nodig_delay_timer >= 0) + runData->nodig_delay_timer -= dtime; - if (args->object_hit_delay_timer >= 0) - args->object_hit_delay_timer -= dtime; + if (runData->object_hit_delay_timer >= 0) + runData->object_hit_delay_timer -= dtime; - args->time_from_last_punch += dtime; + runData->time_from_last_punch += dtime; } @@ -2334,9 +2400,10 @@ inline void Game::updateInteractTimers(GameRunData *args, f32 dtime) inline bool Game::checkConnection() { if (client->accessDenied()) { - *error_message = L"Access denied. Reason: " + *error_message = "Access denied. Reason: " + client->accessDeniedReason(); - errorstream << wide_to_narrow(*error_message) << std::endl; + *reconnect_requested = client->reconnectRequested(); + errorstream << *error_message << std::endl; return false; } @@ -2388,7 +2455,7 @@ void Game::processQueues() } -void Game::updateProfilers(const GameRunData &run_data, const RunStats &stats, +void Game::updateProfilers(const GameRunData &runData, const RunStats &stats, const FpsControl &draw_times, f32 dtime) { float profiler_print_interval = @@ -2407,7 +2474,7 @@ void Game::updateProfilers(const GameRunData &run_data, const RunStats &stats, } update_profiler_gui(guitext_profiler, g_fontengine, - run_data.profiler_current_page, run_data.profiler_max_page, + runData.profiler_current_page, runData.profiler_max_page, driver->getScreenSize().Height); g_profiler->clear(); @@ -2488,7 +2555,7 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times, ****************************************************************************/ void Game::processUserInput(VolatileRunFlags *flags, - GameRunData *interact_args, f32 dtime) + GameRunData *runData, f32 dtime) { // Reset input if window not active or some menu is active if (device->isWindowActive() == false @@ -2519,18 +2586,18 @@ void Game::processUserInput(VolatileRunFlags *flags, #endif // Increase timer for double tap of "keymap_jump" - if (m_cache_doubletap_jump && interact_args->jump_timer <= 0.2) - interact_args->jump_timer += dtime; + if (m_cache_doubletap_jump && runData->jump_timer <= 0.2) + runData->jump_timer += dtime; processKeyboardInput( flags, - &interact_args->statustext_time, - &interact_args->jump_timer, - &interact_args->reset_jump_timer, - &interact_args->profiler_current_page, - interact_args->profiler_max_page); + &runData->statustext_time, + &runData->jump_timer, + &runData->reset_jump_timer, + &runData->profiler_current_page, + runData->profiler_max_page); - processItemSelection(&interact_args->new_playeritem); + processItemSelection(&runData->new_playeritem); } @@ -2568,10 +2635,15 @@ void Game::processKeyboardInput(VolatileRunFlags *flags, toggleFast(statustext_time); } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_NOCLIP])) { toggleNoClip(statustext_time); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CINEMATIC])) { + toggleCinematic(statustext_time); } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_SCREENSHOT])) { client->makeScreenshot(device); } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_HUD])) { toggleHud(statustext_time, &flags->show_hud); + } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_MINIMAP])) { + toggleMinimap(statustext_time, &flags->show_minimap, flags->show_hud, + input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK])); } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_CHAT])) { toggleChat(statustext_time, &flags->show_chat); } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_FORCE_FOG_OFF])) { @@ -2615,7 +2687,7 @@ void Game::processKeyboardInput(VolatileRunFlags *flags, if (quicktune->hasMessage()) { std::string msg = quicktune->getMessage(); - statustext = narrow_to_wide(msg); + statustext = utf8_to_wide(msg); *statustext_time = 0; } } @@ -2673,6 +2745,15 @@ void Game::dropSelectedItem() void Game::openInventory() { + /* + * Don't permit to open inventory is CAO or player doesn't exists. + * This prevent showing an empty inventory at player load + */ + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + if (player == NULL || player->getCAO() == NULL) + return; + infostream << "the_game: " << "Launching inventory" << std::endl; PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client); @@ -2727,8 +2808,14 @@ void Game::toggleFast(float *statustext_time) *statustext_time = 0; statustext = msg[fast_move]; - if (fast_move && !client->checkPrivilege("fast")) + bool has_fast_privs = client->checkPrivilege("fast"); + + if (fast_move && !has_fast_privs) statustext += L" (note: no 'fast' privilege)"; + +#ifdef __ANDROID__ + m_cache_hold_aux1 = fast_move && has_fast_privs; +#endif } @@ -2745,6 +2832,16 @@ void Game::toggleNoClip(float *statustext_time) statustext += L" (note: no 'noclip' privilege)"; } +void Game::toggleCinematic(float *statustext_time) +{ + static const wchar_t *msg[] = { L"cinematic disabled", L"cinematic enabled" }; + bool cinematic = !g_settings->getBool("cinematic"); + g_settings->set("cinematic", bool_to_cstr(cinematic)); + + *statustext_time = 0; + statustext = msg[cinematic]; +} + void Game::toggleChat(float *statustext_time, bool *flag) { @@ -2767,6 +2864,55 @@ void Game::toggleHud(float *statustext_time, bool *flag) client->setHighlighted(client->getHighlighted(), *flag); } +void Game::toggleMinimap(float *statustext_time, bool *flag, + bool show_hud, bool shift_pressed) +{ + if (!show_hud || !g_settings->getBool("enable_minimap")) + return; + + if (shift_pressed) { + mapper->toggleMinimapShape(); + return; + } + + u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags; + + MinimapMode mode = MINIMAP_MODE_OFF; + if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) { + mode = mapper->getMinimapMode(); + mode = (MinimapMode)((int)mode + 1); + } + + *flag = true; + switch (mode) { + case MINIMAP_MODE_SURFACEx1: + statustext = L"Minimap in surface mode, Zoom x1"; + break; + case MINIMAP_MODE_SURFACEx2: + statustext = L"Minimap in surface mode, Zoom x2"; + break; + case MINIMAP_MODE_SURFACEx4: + statustext = L"Minimap in surface mode, Zoom x4"; + break; + case MINIMAP_MODE_RADARx1: + statustext = L"Minimap in radar mode, Zoom x1"; + break; + case MINIMAP_MODE_RADARx2: + statustext = L"Minimap in radar mode, Zoom x2"; + break; + case MINIMAP_MODE_RADARx4: + statustext = L"Minimap in radar mode, Zoom x4"; + break; + default: + mode = MINIMAP_MODE_OFF; + *flag = false; + statustext = (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ? + L"Minimap hidden" : L"Minimap disabled by server"; + } + + *statustext_time = 0; + mapper->setMinimapMode(mode); +} void Game::toggleFog(float *statustext_time, bool *flag) { @@ -2839,7 +2985,7 @@ void Game::increaseViewRange(float *statustext_time) s16 range = g_settings->getS16("viewing_range_nodes_min"); s16 range_new = range + 10; g_settings->set("viewing_range_nodes_min", itos(range_new)); - statustext = narrow_to_wide("Minimum viewing range changed to " + statustext = utf8_to_wide("Minimum viewing range changed to " + itos(range_new)); *statustext_time = 0; } @@ -2854,7 +3000,7 @@ void Game::decreaseViewRange(float *statustext_time) range_new = range; g_settings->set("viewing_range_nodes_min", itos(range_new)); - statustext = narrow_to_wide("Minimum viewing range changed to " + statustext = utf8_to_wide("Minimum viewing range changed to " + itos(range_new)); *statustext_time = 0; } @@ -2954,19 +3100,34 @@ void Game::updatePlayerControl(const CameraOrientation &cam) cam.camera_pitch, cam.camera_yaw ); + + u32 keypress_bits = + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD]) & 0x1) << 0) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]) & 0x1) << 1) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT]) & 0x1) << 2) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT]) & 0x1) << 3) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP]) & 0x1) << 4) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]) & 0x1) << 5) | + ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]) & 0x1) << 6) | + ( (u32)(input->getLeftState() & 0x1) << 7) | + ( (u32)(input->getRightState() & 0x1) << 8 + ); + +#ifdef ANDROID + /* For Android, simulate holding down AUX1 (fast move) if the user has + * the fast_move setting toggled on. If there is an aux1 key defined for + * Android then its meaning is inverted (i.e. holding aux1 means walk and + * not fast) + */ + if (m_cache_hold_aux1) { + control.aux1 = control.aux1 ^ true; + keypress_bits ^= ((u32)(1U << 5)); + } +#endif + client->setPlayerControl(control); LocalPlayer *player = client->getEnv().getLocalPlayer(); - player->keyPressed = - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD]) & 0x1) << 0) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]) & 0x1) << 1) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT]) & 0x1) << 2) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT]) & 0x1) << 3) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP]) & 0x1) << 4) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]) & 0x1) << 5) | - ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]) & 0x1) << 6) | - ( (u32)(input->getLeftState() & 0x1) << 7) | - ( (u32)(input->getRightState() & 0x1) << 8 - ); + player->keyPressed = keypress_bits; //tt.stop(); } @@ -3077,7 +3238,7 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) u32 new_id = player->addHud(e); //if this isn't true our huds aren't consistent - assert(new_id == id); + sanity_check(new_id == id); delete event.hudadd.pos; delete event.hudadd.name; @@ -3169,12 +3330,12 @@ void Game::processClientEvents(CameraOrientation *cam, float *damage_flash) event.set_sky.params->size() == 6) { sky->setFallbackBgColor(*event.set_sky.bgcolor); skybox = smgr->addSkyBoxSceneNode( - texture_src->getTexture((*event.set_sky.params)[0]), - texture_src->getTexture((*event.set_sky.params)[1]), - texture_src->getTexture((*event.set_sky.params)[2]), - texture_src->getTexture((*event.set_sky.params)[3]), - texture_src->getTexture((*event.set_sky.params)[4]), - texture_src->getTexture((*event.set_sky.params)[5])); + texture_src->getTextureForMesh((*event.set_sky.params)[0]), + texture_src->getTextureForMesh((*event.set_sky.params)[1]), + texture_src->getTextureForMesh((*event.set_sky.params)[2]), + texture_src->getTextureForMesh((*event.set_sky.params)[3]), + texture_src->getTextureForMesh((*event.set_sky.params)[4]), + texture_src->getTextureForMesh((*event.set_sky.params)[5])); } // Handle everything else as plain color else { @@ -3231,6 +3392,7 @@ void Game::updateCamera(VolatileRunFlags *flags, u32 busy_time, camera->toggleCameraMode(); playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); } float full_punch_interval = playeritem_toolcap.full_punch_interval; @@ -3453,13 +3615,13 @@ void Game::handlePointingAtNode(GameRunData *runData, NodeMetadata *meta = map.getNodeMetadata(nodepos); if (meta) { - infotext = narrow_to_wide(meta->getString("infotext")); + infotext = utf8_to_wide(meta->getString("infotext")); } else { MapNode n = map.getNodeNoEx(nodepos); if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") { infotext = L"Unknown node: "; - infotext += narrow_to_wide(nodedef_manager->get(n).name); + infotext += utf8_to_wide(nodedef_manager->get(n).name); } } @@ -3525,10 +3687,10 @@ void Game::handlePointingAtObject(GameRunData *runData, const v3f &player_position, bool show_debug) { - infotext = narrow_to_wide(runData->selected_object->infoText()); + infotext = utf8_to_wide(runData->selected_object->infoText()); if (infotext == L"" && show_debug) { - infotext = narrow_to_wide(runData->selected_object->debugInfoText()); + infotext = utf8_to_wide(runData->selected_object->debugInfoText()); } if (input->getLeftState()) { @@ -3885,8 +4047,9 @@ void Game::updateFrame(std::vector<aabb3f> &highlight_boxes, stats->beginscenetime = timer.stop(true); } - draw_scene(driver, smgr, *camera, *client, player, *hud, guienv, - highlight_boxes, screensize, skycolor, flags.show_hud); + draw_scene(driver, smgr, *camera, *client, player, *hud, *mapper, + guienv, highlight_boxes, screensize, skycolor, flags.show_hud, + flags.show_minimap); /* Profiler graph @@ -3920,6 +4083,14 @@ void Game::updateFrame(std::vector<aabb3f> &highlight_boxes, } /* + Update minimap pos and rotation + */ + if (flags.show_minimap && flags.show_hud) { + mapper->setPos(floatToInt(player->getPosition(), BS)); + mapper->setAngle(player->getYaw()); + } + + /* End scene */ { @@ -3933,6 +4104,28 @@ void Game::updateFrame(std::vector<aabb3f> &highlight_boxes, } +inline static const char *yawToDirectionString(int yaw) +{ + // NOTE: TODO: This can be done mathematically without the else/else-if + // cascade. + + const char *player_direction; + + yaw = wrapDegrees_0_360(yaw); + + if (yaw >= 45 && yaw < 135) + player_direction = "West [-X]"; + else if (yaw >= 135 && yaw < 225) + player_direction = "South [-Z]"; + else if (yaw >= 225 && yaw < 315) + player_direction = "East [+X]"; + else + player_direction = "North [+Z]"; + + return player_direction; +} + + void Game::updateGui(float *statustext_time, const RunStats &stats, const GameRunData& runData, f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam) @@ -3950,7 +4143,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, std::ostringstream os(std::ios_base::binary); os << std::fixed - << "Minetest " << minetest_version_hash + << PROJECT_NAME_C " " << g_version_hash << " FPS = " << fps << " (R: range_all=" << draw_control->range_all << ")" << std::setprecision(0) @@ -3962,12 +4155,12 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, << ", v_range = " << draw_control->wanted_range << std::setprecision(3) << ", RTT = " << client->getRTT(); - guitext->setText(narrow_to_wide(os.str()).c_str()); + guitext->setText(utf8_to_wide(os.str()).c_str()); guitext->setVisible(true); } else if (flags.show_hud || flags.show_chat) { std::ostringstream os(std::ios_base::binary); - os << "Minetest " << minetest_version_hash; - guitext->setText(narrow_to_wide(os.str()).c_str()); + os << PROJECT_NAME_C " " << g_version_hash; + guitext->setText(utf8_to_wide(os.str()).c_str()); guitext->setVisible(true); } else { guitext->setVisible(false); @@ -3988,6 +4181,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, << ", " << (player_position.Y / BS) << ", " << (player_position.Z / BS) << ") (yaw=" << (wrapDegrees_0_360(cam.camera_yaw)) + << " " << yawToDirectionString(cam.camera_yaw) << ") (seed = " << ((u64)client->getMapSeed()) << ")"; @@ -4003,7 +4197,7 @@ void Game::updateGui(float *statustext_time, const RunStats &stats, } } - guitext2->setText(narrow_to_wide(os.str()).c_str()); + guitext2->setText(utf8_to_wide(os.str()).c_str()); guitext2->setVisible(true); core::rect<s32> rect( @@ -4081,7 +4275,6 @@ inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime) // not using getRealTime is necessary for wine device->getTimer()->tick(); // Maker sure device time is up-to-date u32 time = device->getTimer()->getTime(); - u32 last_time = fps_timings->last_time; if (time > last_time) // Make sure time hasn't overflowed @@ -4127,10 +4320,29 @@ void Game::showOverlayMessage(const wchar_t *msg, float dtime, delete[] msg; } +void Game::settingChangedCallback(const std::string &setting_name, void *data) +{ + ((Game *)data)->readSettings(); +} +void Game::readSettings() +{ + m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); + m_cache_enable_node_highlighting = g_settings->getBool("enable_node_highlighting"); + m_cache_enable_clouds = g_settings->getBool("enable_clouds"); + m_cache_enable_particles = g_settings->getBool("enable_particles"); + m_cache_enable_fog = g_settings->getBool("enable_fog"); + m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity"); + m_repeat_right_click_time = g_settings->getFloat("repeat_rightclick_time"); + + m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0); +} + +/****************************************************************************/ /**************************************************************************** Shutdown / cleanup ****************************************************************************/ +/****************************************************************************/ void Game::extendedResourceCleanup() { @@ -4154,10 +4366,11 @@ void Game::extendedResourceCleanup() } - +/****************************************************************************/ /**************************************************************************** extern function for launching the game ****************************************************************************/ +/****************************************************************************/ void the_game(bool *kill, bool random_input, @@ -4170,8 +4383,9 @@ void the_game(bool *kill, const std::string &address, // If empty local server is created u16 port, - std::wstring &error_message, + std::string &error_message, ChatBackend &chat_backend, + bool *reconnect_requested, const SubgameSpec &gamespec, // Used for local game bool simple_singleplayer_mode) { @@ -4186,25 +4400,24 @@ void the_game(bool *kill, try { if (game.startup(kill, random_input, input, device, map_dir, - playername, password, &server_address, port, - &error_message, &chat_backend, gamespec, - simple_singleplayer_mode)) { - + playername, password, &server_address, port, error_message, + reconnect_requested, &chat_backend, gamespec, + simple_singleplayer_mode)) { game.run(); game.shutdown(); } } catch (SerializationError &e) { - error_message = L"A serialization error occurred:\n" - + narrow_to_wide(e.what()) + L"\n\nThe server is probably " - L" running a different version of Minetest."; - errorstream << wide_to_narrow(error_message) << std::endl; + error_message = std::string("A serialization error occurred:\n") + + e.what() + "\n\nThe server is probably " + " running a different version of " PROJECT_NAME_C "."; + errorstream << error_message << std::endl; } catch (ServerError &e) { - error_message = narrow_to_wide(e.what()); - errorstream << "ServerError: " << e.what() << std::endl; + error_message = e.what(); + errorstream << "ServerError: " << error_message << std::endl; } catch (ModError &e) { - errorstream << "ModError: " << e.what() << std::endl; - error_message = narrow_to_wide(e.what()) + wstrgettext("\nCheck debug.txt for details."); + error_message = e.what() + strgettext("\nCheck debug.txt for details."); + errorstream << "ModError: " << error_message << std::endl; } } diff --git a/src/game.h b/src/game.h index 61f780bee..e1f4e9346 100644 --- a/src/game.h +++ b/src/game.h @@ -145,8 +145,9 @@ void the_game(bool *kill, const std::string &password, const std::string &address, // If "", local server is used u16 port, - std::wstring &error_message, + std::string &error_message, ChatBackend &chat_backend, + bool *reconnect_requested, const SubgameSpec &gamespec, // Used for local game bool simple_singleplayer_mode); diff --git a/src/gamedef.h b/src/gamedef.h index 793d85b39..a5f6b5968 100644 --- a/src/gamedef.h +++ b/src/gamedef.h @@ -31,6 +31,7 @@ class ISoundManager; class IShaderSource; class MtEventManager; class IRollbackManager; +class EmergeManager; namespace irr { namespace scene { class IAnimatedMesh; class ISceneManager; @@ -55,10 +56,10 @@ public: virtual ITextureSource* getTextureSource()=0; virtual IShaderSource* getShaderSource()=0; - + // Used for keeping track of names/ids of unknown nodes virtual u16 allocateUnknownNodeId(const std::string &name)=0; - + // Only usable on the client virtual ISoundManager* getSoundManager()=0; virtual MtEventManager* getEventManager()=0; @@ -69,20 +70,24 @@ public: // Only usable on the server, and NOT thread-safe. It is usable from the // environment thread. virtual IRollbackManager* getRollbackManager(){return NULL;} - + + // Only usable on the server. Thread safe if not written while running threads. + virtual EmergeManager *getEmergeManager() { return NULL; } + // Used on the client virtual bool checkLocalPrivilege(const std::string &priv) { return false; } - + // Shorthands - IItemDefManager* idef(){return getItemDefManager();} - INodeDefManager* ndef(){return getNodeDefManager();} - ICraftDefManager* cdef(){return getCraftDefManager();} - ITextureSource* tsrc(){return getTextureSource();} - ISoundManager* sound(){return getSoundManager();} - IShaderSource* shsrc(){return getShaderSource();} - MtEventManager* event(){return getEventManager();} - IRollbackManager* rollback(){return getRollbackManager();} + IItemDefManager *idef() { return getItemDefManager(); } + INodeDefManager *ndef() { return getNodeDefManager(); } + ICraftDefManager *cdef() { return getCraftDefManager(); } + ITextureSource *tsrc() { return getTextureSource(); } + ISoundManager *sound() { return getSoundManager(); } + IShaderSource *shsrc() { return getShaderSource(); } + MtEventManager *event() { return getEventManager(); } + IRollbackManager *rollback() { return getRollbackManager();} + EmergeManager *emerge() { return getEmergeManager(); } }; #endif diff --git a/src/content_object.h b/src/gameparams.h index 65a829773..b50e943d2 100644 --- a/src/content_object.h +++ b/src/gameparams.h @@ -17,23 +17,17 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef CONTENT_OBJECT_HEADER -#define CONTENT_OBJECT_HEADER +#ifndef __GAME_PARAMS_H__ +#define __GAME_PARAMS_H__ -#define ACTIVEOBJECT_TYPE_TEST 1 -#define ACTIVEOBJECT_TYPE_ITEM 2 -#define ACTIVEOBJECT_TYPE_RAT 3 -#define ACTIVEOBJECT_TYPE_OERKKI1 4 -#define ACTIVEOBJECT_TYPE_FIREFLY 5 -#define ACTIVEOBJECT_TYPE_MOBV2 6 +#include "irrlichttypes_extrabloated.h" -#define ACTIVEOBJECT_TYPE_LUAENTITY 7 - -// Special type, not stored as a static object -#define ACTIVEOBJECT_TYPE_PLAYER 100 - -// Special type, only exists as CAO -#define ACTIVEOBJECT_TYPE_GENERIC 101 +struct GameParams { + u16 socket_port; + std::string world_path; + SubgameSpec game_spec; + bool is_dedicated_server; + int log_level; +}; #endif - diff --git a/src/genericobject.cpp b/src/genericobject.cpp index 9a1b9d8d0..90e8cf3d3 100644 --- a/src/genericobject.cpp +++ b/src/genericobject.cpp @@ -133,7 +133,7 @@ std::string gob_cmd_update_physics_override(float physics_override_speed, float return os.str(); } -std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend) +std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend, bool frame_loop) { std::ostringstream os(std::ios::binary); // command @@ -142,6 +142,8 @@ std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_ writeV2F1000(os, frames); writeF1000(os, frame_speed); writeF1000(os, frame_blend); + // these are sent inverted so we get true when the server sends nothing + writeU8(os, !frame_loop); return os.str(); } @@ -161,7 +163,7 @@ std::string gob_cmd_update_attachment(int parent_id, std::string bone, v3f posit { std::ostringstream os(std::ios::binary); // command - writeU8(os, GENERIC_CMD_SET_ATTACHMENT); + writeU8(os, GENERIC_CMD_ATTACH_TO); // parameters writeS16(os, parent_id); os<<serializeString(bone); @@ -170,3 +172,13 @@ std::string gob_cmd_update_attachment(int parent_id, std::string bone, v3f posit return os.str(); } +std::string gob_cmd_update_nametag_attributes(video::SColor color) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES); + // parameters + writeU8(os, 1); // version for forward compatibility + writeARGB8(os, color); + return os.str(); +} diff --git a/src/genericobject.h b/src/genericobject.h index 29e5e29cb..b92570831 100644 --- a/src/genericobject.h +++ b/src/genericobject.h @@ -24,16 +24,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include <iostream> -#define GENERIC_CMD_SET_PROPERTIES 0 -#define GENERIC_CMD_UPDATE_POSITION 1 -#define GENERIC_CMD_SET_TEXTURE_MOD 2 -#define GENERIC_CMD_SET_SPRITE 3 -#define GENERIC_CMD_PUNCHED 4 -#define GENERIC_CMD_UPDATE_ARMOR_GROUPS 5 -#define GENERIC_CMD_SET_ANIMATION 6 -#define GENERIC_CMD_SET_BONE_POSITION 7 -#define GENERIC_CMD_SET_ATTACHMENT 8 -#define GENERIC_CMD_SET_PHYSICS_OVERRIDE 9 +enum GenericCMD { + GENERIC_CMD_SET_PROPERTIES, + GENERIC_CMD_UPDATE_POSITION, + GENERIC_CMD_SET_TEXTURE_MOD, + GENERIC_CMD_SET_SPRITE, + GENERIC_CMD_PUNCHED, + GENERIC_CMD_UPDATE_ARMOR_GROUPS, + GENERIC_CMD_SET_ANIMATION, + GENERIC_CMD_SET_BONE_POSITION, + GENERIC_CMD_ATTACH_TO, + GENERIC_CMD_SET_PHYSICS_OVERRIDE, + GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES +}; #include "object_properties.h" std::string gob_cmd_set_properties(const ObjectProperties &prop); @@ -66,11 +69,13 @@ std::string gob_cmd_update_armor_groups(const ItemGroupList &armor_groups); std::string gob_cmd_update_physics_override(float physics_override_speed, float physics_override_jump, float physics_override_gravity, bool sneak, bool sneak_glitch); -std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend); +std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend, bool frame_loop); std::string gob_cmd_update_bone_position(std::string bone, v3f position, v3f rotation); std::string gob_cmd_update_attachment(int parent_id, std::string bone, v3f position, v3f rotation); +std::string gob_cmd_update_nametag_attributes(video::SColor color); + #endif diff --git a/src/gettext.cpp b/src/gettext.cpp index fc7569418..0fd6b574e 100644 --- a/src/gettext.cpp +++ b/src/gettext.cpp @@ -102,8 +102,9 @@ const char* MSVC_LocaleLookup(const char* raw_shortname) { last_raw_value = shortname; - if (glb_supported_locales.find(narrow_to_wide(shortname)) != glb_supported_locales.end()) { - last_full_name = wide_to_narrow(glb_supported_locales[narrow_to_wide(shortname)]); + if (glb_supported_locales.find(utf8_to_wide(shortname)) != glb_supported_locales.end()) { + last_full_name = wide_to_utf8( + glb_supported_locales[utf8_to_wide(shortname)]); return last_full_name.c_str(); } @@ -236,8 +237,9 @@ void init_gettext(const char *path, const std::string &configured_language) { #endif #endif - bindtextdomain(PROJECT_NAME, path); - textdomain(PROJECT_NAME); + static std::string name = lowercase(PROJECT_NAME); + bindtextdomain(name.c_str(), path); + textdomain(name.c_str()); #if defined(_WIN32) // Set character encoding for Win32 diff --git a/src/gettext.h b/src/gettext.h index 8235efa8a..9b4e801f7 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -39,13 +39,13 @@ void init_gettext(const char *path, const std::string &configured_language, void init_gettext(const char *path, const std::string &configured_language); #endif -extern wchar_t *narrow_to_wide_c(const char *str); +extern wchar_t *utf8_to_wide_c(const char *str); // You must free the returned string! // The returned string is allocated using new inline const wchar_t *wgettext(const char *str) { - return narrow_to_wide_c(gettext(str)); + return utf8_to_wide_c(gettext(str)); } inline std::wstring wstrgettext(const std::string &text) diff --git a/src/gettime.h b/src/gettime.h index 2a6a211b8..44c159026 100644 --- a/src/gettime.h +++ b/src/gettime.h @@ -54,8 +54,8 @@ inline std::string getTimestamp() // This is not really thread-safe but it won't break anything // except its own output, so just go with it. struct tm *tm = localtime(&t); - char cs[20]; - strftime(cs, 20, "%H:%M:%S", tm); + char cs[20]; //YYYY-MM-DD HH:MM:SS + '\0' + strftime(cs, 20, "%Y-%m-%d %H:%M:%S", tm); return cs; } diff --git a/src/gmp/CMakeLists.txt b/src/gmp/CMakeLists.txt new file mode 100644 index 000000000..96ae8191d --- /dev/null +++ b/src/gmp/CMakeLists.txt @@ -0,0 +1,7 @@ +if(MSVC) + set(CMAKE_C_FLAGS_RELEASE "/MT /O2 /Ob2 /D NDEBUG") +endif() + +add_library(gmp mini-gmp.c) +target_link_libraries(gmp) + diff --git a/src/gmp/mini-gmp.c b/src/gmp/mini-gmp.c new file mode 100644 index 000000000..f3b43fbe8 --- /dev/null +++ b/src/gmp/mini-gmp.c @@ -0,0 +1,4130 @@ +/* mini-gmp, a minimalistic implementation of a GNU GMP subset. + + Contributed to the GNU project by Niels Möller + +Copyright 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1999, 2000, 2001, +2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 +Free Software Foundation, Inc. + +This file is part of the GNU MP Library. + +The GNU MP Library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3 of the License, or (at your +option) any later version. + +The GNU MP Library 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 the GNU MP Library. If not, see http://www.gnu.org/licenses/. */ + +/* NOTE: All functions in this file which are not declared in + mini-gmp.h are internal, and are not intended to be compatible + neither with GMP nor with future versions of mini-gmp. */ + +/* Much of the material copied from GMP files, including: gmp-impl.h, + longlong.h, mpn/generic/add_n.c, mpn/generic/addmul_1.c, + mpn/generic/lshift.c, mpn/generic/mul_1.c, + mpn/generic/mul_basecase.c, mpn/generic/rshift.c, + mpn/generic/sbpi1_div_qr.c, mpn/generic/sub_n.c, + mpn/generic/submul_1.c. */ + +#include <assert.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mini-gmp.h" + + +/* Macros */ +#define GMP_LIMB_BITS (sizeof(mp_limb_t) * CHAR_BIT) + +#define GMP_LIMB_MAX (~ (mp_limb_t) 0) +#define GMP_LIMB_HIGHBIT ((mp_limb_t) 1 << (GMP_LIMB_BITS - 1)) + +#define GMP_HLIMB_BIT ((mp_limb_t) 1 << (GMP_LIMB_BITS / 2)) +#define GMP_LLIMB_MASK (GMP_HLIMB_BIT - 1) + +#define GMP_ULONG_BITS (sizeof(unsigned long) * CHAR_BIT) +#define GMP_ULONG_HIGHBIT ((unsigned long) 1 << (GMP_ULONG_BITS - 1)) + +#define GMP_ABS(x) ((x) >= 0 ? (x) : -(x)) +#define GMP_NEG_CAST(T,x) (-((T)((x) + 1) - 1)) + +#define GMP_MIN(a, b) ((a) < (b) ? (a) : (b)) +#define GMP_MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define gmp_assert_nocarry(x) do { \ + mp_limb_t __cy = x; \ + assert (__cy == 0); \ + } while (0) + +#define gmp_clz(count, x) do { \ + mp_limb_t __clz_x = (x); \ + unsigned __clz_c; \ + for (__clz_c = 0; \ + (__clz_x & ((mp_limb_t) 0xff << (GMP_LIMB_BITS - 8))) == 0; \ + __clz_c += 8) \ + __clz_x <<= 8; \ + for (; (__clz_x & GMP_LIMB_HIGHBIT) == 0; __clz_c++) \ + __clz_x <<= 1; \ + (count) = __clz_c; \ + } while (0) + +#define gmp_ctz(count, x) do { \ + mp_limb_t __ctz_x = (x); \ + unsigned __ctz_c = 0; \ + gmp_clz (__ctz_c, __ctz_x & - __ctz_x); \ + (count) = GMP_LIMB_BITS - 1 - __ctz_c; \ + } while (0) + +#define gmp_add_ssaaaa(sh, sl, ah, al, bh, bl) \ + do { \ + mp_limb_t __x; \ + __x = (al) + (bl); \ + (sh) = (ah) + (bh) + (__x < (al)); \ + (sl) = __x; \ + } while (0) + +#define gmp_sub_ddmmss(sh, sl, ah, al, bh, bl) \ + do { \ + mp_limb_t __x; \ + __x = (al) - (bl); \ + (sh) = (ah) - (bh) - ((al) < (bl)); \ + (sl) = __x; \ + } while (0) + +#define gmp_umul_ppmm(w1, w0, u, v) \ + do { \ + mp_limb_t __x0, __x1, __x2, __x3; \ + unsigned __ul, __vl, __uh, __vh; \ + mp_limb_t __u = (u), __v = (v); \ + \ + __ul = __u & GMP_LLIMB_MASK; \ + __uh = __u >> (GMP_LIMB_BITS / 2); \ + __vl = __v & GMP_LLIMB_MASK; \ + __vh = __v >> (GMP_LIMB_BITS / 2); \ + \ + __x0 = (mp_limb_t) __ul * __vl; \ + __x1 = (mp_limb_t) __ul * __vh; \ + __x2 = (mp_limb_t) __uh * __vl; \ + __x3 = (mp_limb_t) __uh * __vh; \ + \ + __x1 += __x0 >> (GMP_LIMB_BITS / 2);/* this can't give carry */ \ + __x1 += __x2; /* but this indeed can */ \ + if (__x1 < __x2) /* did we get it? */ \ + __x3 += GMP_HLIMB_BIT; /* yes, add it in the proper pos. */ \ + \ + (w1) = __x3 + (__x1 >> (GMP_LIMB_BITS / 2)); \ + (w0) = (__x1 << (GMP_LIMB_BITS / 2)) + (__x0 & GMP_LLIMB_MASK); \ + } while (0) + +#define gmp_udiv_qrnnd_preinv(q, r, nh, nl, d, di) \ + do { \ + mp_limb_t _qh, _ql, _r, _mask; \ + gmp_umul_ppmm (_qh, _ql, (nh), (di)); \ + gmp_add_ssaaaa (_qh, _ql, _qh, _ql, (nh) + 1, (nl)); \ + _r = (nl) - _qh * (d); \ + _mask = -(mp_limb_t) (_r > _ql); /* both > and >= are OK */ \ + _qh += _mask; \ + _r += _mask & (d); \ + if (_r >= (d)) \ + { \ + _r -= (d); \ + _qh++; \ + } \ + \ + (r) = _r; \ + (q) = _qh; \ + } while (0) + +#define gmp_udiv_qr_3by2(q, r1, r0, n2, n1, n0, d1, d0, dinv) \ + do { \ + mp_limb_t _q0, _t1, _t0, _mask; \ + gmp_umul_ppmm ((q), _q0, (n2), (dinv)); \ + gmp_add_ssaaaa ((q), _q0, (q), _q0, (n2), (n1)); \ + \ + /* Compute the two most significant limbs of n - q'd */ \ + (r1) = (n1) - (d1) * (q); \ + gmp_sub_ddmmss ((r1), (r0), (r1), (n0), (d1), (d0)); \ + gmp_umul_ppmm (_t1, _t0, (d0), (q)); \ + gmp_sub_ddmmss ((r1), (r0), (r1), (r0), _t1, _t0); \ + (q)++; \ + \ + /* Conditionally adjust q and the remainders */ \ + _mask = - (mp_limb_t) ((r1) >= _q0); \ + (q) += _mask; \ + gmp_add_ssaaaa ((r1), (r0), (r1), (r0), _mask & (d1), _mask & (d0)); \ + if ((r1) >= (d1)) \ + { \ + if ((r1) > (d1) || (r0) >= (d0)) \ + { \ + (q)++; \ + gmp_sub_ddmmss ((r1), (r0), (r1), (r0), (d1), (d0)); \ + } \ + } \ + } while (0) + +/* Swap macros. */ +#define MP_LIMB_T_SWAP(x, y) \ + do { \ + mp_limb_t __mp_limb_t_swap__tmp = (x); \ + (x) = (y); \ + (y) = __mp_limb_t_swap__tmp; \ + } while (0) +#define MP_SIZE_T_SWAP(x, y) \ + do { \ + mp_size_t __mp_size_t_swap__tmp = (x); \ + (x) = (y); \ + (y) = __mp_size_t_swap__tmp; \ + } while (0) +#define MP_BITCNT_T_SWAP(x,y) \ + do { \ + mp_bitcnt_t __mp_bitcnt_t_swap__tmp = (x); \ + (x) = (y); \ + (y) = __mp_bitcnt_t_swap__tmp; \ + } while (0) +#define MP_PTR_SWAP(x, y) \ + do { \ + mp_ptr __mp_ptr_swap__tmp = (x); \ + (x) = (y); \ + (y) = __mp_ptr_swap__tmp; \ + } while (0) +#define MP_SRCPTR_SWAP(x, y) \ + do { \ + mp_srcptr __mp_srcptr_swap__tmp = (x); \ + (x) = (y); \ + (y) = __mp_srcptr_swap__tmp; \ + } while (0) + +#define MPN_PTR_SWAP(xp,xs, yp,ys) \ + do { \ + MP_PTR_SWAP (xp, yp); \ + MP_SIZE_T_SWAP (xs, ys); \ + } while(0) +#define MPN_SRCPTR_SWAP(xp,xs, yp,ys) \ + do { \ + MP_SRCPTR_SWAP (xp, yp); \ + MP_SIZE_T_SWAP (xs, ys); \ + } while(0) + +#define MPZ_PTR_SWAP(x, y) \ + do { \ + mpz_ptr __mpz_ptr_swap__tmp = (x); \ + (x) = (y); \ + (y) = __mpz_ptr_swap__tmp; \ + } while (0) +#define MPZ_SRCPTR_SWAP(x, y) \ + do { \ + mpz_srcptr __mpz_srcptr_swap__tmp = (x); \ + (x) = (y); \ + (y) = __mpz_srcptr_swap__tmp; \ + } while (0) + + +/* Memory allocation and other helper functions. */ +static void +gmp_die (const char *msg) +{ + fprintf (stderr, "%s\n", msg); + abort(); +} + +static void * +gmp_default_alloc (size_t size) +{ + void *p; + + assert (size > 0); + + p = malloc (size); + if (!p) + gmp_die("gmp_default_alloc: Virtual memory exhausted."); + + return p; +} + +static void * +gmp_default_realloc (void *old, size_t old_size, size_t new_size) +{ + mp_ptr p; + + p = realloc (old, new_size); + + if (!p) + gmp_die("gmp_default_realoc: Virtual memory exhausted."); + + return p; +} + +static void +gmp_default_free (void *p, size_t size) +{ + free (p); +} + +static void * (*gmp_allocate_func) (size_t) = gmp_default_alloc; +static void * (*gmp_reallocate_func) (void *, size_t, size_t) = gmp_default_realloc; +static void (*gmp_free_func) (void *, size_t) = gmp_default_free; + +void +mp_get_memory_functions (void *(**alloc_func) (size_t), + void *(**realloc_func) (void *, size_t, size_t), + void (**free_func) (void *, size_t)) +{ + if (alloc_func) + *alloc_func = gmp_allocate_func; + + if (realloc_func) + *realloc_func = gmp_reallocate_func; + + if (free_func) + *free_func = gmp_free_func; +} + +void +mp_set_memory_functions (void *(*alloc_func) (size_t), + void *(*realloc_func) (void *, size_t, size_t), + void (*free_func) (void *, size_t)) +{ + if (!alloc_func) + alloc_func = gmp_default_alloc; + if (!realloc_func) + realloc_func = gmp_default_realloc; + if (!free_func) + free_func = gmp_default_free; + + gmp_allocate_func = alloc_func; + gmp_reallocate_func = realloc_func; + gmp_free_func = free_func; +} + +#define gmp_xalloc(size) ((*gmp_allocate_func)((size))) +#define gmp_free(p) ((*gmp_free_func) ((p), 0)) + +static mp_ptr +gmp_xalloc_limbs (mp_size_t size) +{ + return gmp_xalloc (size * sizeof (mp_limb_t)); +} + +static mp_ptr +gmp_xrealloc_limbs (mp_ptr old, mp_size_t size) +{ + assert (size > 0); + return (*gmp_reallocate_func) (old, 0, size * sizeof (mp_limb_t)); +} + + +/* MPN interface */ + +void +mpn_copyi (mp_ptr d, mp_srcptr s, mp_size_t n) +{ + mp_size_t i; + for (i = 0; i < n; i++) + d[i] = s[i]; +} + +void +mpn_copyd (mp_ptr d, mp_srcptr s, mp_size_t n) +{ + while (n-- > 0) + d[n] = s[n]; +} + +int +mpn_cmp (mp_srcptr ap, mp_srcptr bp, mp_size_t n) +{ + for (; n > 0; n--) + { + if (ap[n-1] < bp[n-1]) + return -1; + else if (ap[n-1] > bp[n-1]) + return 1; + } + return 0; +} + +static int +mpn_cmp4 (mp_srcptr ap, mp_size_t an, mp_srcptr bp, mp_size_t bn) +{ + if (an > bn) + return 1; + else if (an < bn) + return -1; + else + return mpn_cmp (ap, bp, an); +} + +static mp_size_t +mpn_normalized_size (mp_srcptr xp, mp_size_t n) +{ + for (; n > 0 && xp[n-1] == 0; n--) + ; + return n; +} + +#define mpn_zero_p(xp, n) (mpn_normalized_size ((xp), (n)) == 0) + +mp_limb_t +mpn_add_1 (mp_ptr rp, mp_srcptr ap, mp_size_t n, mp_limb_t b) +{ + mp_size_t i; + + assert (n > 0); + + for (i = 0; i < n; i++) + { + mp_limb_t r = ap[i] + b; + /* Carry out */ + b = (r < b); + rp[i] = r; + } + return b; +} + +mp_limb_t +mpn_add_n (mp_ptr rp, mp_srcptr ap, mp_srcptr bp, mp_size_t n) +{ + mp_size_t i; + mp_limb_t cy; + + for (i = 0, cy = 0; i < n; i++) + { + mp_limb_t a, b, r; + a = ap[i]; b = bp[i]; + r = a + cy; + cy = (r < cy); + r += b; + cy += (r < b); + rp[i] = r; + } + return cy; +} + +mp_limb_t +mpn_add (mp_ptr rp, mp_srcptr ap, mp_size_t an, mp_srcptr bp, mp_size_t bn) +{ + mp_limb_t cy; + + assert (an >= bn); + + cy = mpn_add_n (rp, ap, bp, bn); + if (an > bn) + cy = mpn_add_1 (rp + bn, ap + bn, an - bn, cy); + return cy; +} + +mp_limb_t +mpn_sub_1 (mp_ptr rp, mp_srcptr ap, mp_size_t n, mp_limb_t b) +{ + mp_size_t i; + + assert (n > 0); + + for (i = 0; i < n; i++) + { + mp_limb_t a = ap[i]; + /* Carry out */ + mp_limb_t cy = a < b;; + rp[i] = a - b; + b = cy; + } + return b; +} + +mp_limb_t +mpn_sub_n (mp_ptr rp, mp_srcptr ap, mp_srcptr bp, mp_size_t n) +{ + mp_size_t i; + mp_limb_t cy; + + for (i = 0, cy = 0; i < n; i++) + { + mp_limb_t a, b; + a = ap[i]; b = bp[i]; + b += cy; + cy = (b < cy); + cy += (a < b); + rp[i] = a - b; + } + return cy; +} + +mp_limb_t +mpn_sub (mp_ptr rp, mp_srcptr ap, mp_size_t an, mp_srcptr bp, mp_size_t bn) +{ + mp_limb_t cy; + + assert (an >= bn); + + cy = mpn_sub_n (rp, ap, bp, bn); + if (an > bn) + cy = mpn_sub_1 (rp + bn, ap + bn, an - bn, cy); + return cy; +} + +mp_limb_t +mpn_mul_1 (mp_ptr rp, mp_srcptr up, mp_size_t n, mp_limb_t vl) +{ + mp_limb_t ul, cl, hpl, lpl; + + assert (n >= 1); + + cl = 0; + do + { + ul = *up++; + gmp_umul_ppmm (hpl, lpl, ul, vl); + + lpl += cl; + cl = (lpl < cl) + hpl; + + *rp++ = lpl; + } + while (--n != 0); + + return cl; +} + +mp_limb_t +mpn_addmul_1 (mp_ptr rp, mp_srcptr up, mp_size_t n, mp_limb_t vl) +{ + mp_limb_t ul, cl, hpl, lpl, rl; + + assert (n >= 1); + + cl = 0; + do + { + ul = *up++; + gmp_umul_ppmm (hpl, lpl, ul, vl); + + lpl += cl; + cl = (lpl < cl) + hpl; + + rl = *rp; + lpl = rl + lpl; + cl += lpl < rl; + *rp++ = lpl; + } + while (--n != 0); + + return cl; +} + +mp_limb_t +mpn_submul_1 (mp_ptr rp, mp_srcptr up, mp_size_t n, mp_limb_t vl) +{ + mp_limb_t ul, cl, hpl, lpl, rl; + + assert (n >= 1); + + cl = 0; + do + { + ul = *up++; + gmp_umul_ppmm (hpl, lpl, ul, vl); + + lpl += cl; + cl = (lpl < cl) + hpl; + + rl = *rp; + lpl = rl - lpl; + cl += lpl > rl; + *rp++ = lpl; + } + while (--n != 0); + + return cl; +} + +mp_limb_t +mpn_mul (mp_ptr rp, mp_srcptr up, mp_size_t un, mp_srcptr vp, mp_size_t vn) +{ + assert (un >= vn); + assert (vn >= 1); + + /* We first multiply by the low order limb. This result can be + stored, not added, to rp. We also avoid a loop for zeroing this + way. */ + + rp[un] = mpn_mul_1 (rp, up, un, vp[0]); + rp += 1, vp += 1, vn -= 1; + + /* Now accumulate the product of up[] and the next higher limb from + vp[]. */ + + while (vn >= 1) + { + rp[un] = mpn_addmul_1 (rp, up, un, vp[0]); + rp += 1, vp += 1, vn -= 1; + } + return rp[un - 1]; +} + +void +mpn_mul_n (mp_ptr rp, mp_srcptr ap, mp_srcptr bp, mp_size_t n) +{ + mpn_mul (rp, ap, n, bp, n); +} + +void +mpn_sqr (mp_ptr rp, mp_srcptr ap, mp_size_t n) +{ + mpn_mul (rp, ap, n, ap, n); +} + +mp_limb_t +mpn_lshift (mp_ptr rp, mp_srcptr up, mp_size_t n, unsigned int cnt) +{ + mp_limb_t high_limb, low_limb; + unsigned int tnc; + mp_size_t i; + mp_limb_t retval; + + assert (n >= 1); + assert (cnt >= 1); + assert (cnt < GMP_LIMB_BITS); + + up += n; + rp += n; + + tnc = GMP_LIMB_BITS - cnt; + low_limb = *--up; + retval = low_limb >> tnc; + high_limb = (low_limb << cnt); + + for (i = n - 1; i != 0; i--) + { + low_limb = *--up; + *--rp = high_limb | (low_limb >> tnc); + high_limb = (low_limb << cnt); + } + *--rp = high_limb; + + return retval; +} + +mp_limb_t +mpn_rshift (mp_ptr rp, mp_srcptr up, mp_size_t n, unsigned int cnt) +{ + mp_limb_t high_limb, low_limb; + unsigned int tnc; + mp_size_t i; + mp_limb_t retval; + + assert (n >= 1); + assert (cnt >= 1); + assert (cnt < GMP_LIMB_BITS); + + tnc = GMP_LIMB_BITS - cnt; + high_limb = *up++; + retval = (high_limb << tnc); + low_limb = high_limb >> cnt; + + for (i = n - 1; i != 0; i--) + { + high_limb = *up++; + *rp++ = low_limb | (high_limb << tnc); + low_limb = high_limb >> cnt; + } + *rp = low_limb; + + return retval; +} + + +/* MPN division interface. */ +mp_limb_t +mpn_invert_3by2 (mp_limb_t u1, mp_limb_t u0) +{ + mp_limb_t r, p, m; + unsigned ul, uh; + unsigned ql, qh; + + /* First, do a 2/1 inverse. */ + /* The inverse m is defined as floor( (B^2 - 1 - u1)/u1 ), so that 0 < + * B^2 - (B + m) u1 <= u1 */ + assert (u1 >= GMP_LIMB_HIGHBIT); + + ul = u1 & GMP_LLIMB_MASK; + uh = u1 >> (GMP_LIMB_BITS / 2); + + qh = ~u1 / uh; + r = ((~u1 - (mp_limb_t) qh * uh) << (GMP_LIMB_BITS / 2)) | GMP_LLIMB_MASK; + + p = (mp_limb_t) qh * ul; + /* Adjustment steps taken from udiv_qrnnd_c */ + if (r < p) + { + qh--; + r += u1; + if (r >= u1) /* i.e. we didn't get carry when adding to r */ + if (r < p) + { + qh--; + r += u1; + } + } + r -= p; + + /* Do a 3/2 division (with half limb size) */ + p = (r >> (GMP_LIMB_BITS / 2)) * qh + r; + ql = (p >> (GMP_LIMB_BITS / 2)) + 1; + + /* By the 3/2 method, we don't need the high half limb. */ + r = (r << (GMP_LIMB_BITS / 2)) + GMP_LLIMB_MASK - ql * u1; + + if (r >= (p << (GMP_LIMB_BITS / 2))) + { + ql--; + r += u1; + } + m = ((mp_limb_t) qh << (GMP_LIMB_BITS / 2)) + ql; + if (r >= u1) + { + m++; + r -= u1; + } + + if (u0 > 0) + { + mp_limb_t th, tl; + r = ~r; + r += u0; + if (r < u0) + { + m--; + if (r >= u1) + { + m--; + r -= u1; + } + r -= u1; + } + gmp_umul_ppmm (th, tl, u0, m); + r += th; + if (r < th) + { + m--; + if (r > u1 || (r == u1 && tl > u0)) + m--; + } + } + + return m; +} + +struct gmp_div_inverse +{ + /* Normalization shift count. */ + unsigned shift; + /* Normalized divisor (d0 unused for mpn_div_qr_1) */ + mp_limb_t d1, d0; + /* Inverse, for 2/1 or 3/2. */ + mp_limb_t di; +}; + +static void +mpn_div_qr_1_invert (struct gmp_div_inverse *inv, mp_limb_t d) +{ + unsigned shift; + + assert (d > 0); + gmp_clz (shift, d); + inv->shift = shift; + inv->d1 = d << shift; + inv->di = mpn_invert_limb (inv->d1); +} + +static void +mpn_div_qr_2_invert (struct gmp_div_inverse *inv, + mp_limb_t d1, mp_limb_t d0) +{ + unsigned shift; + + assert (d1 > 0); + gmp_clz (shift, d1); + inv->shift = shift; + if (shift > 0) + { + d1 = (d1 << shift) | (d0 >> (GMP_LIMB_BITS - shift)); + d0 <<= shift; + } + inv->d1 = d1; + inv->d0 = d0; + inv->di = mpn_invert_3by2 (d1, d0); +} + +static void +mpn_div_qr_invert (struct gmp_div_inverse *inv, + mp_srcptr dp, mp_size_t dn) +{ + assert (dn > 0); + + if (dn == 1) + mpn_div_qr_1_invert (inv, dp[0]); + else if (dn == 2) + mpn_div_qr_2_invert (inv, dp[1], dp[0]); + else + { + unsigned shift; + mp_limb_t d1, d0; + + d1 = dp[dn-1]; + d0 = dp[dn-2]; + assert (d1 > 0); + gmp_clz (shift, d1); + inv->shift = shift; + if (shift > 0) + { + d1 = (d1 << shift) | (d0 >> (GMP_LIMB_BITS - shift)); + d0 = (d0 << shift) | (dp[dn-3] >> (GMP_LIMB_BITS - shift)); + } + inv->d1 = d1; + inv->d0 = d0; + inv->di = mpn_invert_3by2 (d1, d0); + } +} + +/* Not matching current public gmp interface, rather corresponding to + the sbpi1_div_* functions. */ +static mp_limb_t +mpn_div_qr_1_preinv (mp_ptr qp, mp_srcptr np, mp_size_t nn, + const struct gmp_div_inverse *inv) +{ + mp_limb_t d, di; + mp_limb_t r; + mp_ptr tp = NULL; + + if (inv->shift > 0) + { + tp = gmp_xalloc_limbs (nn); + r = mpn_lshift (tp, np, nn, inv->shift); + np = tp; + } + else + r = 0; + + d = inv->d1; + di = inv->di; + while (nn-- > 0) + { + mp_limb_t q; + + gmp_udiv_qrnnd_preinv (q, r, r, np[nn], d, di); + if (qp) + qp[nn] = q; + } + if (inv->shift > 0) + gmp_free (tp); + + return r >> inv->shift; +} + +static mp_limb_t +mpn_div_qr_1 (mp_ptr qp, mp_srcptr np, mp_size_t nn, mp_limb_t d) +{ + assert (d > 0); + + /* Special case for powers of two. */ + if (d > 1 && (d & (d-1)) == 0) + { + unsigned shift; + mp_limb_t r = np[0] & (d-1); + gmp_ctz (shift, d); + if (qp) + mpn_rshift (qp, np, nn, shift); + + return r; + } + else + { + struct gmp_div_inverse inv; + mpn_div_qr_1_invert (&inv, d); + return mpn_div_qr_1_preinv (qp, np, nn, &inv); + } +} + +static void +mpn_div_qr_2_preinv (mp_ptr qp, mp_ptr rp, mp_srcptr np, mp_size_t nn, + const struct gmp_div_inverse *inv) +{ + unsigned shift; + mp_size_t i; + mp_limb_t d1, d0, di, r1, r0; + mp_ptr tp; + + assert (nn >= 2); + shift = inv->shift; + d1 = inv->d1; + d0 = inv->d0; + di = inv->di; + + if (shift > 0) + { + tp = gmp_xalloc_limbs (nn); + r1 = mpn_lshift (tp, np, nn, shift); + np = tp; + } + else + r1 = 0; + + r0 = np[nn - 1]; + + for (i = nn - 2; i >= 0; i--) + { + mp_limb_t n0, q; + n0 = np[i]; + gmp_udiv_qr_3by2 (q, r1, r0, r1, r0, n0, d1, d0, di); + + if (qp) + qp[i] = q; + } + + if (shift > 0) + { + assert ((r0 << (GMP_LIMB_BITS - shift)) == 0); + r0 = (r0 >> shift) | (r1 << (GMP_LIMB_BITS - shift)); + r1 >>= shift; + + gmp_free (tp); + } + + rp[1] = r1; + rp[0] = r0; +} + +#if 0 +static void +mpn_div_qr_2 (mp_ptr qp, mp_ptr rp, mp_srcptr np, mp_size_t nn, + mp_limb_t d1, mp_limb_t d0) +{ + struct gmp_div_inverse inv; + assert (nn >= 2); + + mpn_div_qr_2_invert (&inv, d1, d0); + mpn_div_qr_2_preinv (qp, rp, np, nn, &inv); +} +#endif + +static void +mpn_div_qr_pi1 (mp_ptr qp, + mp_ptr np, mp_size_t nn, mp_limb_t n1, + mp_srcptr dp, mp_size_t dn, + mp_limb_t dinv) +{ + mp_size_t i; + + mp_limb_t d1, d0; + mp_limb_t cy, cy1; + mp_limb_t q; + + assert (dn > 2); + assert (nn >= dn); + + d1 = dp[dn - 1]; + d0 = dp[dn - 2]; + + assert ((d1 & GMP_LIMB_HIGHBIT) != 0); + /* Iteration variable is the index of the q limb. + * + * We divide <n1, np[dn-1+i], np[dn-2+i], np[dn-3+i],..., np[i]> + * by <d1, d0, dp[dn-3], ..., dp[0] > + */ + + for (i = nn - dn; i >= 0; i--) + { + mp_limb_t n0 = np[dn-1+i]; + + if (n1 == d1 && n0 == d0) + { + q = GMP_LIMB_MAX; + mpn_submul_1 (np+i, dp, dn, q); + n1 = np[dn-1+i]; /* update n1, last loop's value will now be invalid */ + } + else + { + gmp_udiv_qr_3by2 (q, n1, n0, n1, n0, np[dn-2+i], d1, d0, dinv); + + cy = mpn_submul_1 (np + i, dp, dn-2, q); + + cy1 = n0 < cy; + n0 = n0 - cy; + cy = n1 < cy1; + n1 = n1 - cy1; + np[dn-2+i] = n0; + + if (cy != 0) + { + n1 += d1 + mpn_add_n (np + i, np + i, dp, dn - 1); + q--; + } + } + + if (qp) + qp[i] = q; + } + + np[dn - 1] = n1; +} + +static void +mpn_div_qr_preinv (mp_ptr qp, mp_ptr np, mp_size_t nn, + mp_srcptr dp, mp_size_t dn, + const struct gmp_div_inverse *inv) +{ + assert (dn > 0); + assert (nn >= dn); + + if (dn == 1) + np[0] = mpn_div_qr_1_preinv (qp, np, nn, inv); + else if (dn == 2) + mpn_div_qr_2_preinv (qp, np, np, nn, inv); + else + { + mp_limb_t nh; + unsigned shift; + + assert (inv->d1 == dp[dn-1]); + assert (inv->d0 == dp[dn-2]); + assert ((inv->d1 & GMP_LIMB_HIGHBIT) != 0); + + shift = inv->shift; + if (shift > 0) + nh = mpn_lshift (np, np, nn, shift); + else + nh = 0; + + mpn_div_qr_pi1 (qp, np, nn, nh, dp, dn, inv->di); + + if (shift > 0) + gmp_assert_nocarry (mpn_rshift (np, np, dn, shift)); + } +} + +static void +mpn_div_qr (mp_ptr qp, mp_ptr np, mp_size_t nn, mp_srcptr dp, mp_size_t dn) +{ + struct gmp_div_inverse inv; + mp_ptr tp = NULL; + + assert (dn > 0); + assert (nn >= dn); + + mpn_div_qr_invert (&inv, dp, dn); + if (dn > 2 && inv.shift > 0) + { + tp = gmp_xalloc_limbs (dn); + gmp_assert_nocarry (mpn_lshift (tp, dp, dn, inv.shift)); + dp = tp; + } + mpn_div_qr_preinv (qp, np, nn, dp, dn, &inv); + if (tp) + gmp_free (tp); +} + + +/* MPN base conversion. */ +static unsigned +mpn_base_power_of_two_p (unsigned b) +{ + switch (b) + { + case 2: return 1; + case 4: return 2; + case 8: return 3; + case 16: return 4; + case 32: return 5; + case 64: return 6; + case 128: return 7; + case 256: return 8; + default: return 0; + } +} + +struct mpn_base_info +{ + /* bb is the largest power of the base which fits in one limb, and + exp is the corresponding exponent. */ + unsigned exp; + mp_limb_t bb; +}; + +static void +mpn_get_base_info (struct mpn_base_info *info, mp_limb_t b) +{ + mp_limb_t m; + mp_limb_t p; + unsigned exp; + + m = GMP_LIMB_MAX / b; + for (exp = 1, p = b; p <= m; exp++) + p *= b; + + info->exp = exp; + info->bb = p; +} + +static mp_bitcnt_t +mpn_limb_size_in_base_2 (mp_limb_t u) +{ + unsigned shift; + + assert (u > 0); + gmp_clz (shift, u); + return GMP_LIMB_BITS - shift; +} + +static size_t +mpn_get_str_bits (unsigned char *sp, unsigned bits, mp_srcptr up, mp_size_t un) +{ + unsigned char mask; + size_t sn, j; + mp_size_t i; + int shift; + + sn = ((un - 1) * GMP_LIMB_BITS + mpn_limb_size_in_base_2 (up[un-1]) + + bits - 1) / bits; + + mask = (1U << bits) - 1; + + for (i = 0, j = sn, shift = 0; j-- > 0;) + { + unsigned char digit = up[i] >> shift; + + shift += bits; + + if (shift >= GMP_LIMB_BITS && ++i < un) + { + shift -= GMP_LIMB_BITS; + digit |= up[i] << (bits - shift); + } + sp[j] = digit & mask; + } + return sn; +} + +/* We generate digits from the least significant end, and reverse at + the end. */ +static size_t +mpn_limb_get_str (unsigned char *sp, mp_limb_t w, + const struct gmp_div_inverse *binv) +{ + mp_size_t i; + for (i = 0; w > 0; i++) + { + mp_limb_t h, l, r; + + h = w >> (GMP_LIMB_BITS - binv->shift); + l = w << binv->shift; + + gmp_udiv_qrnnd_preinv (w, r, h, l, binv->d1, binv->di); + assert ( (r << (GMP_LIMB_BITS - binv->shift)) == 0); + r >>= binv->shift; + + sp[i] = r; + } + return i; +} + +static size_t +mpn_get_str_other (unsigned char *sp, + int base, const struct mpn_base_info *info, + mp_ptr up, mp_size_t un) +{ + struct gmp_div_inverse binv; + size_t sn; + size_t i; + + mpn_div_qr_1_invert (&binv, base); + + sn = 0; + + if (un > 1) + { + struct gmp_div_inverse bbinv; + mpn_div_qr_1_invert (&bbinv, info->bb); + + do + { + mp_limb_t w; + size_t done; + w = mpn_div_qr_1_preinv (up, up, un, &bbinv); + un -= (up[un-1] == 0); + done = mpn_limb_get_str (sp + sn, w, &binv); + + for (sn += done; done < info->exp; done++) + sp[sn++] = 0; + } + while (un > 1); + } + sn += mpn_limb_get_str (sp + sn, up[0], &binv); + + /* Reverse order */ + for (i = 0; 2*i + 1 < sn; i++) + { + unsigned char t = sp[i]; + sp[i] = sp[sn - i - 1]; + sp[sn - i - 1] = t; + } + + return sn; +} + +size_t +mpn_get_str (unsigned char *sp, int base, mp_ptr up, mp_size_t un) +{ + unsigned bits; + + assert (un > 0); + assert (up[un-1] > 0); + + bits = mpn_base_power_of_two_p (base); + if (bits) + return mpn_get_str_bits (sp, bits, up, un); + else + { + struct mpn_base_info info; + + mpn_get_base_info (&info, base); + return mpn_get_str_other (sp, base, &info, up, un); + } +} + +static mp_size_t +mpn_set_str_bits (mp_ptr rp, const unsigned char *sp, size_t sn, + unsigned bits) +{ + mp_size_t rn; + size_t j; + unsigned shift; + + for (j = sn, rn = 0, shift = 0; j-- > 0; ) + { + if (shift == 0) + { + rp[rn++] = sp[j]; + shift += bits; + } + else + { + rp[rn-1] |= (mp_limb_t) sp[j] << shift; + shift += bits; + if (shift >= GMP_LIMB_BITS) + { + shift -= GMP_LIMB_BITS; + if (shift > 0) + rp[rn++] = (mp_limb_t) sp[j] >> (bits - shift); + } + } + } + rn = mpn_normalized_size (rp, rn); + return rn; +} + +static mp_size_t +mpn_set_str_other (mp_ptr rp, const unsigned char *sp, size_t sn, + mp_limb_t b, const struct mpn_base_info *info) +{ + mp_size_t rn; + mp_limb_t w; + unsigned first; + unsigned k; + size_t j; + + first = 1 + (sn - 1) % info->exp; + + j = 0; + w = sp[j++]; + for (k = 1; k < first; k++) + w = w * b + sp[j++]; + + rp[0] = w; + + for (rn = (w > 0); j < sn;) + { + mp_limb_t cy; + + w = sp[j++]; + for (k = 1; k < info->exp; k++) + w = w * b + sp[j++]; + + cy = mpn_mul_1 (rp, rp, rn, info->bb); + cy += mpn_add_1 (rp, rp, rn, w); + if (cy > 0) + rp[rn++] = cy; + } + assert (j == sn); + + return rn; +} + +mp_size_t +mpn_set_str (mp_ptr rp, const unsigned char *sp, size_t sn, int base) +{ + unsigned bits; + + if (sn == 0) + return 0; + + bits = mpn_base_power_of_two_p (base); + if (bits) + return mpn_set_str_bits (rp, sp, sn, bits); + else + { + struct mpn_base_info info; + + mpn_get_base_info (&info, base); + return mpn_set_str_other (rp, sp, sn, base, &info); + } +} + + +/* MPZ interface */ +void +mpz_init (mpz_t r) +{ + r->_mp_alloc = 1; + r->_mp_size = 0; + r->_mp_d = gmp_xalloc_limbs (1); +} + +/* The utility of this function is a bit limited, since many functions + assings the result variable using mpz_swap. */ +void +mpz_init2 (mpz_t r, mp_bitcnt_t bits) +{ + mp_size_t rn; + + bits -= (bits != 0); /* Round down, except if 0 */ + rn = 1 + bits / GMP_LIMB_BITS; + + r->_mp_alloc = rn; + r->_mp_size = 0; + r->_mp_d = gmp_xalloc_limbs (rn); +} + +void +mpz_clear (mpz_t r) +{ + gmp_free (r->_mp_d); +} + +static void * +mpz_realloc (mpz_t r, mp_size_t size) +{ + size = GMP_MAX (size, 1); + + r->_mp_d = gmp_xrealloc_limbs (r->_mp_d, size); + r->_mp_alloc = size; + + if (GMP_ABS (r->_mp_size) > size) + r->_mp_size = 0; + + return r->_mp_d; +} + +/* Realloc for an mpz_t WHAT if it has less than NEEDED limbs. */ +#define MPZ_REALLOC(z,n) ((n) > (z)->_mp_alloc \ + ? mpz_realloc(z,n) \ + : (z)->_mp_d) + +/* MPZ assignment and basic conversions. */ +void +mpz_set_si (mpz_t r, signed long int x) +{ + if (x >= 0) + mpz_set_ui (r, x); + else /* (x < 0) */ + { + r->_mp_size = -1; + r->_mp_d[0] = GMP_NEG_CAST (unsigned long int, x); + } +} + +void +mpz_set_ui (mpz_t r, unsigned long int x) +{ + if (x > 0) + { + r->_mp_size = 1; + r->_mp_d[0] = x; + } + else + r->_mp_size = 0; +} + +void +mpz_set (mpz_t r, const mpz_t x) +{ + /* Allow the NOP r == x */ + if (r != x) + { + mp_size_t n; + mp_ptr rp; + + n = GMP_ABS (x->_mp_size); + rp = MPZ_REALLOC (r, n); + + mpn_copyi (rp, x->_mp_d, n); + r->_mp_size = x->_mp_size; + } +} + +void +mpz_init_set_si (mpz_t r, signed long int x) +{ + mpz_init (r); + mpz_set_si (r, x); +} + +void +mpz_init_set_ui (mpz_t r, unsigned long int x) +{ + mpz_init (r); + mpz_set_ui (r, x); +} + +void +mpz_init_set (mpz_t r, const mpz_t x) +{ + mpz_init (r); + mpz_set (r, x); +} + +int +mpz_fits_slong_p (const mpz_t u) +{ + mp_size_t us = u->_mp_size; + + if (us == 0) + return 1; + else if (us == 1) + return u->_mp_d[0] < GMP_LIMB_HIGHBIT; + else if (us == -1) + return u->_mp_d[0] <= GMP_LIMB_HIGHBIT; + else + return 0; +} + +int +mpz_fits_ulong_p (const mpz_t u) +{ + mp_size_t us = u->_mp_size; + + return us == 0 || us == 1; +} + +long int +mpz_get_si (const mpz_t u) +{ + mp_size_t us = u->_mp_size; + + if (us > 0) + return (long) (u->_mp_d[0] & ~GMP_LIMB_HIGHBIT); + else if (us < 0) + return (long) (- u->_mp_d[0] | GMP_LIMB_HIGHBIT); + else + return 0; +} + +unsigned long int +mpz_get_ui (const mpz_t u) +{ + return u->_mp_size == 0 ? 0 : u->_mp_d[0]; +} + +size_t +mpz_size (const mpz_t u) +{ + return GMP_ABS (u->_mp_size); +} + +mp_limb_t +mpz_getlimbn (const mpz_t u, mp_size_t n) +{ + if (n >= 0 && n < GMP_ABS (u->_mp_size)) + return u->_mp_d[n]; + else + return 0; +} + + +/* Conversions and comparison to double. */ +void +mpz_set_d (mpz_t r, double x) +{ + int sign; + mp_ptr rp; + mp_size_t rn, i; + double B; + double Bi; + mp_limb_t f; + + /* x != x is true when x is a NaN, and x == x * 0.5 is true when x is + zero or infinity. */ + if (x == 0.0 || x != x || x == x * 0.5) + { + r->_mp_size = 0; + return; + } + + if (x < 0.0) + { + x = - x; + sign = 1; + } + else + sign = 0; + + if (x < 1.0) + { + r->_mp_size = 0; + return; + } + B = 2.0 * (double) GMP_LIMB_HIGHBIT; + Bi = 1.0 / B; + for (rn = 1; x >= B; rn++) + x *= Bi; + + rp = MPZ_REALLOC (r, rn); + + f = (mp_limb_t) x; + x -= f; + assert (x < 1.0); + rp[rn-1] = f; + for (i = rn-1; i-- > 0; ) + { + x = B * x; + f = (mp_limb_t) x; + x -= f; + assert (x < 1.0); + rp[i] = f; + } + + r->_mp_size = sign ? - rn : rn; +} + +void +mpz_init_set_d (mpz_t r, double x) +{ + mpz_init (r); + mpz_set_d (r, x); +} + +double +mpz_get_d (const mpz_t u) +{ + mp_size_t un; + double x; + double B = 2.0 * (double) GMP_LIMB_HIGHBIT; + + un = GMP_ABS (u->_mp_size); + + if (un == 0) + return 0.0; + + x = u->_mp_d[--un]; + while (un > 0) + x = B*x + u->_mp_d[--un]; + + if (u->_mp_size < 0) + x = -x; + + return x; +} + +int +mpz_cmpabs_d (const mpz_t x, double d) +{ + mp_size_t xn; + double B, Bi; + mp_size_t i; + + xn = x->_mp_size; + d = GMP_ABS (d); + + if (xn != 0) + { + xn = GMP_ABS (xn); + + B = 2.0 * (double) GMP_LIMB_HIGHBIT; + Bi = 1.0 / B; + + /* Scale d so it can be compared with the top limb. */ + for (i = 1; i < xn; i++) + d *= Bi; + + if (d >= B) + return -1; + + /* Compare floor(d) to top limb, subtract and cancel when equal. */ + for (i = xn; i-- > 0;) + { + mp_limb_t f, xl; + + f = (mp_limb_t) d; + xl = x->_mp_d[i]; + if (xl > f) + return 1; + else if (xl < f) + return -1; + d = B * (d - f); + } + } + return - (d > 0.0); +} + +int +mpz_cmp_d (const mpz_t x, double d) +{ + if (x->_mp_size < 0) + { + if (d >= 0.0) + return -1; + else + return -mpz_cmpabs_d (x, d); + } + else + { + if (d < 0.0) + return 1; + else + return mpz_cmpabs_d (x, d); + } +} + + +/* MPZ comparisons and the like. */ +int +mpz_sgn (const mpz_t u) +{ + mp_size_t usize = u->_mp_size; + + if (usize > 0) + return 1; + else if (usize < 0) + return -1; + else + return 0; +} + +int +mpz_cmp_si (const mpz_t u, long v) +{ + mp_size_t usize = u->_mp_size; + + if (usize < -1) + return -1; + else if (v >= 0) + return mpz_cmp_ui (u, v); + else if (usize >= 0) + return 1; + else /* usize == -1 */ + { + mp_limb_t ul = u->_mp_d[0]; + if ((mp_limb_t)GMP_NEG_CAST (unsigned long int, v) < ul) + return -1; + else if ( (mp_limb_t)GMP_NEG_CAST (unsigned long int, v) > ul) + return 1; + } + return 0; +} + +int +mpz_cmp_ui (const mpz_t u, unsigned long v) +{ + mp_size_t usize = u->_mp_size; + + if (usize > 1) + return 1; + else if (usize < 0) + return -1; + else + { + mp_limb_t ul = (usize > 0) ? u->_mp_d[0] : 0; + if (ul > v) + return 1; + else if (ul < v) + return -1; + } + return 0; +} + +int +mpz_cmp (const mpz_t a, const mpz_t b) +{ + mp_size_t asize = a->_mp_size; + mp_size_t bsize = b->_mp_size; + + if (asize > bsize) + return 1; + else if (asize < bsize) + return -1; + else if (asize > 0) + return mpn_cmp (a->_mp_d, b->_mp_d, asize); + else if (asize < 0) + return -mpn_cmp (a->_mp_d, b->_mp_d, -asize); + else + return 0; +} + +int +mpz_cmpabs_ui (const mpz_t u, unsigned long v) +{ + mp_size_t un = GMP_ABS (u->_mp_size); + mp_limb_t ul; + + if (un > 1) + return 1; + + ul = (un == 1) ? u->_mp_d[0] : 0; + + if (ul > v) + return 1; + else if (ul < v) + return -1; + else + return 0; +} + +int +mpz_cmpabs (const mpz_t u, const mpz_t v) +{ + return mpn_cmp4 (u->_mp_d, GMP_ABS (u->_mp_size), + v->_mp_d, GMP_ABS (v->_mp_size)); +} + +void +mpz_abs (mpz_t r, const mpz_t u) +{ + if (r != u) + mpz_set (r, u); + + r->_mp_size = GMP_ABS (r->_mp_size); +} + +void +mpz_neg (mpz_t r, const mpz_t u) +{ + if (r != u) + mpz_set (r, u); + + r->_mp_size = -r->_mp_size; +} + +void +mpz_swap (mpz_t u, mpz_t v) +{ + MP_SIZE_T_SWAP (u->_mp_size, v->_mp_size); + MP_SIZE_T_SWAP (u->_mp_alloc, v->_mp_alloc); + MP_PTR_SWAP (u->_mp_d, v->_mp_d); +} + + +/* MPZ addition and subtraction */ + +/* Adds to the absolute value. Returns new size, but doesn't store it. */ +static mp_size_t +mpz_abs_add_ui (mpz_t r, const mpz_t a, unsigned long b) +{ + mp_size_t an; + mp_ptr rp; + mp_limb_t cy; + + an = GMP_ABS (a->_mp_size); + if (an == 0) + { + r->_mp_d[0] = b; + return b > 0; + } + + rp = MPZ_REALLOC (r, an + 1); + + cy = mpn_add_1 (rp, a->_mp_d, an, b); + rp[an] = cy; + an += (cy > 0); + + return an; +} + +/* Subtract from the absolute value. Returns new size, (or -1 on underflow), + but doesn't store it. */ +static mp_size_t +mpz_abs_sub_ui (mpz_t r, const mpz_t a, unsigned long b) +{ + mp_size_t an = GMP_ABS (a->_mp_size); + mp_ptr rp = MPZ_REALLOC (r, an); + + if (an == 0) + { + rp[0] = b; + return -(b > 0); + } + else if (an == 1 && a->_mp_d[0] < b) + { + rp[0] = b - a->_mp_d[0]; + return -1; + } + else + { + gmp_assert_nocarry (mpn_sub_1 (rp, a->_mp_d, an, b)); + return mpn_normalized_size (rp, an); + } +} + +void +mpz_add_ui (mpz_t r, const mpz_t a, unsigned long b) +{ + if (a->_mp_size >= 0) + r->_mp_size = mpz_abs_add_ui (r, a, b); + else + r->_mp_size = -mpz_abs_sub_ui (r, a, b); +} + +void +mpz_sub_ui (mpz_t r, const mpz_t a, unsigned long b) +{ + if (a->_mp_size < 0) + r->_mp_size = -mpz_abs_add_ui (r, a, b); + else + r->_mp_size = mpz_abs_sub_ui (r, a, b); +} + +void +mpz_ui_sub (mpz_t r, unsigned long a, const mpz_t b) +{ + if (b->_mp_size < 0) + r->_mp_size = mpz_abs_add_ui (r, b, a); + else + r->_mp_size = -mpz_abs_sub_ui (r, b, a); +} + +static mp_size_t +mpz_abs_add (mpz_t r, const mpz_t a, const mpz_t b) +{ + mp_size_t an = GMP_ABS (a->_mp_size); + mp_size_t bn = GMP_ABS (b->_mp_size); + mp_size_t rn; + mp_ptr rp; + mp_limb_t cy; + + rn = GMP_MAX (an, bn); + rp = MPZ_REALLOC (r, rn + 1); + if (an >= bn) + cy = mpn_add (rp, a->_mp_d, an, b->_mp_d, bn); + else + cy = mpn_add (rp, b->_mp_d, bn, a->_mp_d, an); + + rp[rn] = cy; + + return rn + (cy > 0); +} + +static mp_size_t +mpz_abs_sub (mpz_t r, const mpz_t a, const mpz_t b) +{ + mp_size_t an = GMP_ABS (a->_mp_size); + mp_size_t bn = GMP_ABS (b->_mp_size); + int cmp; + mp_ptr rp; + + cmp = mpn_cmp4 (a->_mp_d, an, b->_mp_d, bn); + if (cmp > 0) + { + rp = MPZ_REALLOC (r, an); + gmp_assert_nocarry (mpn_sub (rp, a->_mp_d, an, b->_mp_d, bn)); + return mpn_normalized_size (rp, an); + } + else if (cmp < 0) + { + rp = MPZ_REALLOC (r, bn); + gmp_assert_nocarry (mpn_sub (rp, b->_mp_d, bn, a->_mp_d, an)); + return -mpn_normalized_size (rp, bn); + } + else + return 0; +} + +void +mpz_add (mpz_t r, const mpz_t a, const mpz_t b) +{ + mp_size_t rn; + + if ( (a->_mp_size ^ b->_mp_size) >= 0) + rn = mpz_abs_add (r, a, b); + else + rn = mpz_abs_sub (r, a, b); + + r->_mp_size = a->_mp_size >= 0 ? rn : - rn; +} + +void +mpz_sub (mpz_t r, const mpz_t a, const mpz_t b) +{ + mp_size_t rn; + + if ( (a->_mp_size ^ b->_mp_size) >= 0) + rn = mpz_abs_sub (r, a, b); + else + rn = mpz_abs_add (r, a, b); + + r->_mp_size = a->_mp_size >= 0 ? rn : - rn; +} + + +/* MPZ multiplication */ +void +mpz_mul_si (mpz_t r, const mpz_t u, long int v) +{ + if (v < 0) + { + mpz_mul_ui (r, u, GMP_NEG_CAST (unsigned long int, v)); + mpz_neg (r, r); + } + else + mpz_mul_ui (r, u, (unsigned long int) v); +} + +void +mpz_mul_ui (mpz_t r, const mpz_t u, unsigned long int v) +{ + mp_size_t un; + mpz_t t; + mp_ptr tp; + mp_limb_t cy; + + un = GMP_ABS (u->_mp_size); + + if (un == 0 || v == 0) + { + r->_mp_size = 0; + return; + } + + mpz_init2 (t, (un + 1) * GMP_LIMB_BITS); + + tp = t->_mp_d; + cy = mpn_mul_1 (tp, u->_mp_d, un, v); + tp[un] = cy; + + t->_mp_size = un + (cy > 0); + if (u->_mp_size < 0) + t->_mp_size = - t->_mp_size; + + mpz_swap (r, t); + mpz_clear (t); +} + +void +mpz_mul (mpz_t r, const mpz_t u, const mpz_t v) +{ + int sign; + mp_size_t un, vn, rn; + mpz_t t; + mp_ptr tp; + + un = GMP_ABS (u->_mp_size); + vn = GMP_ABS (v->_mp_size); + + if (un == 0 || vn == 0) + { + r->_mp_size = 0; + return; + } + + sign = (u->_mp_size ^ v->_mp_size) < 0; + + mpz_init2 (t, (un + vn) * GMP_LIMB_BITS); + + tp = t->_mp_d; + if (un >= vn) + mpn_mul (tp, u->_mp_d, un, v->_mp_d, vn); + else + mpn_mul (tp, v->_mp_d, vn, u->_mp_d, un); + + rn = un + vn; + rn -= tp[rn-1] == 0; + + t->_mp_size = sign ? - rn : rn; + mpz_swap (r, t); + mpz_clear (t); +} + +void +mpz_mul_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t bits) +{ + mp_size_t un, rn; + mp_size_t limbs; + unsigned shift; + mp_ptr rp; + + un = GMP_ABS (u->_mp_size); + if (un == 0) + { + r->_mp_size = 0; + return; + } + + limbs = bits / GMP_LIMB_BITS; + shift = bits % GMP_LIMB_BITS; + + rn = un + limbs + (shift > 0); + rp = MPZ_REALLOC (r, rn); + if (shift > 0) + { + mp_limb_t cy = mpn_lshift (rp + limbs, u->_mp_d, un, shift); + rp[rn-1] = cy; + rn -= (cy == 0); + } + else + mpn_copyd (rp + limbs, u->_mp_d, un); + + while (limbs > 0) + rp[--limbs] = 0; + + r->_mp_size = (u->_mp_size < 0) ? - rn : rn; +} + + +/* MPZ division */ +enum mpz_div_round_mode { GMP_DIV_FLOOR, GMP_DIV_CEIL, GMP_DIV_TRUNC }; + +/* Allows q or r to be zero. Returns 1 iff remainder is non-zero. */ +static int +mpz_div_qr (mpz_t q, mpz_t r, + const mpz_t n, const mpz_t d, enum mpz_div_round_mode mode) +{ + mp_size_t ns, ds, nn, dn, qs; + ns = n->_mp_size; + ds = d->_mp_size; + + if (ds == 0) + gmp_die("mpz_div_qr: Divide by zero."); + + if (ns == 0) + { + if (q) + q->_mp_size = 0; + if (r) + r->_mp_size = 0; + return 0; + } + + nn = GMP_ABS (ns); + dn = GMP_ABS (ds); + + qs = ds ^ ns; + + if (nn < dn) + { + if (mode == GMP_DIV_CEIL && qs >= 0) + { + /* q = 1, r = n - d */ + if (r) + mpz_sub (r, n, d); + if (q) + mpz_set_ui (q, 1); + } + else if (mode == GMP_DIV_FLOOR && qs < 0) + { + /* q = -1, r = n + d */ + if (r) + mpz_add (r, n, d); + if (q) + mpz_set_si (q, -1); + } + else + { + /* q = 0, r = d */ + if (r) + mpz_set (r, n); + if (q) + q->_mp_size = 0; + } + return 1; + } + else + { + mp_ptr np, qp; + mp_size_t qn, rn; + mpz_t tq, tr; + + mpz_init (tr); + mpz_set (tr, n); + np = tr->_mp_d; + + qn = nn - dn + 1; + + if (q) + { + mpz_init2 (tq, qn * GMP_LIMB_BITS); + qp = tq->_mp_d; + } + else + qp = NULL; + + mpn_div_qr (qp, np, nn, d->_mp_d, dn); + + if (qp) + { + qn -= (qp[qn-1] == 0); + + tq->_mp_size = qs < 0 ? -qn : qn; + } + rn = mpn_normalized_size (np, dn); + tr->_mp_size = ns < 0 ? - rn : rn; + + if (mode == GMP_DIV_FLOOR && qs < 0 && rn != 0) + { + if (q) + mpz_sub_ui (tq, tq, 1); + if (r) + mpz_add (tr, tr, d); + } + else if (mode == GMP_DIV_CEIL && qs >= 0 && rn != 0) + { + if (q) + mpz_add_ui (tq, tq, 1); + if (r) + mpz_sub (tr, tr, d); + } + + if (q) + { + mpz_swap (tq, q); + mpz_clear (tq); + } + if (r) + mpz_swap (tr, r); + + mpz_clear (tr); + + return rn != 0; + } +} + +void +mpz_cdiv_qr (mpz_t q, mpz_t r, const mpz_t n, const mpz_t d) +{ + mpz_div_qr (q, r, n, d, GMP_DIV_CEIL); +} + +void +mpz_fdiv_qr (mpz_t q, mpz_t r, const mpz_t n, const mpz_t d) +{ + mpz_div_qr (q, r, n, d, GMP_DIV_FLOOR); +} + +void +mpz_tdiv_qr (mpz_t q, mpz_t r, const mpz_t n, const mpz_t d) +{ + mpz_div_qr (q, r, n, d, GMP_DIV_TRUNC); +} + +void +mpz_cdiv_q (mpz_t q, const mpz_t n, const mpz_t d) +{ + mpz_div_qr (q, NULL, n, d, GMP_DIV_CEIL); +} + +void +mpz_fdiv_q (mpz_t q, const mpz_t n, const mpz_t d) +{ + mpz_div_qr (q, NULL, n, d, GMP_DIV_FLOOR); +} + +void +mpz_tdiv_q (mpz_t q, const mpz_t n, const mpz_t d) +{ + mpz_div_qr (q, NULL, n, d, GMP_DIV_TRUNC); +} + +void +mpz_cdiv_r (mpz_t r, const mpz_t n, const mpz_t d) +{ + mpz_div_qr (NULL, r, n, d, GMP_DIV_CEIL); +} + +void +mpz_fdiv_r (mpz_t r, const mpz_t n, const mpz_t d) +{ + mpz_div_qr (NULL, r, n, d, GMP_DIV_FLOOR); +} + +void +mpz_tdiv_r (mpz_t r, const mpz_t n, const mpz_t d) +{ + mpz_div_qr (NULL, r, n, d, GMP_DIV_TRUNC); +} + +void +mpz_mod (mpz_t r, const mpz_t n, const mpz_t d) +{ + if (d->_mp_size >= 0) + mpz_div_qr (NULL, r, n, d, GMP_DIV_FLOOR); + else + mpz_div_qr (NULL, r, n, d, GMP_DIV_CEIL); +} + +static void +mpz_div_q_2exp (mpz_t q, const mpz_t u, mp_bitcnt_t bit_index, + enum mpz_div_round_mode mode) +{ + mp_size_t un, qn; + mp_size_t limb_cnt; + mp_ptr qp; + mp_limb_t adjust; + + un = u->_mp_size; + if (un == 0) + { + q->_mp_size = 0; + return; + } + limb_cnt = bit_index / GMP_LIMB_BITS; + qn = GMP_ABS (un) - limb_cnt; + bit_index %= GMP_LIMB_BITS; + + if (mode == ((un > 0) ? GMP_DIV_CEIL : GMP_DIV_FLOOR)) /* un != 0 here. */ + /* Note: Below, the final indexing at limb_cnt is valid because at + that point we have qn > 0. */ + adjust = (qn <= 0 + || !mpn_zero_p (u->_mp_d, limb_cnt) + || (u->_mp_d[limb_cnt] + & (((mp_limb_t) 1 << bit_index) - 1))); + else + adjust = 0; + + if (qn <= 0) + qn = 0; + + else + { + qp = MPZ_REALLOC (q, qn); + + if (bit_index != 0) + { + mpn_rshift (qp, u->_mp_d + limb_cnt, qn, bit_index); + qn -= qp[qn - 1] == 0; + } + else + { + mpn_copyi (qp, u->_mp_d + limb_cnt, qn); + } + } + + q->_mp_size = qn; + + mpz_add_ui (q, q, adjust); + if (un < 0) + mpz_neg (q, q); +} + +static void +mpz_div_r_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t bit_index, + enum mpz_div_round_mode mode) +{ + mp_size_t us, un, rn; + mp_ptr rp; + mp_limb_t mask; + + us = u->_mp_size; + if (us == 0 || bit_index == 0) + { + r->_mp_size = 0; + return; + } + rn = (bit_index + GMP_LIMB_BITS - 1) / GMP_LIMB_BITS; + assert (rn > 0); + + rp = MPZ_REALLOC (r, rn); + un = GMP_ABS (us); + + mask = GMP_LIMB_MAX >> (rn * GMP_LIMB_BITS - bit_index); + + if (rn > un) + { + /* Quotient (with truncation) is zero, and remainder is + non-zero */ + if (mode == ((us > 0) ? GMP_DIV_CEIL : GMP_DIV_FLOOR)) /* us != 0 here. */ + { + /* Have to negate and sign extend. */ + mp_size_t i; + mp_limb_t cy; + + for (cy = 1, i = 0; i < un; i++) + { + mp_limb_t s = ~u->_mp_d[i] + cy; + cy = s < cy; + rp[i] = s; + } + assert (cy == 0); + for (; i < rn - 1; i++) + rp[i] = GMP_LIMB_MAX; + + rp[rn-1] = mask; + us = -us; + } + else + { + /* Just copy */ + if (r != u) + mpn_copyi (rp, u->_mp_d, un); + + rn = un; + } + } + else + { + if (r != u) + mpn_copyi (rp, u->_mp_d, rn - 1); + + rp[rn-1] = u->_mp_d[rn-1] & mask; + + if (mode == ((us > 0) ? GMP_DIV_CEIL : GMP_DIV_FLOOR)) /* us != 0 here. */ + { + /* If r != 0, compute 2^{bit_count} - r. */ + mp_size_t i; + + for (i = 0; i < rn && rp[i] == 0; i++) + ; + if (i < rn) + { + /* r > 0, need to flip sign. */ + rp[i] = ~rp[i] + 1; + for (i++; i < rn; i++) + rp[i] = ~rp[i]; + + rp[rn-1] &= mask; + + /* us is not used for anything else, so we can modify it + here to indicate flipped sign. */ + us = -us; + } + } + } + rn = mpn_normalized_size (rp, rn); + r->_mp_size = us < 0 ? -rn : rn; +} + +void +mpz_cdiv_q_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt) +{ + mpz_div_q_2exp (r, u, cnt, GMP_DIV_CEIL); +} + +void +mpz_fdiv_q_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt) +{ + mpz_div_q_2exp (r, u, cnt, GMP_DIV_FLOOR); +} + +void +mpz_tdiv_q_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt) +{ + mpz_div_q_2exp (r, u, cnt, GMP_DIV_TRUNC); +} + +void +mpz_cdiv_r_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt) +{ + mpz_div_r_2exp (r, u, cnt, GMP_DIV_CEIL); +} + +void +mpz_fdiv_r_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt) +{ + mpz_div_r_2exp (r, u, cnt, GMP_DIV_FLOOR); +} + +void +mpz_tdiv_r_2exp (mpz_t r, const mpz_t u, mp_bitcnt_t cnt) +{ + mpz_div_r_2exp (r, u, cnt, GMP_DIV_TRUNC); +} + +void +mpz_divexact (mpz_t q, const mpz_t n, const mpz_t d) +{ + gmp_assert_nocarry (mpz_div_qr (q, NULL, n, d, GMP_DIV_TRUNC)); +} + +int +mpz_divisible_p (const mpz_t n, const mpz_t d) +{ + return mpz_div_qr (NULL, NULL, n, d, GMP_DIV_TRUNC) == 0; +} + +static unsigned long +mpz_div_qr_ui (mpz_t q, mpz_t r, + const mpz_t n, unsigned long d, enum mpz_div_round_mode mode) +{ + mp_size_t ns, qn; + mp_ptr qp; + mp_limb_t rl; + mp_size_t rs; + + ns = n->_mp_size; + if (ns == 0) + { + if (q) + q->_mp_size = 0; + if (r) + r->_mp_size = 0; + return 0; + } + + qn = GMP_ABS (ns); + if (q) + qp = MPZ_REALLOC (q, qn); + else + qp = NULL; + + rl = mpn_div_qr_1 (qp, n->_mp_d, qn, d); + assert (rl < d); + + rs = rl > 0; + rs = (ns < 0) ? -rs : rs; + + if (rl > 0 && ( (mode == GMP_DIV_FLOOR && ns < 0) + || (mode == GMP_DIV_CEIL && ns >= 0))) + { + if (q) + gmp_assert_nocarry (mpn_add_1 (qp, qp, qn, 1)); + rl = d - rl; + rs = -rs; + } + + if (r) + { + r->_mp_d[0] = rl; + r->_mp_size = rs; + } + if (q) + { + qn -= (qp[qn-1] == 0); + assert (qn == 0 || qp[qn-1] > 0); + + q->_mp_size = (ns < 0) ? - qn : qn; + } + + return rl; +} + +unsigned long +mpz_cdiv_qr_ui (mpz_t q, mpz_t r, const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (q, r, n, d, GMP_DIV_CEIL); +} + +unsigned long +mpz_fdiv_qr_ui (mpz_t q, mpz_t r, const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (q, r, n, d, GMP_DIV_FLOOR); +} + +unsigned long +mpz_tdiv_qr_ui (mpz_t q, mpz_t r, const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (q, r, n, d, GMP_DIV_TRUNC); +} + +unsigned long +mpz_cdiv_q_ui (mpz_t q, const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (q, NULL, n, d, GMP_DIV_CEIL); +} + +unsigned long +mpz_fdiv_q_ui (mpz_t q, const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (q, NULL, n, d, GMP_DIV_FLOOR); +} + +unsigned long +mpz_tdiv_q_ui (mpz_t q, const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (q, NULL, n, d, GMP_DIV_TRUNC); +} + +unsigned long +mpz_cdiv_r_ui (mpz_t r, const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (NULL, r, n, d, GMP_DIV_CEIL); +} +unsigned long +mpz_fdiv_r_ui (mpz_t r, const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (NULL, r, n, d, GMP_DIV_FLOOR); +} +unsigned long +mpz_tdiv_r_ui (mpz_t r, const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (NULL, r, n, d, GMP_DIV_TRUNC); +} + +unsigned long +mpz_cdiv_ui (const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (NULL, NULL, n, d, GMP_DIV_CEIL); +} + +unsigned long +mpz_fdiv_ui (const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (NULL, NULL, n, d, GMP_DIV_FLOOR); +} + +unsigned long +mpz_tdiv_ui (const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (NULL, NULL, n, d, GMP_DIV_TRUNC); +} + +unsigned long +mpz_mod_ui (mpz_t r, const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (NULL, r, n, d, GMP_DIV_FLOOR); +} + +void +mpz_divexact_ui (mpz_t q, const mpz_t n, unsigned long d) +{ + gmp_assert_nocarry (mpz_div_qr_ui (q, NULL, n, d, GMP_DIV_TRUNC)); +} + +int +mpz_divisible_ui_p (const mpz_t n, unsigned long d) +{ + return mpz_div_qr_ui (NULL, NULL, n, d, GMP_DIV_TRUNC) == 0; +} + + +/* GCD */ +static mp_limb_t +mpn_gcd_11 (mp_limb_t u, mp_limb_t v) +{ + unsigned shift; + + assert ( (u | v) > 0); + + if (u == 0) + return v; + else if (v == 0) + return u; + + gmp_ctz (shift, u | v); + + u >>= shift; + v >>= shift; + + if ( (u & 1) == 0) + MP_LIMB_T_SWAP (u, v); + + while ( (v & 1) == 0) + v >>= 1; + + while (u != v) + { + if (u > v) + { + u -= v; + do + u >>= 1; + while ( (u & 1) == 0); + } + else + { + v -= u; + do + v >>= 1; + while ( (v & 1) == 0); + } + } + return u << shift; +} + +unsigned long +mpz_gcd_ui (mpz_t g, const mpz_t u, unsigned long v) +{ + mp_size_t un; + + if (v == 0) + { + if (g) + mpz_abs (g, u); + } + else + { + un = GMP_ABS (u->_mp_size); + if (un != 0) + v = mpn_gcd_11 (mpn_div_qr_1 (NULL, u->_mp_d, un, v), v); + + if (g) + mpz_set_ui (g, v); + } + + return v; +} + +static mp_bitcnt_t +mpz_make_odd (mpz_t r, const mpz_t u) +{ + mp_size_t un, rn, i; + mp_ptr rp; + unsigned shift; + + un = GMP_ABS (u->_mp_size); + assert (un > 0); + + for (i = 0; u->_mp_d[i] == 0; i++) + ; + + gmp_ctz (shift, u->_mp_d[i]); + + rn = un - i; + rp = MPZ_REALLOC (r, rn); + if (shift > 0) + { + mpn_rshift (rp, u->_mp_d + i, rn, shift); + rn -= (rp[rn-1] == 0); + } + else + mpn_copyi (rp, u->_mp_d + i, rn); + + r->_mp_size = rn; + return i * GMP_LIMB_BITS + shift; +} + +void +mpz_gcd (mpz_t g, const mpz_t u, const mpz_t v) +{ + mpz_t tu, tv; + mp_bitcnt_t uz, vz, gz; + + if (u->_mp_size == 0) + { + mpz_abs (g, v); + return; + } + if (v->_mp_size == 0) + { + mpz_abs (g, u); + return; + } + + mpz_init (tu); + mpz_init (tv); + + uz = mpz_make_odd (tu, u); + vz = mpz_make_odd (tv, v); + gz = GMP_MIN (uz, vz); + + if (tu->_mp_size < tv->_mp_size) + mpz_swap (tu, tv); + + mpz_tdiv_r (tu, tu, tv); + if (tu->_mp_size == 0) + { + mpz_swap (g, tv); + } + else + for (;;) + { + int c; + + mpz_make_odd (tu, tu); + c = mpz_cmp (tu, tv); + if (c == 0) + { + mpz_swap (g, tu); + break; + } + if (c < 0) + mpz_swap (tu, tv); + + if (tv->_mp_size == 1) + { + mp_limb_t vl = tv->_mp_d[0]; + mp_limb_t ul = mpz_tdiv_ui (tu, vl); + mpz_set_ui (g, mpn_gcd_11 (ul, vl)); + break; + } + mpz_sub (tu, tu, tv); + } + mpz_clear (tu); + mpz_clear (tv); + mpz_mul_2exp (g, g, gz); +} + +void +mpz_gcdext (mpz_t g, mpz_t s, mpz_t t, const mpz_t u, const mpz_t v) +{ + mpz_t tu, tv, s0, s1, t0, t1; + mp_bitcnt_t uz, vz, gz; + mp_bitcnt_t power; + + if (u->_mp_size == 0) + { + /* g = 0 u + sgn(v) v */ + signed long sign = mpz_sgn (v); + mpz_abs (g, v); + if (s) + mpz_set_ui (s, 0); + if (t) + mpz_set_si (t, sign); + return; + } + + if (v->_mp_size == 0) + { + /* g = sgn(u) u + 0 v */ + signed long sign = mpz_sgn (u); + mpz_abs (g, u); + if (s) + mpz_set_si (s, sign); + if (t) + mpz_set_ui (t, 0); + return; + } + + mpz_init (tu); + mpz_init (tv); + mpz_init (s0); + mpz_init (s1); + mpz_init (t0); + mpz_init (t1); + + uz = mpz_make_odd (tu, u); + vz = mpz_make_odd (tv, v); + gz = GMP_MIN (uz, vz); + + uz -= gz; + vz -= gz; + + /* Cofactors corresponding to odd gcd. gz handled later. */ + if (tu->_mp_size < tv->_mp_size) + { + mpz_swap (tu, tv); + MPZ_SRCPTR_SWAP (u, v); + MPZ_PTR_SWAP (s, t); + MP_BITCNT_T_SWAP (uz, vz); + } + + /* Maintain + * + * u = t0 tu + t1 tv + * v = s0 tu + s1 tv + * + * where u and v denote the inputs with common factors of two + * eliminated, and det (s0, t0; s1, t1) = 2^p. Then + * + * 2^p tu = s1 u - t1 v + * 2^p tv = -s0 u + t0 v + */ + + /* After initial division, tu = q tv + tu', we have + * + * u = 2^uz (tu' + q tv) + * v = 2^vz tv + * + * or + * + * t0 = 2^uz, t1 = 2^uz q + * s0 = 0, s1 = 2^vz + */ + + mpz_setbit (t0, uz); + mpz_tdiv_qr (t1, tu, tu, tv); + mpz_mul_2exp (t1, t1, uz); + + mpz_setbit (s1, vz); + power = uz + vz; + + if (tu->_mp_size > 0) + { + mp_bitcnt_t shift; + shift = mpz_make_odd (tu, tu); + mpz_mul_2exp (t0, t0, shift); + mpz_mul_2exp (s0, s0, shift); + power += shift; + + for (;;) + { + int c; + c = mpz_cmp (tu, tv); + if (c == 0) + break; + + if (c < 0) + { + /* tv = tv' + tu + * + * u = t0 tu + t1 (tv' + tu) = (t0 + t1) tu + t1 tv' + * v = s0 tu + s1 (tv' + tu) = (s0 + s1) tu + s1 tv' */ + + mpz_sub (tv, tv, tu); + mpz_add (t0, t0, t1); + mpz_add (s0, s0, s1); + + shift = mpz_make_odd (tv, tv); + mpz_mul_2exp (t1, t1, shift); + mpz_mul_2exp (s1, s1, shift); + } + else + { + mpz_sub (tu, tu, tv); + mpz_add (t1, t0, t1); + mpz_add (s1, s0, s1); + + shift = mpz_make_odd (tu, tu); + mpz_mul_2exp (t0, t0, shift); + mpz_mul_2exp (s0, s0, shift); + } + power += shift; + } + } + + /* Now tv = odd part of gcd, and -s0 and t0 are corresponding + cofactors. */ + + mpz_mul_2exp (tv, tv, gz); + mpz_neg (s0, s0); + + /* 2^p g = s0 u + t0 v. Eliminate one factor of two at a time. To + adjust cofactors, we need u / g and v / g */ + + mpz_divexact (s1, v, tv); + mpz_abs (s1, s1); + mpz_divexact (t1, u, tv); + mpz_abs (t1, t1); + + while (power-- > 0) + { + /* s0 u + t0 v = (s0 - v/g) u - (t0 + u/g) v */ + if (mpz_odd_p (s0) || mpz_odd_p (t0)) + { + mpz_sub (s0, s0, s1); + mpz_add (t0, t0, t1); + } + mpz_divexact_ui (s0, s0, 2); + mpz_divexact_ui (t0, t0, 2); + } + + /* Arrange so that |s| < |u| / 2g */ + mpz_add (s1, s0, s1); + if (mpz_cmpabs (s0, s1) > 0) + { + mpz_swap (s0, s1); + mpz_sub (t0, t0, t1); + } + if (u->_mp_size < 0) + mpz_neg (s0, s0); + if (v->_mp_size < 0) + mpz_neg (t0, t0); + + mpz_swap (g, tv); + if (s) + mpz_swap (s, s0); + if (t) + mpz_swap (t, t0); + + mpz_clear (tu); + mpz_clear (tv); + mpz_clear (s0); + mpz_clear (s1); + mpz_clear (t0); + mpz_clear (t1); +} + +void +mpz_lcm (mpz_t r, const mpz_t u, const mpz_t v) +{ + mpz_t g; + + if (u->_mp_size == 0 || v->_mp_size == 0) + { + r->_mp_size = 0; + return; + } + + mpz_init (g); + + mpz_gcd (g, u, v); + mpz_divexact (g, u, g); + mpz_mul (r, g, v); + + mpz_clear (g); + mpz_abs (r, r); +} + +void +mpz_lcm_ui (mpz_t r, const mpz_t u, unsigned long v) +{ + if (v == 0 || u->_mp_size == 0) + { + r->_mp_size = 0; + return; + } + + v /= mpz_gcd_ui (NULL, u, v); + mpz_mul_ui (r, u, v); + + mpz_abs (r, r); +} + +int +mpz_invert (mpz_t r, const mpz_t u, const mpz_t m) +{ + mpz_t g, tr; + int invertible; + + if (u->_mp_size == 0 || mpz_cmpabs_ui (m, 1) <= 0) + return 0; + + mpz_init (g); + mpz_init (tr); + + mpz_gcdext (g, tr, NULL, u, m); + invertible = (mpz_cmp_ui (g, 1) == 0); + + if (invertible) + { + if (tr->_mp_size < 0) + { + if (m->_mp_size >= 0) + mpz_add (tr, tr, m); + else + mpz_sub (tr, tr, m); + } + mpz_swap (r, tr); + } + + mpz_clear (g); + mpz_clear (tr); + return invertible; +} + + +/* Higher level operations (sqrt, pow and root) */ + +void +mpz_pow_ui (mpz_t r, const mpz_t b, unsigned long e) +{ + unsigned long bit; + mpz_t tr; + mpz_init_set_ui (tr, 1); + + for (bit = GMP_ULONG_HIGHBIT; bit > 0; bit >>= 1) + { + mpz_mul (tr, tr, tr); + if (e & bit) + mpz_mul (tr, tr, b); + } + mpz_swap (r, tr); + mpz_clear (tr); +} + +void +mpz_ui_pow_ui (mpz_t r, unsigned long blimb, unsigned long e) +{ + mpz_t b; + mpz_init_set_ui (b, blimb); + mpz_pow_ui (r, b, e); + mpz_clear (b); +} + +void +mpz_powm (mpz_t r, const mpz_t b, const mpz_t e, const mpz_t m) +{ + mpz_t tr; + mpz_t base; + mp_size_t en, mn; + mp_srcptr mp; + struct gmp_div_inverse minv; + unsigned shift; + mp_ptr tp = NULL; + + en = GMP_ABS (e->_mp_size); + mn = GMP_ABS (m->_mp_size); + if (mn == 0) + gmp_die ("mpz_powm: Zero modulo."); + + if (en == 0) + { + mpz_set_ui (r, 1); + return; + } + + mp = m->_mp_d; + mpn_div_qr_invert (&minv, mp, mn); + shift = minv.shift; + + if (shift > 0) + { + /* To avoid shifts, we do all our reductions, except the final + one, using a *normalized* m. */ + minv.shift = 0; + + tp = gmp_xalloc_limbs (mn); + gmp_assert_nocarry (mpn_lshift (tp, mp, mn, shift)); + mp = tp; + } + + mpz_init (base); + + if (e->_mp_size < 0) + { + if (!mpz_invert (base, b, m)) + gmp_die ("mpz_powm: Negative exponent and non-invertibe base."); + } + else + { + mp_size_t bn; + mpz_abs (base, b); + + bn = base->_mp_size; + if (bn >= mn) + { + mpn_div_qr_preinv (NULL, base->_mp_d, base->_mp_size, mp, mn, &minv); + bn = mn; + } + + /* We have reduced the absolute value. Now take care of the + sign. Note that we get zero represented non-canonically as + m. */ + if (b->_mp_size < 0) + { + mp_ptr bp = MPZ_REALLOC (base, mn); + gmp_assert_nocarry (mpn_sub (bp, mp, mn, bp, bn)); + bn = mn; + } + base->_mp_size = mpn_normalized_size (base->_mp_d, bn); + } + mpz_init_set_ui (tr, 1); + + while (en-- > 0) + { + mp_limb_t w = e->_mp_d[en]; + mp_limb_t bit; + + for (bit = GMP_LIMB_HIGHBIT; bit > 0; bit >>= 1) + { + mpz_mul (tr, tr, tr); + if (w & bit) + mpz_mul (tr, tr, base); + if (tr->_mp_size > mn) + { + mpn_div_qr_preinv (NULL, tr->_mp_d, tr->_mp_size, mp, mn, &minv); + tr->_mp_size = mpn_normalized_size (tr->_mp_d, mn); + } + } + } + + /* Final reduction */ + if (tr->_mp_size >= mn) + { + minv.shift = shift; + mpn_div_qr_preinv (NULL, tr->_mp_d, tr->_mp_size, mp, mn, &minv); + tr->_mp_size = mpn_normalized_size (tr->_mp_d, mn); + } + if (tp) + gmp_free (tp); + + mpz_swap (r, tr); + mpz_clear (tr); + mpz_clear (base); +} + +void +mpz_powm_ui (mpz_t r, const mpz_t b, unsigned long elimb, const mpz_t m) +{ + mpz_t e; + mpz_init_set_ui (e, elimb); + mpz_powm (r, b, e, m); + mpz_clear (e); +} + +/* x=trunc(y^(1/z)), r=y-x^z */ +void +mpz_rootrem (mpz_t x, mpz_t r, const mpz_t y, unsigned long z) +{ + int sgn; + mpz_t t, u; + + sgn = y->_mp_size < 0; + if (sgn && (z & 1) == 0) + gmp_die ("mpz_rootrem: Negative argument, with even root."); + if (z == 0) + gmp_die ("mpz_rootrem: Zeroth root."); + + if (mpz_cmpabs_ui (y, 1) <= 0) { + mpz_set (x, y); + if (r) + r->_mp_size = 0; + return; + } + + mpz_init (t); + mpz_init (u); + mpz_setbit (t, mpz_sizeinbase (y, 2) / z + 1); + + if (z == 2) /* simplify sqrt loop: z-1 == 1 */ + do { + mpz_swap (u, t); /* u = x */ + mpz_tdiv_q (t, y, u); /* t = y/x */ + mpz_add (t, t, u); /* t = y/x + x */ + mpz_tdiv_q_2exp (t, t, 1); /* x'= (y/x + x)/2 */ + } while (mpz_cmpabs (t, u) < 0); /* |x'| < |x| */ + else /* z != 2 */ { + mpz_t v; + + mpz_init (v); + if (sgn) + mpz_neg (t, t); + + do { + mpz_swap (u, t); /* u = x */ + mpz_pow_ui (t, u, z - 1); /* t = x^(z-1) */ + mpz_tdiv_q (t, y, t); /* t = y/x^(z-1) */ + mpz_mul_ui (v, u, z - 1); /* v = x*(z-1) */ + mpz_add (t, t, v); /* t = y/x^(z-1) + x*(z-1) */ + mpz_tdiv_q_ui (t, t, z); /* x'=(y/x^(z-1) + x*(z-1))/z */ + } while (mpz_cmpabs (t, u) < 0); /* |x'| < |x| */ + + mpz_clear (v); + } + + if (r) { + mpz_pow_ui (t, u, z); + mpz_sub (r, y, t); + } + mpz_swap (x, u); + mpz_clear (u); + mpz_clear (t); +} + +int +mpz_root (mpz_t x, const mpz_t y, unsigned long z) +{ + int res; + mpz_t r; + + mpz_init (r); + mpz_rootrem (x, r, y, z); + res = r->_mp_size == 0; + mpz_clear (r); + + return res; +} + +/* Compute s = floor(sqrt(u)) and r = u - s^2. Allows r == NULL */ +void +mpz_sqrtrem (mpz_t s, mpz_t r, const mpz_t u) +{ + mpz_rootrem (s, r, u, 2); +} + +void +mpz_sqrt (mpz_t s, const mpz_t u) +{ + mpz_rootrem (s, NULL, u, 2); +} + + +/* Combinatorics */ + +void +mpz_fac_ui (mpz_t x, unsigned long n) +{ + if (n < 2) { + mpz_set_ui (x, 1); + return; + } + mpz_set_ui (x, n); + for (;--n > 1;) + mpz_mul_ui (x, x, n); +} + +void +mpz_bin_uiui (mpz_t r, unsigned long n, unsigned long k) +{ + mpz_t t; + + if (k > n) { + r->_mp_size = 0; + return; + } + mpz_fac_ui (r, n); + mpz_init (t); + mpz_fac_ui (t, k); + mpz_divexact (r, r, t); + mpz_fac_ui (t, n - k); + mpz_divexact (r, r, t); + mpz_clear (t); +} + + +/* Logical operations and bit manipulation. */ + +/* Numbers are treated as if represented in two's complement (and + infinitely sign extended). For a negative values we get the two's + complement from -x = ~x + 1, where ~ is bitwise complementt. + Negation transforms + + xxxx10...0 + + into + + yyyy10...0 + + where yyyy is the bitwise complement of xxxx. So least significant + bits, up to and including the first one bit, are unchanged, and + the more significant bits are all complemented. + + To change a bit from zero to one in a negative number, subtract the + corresponding power of two from the absolute value. This can never + underflow. To change a bit from one to zero, add the corresponding + power of two, and this might overflow. E.g., if x = -001111, the + two's complement is 110001. Clearing the least significant bit, we + get two's complement 110000, and -010000. */ + +int +mpz_tstbit (const mpz_t d, mp_bitcnt_t bit_index) +{ + mp_size_t limb_index; + unsigned shift; + mp_size_t ds; + mp_size_t dn; + mp_limb_t w; + int bit; + + ds = d->_mp_size; + dn = GMP_ABS (ds); + limb_index = bit_index / GMP_LIMB_BITS; + if (limb_index >= dn) + return ds < 0; + + shift = bit_index % GMP_LIMB_BITS; + w = d->_mp_d[limb_index]; + bit = (w >> shift) & 1; + + if (ds < 0) + { + /* d < 0. Check if any of the bits below is set: If so, our bit + must be complemented. */ + if (shift > 0 && (w << (GMP_LIMB_BITS - shift)) > 0) + return bit ^ 1; + while (limb_index-- > 0) + if (d->_mp_d[limb_index] > 0) + return bit ^ 1; + } + return bit; +} + +static void +mpz_abs_add_bit (mpz_t d, mp_bitcnt_t bit_index) +{ + mp_size_t dn, limb_index; + mp_limb_t bit; + mp_ptr dp; + + dn = GMP_ABS (d->_mp_size); + + limb_index = bit_index / GMP_LIMB_BITS; + bit = (mp_limb_t) 1 << (bit_index % GMP_LIMB_BITS); + + if (limb_index >= dn) + { + mp_size_t i; + /* The bit should be set outside of the end of the number. + We have to increase the size of the number. */ + dp = MPZ_REALLOC (d, limb_index + 1); + + dp[limb_index] = bit; + for (i = dn; i < limb_index; i++) + dp[i] = 0; + dn = limb_index + 1; + } + else + { + mp_limb_t cy; + + dp = d->_mp_d; + + cy = mpn_add_1 (dp + limb_index, dp + limb_index, dn - limb_index, bit); + if (cy > 0) + { + dp = MPZ_REALLOC (d, dn + 1); + dp[dn++] = cy; + } + } + + d->_mp_size = (d->_mp_size < 0) ? - dn : dn; +} + +static void +mpz_abs_sub_bit (mpz_t d, mp_bitcnt_t bit_index) +{ + mp_size_t dn, limb_index; + mp_ptr dp; + mp_limb_t bit; + + dn = GMP_ABS (d->_mp_size); + dp = d->_mp_d; + + limb_index = bit_index / GMP_LIMB_BITS; + bit = (mp_limb_t) 1 << (bit_index % GMP_LIMB_BITS); + + assert (limb_index < dn); + + gmp_assert_nocarry (mpn_sub_1 (dp + limb_index, dp + limb_index, + dn - limb_index, bit)); + dn -= (dp[dn-1] == 0); + d->_mp_size = (d->_mp_size < 0) ? - dn : dn; +} + +void +mpz_setbit (mpz_t d, mp_bitcnt_t bit_index) +{ + if (!mpz_tstbit (d, bit_index)) + { + if (d->_mp_size >= 0) + mpz_abs_add_bit (d, bit_index); + else + mpz_abs_sub_bit (d, bit_index); + } +} + +void +mpz_clrbit (mpz_t d, mp_bitcnt_t bit_index) +{ + if (mpz_tstbit (d, bit_index)) + { + if (d->_mp_size >= 0) + mpz_abs_sub_bit (d, bit_index); + else + mpz_abs_add_bit (d, bit_index); + } +} + +void +mpz_combit (mpz_t d, mp_bitcnt_t bit_index) +{ + if (mpz_tstbit (d, bit_index) ^ (d->_mp_size < 0)) + mpz_abs_sub_bit (d, bit_index); + else + mpz_abs_add_bit (d, bit_index); +} + +void +mpz_com (mpz_t r, const mpz_t u) +{ + mpz_neg (r, u); + mpz_sub_ui (r, r, 1); +} + +void +mpz_and (mpz_t r, const mpz_t u, const mpz_t v) +{ + mp_size_t un, vn, rn, i; + mp_ptr up, vp, rp; + + mp_limb_t ux, vx, rx; + mp_limb_t uc, vc, rc; + mp_limb_t ul, vl, rl; + + un = GMP_ABS (u->_mp_size); + vn = GMP_ABS (v->_mp_size); + if (un < vn) + { + MPZ_SRCPTR_SWAP (u, v); + MP_SIZE_T_SWAP (un, vn); + } + if (vn == 0) + { + r->_mp_size = 0; + return; + } + + uc = u->_mp_size < 0; + vc = v->_mp_size < 0; + rc = uc & vc; + + ux = -uc; + vx = -vc; + rx = -rc; + + /* If the smaller input is positive, higher limbs don't matter. */ + rn = vx ? un : vn; + + rp = MPZ_REALLOC (r, rn + rc); + + up = u->_mp_d; + vp = v->_mp_d; + + for (i = 0; i < vn; i++) + { + ul = (up[i] ^ ux) + uc; + uc = ul < uc; + + vl = (vp[i] ^ vx) + vc; + vc = vl < vc; + + rl = ( (ul & vl) ^ rx) + rc; + rc = rl < rc; + rp[i] = rl; + } + assert (vc == 0); + + for (; i < rn; i++) + { + ul = (up[i] ^ ux) + uc; + uc = ul < uc; + + rl = ( (ul & vx) ^ rx) + rc; + rc = rl < rc; + rp[i] = rl; + } + if (rc) + rp[rn++] = rc; + else + rn = mpn_normalized_size (rp, rn); + + r->_mp_size = rx ? -rn : rn; +} + +void +mpz_ior (mpz_t r, const mpz_t u, const mpz_t v) +{ + mp_size_t un, vn, rn, i; + mp_ptr up, vp, rp; + + mp_limb_t ux, vx, rx; + mp_limb_t uc, vc, rc; + mp_limb_t ul, vl, rl; + + un = GMP_ABS (u->_mp_size); + vn = GMP_ABS (v->_mp_size); + if (un < vn) + { + MPZ_SRCPTR_SWAP (u, v); + MP_SIZE_T_SWAP (un, vn); + } + if (vn == 0) + { + mpz_set (r, u); + return; + } + + uc = u->_mp_size < 0; + vc = v->_mp_size < 0; + rc = uc | vc; + + ux = -uc; + vx = -vc; + rx = -rc; + + /* If the smaller input is negative, by sign extension higher limbs + don't matter. */ + rn = vx ? vn : un; + + rp = MPZ_REALLOC (r, rn + rc); + + up = u->_mp_d; + vp = v->_mp_d; + + for (i = 0; i < vn; i++) + { + ul = (up[i] ^ ux) + uc; + uc = ul < uc; + + vl = (vp[i] ^ vx) + vc; + vc = vl < vc; + + rl = ( (ul | vl) ^ rx) + rc; + rc = rl < rc; + rp[i] = rl; + } + assert (vc == 0); + + for (; i < rn; i++) + { + ul = (up[i] ^ ux) + uc; + uc = ul < uc; + + rl = ( (ul | vx) ^ rx) + rc; + rc = rl < rc; + rp[i] = rl; + } + if (rc) + rp[rn++] = rc; + else + rn = mpn_normalized_size (rp, rn); + + r->_mp_size = rx ? -rn : rn; +} + +void +mpz_xor (mpz_t r, const mpz_t u, const mpz_t v) +{ + mp_size_t un, vn, i; + mp_ptr up, vp, rp; + + mp_limb_t ux, vx, rx; + mp_limb_t uc, vc, rc; + mp_limb_t ul, vl, rl; + + un = GMP_ABS (u->_mp_size); + vn = GMP_ABS (v->_mp_size); + if (un < vn) + { + MPZ_SRCPTR_SWAP (u, v); + MP_SIZE_T_SWAP (un, vn); + } + if (vn == 0) + { + mpz_set (r, u); + return; + } + + uc = u->_mp_size < 0; + vc = v->_mp_size < 0; + rc = uc ^ vc; + + ux = -uc; + vx = -vc; + rx = -rc; + + rp = MPZ_REALLOC (r, un + rc); + + up = u->_mp_d; + vp = v->_mp_d; + + for (i = 0; i < vn; i++) + { + ul = (up[i] ^ ux) + uc; + uc = ul < uc; + + vl = (vp[i] ^ vx) + vc; + vc = vl < vc; + + rl = (ul ^ vl ^ rx) + rc; + rc = rl < rc; + rp[i] = rl; + } + assert (vc == 0); + + for (; i < un; i++) + { + ul = (up[i] ^ ux) + uc; + uc = ul < uc; + + rl = (ul ^ ux) + rc; + rc = rl < rc; + rp[i] = rl; + } + if (rc) + rp[un++] = rc; + else + un = mpn_normalized_size (rp, un); + + r->_mp_size = rx ? -un : un; +} + +static unsigned +gmp_popcount_limb (mp_limb_t x) +{ + unsigned c; + + /* Do 16 bits at a time, to avoid limb-sized constants. */ + for (c = 0; x > 0; x >>= 16) + { + unsigned w = ((x >> 1) & 0x5555) + (x & 0x5555); + w = ((w >> 2) & 0x3333) + (w & 0x3333); + w = ((w >> 4) & 0x0f0f) + (w & 0x0f0f); + w = (w >> 8) + (w & 0x00ff); + c += w; + } + return c; +} + +mp_bitcnt_t +mpz_popcount (const mpz_t u) +{ + mp_size_t un, i; + mp_bitcnt_t c; + + un = u->_mp_size; + + if (un < 0) + return ~(mp_bitcnt_t) 0; + + for (c = 0, i = 0; i < un; i++) + c += gmp_popcount_limb (u->_mp_d[i]); + + return c; +} + +mp_bitcnt_t +mpz_hamdist (const mpz_t u, const mpz_t v) +{ + mp_size_t un, vn, i; + mp_limb_t uc, vc, ul, vl, comp; + mp_srcptr up, vp; + mp_bitcnt_t c; + + un = u->_mp_size; + vn = v->_mp_size; + + if ( (un ^ vn) < 0) + return ~(mp_bitcnt_t) 0; + + if (un < 0) + { + assert (vn < 0); + un = -un; + vn = -vn; + uc = vc = 1; + comp = - (mp_limb_t) 1; + } + else + uc = vc = comp = 0; + + up = u->_mp_d; + vp = v->_mp_d; + + if (un < vn) + MPN_SRCPTR_SWAP (up, un, vp, vn); + + for (i = 0, c = 0; i < vn; i++) + { + ul = (up[i] ^ comp) + uc; + uc = ul < uc; + + vl = (vp[i] ^ comp) + vc; + vc = vl < vc; + + c += gmp_popcount_limb (ul ^ vl); + } + assert (vc == 0); + + for (; i < un; i++) + { + ul = (up[i] ^ comp) + uc; + uc = ul < uc; + + c += gmp_popcount_limb (ul ^ comp); + } + + return c; +} + +mp_bitcnt_t +mpz_scan1 (const mpz_t u, mp_bitcnt_t starting_bit) +{ + mp_ptr up; + mp_size_t us, un, i; + mp_limb_t limb, ux, uc; + unsigned cnt; + + up = u->_mp_d; + us = u->_mp_size; + un = GMP_ABS (us); + i = starting_bit / GMP_LIMB_BITS; + + /* Past the end there's no 1 bits for u>=0, or an immediate 1 bit + for u<0. Notice this test picks up any u==0 too. */ + if (i >= un) + return (us >= 0 ? ~(mp_bitcnt_t) 0 : starting_bit); + + if (us < 0) + { + ux = GMP_LIMB_MAX; + uc = mpn_zero_p (up, i); + } + else + ux = uc = 0; + + limb = (ux ^ up[i]) + uc; + uc = limb < uc; + + /* Mask to 0 all bits before starting_bit, thus ignoring them. */ + limb &= (GMP_LIMB_MAX << (starting_bit % GMP_LIMB_BITS)); + + while (limb == 0) + { + i++; + if (i == un) + { + assert (uc == 0); + /* For the u > 0 case, this can happen only for the first + masked limb. For the u < 0 case, it happens when the + highest limbs of the absolute value are all ones. */ + return (us >= 0 ? ~(mp_bitcnt_t) 0 : un * GMP_LIMB_BITS); + } + limb = (ux ^ up[i]) + uc; + uc = limb < uc; + } + gmp_ctz (cnt, limb); + return (mp_bitcnt_t) i * GMP_LIMB_BITS + cnt; +} + +mp_bitcnt_t +mpz_scan0 (const mpz_t u, mp_bitcnt_t starting_bit) +{ + mp_ptr up; + mp_size_t us, un, i; + mp_limb_t limb, ux, uc; + unsigned cnt; + + up = u->_mp_d; + us = u->_mp_size; + un = GMP_ABS (us); + i = starting_bit / GMP_LIMB_BITS; + + /* When past end, there's an immediate 0 bit for u>=0, or no 0 bits for + u<0. Notice this test picks up all cases of u==0 too. */ + if (i >= un) + return (us >= 0 ? starting_bit : ~(mp_bitcnt_t) 0); + + if (us < 0) + { + ux = GMP_LIMB_MAX; + uc = mpn_zero_p (up, i); + } + else + ux = uc = 0; + + limb = (ux ^ up[i]) + uc; + uc = limb < uc; + + /* Mask to 1 all bits before starting_bit, thus ignoring them. */ + limb |= ((mp_limb_t) 1 << (starting_bit % GMP_LIMB_BITS)) - 1; + + while (limb == GMP_LIMB_MAX) + { + i++; + if (i == un) + { + assert (uc == 0); + return (us >= 0 ? un * GMP_LIMB_BITS : ~(mp_bitcnt_t) 0); + } + limb = (ux ^ up[i]) + uc; + uc = limb < uc; + } + gmp_ctz (cnt, ~limb); + return (mp_bitcnt_t) i * GMP_LIMB_BITS + cnt; +} + + +/* MPZ base conversion. */ + +size_t +mpz_sizeinbase (const mpz_t u, int base) +{ + mp_size_t un; + mp_srcptr up; + mp_ptr tp; + mp_bitcnt_t bits; + struct gmp_div_inverse bi; + size_t ndigits; + + assert (base >= 2); + assert (base <= 36); + + un = GMP_ABS (u->_mp_size); + if (un == 0) + return 1; + + up = u->_mp_d; + + bits = (un - 1) * GMP_LIMB_BITS + mpn_limb_size_in_base_2 (up[un-1]); + switch (base) + { + case 2: + return bits; + case 4: + return (bits + 1) / 2; + case 8: + return (bits + 2) / 3; + case 16: + return (bits + 3) / 4; + case 32: + return (bits + 4) / 5; + /* FIXME: Do something more clever for the common case of base + 10. */ + } + + tp = gmp_xalloc_limbs (un); + mpn_copyi (tp, up, un); + mpn_div_qr_1_invert (&bi, base); + + for (ndigits = 0; un > 0; ndigits++) + { + mpn_div_qr_1_preinv (tp, tp, un, &bi); + un -= (tp[un-1] == 0); + } + gmp_free (tp); + return ndigits; +} + +char * +mpz_get_str (char *sp, int base, const mpz_t u) +{ + unsigned bits; + const char *digits; + mp_size_t un; + size_t i, sn; + + if (base >= 0) + { + digits = "0123456789abcdefghijklmnopqrstuvwxyz"; + } + else + { + base = -base; + digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + } + if (base <= 1) + base = 10; + if (base > 36) + return NULL; + + sn = 1 + mpz_sizeinbase (u, base); + if (!sp) + sp = gmp_xalloc (1 + sn); + + un = GMP_ABS (u->_mp_size); + + if (un == 0) + { + sp[0] = '0'; + sp[1] = '\0'; + return sp; + } + + i = 0; + + if (u->_mp_size < 0) + sp[i++] = '-'; + + bits = mpn_base_power_of_two_p (base); + + if (bits) + /* Not modified in this case. */ + sn = i + mpn_get_str_bits ((unsigned char *) sp + i, bits, u->_mp_d, un); + else + { + struct mpn_base_info info; + mp_ptr tp; + + mpn_get_base_info (&info, base); + tp = gmp_xalloc_limbs (un); + mpn_copyi (tp, u->_mp_d, un); + + sn = i + mpn_get_str_other ((unsigned char *) sp + i, base, &info, tp, un); + gmp_free (tp); + } + + for (; i < sn; i++) + sp[i] = digits[(unsigned char) sp[i]]; + + sp[sn] = '\0'; + return sp; +} + +int +mpz_set_str (mpz_t r, const char *sp, int base) +{ + unsigned bits; + mp_size_t rn, alloc; + mp_ptr rp; + size_t sn; + size_t dn; + int sign; + unsigned char *dp; + + assert (base == 0 || (base >= 2 && base <= 36)); + + while (isspace( (unsigned char) *sp)) + sp++; + + if (*sp == '-') + { + sign = 1; + sp++; + } + else + sign = 0; + + if (base == 0) + { + if (*sp == '0') + { + sp++; + if (*sp == 'x' || *sp == 'X') + { + base = 16; + sp++; + } + else if (*sp == 'b' || *sp == 'B') + { + base = 2; + sp++; + } + else + base = 8; + } + else + base = 10; + } + + sn = strlen (sp); + dp = gmp_xalloc (sn + (sn == 0)); + + for (dn = 0; *sp; sp++) + { + unsigned digit; + + if (isspace ((unsigned char) *sp)) + continue; + if (*sp >= '0' && *sp <= '9') + digit = *sp - '0'; + else if (*sp >= 'a' && *sp <= 'z') + digit = *sp - 'a' + 10; + else if (*sp >= 'A' && *sp <= 'Z') + digit = *sp - 'A' + 10; + else + digit = base; /* fail */ + + if (digit >= base) + { + gmp_free (dp); + r->_mp_size = 0; + return -1; + } + + dp[dn++] = digit; + } + + bits = mpn_base_power_of_two_p (base); + + if (bits > 0) + { + alloc = (sn * bits + GMP_LIMB_BITS - 1) / GMP_LIMB_BITS; + rp = MPZ_REALLOC (r, alloc); + rn = mpn_set_str_bits (rp, dp, dn, bits); + } + else + { + struct mpn_base_info info; + mpn_get_base_info (&info, base); + alloc = (sn + info.exp - 1) / info.exp; + rp = MPZ_REALLOC (r, alloc); + rn = mpn_set_str_other (rp, dp, dn, base, &info); + } + assert (rn <= alloc); + gmp_free (dp); + + r->_mp_size = sign ? - rn : rn; + + return 0; +} + +int +mpz_init_set_str (mpz_t r, const char *sp, int base) +{ + mpz_init (r); + return mpz_set_str (r, sp, base); +} + +size_t +mpz_out_str (FILE *stream, int base, const mpz_t x) +{ + char *str; + size_t len; + + str = mpz_get_str (NULL, base, x); + len = strlen (str); + len = fwrite (str, 1, len, stream); + gmp_free (str); + return len; +} + + +static int +gmp_detect_endian (void) +{ + static const int i = 1; + const unsigned char *p = (const unsigned char *) &i; + if (*p == 1) + /* Little endian */ + return -1; + else + /* Big endian */ + return 1; +} + +/* Import and export. Does not support nails. */ +void +mpz_import (mpz_t r, size_t count, int order, size_t size, int endian, + size_t nails, const void *src) +{ + const unsigned char *p; + ptrdiff_t word_step; + mp_ptr rp; + mp_size_t rn; + + /* The current (partial) limb. */ + mp_limb_t limb; + /* The number of bytes already copied to this limb (starting from + the low end). */ + size_t bytes; + /* The index where the limb should be stored, when completed. */ + mp_size_t i; + + if (nails != 0) + gmp_die ("mpz_import: Nails not supported."); + + assert (order == 1 || order == -1); + assert (endian >= -1 && endian <= 1); + + if (endian == 0) + endian = gmp_detect_endian (); + + p = (unsigned char *) src; + + word_step = (order != endian) ? 2 * size : 0; + + /* Process bytes from the least significant end, so point p at the + least significant word. */ + if (order == 1) + { + p += size * (count - 1); + word_step = - word_step; + } + + /* And at least significant byte of that word. */ + if (endian == 1) + p += (size - 1); + + rn = (size * count + sizeof(mp_limb_t) - 1) / sizeof(mp_limb_t); + rp = MPZ_REALLOC (r, rn); + + for (limb = 0, bytes = 0, i = 0; count > 0; count--, p += word_step) + { + size_t j; + for (j = 0; j < size; j++, p -= (ptrdiff_t) endian) + { + limb |= (mp_limb_t) *p << (bytes++ * CHAR_BIT); + if (bytes == sizeof(mp_limb_t)) + { + rp[i++] = limb; + bytes = 0; + limb = 0; + } + } + } + if (bytes > 0) + rp[i++] = limb; + assert (i == rn); + + r->_mp_size = mpn_normalized_size (rp, i); +} + +void * +mpz_export (void *r, size_t *countp, int order, size_t size, int endian, + size_t nails, const mpz_t u) +{ + unsigned char *p; + ptrdiff_t word_step; + size_t count, k; + mp_size_t un; + + /* The current (partial) limb. */ + mp_limb_t limb; + /* The number of bytes left to to in this limb. */ + size_t bytes; + /* The index where the limb was read. */ + mp_size_t i; + + if (nails != 0) + gmp_die ("mpz_import: Nails not supported."); + + assert (order == 1 || order == -1); + assert (endian >= -1 && endian <= 1); + assert (size > 0 || u->_mp_size == 0); + + un = GMP_ABS (u->_mp_size); + if (un == 0) + { + if (countp) + *countp = 0; + return r; + } + + /* Count bytes in top limb. */ + for (limb = u->_mp_d[un-1], k = 0; limb > 0; k++, limb >>= CHAR_BIT) + ; + + assert (k > 0); + + count = (k + (un-1) * sizeof (mp_limb_t) + size - 1) / size; + + if (!r) + r = gmp_xalloc (count * size); + + if (endian == 0) + endian = gmp_detect_endian (); + + p = (unsigned char *) r; + + word_step = (order != endian) ? 2 * size : 0; + + /* Process bytes from the least significant end, so point p at the + least significant word. */ + if (order == 1) + { + p += size * (count - 1); + word_step = - word_step; + } + + /* And at least significant byte of that word. */ + if (endian == 1) + p += (size - 1); + + for (bytes = 0, i = 0, k = 0; k < count; k++, p += word_step) + { + size_t j; + for (j = 0; j < size; j++, p -= (ptrdiff_t) endian) + { + if (bytes == 0) + { + if (i < un) + limb = u->_mp_d[i++]; + bytes = sizeof (mp_limb_t); + } + *p = limb; + limb >>= CHAR_BIT; + bytes--; + } + } + assert (i == un); + assert (k == count); + + if (countp) + *countp = count; + + return r; +} diff --git a/src/gmp/mini-gmp.h b/src/gmp/mini-gmp.h new file mode 100644 index 000000000..8c94ca2ed --- /dev/null +++ b/src/gmp/mini-gmp.h @@ -0,0 +1,256 @@ +/* mini-gmp, a minimalistic implementation of a GNU GMP subset. + +Copyright 2011, 2012, 2013 Free Software Foundation, Inc. + +This file is part of the GNU MP Library. + +The GNU MP Library is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 3 of the License, or (at your +option) any later version. + +The GNU MP Library 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 the GNU MP Library. If not, see http://www.gnu.org/licenses/. */ + +/* About mini-gmp: This is a minimal implementation of a subset of the + GMP interface. It is intended for inclusion into applications which + have modest bignums needs, as a fallback when the real GMP library + is not installed. + + This file defines the public interface. */ + +#ifndef __MINI_GMP_H__ +#define __MINI_GMP_H__ + +/* For size_t */ +#include <stddef.h> + +#if defined (__cplusplus) +extern "C" { +#endif + +void mp_set_memory_functions (void *(*) (size_t), + void *(*) (void *, size_t, size_t), + void (*) (void *, size_t)); + +void mp_get_memory_functions (void *(**) (size_t), + void *(**) (void *, size_t, size_t), + void (**) (void *, size_t)); + +typedef unsigned long mp_limb_t; +typedef long mp_size_t; +typedef unsigned long mp_bitcnt_t; + +typedef mp_limb_t *mp_ptr; +typedef const mp_limb_t *mp_srcptr; + +typedef struct +{ + int _mp_alloc; /* Number of *limbs* allocated and pointed + to by the _mp_d field. */ + int _mp_size; /* abs(_mp_size) is the number of limbs the + last field points to. If _mp_size is + negative this is a negative number. */ + mp_limb_t *_mp_d; /* Pointer to the limbs. */ +} __mpz_struct; + +typedef __mpz_struct mpz_t[1]; + +typedef __mpz_struct *mpz_ptr; +typedef const __mpz_struct *mpz_srcptr; + +void mpn_copyi (mp_ptr, mp_srcptr, mp_size_t); +void mpn_copyd (mp_ptr, mp_srcptr, mp_size_t); + +int mpn_cmp (mp_srcptr, mp_srcptr, mp_size_t); + +mp_limb_t mpn_add_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t); +mp_limb_t mpn_add_n (mp_ptr, mp_srcptr, mp_srcptr, mp_size_t); +mp_limb_t mpn_add (mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t); + +mp_limb_t mpn_sub_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t); +mp_limb_t mpn_sub_n (mp_ptr, mp_srcptr, mp_srcptr, mp_size_t); +mp_limb_t mpn_sub (mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t); + +mp_limb_t mpn_mul_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t); +mp_limb_t mpn_addmul_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t); +mp_limb_t mpn_submul_1 (mp_ptr, mp_srcptr, mp_size_t, mp_limb_t); + +mp_limb_t mpn_mul (mp_ptr, mp_srcptr, mp_size_t, mp_srcptr, mp_size_t); +void mpn_mul_n (mp_ptr, mp_srcptr, mp_srcptr, mp_size_t); +void mpn_sqr (mp_ptr, mp_srcptr, mp_size_t); + +mp_limb_t mpn_lshift (mp_ptr, mp_srcptr, mp_size_t, unsigned int); +mp_limb_t mpn_rshift (mp_ptr, mp_srcptr, mp_size_t, unsigned int); + +mp_limb_t mpn_invert_3by2 (mp_limb_t, mp_limb_t); +#define mpn_invert_limb(x) mpn_invert_3by2 ((x), 0) + +size_t mpn_get_str (unsigned char *, int, mp_ptr, mp_size_t); +mp_size_t mpn_set_str (mp_ptr, const unsigned char *, size_t, int); + +void mpz_init (mpz_t); +void mpz_init2 (mpz_t, mp_bitcnt_t); +void mpz_clear (mpz_t); + +#define mpz_odd_p(z) (((z)->_mp_size != 0) & (int) (z)->_mp_d[0]) +#define mpz_even_p(z) (! mpz_odd_p (z)) + +int mpz_sgn (const mpz_t); +int mpz_cmp_si (const mpz_t, long); +int mpz_cmp_ui (const mpz_t, unsigned long); +int mpz_cmp (const mpz_t, const mpz_t); +int mpz_cmpabs_ui (const mpz_t, unsigned long); +int mpz_cmpabs (const mpz_t, const mpz_t); +int mpz_cmp_d (const mpz_t, double); +int mpz_cmpabs_d (const mpz_t, double); + +void mpz_abs (mpz_t, const mpz_t); +void mpz_neg (mpz_t, const mpz_t); +void mpz_swap (mpz_t, mpz_t); + +void mpz_add_ui (mpz_t, const mpz_t, unsigned long); +void mpz_add (mpz_t, const mpz_t, const mpz_t); +void mpz_sub_ui (mpz_t, const mpz_t, unsigned long); +void mpz_ui_sub (mpz_t, unsigned long, const mpz_t); +void mpz_sub (mpz_t, const mpz_t, const mpz_t); + +void mpz_mul_si (mpz_t, const mpz_t, long int); +void mpz_mul_ui (mpz_t, const mpz_t, unsigned long int); +void mpz_mul (mpz_t, const mpz_t, const mpz_t); +void mpz_mul_2exp (mpz_t, const mpz_t, mp_bitcnt_t); + +void mpz_cdiv_qr (mpz_t, mpz_t, const mpz_t, const mpz_t); +void mpz_fdiv_qr (mpz_t, mpz_t, const mpz_t, const mpz_t); +void mpz_tdiv_qr (mpz_t, mpz_t, const mpz_t, const mpz_t); +void mpz_cdiv_q (mpz_t, const mpz_t, const mpz_t); +void mpz_fdiv_q (mpz_t, const mpz_t, const mpz_t); +void mpz_tdiv_q (mpz_t, const mpz_t, const mpz_t); +void mpz_cdiv_r (mpz_t, const mpz_t, const mpz_t); +void mpz_fdiv_r (mpz_t, const mpz_t, const mpz_t); +void mpz_tdiv_r (mpz_t, const mpz_t, const mpz_t); + +void mpz_cdiv_q_2exp (mpz_t, const mpz_t, mp_bitcnt_t); +void mpz_fdiv_q_2exp (mpz_t, const mpz_t, mp_bitcnt_t); +void mpz_tdiv_q_2exp (mpz_t, const mpz_t, mp_bitcnt_t); +void mpz_cdiv_r_2exp (mpz_t, const mpz_t, mp_bitcnt_t); +void mpz_fdiv_r_2exp (mpz_t, const mpz_t, mp_bitcnt_t); +void mpz_tdiv_r_2exp (mpz_t, const mpz_t, mp_bitcnt_t); + +void mpz_mod (mpz_t, const mpz_t, const mpz_t); + +void mpz_divexact (mpz_t, const mpz_t, const mpz_t); + +int mpz_divisible_p (const mpz_t, const mpz_t); + +unsigned long mpz_cdiv_qr_ui (mpz_t, mpz_t, const mpz_t, unsigned long); +unsigned long mpz_fdiv_qr_ui (mpz_t, mpz_t, const mpz_t, unsigned long); +unsigned long mpz_tdiv_qr_ui (mpz_t, mpz_t, const mpz_t, unsigned long); +unsigned long mpz_cdiv_q_ui (mpz_t, const mpz_t, unsigned long); +unsigned long mpz_fdiv_q_ui (mpz_t, const mpz_t, unsigned long); +unsigned long mpz_tdiv_q_ui (mpz_t, const mpz_t, unsigned long); +unsigned long mpz_cdiv_r_ui (mpz_t, const mpz_t, unsigned long); +unsigned long mpz_fdiv_r_ui (mpz_t, const mpz_t, unsigned long); +unsigned long mpz_tdiv_r_ui (mpz_t, const mpz_t, unsigned long); +unsigned long mpz_cdiv_ui (const mpz_t, unsigned long); +unsigned long mpz_fdiv_ui (const mpz_t, unsigned long); +unsigned long mpz_tdiv_ui (const mpz_t, unsigned long); + +unsigned long mpz_mod_ui (mpz_t, const mpz_t, unsigned long); + +void mpz_divexact_ui (mpz_t, const mpz_t, unsigned long); + +int mpz_divisible_ui_p (const mpz_t, unsigned long); + +unsigned long mpz_gcd_ui (mpz_t, const mpz_t, unsigned long); +void mpz_gcd (mpz_t, const mpz_t, const mpz_t); +void mpz_gcdext (mpz_t, mpz_t, mpz_t, const mpz_t, const mpz_t); +void mpz_lcm_ui (mpz_t, const mpz_t, unsigned long); +void mpz_lcm (mpz_t, const mpz_t, const mpz_t); +int mpz_invert (mpz_t, const mpz_t, const mpz_t); + +void mpz_sqrtrem (mpz_t, mpz_t, const mpz_t); +void mpz_sqrt (mpz_t, const mpz_t); + +void mpz_pow_ui (mpz_t, const mpz_t, unsigned long); +void mpz_ui_pow_ui (mpz_t, unsigned long, unsigned long); +void mpz_powm (mpz_t, const mpz_t, const mpz_t, const mpz_t); +void mpz_powm_ui (mpz_t, const mpz_t, unsigned long, const mpz_t); + +void mpz_rootrem (mpz_t, mpz_t, const mpz_t, unsigned long); +int mpz_root (mpz_t, const mpz_t, unsigned long); + +void mpz_fac_ui (mpz_t, unsigned long); +void mpz_bin_uiui (mpz_t, unsigned long, unsigned long); + +int mpz_tstbit (const mpz_t, mp_bitcnt_t); +void mpz_setbit (mpz_t, mp_bitcnt_t); +void mpz_clrbit (mpz_t, mp_bitcnt_t); +void mpz_combit (mpz_t, mp_bitcnt_t); + +void mpz_com (mpz_t, const mpz_t); +void mpz_and (mpz_t, const mpz_t, const mpz_t); +void mpz_ior (mpz_t, const mpz_t, const mpz_t); +void mpz_xor (mpz_t, const mpz_t, const mpz_t); + +mp_bitcnt_t mpz_popcount (const mpz_t); +mp_bitcnt_t mpz_hamdist (const mpz_t, const mpz_t); +mp_bitcnt_t mpz_scan0 (const mpz_t, mp_bitcnt_t); +mp_bitcnt_t mpz_scan1 (const mpz_t, mp_bitcnt_t); + +int mpz_fits_slong_p (const mpz_t); +int mpz_fits_ulong_p (const mpz_t); +long int mpz_get_si (const mpz_t); +unsigned long int mpz_get_ui (const mpz_t); +double mpz_get_d (const mpz_t); +size_t mpz_size (const mpz_t); +mp_limb_t mpz_getlimbn (const mpz_t, mp_size_t); + +void mpz_set_si (mpz_t, signed long int); +void mpz_set_ui (mpz_t, unsigned long int); +void mpz_set (mpz_t, const mpz_t); +void mpz_set_d (mpz_t, double); + +void mpz_init_set_si (mpz_t, signed long int); +void mpz_init_set_ui (mpz_t, unsigned long int); +void mpz_init_set (mpz_t, const mpz_t); +void mpz_init_set_d (mpz_t, double); + +size_t mpz_sizeinbase (const mpz_t, int); +char *mpz_get_str (char *, int, const mpz_t); +int mpz_set_str (mpz_t, const char *, int); +int mpz_init_set_str (mpz_t, const char *, int); + +/* This long list taken from gmp.h. */ +/* For reference, "defined(EOF)" cannot be used here. In g++ 2.95.4, + <iostream> defines EOF but not FILE. */ +#if defined (FILE) \ + || defined (H_STDIO) \ + || defined (_H_STDIO) /* AIX */ \ + || defined (_STDIO_H) /* glibc, Sun, SCO */ \ + || defined (_STDIO_H_) /* BSD, OSF */ \ + || defined (__STDIO_H) /* Borland */ \ + || defined (__STDIO_H__) /* IRIX */ \ + || defined (_STDIO_INCLUDED) /* HPUX */ \ + || defined (__dj_include_stdio_h_) /* DJGPP */ \ + || defined (_FILE_DEFINED) /* Microsoft */ \ + || defined (__STDIO__) /* Apple MPW MrC */ \ + || defined (_MSL_STDIO_H) /* Metrowerks */ \ + || defined (_STDIO_H_INCLUDED) /* QNX4 */ \ + || defined (_ISO_STDIO_ISO_H) /* Sun C++ */ \ + || defined (__STDIO_LOADED) /* VMS */ +size_t mpz_out_str (FILE *, int, const mpz_t); +#endif + +void mpz_import (mpz_t, size_t, int, size_t, int, size_t, const void *); +void *mpz_export (void *, size_t *, int, size_t, int, size_t, const mpz_t); + +#if defined (__cplusplus) +} +#endif +#endif /* __MINI_GMP_H__ */ diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp index 8210e0bf4..3937e405c 100644 --- a/src/guiChatConsole.cpp +++ b/src/guiChatConsole.cpp @@ -24,9 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gettime.h" #include "keycode.h" #include "settings.h" -#include "main.h" // for g_settings #include "porting.h" -#include "tile.h" +#include "client/tile.h" #include "fontengine.h" #include "log.h" #include "gettext.h" diff --git a/src/guiChatConsole.h b/src/guiChatConsole.h index 2bf45fdf4..652b265a4 100644 --- a/src/guiChatConsole.h +++ b/src/guiChatConsole.h @@ -122,9 +122,6 @@ private: // font gui::IGUIFont* m_font; v2u32 m_fontsize; -#if USE_FREETYPE - bool m_use_freetype; -#endif }; diff --git a/src/guiEngine.cpp b/src/guiEngine.cpp index de6f44134..c616bc322 100644 --- a/src/guiEngine.cpp +++ b/src/guiEngine.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiEngine.h" +#include <fstream> #include <IGUIStaticText.h> #include <ICameraSceneNode.h> #include "scripting_mainmenu.h" @@ -27,7 +28,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "porting.h" #include "filesys.h" -#include "main.h" #include "settings.h" #include "guiMainMenu.h" #include "sound.h" @@ -36,9 +36,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "httpfetch.h" #include "log.h" #include "fontengine.h" +#include "guiscalingfilter.h" #ifdef __ANDROID__ -#include "tile.h" +#include "client/tile.h" #include <GLES/gl.h> #endif @@ -52,7 +53,7 @@ TextDestGuiEngine::TextDestGuiEngine(GUIEngine* engine) } /******************************************************************************/ -void TextDestGuiEngine::gotText(std::map<std::string, std::string> fields) +void TextDestGuiEngine::gotText(const StringMap &fields) { m_engine->getScriptIface()->handleMainMenuButtons(fields); } @@ -60,7 +61,7 @@ void TextDestGuiEngine::gotText(std::map<std::string, std::string> fields) /******************************************************************************/ void TextDestGuiEngine::gotText(std::wstring text) { - m_engine->getScriptIface()->handleMainMenuEvent(wide_to_narrow(text)); + m_engine->getScriptIface()->handleMainMenuEvent(wide_to_utf8(text)); } /******************************************************************************/ @@ -171,8 +172,8 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, m_sound_manager = &dummySoundManager; //create topleft header - std::wstring t = narrow_to_wide(std::string("Minetest ") + - minetest_version_hash); + std::wstring t = utf8_to_wide(std::string(PROJECT_NAME_C " ") + + g_version_hash); core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(t), g_fontengine->getTextHeight()); rect += v2s32(4, 0); @@ -194,7 +195,8 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, m_texture_source, m_formspecgui, m_buttonhandler, - NULL); + NULL, + false); m_menu->allowClose(false); m_menu->lockSize(true,v2u32(800,600)); @@ -206,10 +208,8 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, m_script = new MainMenuScripting(this); try { - if (m_data->errormessage != "") { - m_script->setMainMenuErrorMessage(m_data->errormessage); - m_data->errormessage = ""; - } + m_script->setMainMenuData(&m_data->script_data); + m_data->script_data.errormessage = ""; if (!loadMainMenuScript()) { errorstream << "No future without mainmenu" << std::endl; @@ -217,10 +217,9 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev, } run(); - } - catch(LuaError &e) { + } catch (LuaError &e) { errorstream << "MAINMENU ERROR: " << e.what() << std::endl; - m_data->errormessage = e.what(); + m_data->script_data.errormessage = e.what(); } m_menu->quitMenu(); @@ -304,7 +303,7 @@ void GUIEngine::run() GUIEngine::~GUIEngine() { video::IVideoDriver* driver = m_device->getVideoDriver(); - assert(driver != 0); + FATAL_ERROR_IF(driver == 0, "Could not get video driver"); if(m_sound_manager != &dummySoundManager){ delete m_sound_manager; @@ -408,7 +407,7 @@ void GUIEngine::drawBackground(video::IVideoDriver* driver) { for (unsigned int y = 0; y < screensize.Y; y += tilesize.Y ) { - driver->draw2DImage(texture, + draw2DImageFilterScaled(driver, texture, core::rect<s32>(x, y, x+tilesize.X, y+tilesize.Y), core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), NULL, NULL, true); @@ -418,7 +417,7 @@ void GUIEngine::drawBackground(video::IVideoDriver* driver) } /* Draw background texture */ - driver->draw2DImage(texture, + draw2DImageFilterScaled(driver, texture, core::rect<s32>(0, 0, screensize.X, screensize.Y), core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), NULL, NULL, true); @@ -437,7 +436,7 @@ void GUIEngine::drawOverlay(video::IVideoDriver* driver) /* Draw background texture */ v2u32 sourcesize = texture->getOriginalSize(); - driver->draw2DImage(texture, + draw2DImageFilterScaled(driver, texture, core::rect<s32>(0, 0, screensize.X, screensize.Y), core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y), NULL, NULL, true); @@ -470,7 +469,7 @@ void GUIEngine::drawHeader(video::IVideoDriver* driver) video::SColor bgcolor(255,50,50,50); - driver->draw2DImage(texture, splashrect, + draw2DImageFilterScaled(driver, texture, splashrect, core::rect<s32>(core::position2d<s32>(0,0), core::dimension2di(texture->getOriginalSize())), NULL, NULL, true); @@ -502,7 +501,7 @@ void GUIEngine::drawFooter(video::IVideoDriver* driver) rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y); rect -= v2s32(footersize.X/2, 0); - driver->draw2DImage(texture, rect, + draw2DImageFilterScaled(driver, texture, rect, core::rect<s32>(core::position2d<s32>(0,0), core::dimension2di(texture->getOriginalSize())), NULL, NULL, true); @@ -514,7 +513,7 @@ bool GUIEngine::setTexture(texture_layer layer, std::string texturepath, bool tile_image, unsigned int minsize) { video::IVideoDriver* driver = m_device->getVideoDriver(); - assert(driver != 0); + FATAL_ERROR_IF(driver == 0, "Could not get video driver"); if (m_textures[layer].texture != NULL) { @@ -570,13 +569,13 @@ bool GUIEngine::downloadFile(std::string url, std::string target) /******************************************************************************/ void GUIEngine::setTopleftText(std::string append) { - std::wstring toset = narrow_to_wide( std::string("Minetest ") + - minetest_version_hash); + std::wstring toset = utf8_to_wide(std::string(PROJECT_NAME_C " ") + + g_version_hash); if (append != "") { toset += L" / "; - toset += narrow_to_wide(append); + toset += utf8_to_wide(append); } m_irr_toplefttext->setText(toset.c_str()); diff --git a/src/guiEngine.h b/src/guiEngine.h index 0be8634dd..d527f7222 100644 --- a/src/guiEngine.h +++ b/src/guiEngine.h @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "modalMenu.h" #include "guiFormSpecMenu.h" #include "sound.h" -#include "tile.h" +#include "client/tile.h" /******************************************************************************/ /* Typedefs and macros */ @@ -73,7 +73,7 @@ public: * receive fields transmitted by guiFormSpecMenu * @param fields map containing formspec field elements currently active */ - void gotText(std::map<std::string, std::string> fields); + void gotText(const StringMap &fields); /** * receive text/events transmitted by guiFormSpecMenu diff --git a/src/guiFileSelectMenu.cpp b/src/guiFileSelectMenu.cpp index e98b025c6..e02407427 100644 --- a/src/guiFileSelectMenu.cpp +++ b/src/guiFileSelectMenu.cpp @@ -26,7 +26,7 @@ GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env, std::string title, std::string formname) : GUIModalMenu(env, parent, id, menumgr) { - m_title = narrow_to_wide(title); + m_title = utf8_to_wide(title); m_parent = parent; m_formname = formname; m_text_dst = 0; @@ -84,10 +84,10 @@ void GUIFileSelectMenu::drawMenu() void GUIFileSelectMenu::acceptInput() { if ((m_text_dst != 0) && (this->m_formname != "")){ - std::map<std::string, std::string> fields; + StringMap fields; if (m_accepted) - fields[m_formname + "_accepted"] = wide_to_narrow(m_fileOpenDialog->getFileName()); + fields[m_formname + "_accepted"] = wide_to_utf8(m_fileOpenDialog->getFileName()); else fields[m_formname + "_canceled"] = m_formname; diff --git a/src/guiFormSpecMenu.cpp b/src/guiFormSpecMenu.cpp index 3f285fa5e..62a84460f 100644 --- a/src/guiFormSpecMenu.cpp +++ b/src/guiFormSpecMenu.cpp @@ -37,21 +37,24 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <IGUITabControl.h> #include <IGUIComboBox.h> #include "log.h" -#include "tile.h" // ITextureSource +#include "client/tile.h" // ITextureSource #include "hud.h" // drawItemStack -#include "hex.h" -#include "util/string.h" -#include "util/numeric.h" #include "filesys.h" #include "gettime.h" #include "gettext.h" #include "scripting_game.h" #include "porting.h" -#include "main.h" #include "settings.h" #include "client.h" -#include "util/string.h" // for parseColorString() #include "fontengine.h" +#include "util/hex.h" +#include "util/numeric.h" +#include "util/string.h" // for parseColorString() +#include "guiscalingfilter.h" + +#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 +#include "intlGUIEditBox.h" +#endif #define MY_CHECKPOS(a,b) \ if (v_pos.size() != 2) { \ @@ -78,7 +81,7 @@ GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev, gui::IGUIElement* parent, s32 id, IMenuManager *menumgr, InventoryManager *invmgr, IGameDef *gamedef, ISimpleTextureSource *tsrc, IFormSource* fsrc, TextDest* tdst, - Client* client) : + Client* client, bool remap_dbl_click) : GUIModalMenu(dev->getGUIEnvironment(), parent, id, menumgr), m_device(dev), m_invmgr(invmgr), @@ -97,10 +100,11 @@ GUIFormSpecMenu::GUIFormSpecMenu(irr::IrrlichtDevice* dev, m_form_src(fsrc), m_text_dst(tdst), m_formspec_version(0), - m_focused_element(L""), - m_font(NULL) + m_focused_element(""), + m_font(NULL), + m_remap_dbl_click(remap_dbl_click) #ifdef __ANDROID__ - ,m_JavaDialogFieldName(L"") + , m_JavaDialogFieldName("") #endif { current_keys_pending.key_down = false; @@ -227,7 +231,7 @@ void GUIFormSpecMenu::setInitialFocus() Environment->setFocus(*(children.begin())); } -GUITable* GUIFormSpecMenu::getTable(std::wstring tablename) +GUITable* GUIFormSpecMenu::getTable(const std::string &tablename) { for (u32 i = 0; i < m_tables.size(); ++i) { if (tablename == m_tables[i].first.fname) @@ -236,28 +240,27 @@ GUITable* GUIFormSpecMenu::getTable(std::wstring tablename) return 0; } -std::vector<std::string> split(const std::string &s, char delim) { +static std::vector<std::string> split(const std::string &s, char delim) +{ std::vector<std::string> tokens; std::string current = ""; bool last_was_escape = false; - for(unsigned int i=0; i < s.size(); i++) { + for (unsigned int i = 0; i < s.size(); i++) { + char si = s.c_str()[i]; if (last_was_escape) { current += '\\'; - current += s.c_str()[i]; + current += si; last_was_escape = false; - } - else { - if (s.c_str()[i] == delim) { + } else { + if (si == delim) { tokens.push_back(current); current = ""; last_was_escape = false; - } - else if (s.c_str()[i] == '\\'){ + } else if (si == '\\') { last_was_escape = true; - } - else { - current += s.c_str()[i]; + } else { + current += si; last_was_escape = false; } } @@ -349,6 +352,41 @@ void GUIFormSpecMenu::parseList(parserData* data,std::string element) errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl; } +void GUIFormSpecMenu::parseListRing(parserData* data, std::string element) +{ + if (m_gamedef == 0) { + errorstream << "WARNING: invalid use of 'listring' with m_gamedef==0" << std::endl; + return; + } + + std::vector<std::string> parts = split(element, ';'); + + if (parts.size() == 2) { + std::string location = parts[0]; + std::string listname = parts[1]; + + InventoryLocation loc; + + if (location == "context" || location == "current_name") + loc = m_current_inventory_location; + else + loc.deSerialize(location); + + m_inventory_rings.push_back(ListRingSpec(loc, listname)); + return; + } else if ((element == "") && (m_inventorylists.size() > 1)) { + size_t siz = m_inventorylists.size(); + // insert the last two inv list elements into the list ring + const ListDrawSpec &spa = m_inventorylists[siz - 2]; + const ListDrawSpec &spb = m_inventorylists[siz - 1]; + m_inventory_rings.push_back(ListRingSpec(spa.inventoryloc, spa.listname)); + m_inventory_rings.push_back(ListRingSpec(spb.inventoryloc, spb.listname)); + return; + } + errorstream<< "Invalid list ring element(" << parts.size() << ", " + << m_inventorylists.size() << "): '" << element << "'" << std::endl; +} + void GUIFormSpecMenu::parseCheckbox(parserData* data,std::string element) { std::vector<std::string> parts = split(element,';'); @@ -375,7 +413,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data,std::string element) if (selected == "true") fselected = true; - std::wstring wlabel = narrow_to_wide(label); + std::wstring wlabel = utf8_to_wide(label); core::rect<s32> rect = core::rect<s32>( pos.X, pos.Y + ((imgsize.Y/2) - m_btn_height), @@ -383,7 +421,7 @@ void GUIFormSpecMenu::parseCheckbox(parserData* data,std::string element) pos.Y + ((imgsize.Y/2) + m_btn_height)); FieldSpec spec( - narrow_to_wide(name), + name, wlabel, //Needed for displaying text on MSVC wlabel, 258+m_fields.size() @@ -435,7 +473,7 @@ void GUIFormSpecMenu::parseScrollBar(parserData* data, std::string element) core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y); FieldSpec spec( - narrow_to_wide(name), + name, L"", L"", 258+m_fields.size() @@ -573,10 +611,10 @@ void GUIFormSpecMenu::parseButton(parserData* data,std::string element, label = unescape_string(label); - std::wstring wlabel = narrow_to_wide(label); + std::wstring wlabel = utf8_to_wide(label); FieldSpec spec( - narrow_to_wide(name), + name, wlabel, L"", 258+m_fields.size() @@ -698,10 +736,8 @@ void GUIFormSpecMenu::parseTable(parserData* data,std::string element) core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - std::wstring fname_w = narrow_to_wide(name); - FieldSpec spec( - fname_w, + name, L"", L"", 258+m_fields.size() @@ -723,8 +759,8 @@ void GUIFormSpecMenu::parseTable(parserData* data,std::string element) e->setTable(data->table_options, data->table_columns, items); - if (data->table_dyndata.find(fname_w) != data->table_dyndata.end()) { - e->setDynamicData(data->table_dyndata[fname_w]); + if (data->table_dyndata.find(name) != data->table_dyndata.end()) { + e->setDynamicData(data->table_dyndata[name]); } if ((str_initial_selection != "") && @@ -772,10 +808,8 @@ void GUIFormSpecMenu::parseTextList(parserData* data,std::string element) core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); - std::wstring fname_w = narrow_to_wide(name); - FieldSpec spec( - fname_w, + name, L"", L"", 258+m_fields.size() @@ -797,8 +831,8 @@ void GUIFormSpecMenu::parseTextList(parserData* data,std::string element) e->setTextList(items, is_yes(str_transparent)); - if (data->table_dyndata.find(fname_w) != data->table_dyndata.end()) { - e->setDynamicData(data->table_dyndata[fname_w]); + if (data->table_dyndata.find(name) != data->table_dyndata.end()) { + e->setDynamicData(data->table_dyndata[name]); } if ((str_initial_selection != "") && @@ -837,10 +871,8 @@ void GUIFormSpecMenu::parseDropDown(parserData* data,std::string element) core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X + width, pos.Y + (m_btn_height * 2)); - std::wstring fname_w = narrow_to_wide(name); - FieldSpec spec( - fname_w, + name, L"", L"", 258+m_fields.size() @@ -857,7 +889,7 @@ void GUIFormSpecMenu::parseDropDown(parserData* data,std::string element) } for (unsigned int i=0; i < items.size(); i++) { - e->addItem(narrow_to_wide(items[i]).c_str()); + e->addItem(utf8_to_wide(items[i]).c_str()); } if (str_initial_selection != "") @@ -900,10 +932,10 @@ void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element) label = unescape_string(label); - std::wstring wlabel = narrow_to_wide(label); + std::wstring wlabel = utf8_to_wide(label); FieldSpec spec( - narrow_to_wide(name), + name, wlabel, L"", 258+m_fields.size() @@ -966,12 +998,12 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data, default_val = unescape_string(default_val); label = unescape_string(label); - std::wstring wlabel = narrow_to_wide(label); + std::wstring wlabel = utf8_to_wide(label); FieldSpec spec( - narrow_to_wide(name), + name, wlabel, - narrow_to_wide(default_val), + utf8_to_wide(default_val), 258+m_fields.size() ); @@ -983,9 +1015,18 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data, else { spec.send = true; - gui::IGUIEditBox *e = - Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid); - + gui::IGUIElement *e; +#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 + if (g_settings->getBool("freetype")) { + e = (gui::IGUIElement *) new gui::intlGUIEditBox(spec.fdefault.c_str(), + true, Environment, this, spec.fid, rect); + e->drop(); + } else { +#else + { +#endif + e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid); + } if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); } @@ -1056,12 +1097,12 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, default_val = unescape_string(default_val); label = unescape_string(label); - std::wstring wlabel = narrow_to_wide(label); + std::wstring wlabel = utf8_to_wide(label); FieldSpec spec( - narrow_to_wide(name), + name, wlabel, - narrow_to_wide(default_val), + utf8_to_wide(default_val), 258+m_fields.size() ); @@ -1073,8 +1114,19 @@ void GUIFormSpecMenu::parseTextArea(parserData* data, else { spec.send = true; - gui::IGUIEditBox *e = - Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid); + + gui::IGUIEditBox *e; +#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 + if (g_settings->getBool("freetype")) { + e = (gui::IGUIEditBox *) new gui::intlGUIEditBox(spec.fdefault.c_str(), + true, Environment, this, spec.fid, rect); + e->drop(); + } else { +#else + { +#endif + e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid); + } if (spec.fname == data->focused_fieldname) { Environment->setFocus(e); @@ -1159,13 +1211,13 @@ void GUIFormSpecMenu::parseLabel(parserData* data,std::string element) // 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 = narrow_to_wide(lines[i]); + std::wstring wlabel = utf8_to_wide(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); FieldSpec spec( - L"", + "", wlabel, L"", 258+m_fields.size() @@ -1191,7 +1243,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element) ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) { std::vector<std::string> v_pos = split(parts[0],','); - std::wstring text = narrow_to_wide(unescape_string(parts[1])); + std::wstring text = utf8_to_wide(unescape_string(parts[1])); MY_CHECKPOS("vertlabel",1); @@ -1218,7 +1270,7 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element) } FieldSpec spec( - L"", + "", label, L"", 258+m_fields.size() @@ -1280,12 +1332,12 @@ void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element, pressed_image_name = unescape_string(pressed_image_name); label = unescape_string(label); - std::wstring wlabel = narrow_to_wide(label); + std::wstring wlabel = utf8_to_wide(label); FieldSpec spec( - narrow_to_wide(name), + name, wlabel, - narrow_to_wide(image_name), + utf8_to_wide(image_name), 258+m_fields.size() ); spec.ftype = f_Button; @@ -1307,8 +1359,10 @@ void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element, } e->setUseAlphaChannel(true); - e->setImage(texture); - e->setPressedImage(pressed_texture); + 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); @@ -1345,7 +1399,7 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data,std::string element) } FieldSpec spec( - narrow_to_wide(name), + name, L"", L"", 258+m_fields.size() @@ -1375,8 +1429,8 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data,std::string element) e->setNotClipped(true); - for (unsigned int i=0; i< buttons.size(); i++) { - e->addTab(narrow_to_wide(buttons[i]).c_str(), -1); + for (unsigned int i = 0; i < buttons.size(); i++) { + e->addTab(utf8_to_wide(buttons[i]).c_str(), -1); } if ((tab_index >= 0) && @@ -1432,17 +1486,17 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element) item.deSerialize(item_name, idef); video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef); - m_tooltips[narrow_to_wide(name)] = - TooltipSpec (item.getDefinition(idef).description, + m_tooltips[name] = + TooltipSpec(item.getDefinition(idef).description, m_default_tooltip_bgcolor, m_default_tooltip_color); label = unescape_string(label); FieldSpec spec( - narrow_to_wide(name), - narrow_to_wide(label), - narrow_to_wide(item_name), - 258+m_fields.size() + name, + utf8_to_wide(label), + utf8_to_wide(item_name), + 258 + m_fields.size() ); gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str()); @@ -1452,8 +1506,8 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element) } e->setUseAlphaChannel(true); - e->setImage(texture); - e->setPressedImage(texture); + e->setImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y)); + e->setPressedImage(guiScalingImageButton(Environment->getVideoDriver(), texture, geom.X, geom.Y)); e->setScaleImage(true); spec.ftype = f_Button; rect+=data->basepos-padding; @@ -1551,13 +1605,15 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, std::string element) std::vector<std::string> parts = split(element,';'); if (parts.size() == 2) { std::string name = parts[0]; - m_tooltips[narrow_to_wide(name)] = TooltipSpec (parts[1], m_default_tooltip_bgcolor, m_default_tooltip_color); + m_tooltips[name] = TooltipSpec(unescape_string(parts[1]), + m_default_tooltip_bgcolor, m_default_tooltip_color); return; } else if (parts.size() == 4) { std::string name = parts[0]; video::SColor tmp_color1, tmp_color2; if ( parseColorString(parts[2], tmp_color1, false) && parseColorString(parts[3], tmp_color2, false) ) { - m_tooltips[narrow_to_wide(name)] = TooltipSpec (parts[1], tmp_color1, tmp_color2); + m_tooltips[name] = TooltipSpec(unescape_string(parts[1]), + tmp_color1, tmp_color2); return; } } @@ -1642,6 +1698,11 @@ void GUIFormSpecMenu::parseElement(parserData* data, std::string element) return; } + if (type == "listring") { + parseListRing(data, description); + return; + } + if (type == "checkbox") { parseCheckbox(data,description); return; @@ -1769,7 +1830,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) //preserve tables for (u32 i = 0; i < m_tables.size(); ++i) { - std::wstring tablename = m_tables[i].first.fname; + std::string tablename = m_tables[i].first.fname; GUITable *table = m_tables[i].second; mydata.table_dyndata[tablename] = table->getDynamicData(); } @@ -1974,7 +2035,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) m_tooltip_element->setOverrideFont(m_font); gui::IGUISkin* skin = Environment->getSkin(); - assert(skin != NULL); + sanity_check(skin != NULL); gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); @@ -2024,7 +2085,7 @@ void GUIFormSpecMenu::regenerateGui(v2u32 screensize) bool GUIFormSpecMenu::getAndroidUIInput() { /* no dialog shown */ - if (m_JavaDialogFieldName == L"") { + if (m_JavaDialogFieldName == "") { return false; } @@ -2033,8 +2094,8 @@ bool GUIFormSpecMenu::getAndroidUIInput() return true; } - std::wstring fieldname = m_JavaDialogFieldName; - m_JavaDialogFieldName = L""; + std::string fieldname = m_JavaDialogFieldName; + m_JavaDialogFieldName = ""; /* no value abort dialog processing */ if (porting::getInputDialogState() != 0) { @@ -2060,7 +2121,7 @@ bool GUIFormSpecMenu::getAndroidUIInput() std::string text = porting::getInputDialogValue(); ((gui::IGUIEditBox*) tochange)-> - setText(narrow_to_wide(text).c_str()); + setText(utf8_to_wide(text).c_str()); } return false; } @@ -2184,7 +2245,7 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase) m_tooltip_element->setOverrideColor(m_default_tooltip_color); m_tooltip_element->setVisible(true); this->bringToFront(m_tooltip_element); - m_tooltip_element->setText(narrow_to_wide(tooltip_text).c_str()); + m_tooltip_element->setText(utf8_to_wide(tooltip_text).c_str()); s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5; v2u32 screenSize = driver->getScreenSize(); @@ -2218,9 +2279,9 @@ void GUIFormSpecMenu::drawSelectedItem() video::IVideoDriver* driver = Environment->getVideoDriver(); Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc); - assert(inv); + sanity_check(inv); InventoryList *list = inv->getList(m_selected_item->listname); - assert(list); + sanity_check(list); ItemStack stack = list->getItem(m_selected_item->i); stack.count = m_selected_amount; @@ -2240,7 +2301,7 @@ void GUIFormSpecMenu::drawMenu() } gui::IGUISkin* skin = Environment->getSkin(); - assert(skin != NULL); + sanity_check(skin != NULL); gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); @@ -2281,7 +2342,7 @@ void GUIFormSpecMenu::drawMenu() const video::SColor color(255,255,255,255); const video::SColor colors[] = {color,color,color,color}; - driver->draw2DImage(texture, rect, + draw2DImageFilterScaled(driver, texture, rect, core::rect<s32>(core::position2d<s32>(0,0), core::dimension2di(texture->getOriginalSize())), NULL/*&AbsoluteClippingRect*/, colors, true); @@ -2331,7 +2392,7 @@ void GUIFormSpecMenu::drawMenu() core::rect<s32> rect = imgrect + spec.pos; const video::SColor color(255,255,255,255); const video::SColor colors[] = {color,color,color,color}; - driver->draw2DImage(texture, rect, + draw2DImageFilterScaled(driver, texture, rect, core::rect<s32>(core::position2d<s32>(0,0),img_origsize), NULL/*&AbsoluteClippingRect*/, colors, true); } @@ -2360,7 +2421,7 @@ void GUIFormSpecMenu::drawMenu() core::rect<s32> rect = imgrect + spec.pos; const video::SColor color(255,255,255,255); const video::SColor colors[] = {color,color,color,color}; - driver->draw2DImage(texture, rect, + draw2DImageFilterScaled(driver, texture, rect, core::rect<s32>(core::position2d<s32>(0,0), core::dimension2di(texture->getOriginalSize())), NULL/*&AbsoluteClippingRect*/, colors, true); @@ -2416,7 +2477,7 @@ void GUIFormSpecMenu::drawMenu() if ( (iter->fid == id) && (m_tooltips[iter->fname].tooltip != "") ){ if (m_old_tooltip != m_tooltips[iter->fname].tooltip) { m_old_tooltip = m_tooltips[iter->fname].tooltip; - m_tooltip_element->setText(narrow_to_wide(m_tooltips[iter->fname].tooltip).c_str()); + m_tooltip_element->setText(utf8_to_wide(m_tooltips[iter->fname].tooltip).c_str()); std::vector<std::string> tt_rows = str_split(m_tooltips[iter->fname].tooltip, '\n'); s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5; @@ -2575,7 +2636,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) { if(m_text_dst) { - std::map<std::string, std::string> fields; + StringMap fields; if (quitmode == quit_mode_accept) { fields["quit"] = "true"; @@ -2610,11 +2671,10 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) for(unsigned int i=0; i<m_fields.size(); i++) { const FieldSpec &s = m_fields[i]; if(s.send) { - std::string name = wide_to_narrow(s.fname); - if(s.ftype == f_Button) { - fields[name] = wide_to_narrow(s.flabel); - } - else if(s.ftype == f_Table) { + std::string name = s.fname; + if (s.ftype == f_Button) { + fields[name] = wide_to_utf8(s.flabel); + } else if (s.ftype == f_Table) { GUITable *table = getTable(s.fname); if (table) { fields[name] = table->checkEvent(); @@ -2631,7 +2691,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) s32 selected = e->getSelected(); if (selected >= 0) { fields[name] = - wide_to_narrow(e->getItem(selected)); + wide_to_utf8(e->getItem(selected)); } } else if (s.ftype == f_TabHeader) { @@ -2687,7 +2747,7 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no) { IGUIElement* e = getElementFromId(s.fid); if(e != NULL) { - fields[name] = wide_to_narrow(e->getText()); + fields[name] = wide_to_utf8(e->getText()); } } } @@ -2726,7 +2786,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) if (hovered && isMyChild(hovered) && hovered->getType() == gui::EGUIET_TAB_CONTROL) { gui::IGUISkin* skin = Environment->getSkin(); - assert(skin != NULL); + sanity_check(skin != NULL); gui::IGUIFont *old_font = skin->getFont(); skin->setFont(m_font); bool retval = hovered->OnEvent(event); @@ -2795,7 +2855,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) } m_JavaDialogFieldName = getNameByID(hovered->getID()); std::string message = gettext("Enter "); - std::string label = wide_to_narrow(getLabelByID(hovered->getID())); + std::string label = wide_to_utf8(getLabelByID(hovered->getID())); if (label == "") { label = "text"; } @@ -2815,7 +2875,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) } porting::showInputDialog(gettext("ok"), "", - wide_to_narrow(((gui::IGUIEditBox*) hovered)->getText()), + wide_to_utf8(((gui::IGUIEditBox*) hovered)->getText()), type); return retval; } @@ -2937,6 +2997,19 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) /******************************************************************************/ bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event) { + /* The following code is for capturing double-clicks of the mouse button + * and translating the double-click into an EET_KEY_INPUT_EVENT event + * -- which closes the form -- under some circumstances. + * + * There have been many github issues reporting this as a bug even though it + * was an intended feature. For this reason, remapping the double-click as + * an ESC must be explicitly set when creating this class via the + * /p remap_dbl_click parameter of the constructor. + */ + + if (!m_remap_dbl_click) + return false; + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos; m_doubleclickdetect[0].time = m_doubleclickdetect[1].time; @@ -2975,26 +3048,27 @@ bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event) delete translated; return true; } + return false; } bool GUIFormSpecMenu::OnEvent(const SEvent& event) { - if(event.EventType==EET_KEY_INPUT_EVENT) { + if (event.EventType==EET_KEY_INPUT_EVENT) { KeyPress kp(event.KeyInput); if (event.KeyInput.PressedDown && ( (kp == EscapeKey) || - (kp == getKeySetting("keymap_inventory")) || (kp == CancelKey))) { + (kp == getKeySetting("keymap_inventory")) || (kp == CancelKey))) { if (m_allowclose) { doPause = false; acceptInput(quit_mode_cancel); quitMenu(); } else { - m_text_dst->gotText(narrow_to_wide("MenuQuit")); + m_text_dst->gotText(L"MenuQuit"); } return true; } else if (m_client != NULL && event.KeyInput.PressedDown && - (kp == getKeySetting("keymap_screenshot"))) { - m_client->makeScreenshot(m_device); + (kp == getKeySetting("keymap_screenshot"))) { + m_client->makeScreenshot(m_device); } if (event.KeyInput.PressedDown && (event.KeyInput.Key==KEY_RETURN || @@ -3014,7 +3088,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) break; default: //can't happen at all! - assert("reached a source line that can't ever been reached" == 0); + FATAL_ERROR("Reached a source line that can't ever been reached"); break; } if (current_keys_pending.key_enter && m_allowclose) { @@ -3045,44 +3119,45 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) Inventory *inv_selected = NULL; Inventory *inv_s = NULL; + InventoryList *list_s = NULL; - if(m_selected_item) { + if (m_selected_item) { inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc); - assert(inv_selected); - assert(inv_selected->getList(m_selected_item->listname) != NULL); + sanity_check(inv_selected); + sanity_check(inv_selected->getList(m_selected_item->listname) != NULL); } u32 s_count = 0; - if(s.isValid()) + if (s.isValid()) do { // breakable inv_s = m_invmgr->getInventory(s.inventoryloc); - if(!inv_s) { - errorstream<<"InventoryMenu: The selected inventory location " - <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist" - <<std::endl; + if (!inv_s) { + errorstream << "InventoryMenu: The selected inventory location " + << "\"" << s.inventoryloc.dump() << "\" doesn't exist" + << std::endl; s.i = -1; // make it invalid again break; } - InventoryList *list = inv_s->getList(s.listname); - if(list == NULL) { - verbosestream<<"InventoryMenu: The selected inventory list \"" - <<s.listname<<"\" does not exist"<<std::endl; + list_s = inv_s->getList(s.listname); + if (list_s == NULL) { + verbosestream << "InventoryMenu: The selected inventory list \"" + << s.listname << "\" does not exist" << std::endl; s.i = -1; // make it invalid again break; } - if((u32)s.i >= list->getSize()) { - infostream<<"InventoryMenu: The selected inventory list \"" - <<s.listname<<"\" is too small (i="<<s.i<<", size=" - <<list->getSize()<<")"<<std::endl; + if ((u32)s.i >= list_s->getSize()) { + infostream << "InventoryMenu: The selected inventory list \"" + << s.listname << "\" is too small (i=" << s.i << ", size=" + << list_s->getSize() << ")" << std::endl; s.i = -1; // make it invalid again break; } - s_count = list->getItem(s.i).count; + s_count = list_s->getItem(s.i).count; } while(0); bool identical = (m_selected_item != NULL) && s.isValid() && @@ -3094,25 +3169,29 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) // up/down: 0 = down (press), 1 = up (release), 2 = unknown event, -1 movement int button = 0; int updown = 2; - if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) + if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { button = 0; updown = 0; } - else if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN) + else if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN) { button = 1; updown = 0; } - else if(event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN) + else if (event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN) { button = 2; updown = 0; } - else if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) + else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { button = 0; updown = 1; } - else if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP) + else if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP) { button = 1; updown = 1; } - else if(event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP) + else if (event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP) { button = 2; updown = 1; } - else if(event.MouseInput.Event == EMIE_MOUSE_MOVED) + else if (event.MouseInput.Event == EMIE_MOUSE_MOVED) { updown = -1;} // Set this number to a positive value to generate a move action // from m_selected_item to s. u32 move_amount = 0; + // Set this number to a positive value to generate a move action + // from s to the next inventory ring. + u32 shift_move_amount = 0; + // Set this number to a positive value to generate a drop action // from m_selected_item. u32 drop_amount = 0; @@ -3120,7 +3199,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) // Set this number to a positive value to generate a craft action at s. u32 craft_amount = 0; - if(updown == 0) { + if (updown == 0) { // Some mouse button has been pressed //infostream<<"Mouse button "<<button<<" pressed at p=(" @@ -3128,40 +3207,49 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) m_selected_dragging = false; - if(s.isValid() && s.listname == "craftpreview") { + if (s.isValid() && s.listname == "craftpreview") { // Craft preview has been clicked: craft craft_amount = (button == 2 ? 10 : 1); - } - else if(m_selected_item == NULL) { - if(s_count != 0) { - // Non-empty stack has been clicked: select it + } else if (m_selected_item == NULL) { + if (s_count != 0) { + // Non-empty stack has been clicked: select or shift-move it m_selected_item = new ItemSpec(s); - if(button == 1) // right - m_selected_amount = (s_count + 1) / 2; - else if(button == 2) // middle - m_selected_amount = MYMIN(s_count, 10); + u32 count; + if (button == 1) // right + count = (s_count + 1) / 2; + else if (button == 2) // middle + count = MYMIN(s_count, 10); else // left - m_selected_amount = s_count; + count = s_count; - m_selected_dragging = true; - m_rmouse_auto_place = false; + if (!event.MouseInput.Shift) { + // no shift: select item + m_selected_amount = count; + m_selected_dragging = true; + m_rmouse_auto_place = false; + } else { + // shift pressed: move item + if (button != 1) + shift_move_amount = count; + else // count of 1 at left click like after drag & drop + shift_move_amount = 1; + } } - } - else { // m_selected_item != NULL + } else { // m_selected_item != NULL assert(m_selected_amount >= 1); - if(s.isValid()) { + if (s.isValid()) { // Clicked a slot: move - if(button == 1) // right + if (button == 1) // right move_amount = 1; - else if(button == 2) // middle + else if (button == 2) // middle move_amount = MYMIN(m_selected_amount, 10); else // left move_amount = m_selected_amount; - if(identical) { - if(move_amount >= m_selected_amount) + if (identical) { + if (move_amount >= m_selected_amount) m_selected_amount = 0; else m_selected_amount -= move_amount; @@ -3170,29 +3258,28 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) { // Clicked outside of the window: drop - if(button == 1) // right + if (button == 1) // right drop_amount = 1; - else if(button == 2) // middle + else if (button == 2) // middle drop_amount = MYMIN(m_selected_amount, 10); else // left drop_amount = m_selected_amount; } } } - else if(updown == 1) { + else if (updown == 1) { // Some mouse button has been released //infostream<<"Mouse button "<<button<<" released at p=(" // <<p.X<<","<<p.Y<<")"<<std::endl; - if(m_selected_item != NULL && m_selected_dragging && s.isValid()) { - if(!identical) { + if (m_selected_item != NULL && m_selected_dragging && s.isValid()) { + if (!identical) { // Dragged to different slot: move all selected move_amount = m_selected_amount; } - } - else if(m_selected_item != NULL && m_selected_dragging && - !(getAbsoluteClippingRect().isPointInside(m_pointer))) { + } else if (m_selected_item != NULL && m_selected_dragging && + !(getAbsoluteClippingRect().isPointInside(m_pointer))) { // Dragged outside of window: drop all selected drop_amount = m_selected_amount; } @@ -3201,14 +3288,13 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) // Keep count of how many times right mouse button has been // clicked. One click is drag without dropping. Click + release // + click changes to drop one item when moved mode - if(button == 1 && m_selected_item != NULL) + if (button == 1 && m_selected_item != NULL) m_rmouse_auto_place = !m_rmouse_auto_place; - } - else if(updown == -1) { + } else if (updown == -1) { // 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 != NULL && s.isValid()){ + if (m_selected_item != NULL && s.isValid()) { // Move 1 item // TODO: middle mouse to move 10 items might be handy if (m_rmouse_auto_place) { @@ -3216,7 +3302,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) // or contains the same item type as what is going to be // moved InventoryList *list_from = inv_selected->getList(m_selected_item->listname); - InventoryList *list_to = inv_s->getList(s.listname); + InventoryList *list_to = list_s; assert(list_from && list_to); ItemStack stack_from = list_from->getItem(m_selected_item->i); ItemStack stack_to = list_to->getItem(s.i); @@ -3227,8 +3313,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } // Possibly send inventory action to server - if(move_amount > 0) - { + if (move_amount > 0) { // Send IACTION_MOVE assert(m_selected_item && m_selected_item->isValid()); @@ -3236,7 +3321,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) assert(inv_selected && inv_s); InventoryList *list_from = inv_selected->getList(m_selected_item->listname); - InventoryList *list_to = inv_s->getList(s.listname); + InventoryList *list_to = list_s; assert(list_from && list_to); ItemStack stack_from = list_from->getItem(m_selected_item->i); ItemStack stack_to = list_to->getItem(s.i); @@ -3255,7 +3340,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) m_selected_content_guess_inventory = s.inventoryloc; } // Source stack goes fully into destination stack - else if(leftover.empty()) { + else if (leftover.empty()) { m_selected_amount -= move_amount; m_selected_content_guess = ItemStack(); // Clear } @@ -3266,7 +3351,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) m_selected_content_guess = ItemStack(); // Clear } - infostream<<"Handing IACTION_MOVE to manager"<<std::endl; + infostream << "Handing IACTION_MOVE to manager" << std::endl; IMoveAction *a = new IMoveAction(); a->count = move_amount; a->from_inv = m_selected_item->inventoryloc; @@ -3276,8 +3361,68 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) a->to_list = s.listname; a->to_i = s.i; m_invmgr->inventoryAction(a); - } - else if(drop_amount > 0) { + } else if (shift_move_amount > 0) { + u32 mis = m_inventory_rings.size(); + u32 i = 0; + for (; i < mis; i++) { + const ListRingSpec &sp = m_inventory_rings[i]; + if (sp.inventoryloc == s.inventoryloc + && sp.listname == s.listname) + break; + } + do { + if (i >= mis) // if not found + break; + u32 to_inv_ind = (i + 1) % mis; + const ListRingSpec &to_inv_sp = m_inventory_rings[to_inv_ind]; + InventoryList *list_from = list_s; + if (!s.isValid()) + break; + Inventory *inv_to = m_invmgr->getInventory(to_inv_sp.inventoryloc); + if (!inv_to) + break; + InventoryList *list_to = inv_to->getList(to_inv_sp.listname); + if (!list_to) + break; + ItemStack stack_from = list_from->getItem(s.i); + assert(shift_move_amount <= stack_from.count); + if (m_client->getProtoVersion() >= 25) { + infostream << "Handing IACTION_MOVE to manager" << std::endl; + IMoveAction *a = new IMoveAction(); + a->count = shift_move_amount; + a->from_inv = s.inventoryloc; + a->from_list = s.listname; + a->from_i = s.i; + a->to_inv = to_inv_sp.inventoryloc; + a->to_list = to_inv_sp.listname; + a->move_somewhere = true; + m_invmgr->inventoryAction(a); + } else { + // find a place (or more than one) to add the new item + u32 ilt_size = list_to->getSize(); + ItemStack leftover; + for (u32 slot_to = 0; slot_to < ilt_size + && shift_move_amount > 0; slot_to++) { + list_to->itemFits(slot_to, stack_from, &leftover); + if (leftover.count < stack_from.count) { + infostream << "Handing IACTION_MOVE to manager" << std::endl; + IMoveAction *a = new IMoveAction(); + a->count = MYMIN(shift_move_amount, + (u32) (stack_from.count - leftover.count)); + shift_move_amount -= a->count; + a->from_inv = s.inventoryloc; + a->from_list = s.listname; + a->from_i = s.i; + a->to_inv = to_inv_sp.inventoryloc; + a->to_list = to_inv_sp.listname; + a->to_i = slot_to; + m_invmgr->inventoryAction(a); + stack_from = leftover; + } + } + } + } while (0); + } else if (drop_amount > 0) { m_selected_content_guess = ItemStack(); // Clear // Send IACTION_DROP @@ -3293,15 +3438,14 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) assert(drop_amount > 0 && drop_amount <= m_selected_amount); m_selected_amount -= drop_amount; - infostream<<"Handing IACTION_DROP to manager"<<std::endl; + infostream << "Handing IACTION_DROP to manager" << std::endl; IDropAction *a = new IDropAction(); a->count = drop_amount; a->from_inv = m_selected_item->inventoryloc; a->from_list = m_selected_item->listname; a->from_i = m_selected_item->i; m_invmgr->inventoryAction(a); - } - else if(craft_amount > 0) { + } else if (craft_amount > 0) { m_selected_content_guess = ItemStack(); // Clear // Send IACTION_CRAFT @@ -3309,7 +3453,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) assert(s.isValid()); assert(inv_s); - infostream<<"Handing IACTION_CRAFT to manager"<<std::endl; + infostream << "Handing IACTION_CRAFT to manager" << std::endl; ICraftAction *a = new ICraftAction(); a->count = craft_amount; a->craft_inv = s.inventoryloc; @@ -3317,7 +3461,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } // If m_selected_amount has been decreased to zero, deselect - if(m_selected_amount == 0) { + if (m_selected_amount == 0) { delete m_selected_item; m_selected_item = NULL; m_selected_amount = 0; @@ -3326,12 +3470,12 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } m_old_pointer = m_pointer; } - if(event.EventType==EET_GUI_EVENT) { + if (event.EventType == EET_GUI_EVENT) { - if(event.GUIEvent.EventType==gui::EGET_TAB_CHANGED + if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED && isVisible()) { // find the element that was clicked - for(unsigned int i=0; i<m_fields.size(); i++) { + for (unsigned int i=0; i<m_fields.size(); i++) { FieldSpec &s = m_fields[i]; if ((s.ftype == f_TabHeader) && (s.fid == event.GUIEvent.Caller->getID())) { @@ -3342,16 +3486,16 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } } - if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST + if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) { - if(!canTakeFocus(event.GUIEvent.Element)) { + if (!canTakeFocus(event.GUIEvent.Element)) { infostream<<"GUIFormSpecMenu: Not allowing focus change." <<std::endl; // Returning true disables focus change return true; } } - if((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) || + if ((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) || (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) || (event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) || (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) { @@ -3363,26 +3507,26 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) quitMenu(); } else { acceptInput(); - m_text_dst->gotText(narrow_to_wide("ExitButton")); + m_text_dst->gotText(L"ExitButton"); } // quitMenu deallocates menu return true; } // find the element that was clicked - for(u32 i=0; i<m_fields.size(); i++) { + for (u32 i = 0; i < m_fields.size(); i++) { FieldSpec &s = m_fields[i]; // if its a button, set the send field so // lua knows which button was pressed if (((s.ftype == f_Button) || (s.ftype == f_CheckBox)) && (s.fid == event.GUIEvent.Caller->getID())) { s.send = true; - if(s.is_exit) { + if (s.is_exit) { if (m_allowclose) { acceptInput(quit_mode_accept); quitMenu(); } else { - m_text_dst->gotText(narrow_to_wide("ExitButton")); + m_text_dst->gotText(L"ExitButton"); } return true; } else { @@ -3390,11 +3534,10 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) s.send = false; return true; } - } - else if ((s.ftype == f_DropDown) && + } else if ((s.ftype == f_DropDown) && (s.fid == event.GUIEvent.Caller->getID())) { // only send the changed dropdown - for(u32 i=0; i<m_fields.size(); i++) { + for (u32 i = 0; i < m_fields.size(); i++) { FieldSpec &s2 = m_fields[i]; if (s2.ftype == f_DropDown) { s2.send = false; @@ -3405,17 +3548,15 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) // revert configuration to make sure dropdowns are sent on // regular button click - for(u32 i=0; i<m_fields.size(); i++) { + for (u32 i = 0; i < m_fields.size(); i++) { FieldSpec &s2 = m_fields[i]; if (s2.ftype == f_DropDown) { s2.send = true; } } return true; - } - else if ((s.ftype == f_ScrollBar) && - (s.fid == event.GUIEvent.Caller->getID())) - { + } else if ((s.ftype == f_ScrollBar) && + (s.fid == event.GUIEvent.Caller->getID())) { s.fdefault = L"Changed"; acceptInput(quit_mode_no); s.fdefault = L""; @@ -3423,8 +3564,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } - if(event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) { - if(event.GUIEvent.Caller->getID() > 257) { + if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) { + if (event.GUIEvent.Caller->getID() > 257) { if (m_allowclose) { acceptInput(quit_mode_accept); @@ -3438,11 +3579,11 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) } } - if(event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) { + if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) { int current_id = event.GUIEvent.Caller->getID(); - if(current_id > 257) { + if (current_id > 257) { // find the element that was clicked - for(u32 i=0; i<m_fields.size(); i++) { + for (u32 i = 0; i < m_fields.size(); i++) { FieldSpec &s = m_fields[i]; // if it's a table, set the send field // so lua knows which table was changed @@ -3465,7 +3606,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event) * @param id of element * @return name string or empty string */ -std::wstring GUIFormSpecMenu::getNameByID(s32 id) +std::string GUIFormSpecMenu::getNameByID(s32 id) { for(std::vector<FieldSpec>::iterator iter = m_fields.begin(); iter != m_fields.end(); iter++) { @@ -3473,7 +3614,7 @@ std::wstring GUIFormSpecMenu::getNameByID(s32 id) return iter->fname; } } - return L""; + return ""; } /** diff --git a/src/guiFormSpecMenu.h b/src/guiFormSpecMenu.h index 48cb5c553..2ba47f7ff 100644 --- a/src/guiFormSpecMenu.h +++ b/src/guiFormSpecMenu.h @@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "inventorymanager.h" #include "modalMenu.h" #include "guiTable.h" -#include "clientserver.h" +#include "network/networkprotocol.h" class IGameDef; class InventoryManager; @@ -56,7 +56,7 @@ struct TextDest virtual ~TextDest() {}; // This is deprecated I guess? -celeron55 virtual void gotText(std::wstring text){} - virtual void gotText(std::map<std::string, std::string> fields) = 0; + virtual void gotText(const StringMap &fields) = 0; virtual void setFormName(std::string formname) { m_formname = formname;}; @@ -121,6 +121,22 @@ class GUIFormSpecMenu : public GUIModalMenu s32 start_item_i; }; + struct ListRingSpec + { + ListRingSpec() + { + } + ListRingSpec(const InventoryLocation &a_inventoryloc, + const std::string &a_listname): + inventoryloc(a_inventoryloc), + listname(a_listname) + { + } + + InventoryLocation inventoryloc; + std::string listname; + }; + struct ImageDrawSpec { ImageDrawSpec() @@ -152,7 +168,7 @@ class GUIFormSpecMenu : public GUIModalMenu FieldSpec() { } - FieldSpec(const std::wstring &name, const std::wstring &label, + FieldSpec(const std::string &name, const std::wstring &label, const std::wstring &fdeflt, int id) : fname(name), flabel(label), @@ -163,7 +179,7 @@ class GUIFormSpecMenu : public GUIModalMenu ftype = f_Unknown; is_exit = false; } - std::wstring fname; + std::string fname; std::wstring flabel; std::wstring fdefault; int fid; @@ -210,8 +226,8 @@ public: ISimpleTextureSource *tsrc, IFormSource* fs_src, TextDest* txt_dst, - Client* client - ); + Client* client, + bool remap_dbl_click = true); ~GUIFormSpecMenu(); @@ -255,7 +271,7 @@ public: void removeChildren(); void setInitialFocus(); - void setFocus(std::wstring elementname) + void setFocus(std::string &elementname) { m_focused_element = elementname; } @@ -278,7 +294,7 @@ public: bool doPause; bool pausesGame() { return doPause; } - GUITable* getTable(std::wstring tablename); + GUITable* getTable(const std::string &tablename); #ifdef __ANDROID__ bool getAndroidUIInput(); @@ -306,6 +322,7 @@ protected: std::vector<ListDrawSpec> m_inventorylists; + std::vector<ListRingSpec> m_inventory_rings; std::vector<ImageDrawSpec> m_backgrounds; std::vector<ImageDrawSpec> m_images; std::vector<ImageDrawSpec> m_itemimages; @@ -313,7 +330,7 @@ protected: std::vector<FieldSpec> m_fields; std::vector<std::pair<FieldSpec,GUITable*> > m_tables; std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes; - std::map<std::wstring, TooltipSpec> m_tooltips; + std::map<std::string, TooltipSpec> m_tooltips; std::vector<std::pair<FieldSpec,gui::IGUIScrollBar*> > m_scrollbars; ItemSpec *m_selected_item; @@ -355,7 +372,7 @@ private: IFormSource *m_form_src; TextDest *m_text_dst; unsigned int m_formspec_version; - std::wstring m_focused_element; + std::string m_focused_element; typedef struct { bool explicit_size; @@ -364,11 +381,11 @@ private: core::rect<s32> rect; v2s32 basepos; v2u32 screensize; - std::wstring focused_fieldname; + std::string focused_fieldname; GUITable::TableOptions table_options; GUITable::TableColumns table_columns; // used to restore table selection/scroll/treeview state - std::map<std::wstring,GUITable::DynamicData> table_dyndata; + std::map<std::string, GUITable::DynamicData> table_dyndata; } parserData; typedef struct { @@ -384,6 +401,7 @@ private: void parseSize(parserData* data,std::string element); void parseList(parserData* data,std::string element); + void parseListRing(parserData* data,std::string element); void parseCheckbox(parserData* data,std::string element); void parseImage(parserData* data,std::string element); void parseItemImage(parserData* data,std::string element); @@ -430,12 +448,20 @@ private: gui::IGUIFont *m_font; std::wstring getLabelByID(s32 id); - std::wstring getNameByID(s32 id); + std::string getNameByID(s32 id); #ifdef __ANDROID__ v2s32 m_down_pos; - std::wstring m_JavaDialogFieldName; + std::string m_JavaDialogFieldName; #endif + /* If true, remap a double-click (or double-tap) action to ESC. This is so + * that, for example, Android users can double-tap to close a formspec. + * + * This value can (currently) only be set by the class constructor + * and the default value for the setting is true. + */ + bool m_remap_dbl_click; + }; class FormspecFormSource: public IFormSource diff --git a/src/guiKeyChangeMenu.cpp b/src/guiKeyChangeMenu.cpp index 4cd9f36d9..261592394 100644 --- a/src/guiKeyChangeMenu.cpp +++ b/src/guiKeyChangeMenu.cpp @@ -22,7 +22,6 @@ #include "guiKeyChangeMenu.h" #include "debug.h" #include "serialization.h" -#include "main.h" #include <string> #include <IGUICheckBox.h> #include <IGUIEditBox.h> @@ -51,6 +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_CHAT_BUTTON, GUI_ID_KEY_CMD_BUTTON, GUI_ID_KEY_CONSOLE_BUTTON, @@ -137,20 +137,20 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize) { key_setting *k = key_settings.at(i); { - core::rect < s32 > rect(0, 0, 100, 20); + core::rect < s32 > rect(0, 0, 110, 20); rect += topleft + v2s32(offset.X, offset.Y); Environment->addStaticText(k->button_name, rect, false, true, this, -1); } { core::rect < s32 > rect(0, 0, 100, 30); - rect += topleft + v2s32(offset.X + 105, offset.Y - 5); + rect += topleft + v2s32(offset.X + 115, offset.Y - 5); const wchar_t *text = wgettext(k->key.name()); k->button = Environment->addButton(rect, this, k->id, text); delete[] text; } if(i + 1 == KMaxButtonPerColumns) - offset = v2s32(250, 60); + offset = v2s32(260, 60); else offset += v2s32(0, 25); } @@ -269,8 +269,7 @@ bool GUIKeyChangeMenu::resetMenu() bool GUIKeyChangeMenu::OnEvent(const SEvent& event) { if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0 - && event.KeyInput.PressedDown) - { + && event.KeyInput.PressedDown) { bool prefer_character = shift_down; KeyPress kp(event.KeyInput, prefer_character); @@ -302,7 +301,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) // But go on { - key_setting *k=NULL; + key_setting *k = NULL; for(size_t i = 0; i < key_settings.size(); i++) { if(key_settings.at(i)->id == activeKey) @@ -311,7 +310,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) break; } } - assert(k); + FATAL_ERROR_IF(k == NULL, "Key setting not found"); k->key = kp; const wchar_t *text = wgettext(k->key.name()); k->button->setText(text); @@ -328,9 +327,12 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) return true; } } - } - if (event.EventType == EET_GUI_EVENT) - { + } else if (event.EventType == EET_KEY_INPUT_EVENT && activeKey < 0 + && event.KeyInput.PressedDown + && event.KeyInput.Key == irr::KEY_ESCAPE) { + quitMenu(); + return true; + } else if (event.EventType == EET_GUI_EVENT) { if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST && isVisible()) { @@ -363,7 +365,7 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event) break; } } - assert(k); + FATAL_ERROR_IF(k == NULL, "Key setting not found"); resetMenu(); shift_down = false; @@ -394,22 +396,23 @@ void GUIKeyChangeMenu::add_key(int id, const wchar_t *button_name, const std::st void GUIKeyChangeMenu::init_keys() { - this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wgettext("Forward"), "keymap_forward"); - this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wgettext("Backward"), "keymap_backward"); - this->add_key(GUI_ID_KEY_LEFT_BUTTON, wgettext("Left"), "keymap_left"); - this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wgettext("Right"), "keymap_right"); - this->add_key(GUI_ID_KEY_USE_BUTTON, wgettext("Use"), "keymap_special1"); - this->add_key(GUI_ID_KEY_JUMP_BUTTON, wgettext("Jump"), "keymap_jump"); - this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wgettext("Sneak"), "keymap_sneak"); - this->add_key(GUI_ID_KEY_DROP_BUTTON, wgettext("Drop"), "keymap_drop"); - this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"), "keymap_inventory"); - this->add_key(GUI_ID_KEY_CHAT_BUTTON, wgettext("Chat"), "keymap_chat"); - this->add_key(GUI_ID_KEY_CMD_BUTTON, wgettext("Command"), "keymap_cmd"); - this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, wgettext("Console"), "keymap_console"); - this->add_key(GUI_ID_KEY_FLY_BUTTON, wgettext("Toggle fly"), "keymap_freemove"); - 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_RANGE_BUTTON, wgettext("Range select"), "keymap_rangeselect"); - this->add_key(GUI_ID_KEY_DUMP_BUTTON, wgettext("Print stacks"), "keymap_print_debug_stacks"); + this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wgettext("Forward"), "keymap_forward"); + this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wgettext("Backward"), "keymap_backward"); + this->add_key(GUI_ID_KEY_LEFT_BUTTON, wgettext("Left"), "keymap_left"); + this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wgettext("Right"), "keymap_right"); + this->add_key(GUI_ID_KEY_USE_BUTTON, wgettext("Use"), "keymap_special1"); + this->add_key(GUI_ID_KEY_JUMP_BUTTON, wgettext("Jump"), "keymap_jump"); + this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wgettext("Sneak"), "keymap_sneak"); + this->add_key(GUI_ID_KEY_DROP_BUTTON, wgettext("Drop"), "keymap_drop"); + this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"), "keymap_inventory"); + this->add_key(GUI_ID_KEY_CHAT_BUTTON, wgettext("Chat"), "keymap_chat"); + this->add_key(GUI_ID_KEY_CMD_BUTTON, wgettext("Command"), "keymap_cmd"); + this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, wgettext("Console"), "keymap_console"); + this->add_key(GUI_ID_KEY_FLY_BUTTON, wgettext("Toggle fly"), "keymap_freemove"); + this->add_key(GUI_ID_KEY_FAST_BUTTON, wgettext("Toggle fast"), "keymap_fastmove"); + this->add_key(GUI_ID_KEY_CINEMATIC_BUTTON, wgettext("Toggle Cinematic"), "keymap_cinematic"); + this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wgettext("Toggle noclip"), "keymap_noclip"); + this->add_key(GUI_ID_KEY_RANGE_BUTTON, wgettext("Range select"), "keymap_rangeselect"); + this->add_key(GUI_ID_KEY_DUMP_BUTTON, wgettext("Print stacks"), "keymap_print_debug_stacks"); } diff --git a/src/guiMainMenu.h b/src/guiMainMenu.h index 34362dba6..711ad10f8 100644 --- a/src/guiMainMenu.h +++ b/src/guiMainMenu.h @@ -25,17 +25,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #include <list> -enum -{ - TAB_SINGLEPLAYER=0, - TAB_MULTIPLAYER, - TAB_ADVANCED, - TAB_SETTINGS, - TAB_CREDITS +struct MainMenuDataForScript { + + MainMenuDataForScript() : + reconnect_requested(false) + {} + + // Whether the server has requested a reconnect + bool reconnect_requested; + + std::string errormessage; }; -struct MainMenuData -{ +struct MainMenuData { // Client options std::string servername; std::string serverdescription; @@ -43,19 +45,22 @@ struct MainMenuData std::string port; std::string name; std::string password; + // Whether to reconnect + bool do_reconnect; // Server options bool enable_public; int selected_world; bool simple_singleplayer_mode; - //error handling - std::string errormessage; + // Data to be passed to the script + MainMenuDataForScript script_data; + MainMenuData(): + do_reconnect(false), enable_public(false), selected_world(0), - simple_singleplayer_mode(false), - errormessage("") + simple_singleplayer_mode(false) {} }; diff --git a/src/guiPasswordChange.cpp b/src/guiPasswordChange.cpp index 0e19f571d..e2f9994be 100644 --- a/src/guiPasswordChange.cpp +++ b/src/guiPasswordChange.cpp @@ -79,7 +79,7 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) Remove stuff */ removeChildren(); - + /* Calculate new sizes and positions */ @@ -89,7 +89,7 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) screensize.X/2 + 580/2, screensize.Y/2 + 300/2 ); - + DesiredRect = rect; recalculateAbsolutePosition(false); @@ -112,7 +112,7 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) { core::rect<s32> rect(0, 0, 230, 30); rect += topleft_client + v2s32(160, ypos); - gui::IGUIEditBox *e = + gui::IGUIEditBox *e = Environment->addEditBox(L"", rect, true, this, ID_oldPassword); Environment->setFocus(e); e->setPasswordBox(true); @@ -128,7 +128,7 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) { core::rect<s32> rect(0, 0, 230, 30); rect += topleft_client + v2s32(160, ypos); - gui::IGUIEditBox *e = + gui::IGUIEditBox *e = Environment->addEditBox(L"", rect, true, this, ID_newPassword1); e->setPasswordBox(true); } @@ -143,7 +143,7 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) { core::rect<s32> rect(0, 0, 230, 30); rect += topleft_client + v2s32(160, ypos); - gui::IGUIEditBox *e = + gui::IGUIEditBox *e = Environment->addEditBox(L"", rect, true, this, ID_newPassword2); e->setPasswordBox(true); } @@ -162,7 +162,7 @@ void GUIPasswordChange::regenerateGui(v2u32 screensize) core::rect<s32> rect(0, 0, 300, 20); rect += topleft_client + v2s32(35, ypos); text = wgettext("Passwords do not match!"); - IGUIElement *e = + IGUIElement *e = Environment->addStaticText( text, rect, false, true, this, ID_message); @@ -177,7 +177,7 @@ void GUIPasswordChange::drawMenu() if (!skin) return; video::IVideoDriver* driver = Environment->getVideoDriver(); - + video::SColor bgcolor(140,0,0,0); driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); @@ -203,7 +203,8 @@ bool GUIPasswordChange::acceptInput() e->setVisible(true); return false; } - m_client->sendChangePassword(oldpass, newpass); + m_client->sendChangePassword(wide_to_utf8(oldpass), + wide_to_utf8(newpass)); return true; } diff --git a/src/guiTable.cpp b/src/guiTable.cpp index 05db228da..ed5b0d87b 100644 --- a/src/guiTable.cpp +++ b/src/guiTable.cpp @@ -28,14 +28,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <IGUIScrollBar.h> #include "debug.h" #include "log.h" -#include "tile.h" +#include "client/tile.h" #include "gettime.h" #include "util/string.h" #include "util/numeric.h" #include "util/string.h" // for parseColorString() -#include "main.h" #include "settings.h" // for settings #include "porting.h" // for dpi +#include "guiscalingfilter.h" /* GUITable @@ -929,7 +929,7 @@ s32 GUITable::allocString(const std::string &text) std::map<std::string, s32>::iterator it = m_alloc_strings.find(text); if (it == m_alloc_strings.end()) { s32 id = m_strings.size(); - std::wstring wtext = narrow_to_wide(text); + std::wstring wtext = utf8_to_wide(text); m_strings.push_back(core::stringw(wtext.c_str())); m_alloc_strings.insert(std::make_pair(text, id)); return id; diff --git a/src/guiVolumeChange.cpp b/src/guiVolumeChange.cpp index b31b99a98..c8e257f7f 100644 --- a/src/guiVolumeChange.cpp +++ b/src/guiVolumeChange.cpp @@ -26,7 +26,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include <IGUIScrollBar.h> #include <IGUIStaticText.h> #include <IGUIFont.h> -#include "main.h" #include "settings.h" #include "gettext.h" diff --git a/src/guiscalingfilter.cpp b/src/guiscalingfilter.cpp new file mode 100644 index 000000000..26a2265a8 --- /dev/null +++ b/src/guiscalingfilter.cpp @@ -0,0 +1,169 @@ +/* +Copyright (C) 2015 Aaron Suen <warr1024@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "guiscalingfilter.h" +#include "imagefilters.h" +#include "settings.h" +#include "util/numeric.h" +#include <stdio.h> + +/* Maintain a static cache to store the images that correspond to textures + * in a format that's manipulable by code. Some platforms exhibit issues + * converting textures back into images repeatedly, and some don't even + * allow it at all. + */ +std::map<io::path, video::IImage *> g_imgCache; + +/* Maintain a static cache of all pre-scaled textures. These need to be + * cleared as well when the cached images. + */ +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) +{ + if (!g_settings->getBool("gui_scaling_filter")) + return; + video::IImage *copied = driver->createImage(value->getColorFormat(), + value->getDimension()); + value->copyTo(copied); + g_imgCache[key] = copied; +} + +// Manually clear the cache, e.g. when switching to different worlds. +void guiScalingCacheClear(video::IVideoDriver *driver) +{ + for (std::map<io::path, video::IImage *>::iterator it = g_imgCache.begin(); + it != g_imgCache.end(); it++) { + if (it->second != NULL) + it->second->drop(); + } + g_imgCache.clear(); + for (std::map<io::path, video::ITexture *>::iterator it = g_txrCache.begin(); + it != g_txrCache.end(); it++) { + if (it->second != NULL) + driver->removeTexture(it->second); + } + g_txrCache.clear(); +} + +/* Get a cached, high-quality pre-scaled texture for display purposes. If the + * texture is not already cached, attempt to create it. Returns a pre-scaled texture, + * or the original texture if unable to pre-scale it. + */ +video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, + video::ITexture *src, const core::rect<s32> &srcrect, + const core::rect<s32> &destrect) +{ + if (src == NULL) + return src; + if (!g_settings->getBool("gui_scaling_filter")) + return src; + + // Calculate scaled texture name. + char rectstr[200]; + snprintf(rectstr, sizeof(rectstr), "%d:%d:%d:%d:%d:%d", + srcrect.UpperLeftCorner.X, + srcrect.UpperLeftCorner.Y, + srcrect.getWidth(), + srcrect.getHeight(), + destrect.getWidth(), + destrect.getHeight()); + io::path origname = src->getName().getPath(); + io::path scalename = origname + "@guiScalingFilter:" + rectstr; + + // Search for existing scaled texture. + video::ITexture *scaled = g_txrCache[scalename]; + if (scaled) + return scaled; + + // Try to find the texture converted to an image in the cache. + // If the image was not found, try to extract it from the texture. + video::IImage* srcimg = g_imgCache[origname]; + if (srcimg == NULL) { + if (!g_settings->getBool("gui_scaling_filter_txr2img")) + return src; + srcimg = driver->createImageFromData(src->getColorFormat(), + src->getSize(), src->lock(), false); + src->unlock(); + g_imgCache[origname] = srcimg; + } + + // Create a new destination image and scale the source into it. + imageCleanTransparent(srcimg, 0); + video::IImage *destimg = driver->createImage(src->getColorFormat(), + core::dimension2d<u32>((u32)destrect.getWidth(), + (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; +#endif + + // Convert the scaled image back into a texture. + scaled = driver->addTexture(scalename, destimg, NULL); + destimg->drop(); + g_txrCache[scalename] = scaled; + + return scaled; +} + +/* Convenience wrapper for guiScalingResizeCached that accepts parameters that + * are available at GUI imagebutton creation time. + */ +video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, + video::ITexture *src, s32 width, s32 height) +{ + if (src == NULL) + return src; + return guiScalingResizeCached(driver, src, + core::rect<s32>(0, 0, src->getSize().Width, src->getSize().Height), + core::rect<s32>(0, 0, width, height)); +} + +/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled + * texture, if configured. + */ +void draw2DImageFilterScaled(video::IVideoDriver *driver, video::ITexture *txr, + const core::rect<s32> &destrect, const core::rect<s32> &srcrect, + const core::rect<s32> *cliprect, const video::SColor *const colors, + bool usealpha) +{ + // Attempt to pre-scale image in software in high quality. + video::ITexture *scaled = guiScalingResizeCached(driver, txr, srcrect, destrect); + if (scaled == NULL) + return; + + // Correct source rect based on scaled image. + const core::rect<s32> mysrcrect = (scaled != txr) + ? core::rect<s32>(0, 0, destrect.getWidth(), destrect.getHeight()) + : srcrect; + + driver->draw2DImage(scaled, destrect, mysrcrect, cliprect, colors, usealpha); +} diff --git a/src/guiscalingfilter.h b/src/guiscalingfilter.h new file mode 100644 index 000000000..768fe8d52 --- /dev/null +++ b/src/guiscalingfilter.h @@ -0,0 +1,52 @@ +/* +Copyright (C) 2015 Aaron Suen <warr1024@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef _GUI_SCALING_FILTER_H_ +#define _GUI_SCALING_FILTER_H_ + +#include "irrlichttypes_extrabloated.h" + +/* 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); + +// Manually clear the cache, e.g. when switching to different worlds. +void guiScalingCacheClear(video::IVideoDriver *driver); + +/* Get a cached, high-quality pre-scaled texture for display purposes. If the + * texture is not already cached, attempt to create it. Returns a pre-scaled texture, + * or the original texture if unable to pre-scale it. + */ +video::ITexture *guiScalingResizeCached(video::IVideoDriver *driver, video::ITexture *src, + const core::rect<s32> &srcrect, const core::rect<s32> &destrect); + +/* Convenience wrapper for guiScalingResizeCached that accepts parameters that + * are available at GUI imagebutton creation time. + */ +video::ITexture *guiScalingImageButton(video::IVideoDriver *driver, video::ITexture *src, + s32 width, s32 height); + +/* Replacement for driver->draw2DImage() that uses the high-quality pre-scaled + * texture, if configured. + */ +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); + +#endif diff --git a/src/httpfetch.cpp b/src/httpfetch.cpp index 47e33480b..56cdad2b1 100644 --- a/src/httpfetch.cpp +++ b/src/httpfetch.cpp @@ -33,11 +33,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/container.h" #include "util/thread.h" #include "version.h" -#include "main.h" #include "settings.h" JMutex g_httpfetch_mutex; -std::map<unsigned long, std::list<HTTPFetchResult> > g_httpfetch_results; +std::map<unsigned long, std::queue<HTTPFetchResult> > g_httpfetch_results; HTTPFetchRequest::HTTPFetchRequest() { @@ -48,7 +47,7 @@ HTTPFetchRequest::HTTPFetchRequest() connect_timeout = timeout; multipart = false; - useragent = std::string("Minetest/") + minetest_version_hash + " (" + porting::get_sysinfo() + ")"; + useragent = std::string(PROJECT_NAME_C "/") + g_version_hash + " (" + porting::get_sysinfo() + ")"; } @@ -57,7 +56,7 @@ static void httpfetch_deliver_result(const HTTPFetchResult &fetch_result) unsigned long caller = fetch_result.caller; if (caller != HTTPFETCH_DISCARD) { JMutexAutoLock lock(g_httpfetch_mutex); - g_httpfetch_results[caller].push_back(fetch_result); + g_httpfetch_results[caller].push(fetch_result); } } @@ -70,18 +69,18 @@ unsigned long httpfetch_caller_alloc() // Check each caller ID except HTTPFETCH_DISCARD const unsigned long discard = HTTPFETCH_DISCARD; for (unsigned long caller = discard + 1; caller != discard; ++caller) { - std::map<unsigned long, std::list<HTTPFetchResult> >::iterator + std::map<unsigned long, std::queue<HTTPFetchResult> >::iterator it = g_httpfetch_results.find(caller); if (it == g_httpfetch_results.end()) { - verbosestream<<"httpfetch_caller_alloc: allocating " - <<caller<<std::endl; + verbosestream << "httpfetch_caller_alloc: allocating " + << caller << std::endl; // Access element to create it g_httpfetch_results[caller]; return caller; } } - assert("httpfetch_caller_alloc: ran out of caller IDs" == 0); + FATAL_ERROR("httpfetch_caller_alloc: ran out of caller IDs"); return discard; } @@ -102,19 +101,19 @@ bool httpfetch_async_get(unsigned long caller, HTTPFetchResult &fetch_result) JMutexAutoLock lock(g_httpfetch_mutex); // Check that caller exists - std::map<unsigned long, std::list<HTTPFetchResult> >::iterator + std::map<unsigned long, std::queue<HTTPFetchResult> >::iterator it = g_httpfetch_results.find(caller); if (it == g_httpfetch_results.end()) return false; // Check that result queue is nonempty - std::list<HTTPFetchResult> &caller_results = it->second; + std::queue<HTTPFetchResult> &caller_results = it->second; if (caller_results.empty()) return false; // Pop first result fetch_result = caller_results.front(); - caller_results.pop_front(); + caller_results.pop(); return true; } @@ -194,7 +193,6 @@ private: HTTPFetchRequest request; HTTPFetchResult result; std::ostringstream oss; - char *post_fields; struct curl_slist *http_header; curl_httppost *post; }; @@ -268,8 +266,7 @@ HTTPFetchOngoing::HTTPFetchOngoing(HTTPFetchRequest request_, CurlHandlePool *po curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); } else if (request.multipart) { curl_httppost *last = NULL; - for (std::map<std::string, std::string>::iterator it = - request.post_fields.begin(); + for (StringMap::iterator it = request.post_fields.begin(); it != request.post_fields.end(); ++it) { curl_formadd(&post, &last, CURLFORM_NAMELENGTH, it->first.size(), @@ -284,10 +281,8 @@ HTTPFetchOngoing::HTTPFetchOngoing(HTTPFetchRequest request_, CurlHandlePool *po } else if (request.post_data.empty()) { curl_easy_setopt(curl, CURLOPT_POST, 1); std::string str; - for (std::map<std::string, std::string>::iterator it = - request.post_fields.begin(); - it != request.post_fields.end(); - ++it) { + for (StringMap::iterator it = request.post_fields.begin(); + it != request.post_fields.end(); ++it) { if (str != "") str += "&"; str += urlencode(it->first); @@ -634,7 +629,7 @@ protected: return NULL; } - assert(m_all_ongoing.empty()); + FATAL_ERROR_IF(!m_all_ongoing.empty(), "Expected empty"); while (!StopRequested()) { BEGIN_DEBUG_EXCEPTION_HANDLER @@ -715,7 +710,7 @@ void httpfetch_init(int parallel_limit) <<std::endl; CURLcode res = curl_global_init(CURL_GLOBAL_DEFAULT); - assert(res == CURLE_OK); + FATAL_ERROR_IF(res != CURLE_OK, "CURL init failed"); g_httpfetch_thread = new CurlFetchThread(parallel_limit); } diff --git a/src/httpfetch.h b/src/httpfetch.h index 50a4c93d8..c44c8d2d3 100644 --- a/src/httpfetch.h +++ b/src/httpfetch.h @@ -20,9 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef HTTPFETCH_HEADER #define HTTPFETCH_HEADER -#include <string> #include <vector> -#include <map> +#include "util/string.h" #include "config.h" // Can be used in place of "caller" in asynchronous transfers to discard result @@ -54,7 +53,7 @@ struct HTTPFetchRequest // POST fields. Fields are escaped properly. // If this is empty a GET request is done instead. - std::map<std::string, std::string> post_fields; + StringMap post_fields; // Raw POST data, overrides post_fields. std::string post_data; diff --git a/src/hud.cpp b/src/hud.cpp index 29ebb8103..dbc4a01a3 100644 --- a/src/hud.cpp +++ b/src/hud.cpp @@ -20,18 +20,18 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "hud.h" -#include "main.h" #include "settings.h" #include "util/numeric.h" #include "log.h" #include "gamedef.h" #include "itemdef.h" #include "inventory.h" -#include "tile.h" +#include "client/tile.h" #include "localplayer.h" #include "camera.h" #include "porting.h" #include "fontengine.h" +#include "guiscalingfilter.h" #include <IGUIStaticText.h> #ifdef HAVE_TOUCHSCREENGUI @@ -94,7 +94,7 @@ void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect, bool sele imgrect2.LowerRightCorner.Y += (m_padding*2); video::ITexture *texture = tsrc->getTexture(hotbar_selected_image); core::dimension2di imgsize(texture->getOriginalSize()); - driver->draw2DImage(texture, imgrect2, + draw2DImageFilterScaled(driver, texture, imgrect2, core::rect<s32>(core::position2d<s32>(0,0), imgsize), NULL, hbar_colors, true); } else { @@ -200,7 +200,7 @@ void Hud::drawItems(v2s32 upperleftpos, s32 itemcount, s32 offset, core::rect<s32> rect2 = imgrect2 + pos; video::ITexture *texture = tsrc->getTexture(hotbar_image); core::dimension2di imgsize(texture->getOriginalSize()); - driver->draw2DImage(texture, rect2, + draw2DImageFilterScaled(driver, texture, rect2, core::rect<s32>(core::position2d<s32>(0,0), imgsize), NULL, hbar_colors, true); } @@ -266,7 +266,7 @@ void Hud::drawLuaElements(v3s16 camera_offset) { (e->align.Y - 1.0) * dstsize.Y / 2); core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y); rect += pos + offset + v2s32(e->offset.X, e->offset.Y); - driver->draw2DImage(texture, rect, + draw2DImageFilterScaled(driver, texture, rect, core::rect<s32>(core::position2d<s32>(0,0), imgsize), NULL, colors, true); break; } @@ -275,7 +275,7 @@ void Hud::drawLuaElements(v3s16 camera_offset) { (e->number >> 8) & 0xFF, (e->number >> 0) & 0xFF); core::rect<s32> size(0, 0, e->scale.X, text_height * e->scale.Y); - std::wstring text = narrow_to_wide(e->text); + std::wstring text = utf8_to_wide(e->text); core::dimension2d<u32> textsize = font->getDimension(text.c_str()); v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2), (e->align.Y - 1.0) * (textsize.Height / 2)); @@ -310,11 +310,11 @@ void Hud::drawLuaElements(v3s16 camera_offset) { (e->number >> 8) & 0xFF, (e->number >> 0) & 0xFF); core::rect<s32> size(0, 0, 200, 2 * text_height); - std::wstring text = narrow_to_wide(e->name); + std::wstring text = utf8_to_wide(e->name); font->draw(text.c_str(), size + pos, color); std::ostringstream os; - os<<distance<<e->text; - text = narrow_to_wide(os.str()); + os << distance << e->text; + text = utf8_to_wide(os.str()); pos.Y += text_height; font->draw(text.c_str(), size + pos, color); break; } @@ -378,7 +378,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture, core::rect<s32> dstrect(0,0, dstd.Width, dstd.Height); dstrect += p; - driver->draw2DImage(stat_texture, dstrect, srcrect, NULL, colors, true); + draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true); p += steppos; } @@ -388,7 +388,7 @@ void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture, core::rect<s32> dstrect(0,0, dstd.Width / 2, dstd.Height); dstrect += p; - driver->draw2DImage(stat_texture, dstrect, srcrect, NULL, colors, true); + draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true); } } @@ -502,7 +502,7 @@ void drawItemStack(video::IVideoDriver *driver, { const video::SColor color(255,255,255,255); const video::SColor colors[] = {color,color,color,color}; - driver->draw2DImage(texture, rect, + draw2DImageFilterScaled(driver, texture, rect, core::rect<s32>(core::position2d<s32>(0,0), core::dimension2di(texture->getOriginalSize())), clip, colors, true); @@ -552,7 +552,7 @@ void drawItemStack(video::IVideoDriver *driver, { // Get the item count as a string std::string text = itos(item.count); - v2u32 dim = font->getDimension(narrow_to_wide(text).c_str()); + v2u32 dim = font->getDimension(utf8_to_wide(text).c_str()); v2s32 sdim(dim.X,dim.Y); core::rect<s32> rect2( @@ -32,11 +32,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #define HUD_CORNER_LOWER 1 #define HUD_CORNER_CENTER 2 +// Note that these visibility flags do not determine if the hud items are +// actually drawn, but rather, allows the item to be drawn should the rest of +// the game state permit it. #define HUD_FLAG_HOTBAR_VISIBLE (1 << 0) #define HUD_FLAG_HEALTHBAR_VISIBLE (1 << 1) #define HUD_FLAG_CROSSHAIR_VISIBLE (1 << 2) #define HUD_FLAG_WIELDITEM_VISIBLE (1 << 3) #define HUD_FLAG_BREATHBAR_VISIBLE (1 << 4) +#define HUD_FLAG_MINIMAP_VISIBLE (1 << 5) #define HUD_PARAM_HOTBAR_ITEMCOUNT 1 #define HUD_PARAM_HOTBAR_IMAGE 2 @@ -116,11 +120,11 @@ public: std::string hotbar_selected_image; bool use_hotbar_selected_image; v3s16 camera_offset; - + Hud(video::IVideoDriver *driver,scene::ISceneManager* smgr, gui::IGUIEnvironment* guienv, IGameDef *gamedef, LocalPlayer *player, Inventory *inventory); - + void drawHotbar(u16 playeritem); void resizeHotbar(); void drawCrosshair(); @@ -129,12 +133,12 @@ public: private: void drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture, s32 count, v2s32 offset, v2s32 size=v2s32()); - + void drawItems(v2s32 upperleftpos, s32 itemcount, s32 offset, InventoryList *mainlist, u16 selectitem, u16 direction); void drawItem(const ItemStack &item, const core::rect<s32>& rect, bool selected); - + v2u32 m_screensize; v2s32 m_displaycenter; s32 m_hotbar_imagesize; diff --git a/src/imagefilters.cpp b/src/imagefilters.cpp new file mode 100644 index 000000000..b34027725 --- /dev/null +++ b/src/imagefilters.cpp @@ -0,0 +1,172 @@ +/* +Copyright (C) 2015 Aaron Suen <warr1024@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "imagefilters.h" +#include "util/numeric.h" +#include <math.h> + +/* Fill in RGB values for transparent pixels, to correct for odd colors + * appearing at borders when blending. This is because many PNG optimizers + * like to discard RGB values of transparent pixels, but when blending then + * with non-transparent neighbors, their RGB values will shpw up nonetheless. + * + * This function modifies the original image in-place. + * + * Parameter "threshold" is the alpha level below which pixels are considered + * transparent. Should be 127 for 3d where alpha is threshold, but 0 for + * 2d where alpha is blended. + */ +void imageCleanTransparent(video::IImage *src, u32 threshold) +{ + core::dimension2d<u32> dim = src->getDimension(); + + // Walk each pixel looking for fully transparent ones. + // Note: loop y around x for better cache locality. + for (u32 ctry = 0; ctry < dim.Height; ctry++) + for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) { + + // Ignore opaque pixels. + irr::video::SColor c = src->getPixel(ctrx, ctry); + if (c.getAlpha() > threshold) + continue; + + // Sample size and total weighted r, g, b values. + u32 ss = 0, sr = 0, sg = 0, sb = 0; + + // Walk each neighbor pixel (clipped to image bounds). + for (u32 sy = (ctry < 1) ? 0 : (ctry - 1); + sy <= (ctry + 1) && sy < dim.Height; sy++) + for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1); + sx <= (ctrx + 1) && sx < dim.Width; sx++) { + + // Ignore transparent pixels. + irr::video::SColor d = src->getPixel(sx, sy); + if (d.getAlpha() <= threshold) + continue; + + // Add RGB values weighted by alpha. + u32 a = d.getAlpha(); + ss += a; + sr += a * d.getRed(); + sg += a * d.getGreen(); + sb += a * d.getBlue(); + } + + // If we found any neighbor RGB data, set pixel to average + // weighted by alpha. + if (ss > 0) { + c.setRed(sr / ss); + c.setGreen(sg / ss); + c.setBlue(sb / ss); + src->setPixel(ctrx, ctry, c); + } + } +} + +/* Scale a region of an image into another image, using nearest-neighbor with + * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries + * to prevent non-integer scaling ratio artifacts. Note that this may cause + * some blending at the edges where pixels don't line up perfectly, but this + * filter is designed to produce the most accurate results for both upscaling + * and downscaling. + */ +void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest) +{ + double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa; + u32 dy, dx; + video::SColor pxl; + + // Cache rectsngle boundaries. + double sox = srcrect.UpperLeftCorner.X * 1.0; + double soy = srcrect.UpperLeftCorner.Y * 1.0; + double sw = srcrect.getWidth() * 1.0; + double sh = srcrect.getHeight() * 1.0; + + // Walk each destination image pixel. + // Note: loop y around x for better cache locality. + core::dimension2d<u32> dim = dest->getDimension(); + for (dy = 0; dy < dim.Height; dy++) + for (dx = 0; dx < dim.Width; dx++) { + + // Calculate floating-point source rectangle bounds. + // Do some basic clipping, and for mirrored/flipped rects, + // make sure min/max are in the right order. + minsx = sox + (dx * sw / dim.Width); + minsx = rangelim(minsx, 0, sw); + maxsx = minsx + sw / dim.Width; + maxsx = rangelim(maxsx, 0, sw); + if (minsx > maxsx) + SWAP(double, minsx, maxsx); + minsy = soy + (dy * sh / dim.Height); + minsy = rangelim(minsy, 0, sh); + maxsy = minsy + sh / dim.Height; + maxsy = rangelim(maxsy, 0, sh); + if (minsy > maxsy) + SWAP(double, minsy, maxsy); + + // Total area, and integral of r, g, b values over that area, + // initialized to zero, to be summed up in next loops. + area = 0; + ra = 0; + ga = 0; + ba = 0; + aa = 0; + + // Loop over the integral pixel positions described by those bounds. + for (sy = floor(minsy); sy < maxsy; sy++) + for (sx = floor(minsx); sx < maxsx; sx++) { + + // Calculate width, height, then area of dest pixel + // that's covered by this source pixel. + pw = 1; + if (minsx > sx) + pw += sx - minsx; + if (maxsx < (sx + 1)) + pw += maxsx - sx - 1; + ph = 1; + if (minsy > sy) + ph += sy - minsy; + if (maxsy < (sy + 1)) + ph += maxsy - sy - 1; + pa = pw * ph; + + // Get source pixel and add it to totals, weighted + // by covered area and alpha. + pxl = src->getPixel((u32)sx, (u32)sy); + area += pa; + ra += pa * pxl.getRed(); + ga += pa * pxl.getGreen(); + ba += pa * pxl.getBlue(); + aa += pa * pxl.getAlpha(); + } + + // Set the destination image pixel to the average color. + if (area > 0) { + pxl.setRed(ra / area + 0.5); + pxl.setGreen(ga / area + 0.5); + pxl.setBlue(ba / area + 0.5); + pxl.setAlpha(aa / area + 0.5); + } else { + pxl.setRed(0); + pxl.setGreen(0); + pxl.setBlue(0); + pxl.setAlpha(0); + } + dest->setPixel(dx, dy, pxl); + } +} diff --git a/src/imagefilters.h b/src/imagefilters.h new file mode 100644 index 000000000..28787027f --- /dev/null +++ b/src/imagefilters.h @@ -0,0 +1,46 @@ +/* +Copyright (C) 2015 Aaron Suen <warr1024@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef _IMAGE_FILTERS_H_ +#define _IMAGE_FILTERS_H_ + +#include "irrlichttypes_extrabloated.h" + +/* Fill in RGB values for transparent pixels, to correct for odd colors + * appearing at borders when blending. This is because many PNG optimizers + * like to discard RGB values of transparent pixels, but when blending then + * with non-transparent neighbors, their RGB values will shpw up nonetheless. + * + * This function modifies the original image in-place. + * + * Parameter "threshold" is the alpha level below which pixels are considered + * transparent. Should be 127 for 3d where alpha is threshold, but 0 for + * 2d where alpha is blended. + */ +void imageCleanTransparent(video::IImage *src, u32 threshold); + +/* Scale a region of an image into another image, using nearest-neighbor with + * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries + * to prevent non-integer scaling ratio artifacts. Note that this may cause + * some blending at the edges where pixels don't line up perfectly, but this + * filter is designed to produce the most accurate results for both upscaling + * and downscaling. + */ +void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest); + +#endif diff --git a/src/intlGUIEditBox.cpp b/src/intlGUIEditBox.cpp new file mode 100644 index 000000000..33bf8a13c --- /dev/null +++ b/src/intlGUIEditBox.cpp @@ -0,0 +1,1509 @@ +// 11.11.2011 11:11 ValkaTR +// +// This is a copy of intlGUIEditBox from the irrlicht, but with a +// fix in the OnEvent function, which doesn't allowed input of +// other keyboard layouts than latin-1 +// +// Characters like: ä ö ü õ ы й ю я ъ № € ° ... +// +// This fix is only needed for linux, because of a bug +// in the CIrrDeviceLinux.cpp:1014-1015 of the irrlicht +// +// Also locale in the programm should not be changed to +// a "C", "POSIX" or whatever, it should be set to "", +// or XLookupString will return nothing for the international +// characters. +// +// From the "man setlocale": +// +// On startup of the main program, the portable "C" locale +// is selected as default. A program may be made +// portable to all locales by calling: +// +// setlocale(LC_ALL, ""); +// +// after program initialization.... +// + +// 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 + +#include "intlGUIEditBox.h" + +#if defined(_IRR_COMPILE_WITH_GUI_) && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 + +#include "IGUISkin.h" +#include "IGUIEnvironment.h" +#include "IGUIFont.h" +#include "IVideoDriver.h" +//#include "rect.h" +//#include "irrlicht/os.cpp" +#include "porting.h" +//#include "Keycodes.h" +#include "log.h" + +/* + todo: + optional scrollbars + ctrl+left/right to select word + double click/ctrl click: word select + drag to select whole words, triple click to select line + optional? dragging selected text + numerical +*/ + +namespace irr +{ +namespace gui +{ + +//! constructor +intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border, + IGUIEnvironment* environment, IGUIElement* parent, s32 id, + const core::rect<s32>& rectangle) + : IGUIEditBox(environment, parent, id, rectangle), MouseMarking(false), + Border(border), OverrideColorEnabled(false), MarkBegin(0), MarkEnd(0), + OverrideColor(video::SColor(101,255,255,255)), OverrideFont(0), LastBreakFont(0), + Operator(0), BlinkStartTime(0), CursorPos(0), HScrollPos(0), VScrollPos(0), Max(0), + WordWrap(false), MultiLine(false), AutoScroll(true), PasswordBox(false), + PasswordChar(L'*'), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER), + CurrentTextRect(0,0,1,1), FrameRect(rectangle) +{ + #ifdef _DEBUG + setDebugName("intlintlGUIEditBox"); + #endif + + Text = text; + + if (Environment) + Operator = Environment->getOSOperator(); + + if (Operator) + Operator->grab(); + + // this element can be tabbed to + setTabStop(true); + setTabOrder(-1); + + IGUISkin *skin = 0; + if (Environment) + skin = Environment->getSkin(); + if (Border && skin) + { + FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + } + + breakText(); + + calculateScrollPos(); +} + + +//! destructor +intlGUIEditBox::~intlGUIEditBox() +{ + if (OverrideFont) + OverrideFont->drop(); + + if (Operator) + Operator->drop(); +} + + +//! Sets another skin independent font. +void intlGUIEditBox::setOverrideFont(IGUIFont* font) +{ + if (OverrideFont == font) + return; + + if (OverrideFont) + OverrideFont->drop(); + + OverrideFont = font; + + if (OverrideFont) + OverrideFont->grab(); + + breakText(); +} + +IGUIFont * intlGUIEditBox::getOverrideFont() const +{ + return OverrideFont; +} + +//! Get the font which is used right now for drawing +IGUIFont* intlGUIEditBox::getActiveFont() const +{ + if ( OverrideFont ) + return OverrideFont; + IGUISkin* skin = Environment->getSkin(); + if (skin) + return skin->getFont(); + return 0; +} + +//! Sets another color for the text. +void intlGUIEditBox::setOverrideColor(video::SColor color) +{ + OverrideColor = color; + OverrideColorEnabled = true; +} + +video::SColor intlGUIEditBox::getOverrideColor() const +{ + return OverrideColor; +} + +//! Turns the border on or off +void intlGUIEditBox::setDrawBorder(bool border) +{ + Border = border; +} + +//! Sets whether to draw the background +void intlGUIEditBox::setDrawBackground(bool draw) +{ +} + +//! Sets if the text should use the overide color or the color in the gui skin. +void intlGUIEditBox::enableOverrideColor(bool enable) +{ + OverrideColorEnabled = enable; +} + +bool intlGUIEditBox::isOverrideColorEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return OverrideColorEnabled; +} + +//! Enables or disables word wrap +void intlGUIEditBox::setWordWrap(bool enable) +{ + WordWrap = enable; + breakText(); +} + + +void intlGUIEditBox::updateAbsolutePosition() +{ + core::rect<s32> oldAbsoluteRect(AbsoluteRect); + IGUIElement::updateAbsolutePosition(); + if ( oldAbsoluteRect != AbsoluteRect ) + { + breakText(); + } +} + + +//! Checks if word wrap is enabled +bool intlGUIEditBox::isWordWrapEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return WordWrap; +} + + +//! Enables or disables newlines. +void intlGUIEditBox::setMultiLine(bool enable) +{ + MultiLine = enable; +} + + +//! Checks if multi line editing is enabled +bool intlGUIEditBox::isMultiLineEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return MultiLine; +} + + +void intlGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar) +{ + PasswordBox = passwordBox; + if (PasswordBox) + { + PasswordChar = passwordChar; + setMultiLine(false); + setWordWrap(false); + BrokenText.clear(); + } +} + + +bool intlGUIEditBox::isPasswordBox() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return PasswordBox; +} + + +//! Sets text justification +void intlGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) +{ + HAlign = horizontal; + VAlign = vertical; +} + + +//! called if an event happened. +bool intlGUIEditBox::OnEvent(const SEvent& event) +{ + if (IsEnabled) + { + + switch(event.EventType) + { + case EET_GUI_EVENT: + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) + { + if (event.GUIEvent.Caller == this) + { + MouseMarking = false; + setTextMarkers(0,0); + } + } + break; + case EET_KEY_INPUT_EVENT: + { +#if (defined(linux) || defined(__linux) || defined(__FreeBSD__)) + // ################################################################ + // ValkaTR: + // This part is the difference from the original intlGUIEditBox + // It converts UTF-8 character into a UCS-2 (wchar_t) + wchar_t wc = L'_'; + mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) ); + + //printf( "char: %lc (%u) \r\n", wc, wc ); + + SEvent irrevent(event); + irrevent.KeyInput.Char = wc; + // ################################################################ + + if (processKey(irrevent)) + return true; +#else + if (processKey(event)) + return true; +#endif // defined(linux) + + break; + } + case EET_MOUSE_INPUT_EVENT: + if (processMouse(event)) + return true; + break; + default: + break; + } + } + + return IGUIElement::OnEvent(event); +} + + +bool intlGUIEditBox::processKey(const SEvent& event) +{ + if (!event.KeyInput.PressedDown) + return false; + + bool textChanged = false; + s32 newMarkBegin = MarkBegin; + s32 newMarkEnd = MarkEnd; + + // control shortcut handling + + if (event.KeyInput.Control) + { + // german backlash '\' entered with control + '?' + if ( event.KeyInput.Char == '\\' ) + { + inputChar(event.KeyInput.Char); + return true; + } + + switch(event.KeyInput.Key) + { + case KEY_KEY_A: + // select all + newMarkBegin = 0; + newMarkEnd = Text.size(); + break; + case KEY_KEY_C: + // copy to clipboard + if (!PasswordBox && Operator && MarkBegin != MarkEnd) + { + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + core::stringc s; + s = Text.subString(realmbgn, realmend - realmbgn).c_str(); + Operator->copyToClipboard(s.c_str()); + } + break; + case KEY_KEY_X: + // cut to the clipboard + if (!PasswordBox && Operator && MarkBegin != MarkEnd) + { + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + // copy + core::stringc sc; + sc = Text.subString(realmbgn, realmend - realmbgn).c_str(); + Operator->copyToClipboard(sc.c_str()); + + if (IsEnabled) + { + // delete + core::stringw s; + s = Text.subString(0, realmbgn); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + + CursorPos = realmbgn; + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + } + break; + case KEY_KEY_V: + if ( !IsEnabled ) + break; + + // paste from the clipboard + if (Operator) + { + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + // add new character + const c8* p = Operator->getTextFromClipboard(); + if (p) + { + if (MarkBegin == MarkEnd) + { + // insert text + core::stringw s = Text.subString(0, CursorPos); + s.append(p); + s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + + if (!Max || s.size()<=Max) // thx to Fish FH for fix + { + Text = s; + s = p; + CursorPos += s.size(); + } + } + else + { + // replace text + + core::stringw s = Text.subString(0, realmbgn); + s.append(p); + s.append( Text.subString(realmend, Text.size()-realmend) ); + + if (!Max || s.size()<=Max) // thx to Fish FH for fix + { + Text = s; + s = p; + CursorPos = realmbgn + s.size(); + } + } + } + + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + case KEY_HOME: + // move/highlight to start of text + if (event.KeyInput.Shift) + { + newMarkEnd = CursorPos; + newMarkBegin = 0; + CursorPos = 0; + } + else + { + CursorPos = 0; + newMarkBegin = 0; + newMarkEnd = 0; + } + break; + case KEY_END: + // move/highlight to end of text + if (event.KeyInput.Shift) + { + newMarkBegin = CursorPos; + newMarkEnd = Text.size(); + CursorPos = 0; + } + else + { + CursorPos = Text.size(); + newMarkBegin = 0; + newMarkEnd = 0; + } + break; + default: + return false; + } + } + // default keyboard handling + else + switch(event.KeyInput.Key) + { + case KEY_END: + { + s32 p = Text.size(); + if (WordWrap || MultiLine) + { + p = getLineFromPos(CursorPos); + p = BrokenTextPositions[p] + (s32)BrokenText[p].size(); + if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' )) + p-=1; + } + + if (event.KeyInput.Shift) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = p; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + CursorPos = p; + BlinkStartTime = porting::getTimeMs(); + } + break; + case KEY_HOME: + { + + s32 p = 0; + if (WordWrap || MultiLine) + { + p = getLineFromPos(CursorPos); + p = BrokenTextPositions[p]; + } + + if (event.KeyInput.Shift) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + newMarkEnd = p; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + CursorPos = p; + BlinkStartTime = porting::getTimeMs(); + } + break; + case KEY_RETURN: + if (MultiLine) + { + inputChar(L'\n'); + return true; + } + else + { + sendGuiEvent( EGET_EDITBOX_ENTER ); + } + break; + case KEY_LEFT: + + if (event.KeyInput.Shift) + { + if (CursorPos > 0) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = CursorPos-1; + } + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + if (CursorPos > 0) CursorPos--; + BlinkStartTime = porting::getTimeMs(); + break; + + case KEY_RIGHT: + if (event.KeyInput.Shift) + { + if (Text.size() > (u32)CursorPos) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = CursorPos+1; + } + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + if (Text.size() > (u32)CursorPos) CursorPos++; + BlinkStartTime = porting::getTimeMs(); + break; + case KEY_UP: + if (MultiLine || (WordWrap && BrokenText.size() > 1) ) + { + s32 lineNo = getLineFromPos(CursorPos); + s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd); + if (lineNo > 0) + { + s32 cp = CursorPos - BrokenTextPositions[lineNo]; + if ((s32)BrokenText[lineNo-1].size() < cp) + CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1; + else + CursorPos = BrokenTextPositions[lineNo-1] + cp; + } + + if (event.KeyInput.Shift) + { + newMarkBegin = mb; + newMarkEnd = CursorPos; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + } + else + { + return false; + } + break; + case KEY_DOWN: + if (MultiLine || (WordWrap && BrokenText.size() > 1) ) + { + s32 lineNo = getLineFromPos(CursorPos); + s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd); + if (lineNo < (s32)BrokenText.size()-1) + { + s32 cp = CursorPos - BrokenTextPositions[lineNo]; + if ((s32)BrokenText[lineNo+1].size() < cp) + CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1; + else + CursorPos = BrokenTextPositions[lineNo+1] + cp; + } + + if (event.KeyInput.Shift) + { + newMarkBegin = mb; + newMarkEnd = CursorPos; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + } + else + { + return false; + } + break; + + case KEY_BACK: + if ( !this->IsEnabled ) + break; + + if (Text.size()) + { + core::stringw s; + + if (MarkBegin != MarkEnd) + { + // delete marked text + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + + CursorPos = realmbgn; + } + else + { + // delete text behind cursor + if (CursorPos>0) + s = Text.subString(0, CursorPos-1); + else + s = L""; + s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + Text = s; + --CursorPos; + } + + if (CursorPos < 0) + CursorPos = 0; + BlinkStartTime = porting::getTimeMs(); + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + case KEY_DELETE: + if ( !this->IsEnabled ) + break; + + if (Text.size() != 0) + { + core::stringw s; + + if (MarkBegin != MarkEnd) + { + // delete marked text + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + + CursorPos = realmbgn; + } + else + { + // delete text before cursor + s = Text.subString(0, CursorPos); + s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) ); + Text = s; + } + + if (CursorPos > (s32)Text.size()) + CursorPos = (s32)Text.size(); + + BlinkStartTime = porting::getTimeMs(); + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + + case KEY_ESCAPE: + case KEY_TAB: + case KEY_SHIFT: + case KEY_F1: + case KEY_F2: + case KEY_F3: + case KEY_F4: + case KEY_F5: + case KEY_F6: + case KEY_F7: + case KEY_F8: + case KEY_F9: + case KEY_F10: + case KEY_F11: + case KEY_F12: + case KEY_F13: + case KEY_F14: + case KEY_F15: + case KEY_F16: + case KEY_F17: + case KEY_F18: + case KEY_F19: + case KEY_F20: + case KEY_F21: + case KEY_F22: + case KEY_F23: + case KEY_F24: + // ignore these keys + return false; + + default: + inputChar(event.KeyInput.Char); + return true; + } + + // Set new text markers + setTextMarkers( newMarkBegin, newMarkEnd ); + + // break the text if it has changed + if (textChanged) + { + breakText(); + sendGuiEvent(EGET_EDITBOX_CHANGED); + } + + calculateScrollPos(); + + return true; +} + + +//! draws the element and its children +void intlGUIEditBox::draw() +{ + if (!IsVisible) + return; + + const bool focus = Environment->hasFocus(this); + + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + + FrameRect = AbsoluteRect; + + // draw the border + + if (Border) + { + skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW), + false, true, FrameRect, &AbsoluteClippingRect); + + FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + } + core::rect<s32> localClipRect = FrameRect; + localClipRect.clipAgainst(AbsoluteClippingRect); + + // draw the text + + IGUIFont* font = OverrideFont; + if (!OverrideFont) + font = skin->getFont(); + + s32 cursorLine = 0; + s32 charcursorpos = 0; + + if (font) + { + if (LastBreakFont != font) + { + breakText(); + } + + // calculate cursor pos + + core::stringw *txtLine = &Text; + s32 startPos = 0; + + core::stringw s, s2; + + // get mark position + const bool ml = (!PasswordBox && (WordWrap || MultiLine)); + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0; + const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1; + const s32 lineCount = ml ? BrokenText.size() : 1; + + // Save the override color information. + // Then, alter it if the edit box is disabled. + const bool prevOver = OverrideColorEnabled; + const video::SColor prevColor = OverrideColor; + + if (Text.size()) + { + if (!IsEnabled && !OverrideColorEnabled) + { + OverrideColorEnabled = true; + OverrideColor = skin->getColor(EGDC_GRAY_TEXT); + } + + for (s32 i=0; i < lineCount; ++i) + { + setTextRect(i); + + // clipping test - don't draw anything outside the visible area + core::rect<s32> c = localClipRect; + c.clipAgainst(CurrentTextRect); + if (!c.isValid()) + continue; + + // get current line + if (PasswordBox) + { + if (BrokenText.size() != 1) + { + BrokenText.clear(); + BrokenText.push_back(core::stringw()); + } + if (BrokenText[0].size() != Text.size()) + { + BrokenText[0] = Text; + for (u32 q = 0; q < Text.size(); ++q) + { + BrokenText[0] [q] = PasswordChar; + } + } + txtLine = &BrokenText[0]; + startPos = 0; + } + else + { + txtLine = ml ? &BrokenText[i] : &Text; + startPos = ml ? BrokenTextPositions[i] : 0; + } + + + // draw normal text + font->draw(txtLine->c_str(), CurrentTextRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &localClipRect); + + // draw mark and marked text + if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount) + { + + s32 mbegin = 0, mend = 0; + s32 lineStartPos = 0, lineEndPos = txtLine->size(); + + if (i == hlineStart) + { + // highlight start is on this line + s = txtLine->subString(0, realmbgn - startPos); + mbegin = font->getDimension(s.c_str()).Width; + + // deal with kerning + mbegin += font->getKerningWidth( + &((*txtLine)[realmbgn - startPos]), + realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0); + + lineStartPos = realmbgn - startPos; + } + if (i == hlineStart + hlineCount - 1) + { + // highlight end is on this line + s2 = txtLine->subString(0, realmend - startPos); + mend = font->getDimension(s2.c_str()).Width; + lineEndPos = (s32)s2.size(); + } + else + mend = font->getDimension(txtLine->c_str()).Width; + + CurrentTextRect.UpperLeftCorner.X += mbegin; + CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin; + + // draw mark + skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect); + + // draw marked text + s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos); + + if (s.size()) + font->draw(s.c_str(), CurrentTextRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT), + false, true, &localClipRect); + + } + } + + // Return the override color information to its previous settings. + OverrideColorEnabled = prevOver; + OverrideColor = prevColor; + } + + // draw cursor + + if (WordWrap || MultiLine) + { + cursorLine = getLineFromPos(CursorPos); + txtLine = &BrokenText[cursorLine]; + startPos = BrokenTextPositions[cursorLine]; + } + s = txtLine->subString(0,CursorPos-startPos); + charcursorpos = font->getDimension(s.c_str()).Width + + font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0); + + if (focus && (porting::getTimeMs() - BlinkStartTime) % 700 < 350) + { + setTextRect(cursorLine); + CurrentTextRect.UpperLeftCorner.X += charcursorpos; + + font->draw(L"_", CurrentTextRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &localClipRect); + } + } + + // draw children + IGUIElement::draw(); +} + + +//! Sets the new caption of this element. +void intlGUIEditBox::setText(const wchar_t* text) +{ + Text = text; + if (u32(CursorPos) > Text.size()) + CursorPos = Text.size(); + HScrollPos = 0; + breakText(); +} + + +//! Enables or disables automatic scrolling with cursor position +//! \param enable: If set to true, the text will move around with the cursor position +void intlGUIEditBox::setAutoScroll(bool enable) +{ + AutoScroll = enable; +} + + +//! Checks to see if automatic scrolling is enabled +//! \return true if automatic scrolling is enabled, false if not +bool intlGUIEditBox::isAutoScrollEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return AutoScroll; +} + + +//! Gets the area of the text in the edit box +//! \return Returns the size in pixels of the text +core::dimension2du intlGUIEditBox::getTextDimension() +{ + core::rect<s32> ret; + + setTextRect(0); + ret = CurrentTextRect; + + for (u32 i=1; i < BrokenText.size(); ++i) + { + setTextRect(i); + ret.addInternalPoint(CurrentTextRect.UpperLeftCorner); + ret.addInternalPoint(CurrentTextRect.LowerRightCorner); + } + + return core::dimension2du(ret.getSize()); +} + + +//! Sets the maximum amount of characters which may be entered in the box. +//! \param max: Maximum amount of characters. If 0, the character amount is +//! infinity. +void intlGUIEditBox::setMax(u32 max) +{ + Max = max; + + if (Text.size() > Max && Max != 0) + Text = Text.subString(0, Max); +} + + +//! Returns maximum amount of characters, previously set by setMax(); +u32 intlGUIEditBox::getMax() const +{ + return Max; +} + + +bool intlGUIEditBox::processMouse(const SEvent& event) +{ + switch(event.MouseInput.Event) + { + case irr::EMIE_LMOUSE_LEFT_UP: + if (Environment->hasFocus(this)) + { + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + if (MouseMarking) + { + setTextMarkers( MarkBegin, CursorPos ); + } + MouseMarking = false; + calculateScrollPos(); + return true; + } + break; + case irr::EMIE_MOUSE_MOVED: + { + if (MouseMarking) + { + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + setTextMarkers( MarkBegin, CursorPos ); + calculateScrollPos(); + return true; + } + } + break; + case EMIE_LMOUSE_PRESSED_DOWN: + if (!Environment->hasFocus(this)) + { + BlinkStartTime = porting::getTimeMs(); + MouseMarking = true; + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + setTextMarkers(CursorPos, CursorPos ); + calculateScrollPos(); + return true; + } + else + { + if (!AbsoluteClippingRect.isPointInside( + core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y))) + { + return false; + } + else + { + // move cursor + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + + s32 newMarkBegin = MarkBegin; + if (!MouseMarking) + newMarkBegin = CursorPos; + + MouseMarking = true; + setTextMarkers( newMarkBegin, CursorPos); + calculateScrollPos(); + return true; + } + } + default: + break; + } + + return false; +} + + +s32 intlGUIEditBox::getCursorPos(s32 x, s32 y) +{ + IGUIFont* font = OverrideFont; + IGUISkin* skin = Environment->getSkin(); + if (!OverrideFont) + font = skin->getFont(); + + const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; + + core::stringw *txtLine=0; + s32 startPos=0; + x+=3; + + for (u32 i=0; i < lineCount; ++i) + { + setTextRect(i); + if (i == 0 && y < CurrentTextRect.UpperLeftCorner.Y) + y = CurrentTextRect.UpperLeftCorner.Y; + if (i == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y ) + y = CurrentTextRect.LowerRightCorner.Y; + + // is it inside this region? + if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) + { + // we've found the clicked line + txtLine = (WordWrap || MultiLine) ? &BrokenText[i] : &Text; + startPos = (WordWrap || MultiLine) ? BrokenTextPositions[i] : 0; + break; + } + } + + if (x < CurrentTextRect.UpperLeftCorner.X) + x = CurrentTextRect.UpperLeftCorner.X; + + s32 idx = font->getCharacterFromPos(Text.c_str(), x - CurrentTextRect.UpperLeftCorner.X); + + // click was on or left of the line + if (idx != -1) + return idx + startPos; + + // click was off the right edge of the line, go to end. + return txtLine->size() + startPos; +} + + +//! Breaks the single text line. +void intlGUIEditBox::breakText() +{ + IGUISkin* skin = Environment->getSkin(); + + if ((!WordWrap && !MultiLine) || !skin) + return; + + BrokenText.clear(); // need to reallocate :/ + BrokenTextPositions.set_used(0); + + IGUIFont* font = OverrideFont; + if (!OverrideFont) + font = skin->getFont(); + + if (!font) + return; + + LastBreakFont = font; + + core::stringw line; + core::stringw word; + core::stringw whitespace; + s32 lastLineStart = 0; + s32 size = Text.size(); + s32 length = 0; + s32 elWidth = RelativeRect.getWidth() - 6; + wchar_t c; + + for (s32 i=0; i<size; ++i) + { + c = Text[i]; + bool lineBreak = false; + + if (c == L'\r') // Mac or Windows breaks + { + lineBreak = true; + c = ' '; + if (Text[i+1] == L'\n') // Windows breaks + { + Text.erase(i+1); + --size; + } + } + else if (c == L'\n') // Unix breaks + { + lineBreak = true; + c = ' '; + } + + // don't break if we're not a multi-line edit box + if (!MultiLine) + lineBreak = false; + + if (c == L' ' || c == 0 || i == (size-1)) + { + if (word.size()) + { + // here comes the next whitespace, look if + // we can break the last word to the next line. + s32 whitelgth = font->getDimension(whitespace.c_str()).Width; + s32 worldlgth = font->getDimension(word.c_str()).Width; + + if (WordWrap && length + worldlgth + whitelgth > elWidth) + { + // break to next line + length = worldlgth; + BrokenText.push_back(line); + BrokenTextPositions.push_back(lastLineStart); + lastLineStart = i - (s32)word.size(); + line = word; + } + else + { + // add word to line + line += whitespace; + line += word; + length += whitelgth + worldlgth; + } + + word = L""; + whitespace = L""; + } + + whitespace += c; + + // compute line break + if (lineBreak) + { + line += whitespace; + line += word; + BrokenText.push_back(line); + BrokenTextPositions.push_back(lastLineStart); + lastLineStart = i+1; + line = L""; + word = L""; + whitespace = L""; + length = 0; + } + } + else + { + // yippee this is a word.. + word += c; + } + } + + line += whitespace; + line += word; + BrokenText.push_back(line); + BrokenTextPositions.push_back(lastLineStart); +} + + +void intlGUIEditBox::setTextRect(s32 line) +{ + core::dimension2du d; + + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + + IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + + if (!font) + return; + + // get text dimension + const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; + if (WordWrap || MultiLine) + { + d = font->getDimension(BrokenText[line].c_str()); + } + else + { + d = font->getDimension(Text.c_str()); + d.Height = AbsoluteRect.getHeight(); + } + d.Height += font->getKerningHeight(); + + // justification + switch (HAlign) + { + case EGUIA_CENTER: + // align to h centre + CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2); + CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2); + break; + case EGUIA_LOWERRIGHT: + // align to right edge + CurrentTextRect.UpperLeftCorner.X = FrameRect.getWidth() - d.Width; + CurrentTextRect.LowerRightCorner.X = FrameRect.getWidth(); + break; + default: + // align to left edge + CurrentTextRect.UpperLeftCorner.X = 0; + CurrentTextRect.LowerRightCorner.X = d.Width; + + } + + switch (VAlign) + { + case EGUIA_CENTER: + // align to v centre + CurrentTextRect.UpperLeftCorner.Y = + (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line; + break; + case EGUIA_LOWERRIGHT: + // align to bottom edge + CurrentTextRect.UpperLeftCorner.Y = + FrameRect.getHeight() - lineCount*d.Height + d.Height*line; + break; + default: + // align to top edge + CurrentTextRect.UpperLeftCorner.Y = d.Height*line; + break; + } + + CurrentTextRect.UpperLeftCorner.X -= HScrollPos; + CurrentTextRect.LowerRightCorner.X -= HScrollPos; + CurrentTextRect.UpperLeftCorner.Y -= VScrollPos; + CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height; + + CurrentTextRect += FrameRect.UpperLeftCorner; + +} + + +s32 intlGUIEditBox::getLineFromPos(s32 pos) +{ + if (!WordWrap && !MultiLine) + return 0; + + s32 i=0; + while (i < (s32)BrokenTextPositions.size()) + { + if (BrokenTextPositions[i] > pos) + return i-1; + ++i; + } + return (s32)BrokenTextPositions.size() - 1; +} + + +void intlGUIEditBox::inputChar(wchar_t c) +{ + if (!IsEnabled) + return; + + if (c != 0) + { + if (Text.size() < Max || Max == 0) + { + core::stringw s; + + if (MarkBegin != MarkEnd) + { + // replace marked text + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append(c); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + CursorPos = realmbgn+1; + } + else + { + // add new character + s = Text.subString(0, CursorPos); + s.append(c); + s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + Text = s; + ++CursorPos; + } + + BlinkStartTime = porting::getTimeMs(); + setTextMarkers(0, 0); + } + } + breakText(); + sendGuiEvent(EGET_EDITBOX_CHANGED); + calculateScrollPos(); +} + + +void intlGUIEditBox::calculateScrollPos() +{ + if (!AutoScroll) + return; + + // calculate horizontal scroll position + s32 cursLine = getLineFromPos(CursorPos); + setTextRect(cursLine); + + // don't do horizontal scrolling when wordwrap is enabled. + if (!WordWrap) + { + // get cursor position + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + if (!font) + return; + + core::stringw *txtLine = MultiLine ? &BrokenText[cursLine] : &Text; + s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] : CursorPos; + + s32 cStart = CurrentTextRect.UpperLeftCorner.X + HScrollPos + + font->getDimension(txtLine->subString(0, cPos).c_str()).Width; + + s32 cEnd = cStart + font->getDimension(L"_ ").Width; + + if (FrameRect.LowerRightCorner.X < cEnd) + HScrollPos = cEnd - FrameRect.LowerRightCorner.X; + else if (FrameRect.UpperLeftCorner.X > cStart) + HScrollPos = cStart - FrameRect.UpperLeftCorner.X; + else + HScrollPos = 0; + + // todo: adjust scrollbar + } + + // vertical scroll position + if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y + VScrollPos) + VScrollPos = CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y + VScrollPos; + + else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y + VScrollPos) + VScrollPos = CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y + VScrollPos; + else + VScrollPos = 0; + + // todo: adjust scrollbar +} + +//! set text markers +void intlGUIEditBox::setTextMarkers(s32 begin, s32 end) +{ + if ( begin != MarkBegin || end != MarkEnd ) + { + MarkBegin = begin; + MarkEnd = end; + sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); + } +} + +//! send some gui event to parent +void intlGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type) +{ + if ( Parent ) + { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = type; + + Parent->OnEvent(e); + } +} + +//! Writes attributes of the element. +void intlGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const +{ + // IGUIEditBox::serializeAttributes(out,options); + + out->addBool ("OverrideColorEnabled",OverrideColorEnabled ); + out->addColor ("OverrideColor", OverrideColor); + // out->addFont("OverrideFont",OverrideFont); + out->addInt ("MaxChars", Max); + out->addBool ("WordWrap", WordWrap); + out->addBool ("MultiLine", MultiLine); + out->addBool ("AutoScroll", AutoScroll); + out->addBool ("PasswordBox", PasswordBox); + core::stringw ch = L" "; + ch[0] = PasswordChar; + out->addString("PasswordChar", ch.c_str()); + out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); + out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); + + IGUIEditBox::serializeAttributes(out,options); +} + + +//! Reads attributes of the element +void intlGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) +{ + IGUIEditBox::deserializeAttributes(in,options); + + setOverrideColor(in->getAttributeAsColor("OverrideColor")); + enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); + setMax(in->getAttributeAsInt("MaxChars")); + setWordWrap(in->getAttributeAsBool("WordWrap")); + setMultiLine(in->getAttributeAsBool("MultiLine")); + setAutoScroll(in->getAttributeAsBool("AutoScroll")); + core::stringw ch = in->getAttributeAsStringW("PasswordChar"); + + if (!ch.size()) + setPasswordBox(in->getAttributeAsBool("PasswordBox")); + else + setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]); + + setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), + (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); + + // setOverrideFont(in->getAttributeAsFont("OverrideFont")); +} + + +} // end namespace gui +} // end namespace irr + +#endif // _IRR_COMPILE_WITH_GUI_ diff --git a/src/intlGUIEditBox.h b/src/intlGUIEditBox.h new file mode 100644 index 000000000..e3ee15a30 --- /dev/null +++ b/src/intlGUIEditBox.h @@ -0,0 +1,178 @@ +// 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 + +#ifndef __C_INTL_GUI_EDIT_BOX_H_INCLUDED__ +#define __C_INTL_GUI_EDIT_BOX_H_INCLUDED__ + +#include "IrrCompileConfig.h" +//#ifdef _IRR_COMPILE_WITH_GUI_ + +#include "IGUIEditBox.h" +#include "irrArray.h" +#include "IOSOperator.h" + +namespace irr +{ +namespace gui +{ + class intlGUIEditBox : public IGUIEditBox + { + public: + + //! constructor + intlGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment, + IGUIElement* parent, s32 id, const core::rect<s32>& rectangle); + + //! destructor + virtual ~intlGUIEditBox(); + + //! Sets another skin independent font. + virtual void setOverrideFont(IGUIFont* font=0); + + //! Gets the override font (if any) + /** \return The override font (may be 0) */ + virtual IGUIFont* getOverrideFont() const; + + //! Get the font which is used right now for drawing + /** Currently this is the override font when one is set and the + font of the active skin otherwise */ + virtual IGUIFont* getActiveFont() const; + + //! Sets another color for the text. + virtual void setOverrideColor(video::SColor color); + + //! Gets the override color + virtual video::SColor getOverrideColor() const; + + //! Sets if the text should use the overide color or the + //! color in the gui skin. + virtual void enableOverrideColor(bool enable); + + //! Checks if an override color is enabled + /** \return true if the override color is enabled, false otherwise */ + virtual bool isOverrideColorEnabled(void) const; + + //! Sets whether to draw the background + virtual void setDrawBackground(bool draw); + + //! Turns the border on or off + virtual void setDrawBorder(bool border); + + //! Enables or disables word wrap for using the edit box as multiline text editor. + virtual void setWordWrap(bool enable); + + //! Checks if word wrap is enabled + //! \return true if word wrap is enabled, false otherwise + virtual bool isWordWrapEnabled() const; + + //! Enables or disables newlines. + /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired, + instead a newline character will be inserted. */ + virtual void setMultiLine(bool enable); + + //! Checks if multi line editing is enabled + //! \return true if mult-line is enabled, false otherwise + virtual bool isMultiLineEnabled() const; + + //! Enables or disables automatic scrolling with cursor position + //! \param enable: If set to true, the text will move around with the cursor position + virtual void setAutoScroll(bool enable); + + //! Checks to see if automatic scrolling is enabled + //! \return true if automatic scrolling is enabled, false if not + virtual bool isAutoScrollEnabled() const; + + //! Gets the size area of the text in the edit box + //! \return Returns the size in pixels of the text + virtual core::dimension2du getTextDimension(); + + //! Sets text justification + virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); + + //! called if an event happened. + virtual bool OnEvent(const SEvent& event); + + //! draws the element and its children + virtual void draw(); + + //! Sets the new caption of this element. + virtual void setText(const wchar_t* text); + + //! Sets the maximum amount of characters which may be entered in the box. + //! \param max: Maximum amount of characters. If 0, the character amount is + //! infinity. + virtual void setMax(u32 max); + + //! Returns maximum amount of characters, previously set by setMax(); + virtual u32 getMax() const; + + //! Sets whether the edit box is a password box. Setting this to true will + /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x + \param passwordBox: true to enable password, false to disable + \param passwordChar: the character that is displayed instead of letters */ + virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*'); + + //! Returns true if the edit box is currently a password box. + virtual bool isPasswordBox() const; + + //! Updates the absolute position, splits text if required + virtual void updateAbsolutePosition(); + + //! Writes attributes of the element. + virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; + + //! Reads attributes of the element + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); + + protected: + //! Breaks the single text line. + void breakText(); + //! sets the area of the given line + void setTextRect(s32 line); + //! returns the line number that the cursor is on + s32 getLineFromPos(s32 pos); + //! adds a letter to the edit box + void inputChar(wchar_t c); + //! calculates the current scroll position + void calculateScrollPos(); + //! send some gui event to parent + void sendGuiEvent(EGUI_EVENT_TYPE type); + //! set text markers + void setTextMarkers(s32 begin, s32 end); + + bool processKey(const SEvent& event); + bool processMouse(const SEvent& event); + s32 getCursorPos(s32 x, s32 y); + + bool MouseMarking; + bool Border; + bool OverrideColorEnabled; + s32 MarkBegin; + s32 MarkEnd; + + video::SColor OverrideColor; + gui::IGUIFont *OverrideFont, *LastBreakFont; + IOSOperator* Operator; + + u32 BlinkStartTime; + s32 CursorPos; + s32 HScrollPos, VScrollPos; // scroll position in characters + u32 Max; + + bool WordWrap, MultiLine, AutoScroll, PasswordBox; + wchar_t PasswordChar; + EGUI_ALIGNMENT HAlign, VAlign; + + core::array< core::stringw > BrokenText; + core::array< s32 > BrokenTextPositions; + + core::rect<s32> CurrentTextRect, FrameRect; // temporary values + }; + + +} // end namespace gui +} // end namespace irr + +//#endif // _IRR_COMPILE_WITH_GUI_ +#endif // __C_GUI_EDIT_BOX_H_INCLUDED__ diff --git a/src/inventory.cpp b/src/inventory.cpp index 3fa31965c..af8b1b301 100644 --- a/src/inventory.cpp +++ b/src/inventory.cpp @@ -163,7 +163,7 @@ void ItemStack::deSerialize(std::istream &is, IItemDefManager *itemdef) std::getline(is, tmp, ' '); if(!tmp.empty()) throw SerializationError("Unexpected text after item name"); - + if(name == "MaterialItem") { // Obsoleted on 2011-07-30 @@ -478,7 +478,7 @@ void InventoryList::setName(const std::string &name) void InventoryList::serialize(std::ostream &os) const { //os.imbue(std::locale("C")); - + os<<"Width "<<m_width<<"\n"; for(u32 i=0; i<m_items.size(); i++) @@ -620,13 +620,13 @@ u32 InventoryList::getFreeSlots() const const ItemStack& InventoryList::getItem(u32 i) const { - assert(i < m_size); + assert(i < m_size); // Pre-condition return m_items[i]; } ItemStack& InventoryList::getItem(u32 i) { - assert(i < m_size); + assert(i < m_size); // Pre-condition return m_items[i]; } @@ -643,7 +643,7 @@ ItemStack InventoryList::changeItem(u32 i, const ItemStack &newitem) void InventoryList::deleteItem(u32 i) { - assert(i < m_items.size()); + assert(i < m_items.size()); // Pre-condition m_items[i].clear(); } @@ -653,7 +653,7 @@ ItemStack InventoryList::addItem(const ItemStack &newitem_) if(newitem.empty()) return newitem; - + /* First try to find if it could be added to some existing items */ @@ -782,11 +782,47 @@ ItemStack InventoryList::peekItem(u32 i, u32 peekcount) const return m_items[i].peekItem(peekcount); } -void InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i, u32 count) +void InventoryList::moveItemSomewhere(u32 i, InventoryList *dest, u32 count) { - if(this == dest && i == dest_i) + // Take item from source list + ItemStack item1; + if (count == 0) + item1 = changeItem(i, ItemStack()); + else + item1 = takeItem(i, 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; + } + } + + // 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 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, + u32 count, bool swap_if_needed, bool *did_swap) +{ + if(this == dest && i == dest_i) + return count; + // Take item from source list ItemStack item1; if(count == 0) @@ -795,7 +831,7 @@ void InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i, u32 count) item1 = takeItem(i, count); if(item1.empty()) - return; + return 0; // Try to add the item to destination list u32 oldcount = item1.count; @@ -813,8 +849,11 @@ void InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i, u32 count) // If olditem is returned, nothing was added. // Swap the items - if(nothing_added) - { + if (nothing_added && swap_if_needed) { + // Tell that we swapped + if (did_swap != NULL) { + *did_swap = true; + } // Take item from source list item1 = changeItem(i, ItemStack()); // Adding was not possible, swap the items. @@ -823,6 +862,7 @@ void InventoryList::moveItem(u32 i, InventoryList *dest, u32 dest_i, u32 count) changeItem(i, item2); } } + return (oldcount - item1.count); } /* diff --git a/src/inventory.h b/src/inventory.h index e4a97e1d3..a690eb5ae 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -71,7 +71,7 @@ struct ItemStack void remove(u16 n) { - assert(count >= n); + assert(count >= n); // Pre-condition count -= n; if(count == 0) clear(); // reset name, wear and metadata too @@ -244,7 +244,13 @@ public: // Move an item to a different list (or a different stack in the same list) // count is the maximum number of items to move (0 for everything) - void moveItem(u32 i, InventoryList *dest, u32 dest_i, u32 count = 0); + // returns number of moved items + u32 moveItem(u32 i, InventoryList *dest, u32 dest_i, + u32 count = 0, bool swap_if_needed = true, bool *did_swap = NULL); + + // like moveItem, but without a fixed destination index + // also with optional rollback recording + void moveItemSomewhere(u32 i, InventoryList *dest, u32 count); private: std::vector<ItemStack> m_items; diff --git a/src/inventorymanager.cpp b/src/inventorymanager.cpp index ed18126d0..476768b8c 100644 --- a/src/inventorymanager.cpp +++ b/src/inventorymanager.cpp @@ -22,7 +22,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "environment.h" #include "scripting_game.h" #include "serverobject.h" -#include "main.h" // for g_settings #include "settings.h" #include "craftdef.h" #include "rollback_interface.h" @@ -62,7 +61,7 @@ void InventoryLocation::serialize(std::ostream &os) const os<<"detached:"<<name; break; default: - assert(0); + FATAL_ERROR("Unhandled inventory location type"); } } @@ -122,16 +121,13 @@ InventoryAction * InventoryAction::deSerialize(std::istream &is) InventoryAction *a = NULL; - if(type == "Move") - { - a = new IMoveAction(is); - } - else if(type == "Drop") - { + if (type == "Move") { + a = new IMoveAction(is, false); + } else if (type == "MoveSomewhere") { + a = new IMoveAction(is, true); + } else if (type == "Drop") { a = new IDropAction(is); - } - else if(type == "Craft") - { + } else if(type == "Craft") { a = new ICraftAction(is); } @@ -142,9 +138,12 @@ InventoryAction * InventoryAction::deSerialize(std::istream &is) IMoveAction */ -IMoveAction::IMoveAction(std::istream &is) +IMoveAction::IMoveAction(std::istream &is, bool somewhere) { std::string ts; + move_somewhere = somewhere; + caused_by_move_somewhere = false; + move_count = 0; std::getline(is, ts, ' '); count = stoi(ts); @@ -162,25 +161,27 @@ IMoveAction::IMoveAction(std::istream &is) std::getline(is, to_list, ' '); - std::getline(is, ts, ' '); - to_i = stoi(ts); + if (!somewhere) { + std::getline(is, ts, ' '); + to_i = stoi(ts); + } } void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef) { Inventory *inv_from = mgr->getInventory(from_inv); Inventory *inv_to = mgr->getInventory(to_inv); - - if(!inv_from){ - infostream<<"IMoveAction::apply(): FAIL: source inventory not found: " - <<"from_inv=\""<<from_inv.dump()<<"\"" - <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl; + + if (!inv_from) { + infostream << "IMoveAction::apply(): FAIL: source inventory not found: " + << "from_inv=\""<<from_inv.dump() << "\"" + << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl; return; } - if(!inv_to){ - infostream<<"IMoveAction::apply(): FAIL: destination inventory not found: " - <<"from_inv=\""<<from_inv.dump()<<"\"" - <<", to_inv=\""<<to_inv.dump()<<"\""<<std::endl; + if (!inv_to) { + infostream << "IMoveAction::apply(): FAIL: destination inventory not found: " + << "from_inv=\"" << from_inv.dump() << "\"" + << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl; return; } @@ -190,19 +191,68 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame /* If a list doesn't exist or the source item doesn't exist */ - if(!list_from){ - infostream<<"IMoveAction::apply(): FAIL: source list not found: " - <<"from_inv=\""<<from_inv.dump()<<"\"" - <<", from_list=\""<<from_list<<"\""<<std::endl; + if (!list_from) { + infostream << "IMoveAction::apply(): FAIL: source list not found: " + << "from_inv=\"" << from_inv.dump() << "\"" + << ", from_list=\"" << from_list << "\"" << std::endl; return; } - if(!list_to){ - infostream<<"IMoveAction::apply(): FAIL: destination list not found: " - <<"to_inv=\""<<to_inv.dump()<<"\"" - <<", to_list=\""<<to_list<<"\""<<std::endl; + if (!list_to) { + infostream << "IMoveAction::apply(): FAIL: destination list not found: " + << "to_inv=\""<<to_inv.dump() << "\"" + << ", to_list=\"" << to_list << "\"" << std::endl; return; } + if (move_somewhere) { + s16 old_to_i = to_i; + u16 old_count = count; + caused_by_move_somewhere = true; + move_somewhere = false; + + infostream << "IMoveAction::apply(): moving item somewhere" + << " msom=" << move_somewhere + << " count=" << count + << " from inv=\"" << from_inv.dump() << "\"" + << " list=\"" << from_list << "\"" + << " i=" << from_i + << " to inv=\"" << to_inv.dump() << "\"" + << " list=\"" << to_list << "\"" + << std::endl; + + // Try to add the item to destination list + s16 dest_size = list_to->getSize(); + // First try all the non-empty slots + for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) { + if (!list_to->getItem(dest_i).empty()) { + to_i = dest_i; + apply(mgr, player, gamedef); + count -= move_count; + } + } + + // Then try all the empty ones + for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) { + if (list_to->getItem(dest_i).empty()) { + to_i = dest_i; + apply(mgr, player, gamedef); + count -= move_count; + } + } + + to_i = old_to_i; + count = old_count; + caused_by_move_somewhere = false; + move_somewhere = true; + return; + } + + if ((u16)to_i > list_to->getSize()) { + infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: " + << "to_i=" << to_i + << ", size=" << list_to->getSize() << std::endl; + return; + } /* Do not handle rollback if both inventories are that of the same player */ @@ -221,7 +271,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame int src_can_take_count = 0xffff; int dst_can_put_count = 0xffff; - + /* Query detached inventories */ // Move occurs in the same detached inventory @@ -288,7 +338,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame } int old_count = count; - + /* Modify count according to collected data */ count = try_take_count; if(src_can_take_count != -1 && count > src_can_take_count) @@ -298,7 +348,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame /* Limit according to source item count */ if(count > list_from->getItem(from_i).count) count = list_from->getItem(from_i).count; - + /* If no items will be moved, don't go further */ if(count == 0) { @@ -325,24 +375,33 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame If something is wrong (source item is empty, destination is the same as source), nothing happens */ - list_from->moveItem(from_i, list_to, to_i, count); + bool did_swap = false; + move_count = list_from->moveItem(from_i, + list_to, to_i, count, !caused_by_move_somewhere, &did_swap); // If source is infinite, reset it's stack - if(src_can_take_count == -1){ - // If destination stack is of different type and there are leftover - // items, attempt to put the leftover items to a different place in the - // destination inventory. - // The client-side GUI will try to guess if this happens. - if(from_stack_was.name != to_stack_was.name){ - for(u32 i=0; i<list_to->getSize(); i++){ - if(list_to->getItem(i).empty()){ - list_to->changeItem(i, to_stack_was); - break; + if (src_can_take_count == -1) { + // For the caused_by_move_somewhere == true case we didn't force-put the item, + // which guarantees there is no leftover, and code below would duplicate the + // (not replaced) to_stack_was item. + if (!caused_by_move_somewhere) { + // If destination stack is of different type and there are leftover + // items, attempt to put the leftover items to a different place in the + // destination inventory. + // The client-side GUI will try to guess if this happens. + if (from_stack_was.name != to_stack_was.name) { + for (u32 i = 0; i < list_to->getSize(); i++) { + if (list_to->getItem(i).empty()) { + list_to->changeItem(i, to_stack_was); + break; + } } } } - list_from->deleteItem(from_i); - list_from->addItem(from_i, from_stack_was); + if (move_count > 0 || did_swap) { + list_from->deleteItem(from_i); + list_from->addItem(from_i, from_stack_was); + } } // If destination is infinite, reset it's stack and take count from source if(dst_can_put_count == -1){ @@ -353,15 +412,24 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame list_from->takeItem(from_i, count); } - infostream<<"IMoveAction::apply(): moved" - <<" count="<<count - <<" from inv=\""<<from_inv.dump()<<"\"" - <<" list=\""<<from_list<<"\"" - <<" i="<<from_i - <<" to inv=\""<<to_inv.dump()<<"\"" - <<" list=\""<<to_list<<"\"" - <<" i="<<to_i - <<std::endl; + infostream << "IMoveAction::apply(): moved" + << " msom=" << move_somewhere + << " caused=" << caused_by_move_somewhere + << " count=" << count + << " from inv=\"" << from_inv.dump() << "\"" + << " list=\"" << from_list << "\"" + << " i=" << from_i + << " to inv=\"" << to_inv.dump() << "\"" + << " list=\"" << to_list << "\"" + << " i=" << to_i + << std::endl; + + // If we are inside the move somewhere loop, we don't need to report + // anything if nothing happened (perhaps we don't need to report + // anything for caused_by_move_somewhere == true, but this way its safer) + if (caused_by_move_somewhere && move_count == 0) { + return; + } /* Record rollback information @@ -401,7 +469,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame /* Report move to endpoints */ - + /* Detached inventories */ // Both endpoints are same detached @@ -454,10 +522,10 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame from_inv.p, from_list, from_i, src_item, player); } } - - mgr->setInventoryModified(from_inv); + + mgr->setInventoryModified(from_inv, false); if(inv_from != inv_to) - mgr->setInventoryModified(to_inv); + mgr->setInventoryModified(to_inv, false); } void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef) @@ -481,7 +549,10 @@ void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef) if(!list_from || !list_to) return; - list_from->moveItem(from_i, list_to, to_i, count); + if (!move_somewhere) + list_from->moveItem(from_i, list_to, to_i, count); + else + list_from->moveItemSomewhere(from_i, list_to, count); mgr->setInventoryModified(from_inv); if(inv_from != inv_to) @@ -511,7 +582,7 @@ IDropAction::IDropAction(std::istream &is) void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef) { Inventory *inv_from = mgr->getInventory(from_inv); - + if(!inv_from){ infostream<<"IDropAction::apply(): FAIL: source inventory not found: " <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl; @@ -571,7 +642,7 @@ 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; ItemStack src_item = list_from->getItem(from_i); @@ -588,7 +659,7 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame infostream<<"Actually dropped no items"<<std::endl; return; } - + // If source isn't infinite if(src_can_take_count != -1){ // Take item from source list @@ -597,7 +668,7 @@ 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); + mgr->setInventoryModified(from_inv, false); } } @@ -606,13 +677,13 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame <<" list=\""<<from_list<<"\"" <<" i="<<from_i <<std::endl; - + src_item.count = actually_dropped_count; /* Report drop to endpoints */ - + // Source is detached if(from_inv.type == InventoryLocation::DETACHED) { @@ -692,72 +763,112 @@ ICraftAction::ICraftAction(std::istream &is) craft_inv.deSerialize(ts); } -void ICraftAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef) +void ICraftAction::apply(InventoryManager *mgr, + ServerActiveObject *player, IGameDef *gamedef) { Inventory *inv_craft = mgr->getInventory(craft_inv); - - if(!inv_craft){ - infostream<<"ICraftAction::apply(): FAIL: inventory not found: " - <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl; + + if (!inv_craft) { + infostream << "ICraftAction::apply(): FAIL: inventory not found: " + << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl; return; } InventoryList *list_craft = inv_craft->getList("craft"); InventoryList *list_craftresult = inv_craft->getList("craftresult"); + InventoryList *list_main = inv_craft->getList("main"); /* If a list doesn't exist or the source item doesn't exist */ - if(!list_craft){ - infostream<<"ICraftAction::apply(): FAIL: craft list not found: " - <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl; + if (!list_craft) { + infostream << "ICraftAction::apply(): FAIL: craft list not found: " + << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl; return; } - if(!list_craftresult){ - infostream<<"ICraftAction::apply(): FAIL: craftresult list not found: " - <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl; + if (!list_craftresult) { + infostream << "ICraftAction::apply(): FAIL: craftresult list not found: " + << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl; return; } - if(list_craftresult->getSize() < 1){ - infostream<<"ICraftAction::apply(): FAIL: craftresult list too short: " - <<"craft_inv=\""<<craft_inv.dump()<<"\""<<std::endl; + if (list_craftresult->getSize() < 1) { + infostream << "ICraftAction::apply(): FAIL: craftresult list too short: " + << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl; return; } ItemStack crafted; ItemStack craftresultitem; int count_remaining = count; - getCraftingResult(inv_craft, crafted, false, gamedef); + std::vector<ItemStack> output_replacements; + getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef); PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv); bool found = !crafted.empty(); - while(found && list_craftresult->itemFits(0, crafted)) - { + while (found && list_craftresult->itemFits(0, crafted)) { InventoryList saved_craft_list = *list_craft; - + + std::vector<ItemStack> temp; // Decrement input and add crafting output - getCraftingResult(inv_craft, crafted, true, gamedef); + getCraftingResult(inv_craft, crafted, temp, true, gamedef); PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv); list_craftresult->addItem(0, crafted); mgr->setInventoryModified(craft_inv); - actionstream<<player->getDescription() - <<" crafts " - <<crafted.getItemString() - <<std::endl; + // Add the new replacements to the list + IItemDefManager *itemdef = gamedef->getItemDefManager(); + for (std::vector<ItemStack>::iterator it = temp.begin(); + it != temp.end(); it++) { + for (std::vector<ItemStack>::iterator jt = output_replacements.begin(); + jt != output_replacements.end(); jt++) { + if (it->name == jt->name) { + *it = jt->addItem(*it, itemdef); + if (it->empty()) + continue; + } + } + output_replacements.push_back(*it); + } + + actionstream << player->getDescription() + << " crafts " + << crafted.getItemString() + << std::endl; // Decrement counter - if(count_remaining == 1) + if (count_remaining == 1) break; - else if(count_remaining > 1) + else if (count_remaining > 1) count_remaining--; // Get next crafting result - found = getCraftingResult(inv_craft, crafted, false, gamedef); + found = getCraftingResult(inv_craft, crafted, temp, false, gamedef); PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv); found = !crafted.empty(); } + // Put the replacements in the inventory or drop them on the floor, if + // the invenotry is full + for (std::vector<ItemStack>::iterator it = output_replacements.begin(); + it != output_replacements.end(); it++) { + if (list_main) + *it = list_main->addItem(*it); + if (it->empty()) + continue; + u16 count = it->count; + do { + PLAYER_TO_SA(player)->item_OnDrop(*it, player, + player->getBasePosition() + v3f(0,1,0)); + if (count >= it->count) { + errorstream << "Couldn't drop replacement stack " << + it->getItemString() << " because drop loop didn't " + "decrease count." << std::endl; + + break; + } + } while (!it->empty()); + } + infostream<<"ICraftAction::apply(): crafted " <<" craft_inv=\""<<craft_inv.dump()<<"\"" <<std::endl; @@ -772,10 +883,11 @@ void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef) // Crafting helper bool getCraftingResult(Inventory *inv, ItemStack& result, + std::vector<ItemStack> &output_replacements, bool decrementInput, IGameDef *gamedef) { DSTACK(__FUNCTION_NAME); - + result.clear(); // Get the InventoryList in which we will operate @@ -793,7 +905,7 @@ bool getCraftingResult(Inventory *inv, ItemStack& result, // Find out what is crafted and add it to result item slot CraftOutput co; bool found = gamedef->getCraftDefManager()->getCraftResult( - ci, co, decrementInput, gamedef); + ci, co, output_replacements, decrementInput, gamedef); if(found) result.deSerialize(co.item, gamedef->getItemDefManager()); diff --git a/src/inventorymanager.h b/src/inventorymanager.h index 8e2abc961..35fcf4b99 100644 --- a/src/inventorymanager.h +++ b/src/inventorymanager.h @@ -108,11 +108,11 @@ class InventoryManager public: InventoryManager(){} virtual ~InventoryManager(){} - + // 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){} + virtual void setInventoryModified(const InventoryLocation &loc, bool playerSend = true){} // Send inventory action to server (only on client) virtual void inventoryAction(InventoryAction *a){} }; @@ -124,7 +124,7 @@ public: struct InventoryAction { static InventoryAction * deSerialize(std::istream &is); - + virtual u16 getType() const = 0; virtual void serialize(std::ostream &os) const = 0; virtual void apply(InventoryManager *mgr, ServerActiveObject *player, @@ -143,15 +143,24 @@ struct IMoveAction : public InventoryAction InventoryLocation to_inv; std::string to_list; s16 to_i; - + bool move_somewhere; + + // treat these as private + // related to movement to somewhere + bool caused_by_move_somewhere; + u32 move_count; + IMoveAction() { count = 0; from_i = -1; to_i = -1; + move_somewhere = false; + caused_by_move_somewhere = false; + move_count = 0; } - - IMoveAction(std::istream &is); + + IMoveAction(std::istream &is, bool somewhere); u16 getType() const { @@ -160,14 +169,18 @@ struct IMoveAction : public InventoryAction void serialize(std::ostream &os) const { - os<<"Move "; - os<<count<<" "; - os<<from_inv.dump()<<" "; - os<<from_list<<" "; - os<<from_i<<" "; - os<<to_inv.dump()<<" "; - os<<to_list<<" "; - os<<to_i; + if (!move_somewhere) + os << "Move "; + else + os << "MoveSomewhere "; + os << count << " "; + os << from_inv.dump() << " "; + os << from_list << " "; + os << from_i << " "; + os << to_inv.dump() << " "; + os << to_list; + if (!move_somewhere) + os << " " << to_i; } void apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef); @@ -182,13 +195,13 @@ struct IDropAction : public InventoryAction InventoryLocation from_inv; std::string from_list; s16 from_i; - + IDropAction() { count = 0; from_i = -1; } - + IDropAction(std::istream &is); u16 getType() const @@ -215,12 +228,12 @@ struct ICraftAction : public InventoryAction // count=0 means "everything" u16 count; InventoryLocation craft_inv; - + ICraftAction() { count = 0; } - + ICraftAction(std::istream &is); u16 getType() const @@ -242,6 +255,7 @@ struct ICraftAction : public InventoryAction // Crafting helper bool getCraftingResult(Inventory *inv, ItemStack& result, + std::vector<ItemStack> &output_replacements, bool decrementInput, IGameDef *gamedef); #endif diff --git a/src/irr_v3d.h b/src/irr_v3d.h index 7bc73ad10..f74d601e8 100644 --- a/src/irr_v3d.h +++ b/src/irr_v3d.h @@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc., typedef core::vector3df v3f; typedef core::vector3d<s16> v3s16; +typedef core::vector3d<u16> v3u16; typedef core::vector3d<s32> v3s32; #endif diff --git a/src/itemdef.cpp b/src/itemdef.cpp index ac67c5b27..0133b1b3f 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -28,10 +28,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapblock_mesh.h" #include "mesh.h" #include "wieldmesh.h" -#include "tile.h" +#include "client/tile.h" #endif #include "log.h" -#include "main.h" // g_settings #include "settings.h" #include "util/serialize.h" #include "util/container.h" @@ -249,8 +248,8 @@ public: virtual ~CItemDefManager() { #ifndef SERVER - const std::list<ClientCached*> &values = m_clientcached.getValues(); - for(std::list<ClientCached*>::const_iterator + const std::vector<ClientCached*> &values = m_clientcached.getValues(); + for(std::vector<ClientCached*>::const_iterator i = values.begin(); i != values.end(); ++i) { ClientCached *cc = *i; @@ -281,26 +280,23 @@ public: } virtual std::string getAlias(const std::string &name) const { - std::map<std::string, std::string>::const_iterator i; - i = m_aliases.find(name); - if(i != m_aliases.end()) - return i->second; + StringMap::const_iterator it = m_aliases.find(name); + if (it != m_aliases.end()) + return it->second; return name; } virtual std::set<std::string> getAll() const { std::set<std::string> result; - for(std::map<std::string, ItemDefinition*>::const_iterator - i = m_item_definitions.begin(); - i != m_item_definitions.end(); i++) - { - result.insert(i->first); + for(std::map<std::string, ItemDefinition *>::const_iterator + it = m_item_definitions.begin(); + it != m_item_definitions.end(); ++it) { + result.insert(it->first); } - for(std::map<std::string, std::string>::const_iterator - i = m_aliases.begin(); - i != m_aliases.end(); i++) - { - result.insert(i->first); + for (StringMap::const_iterator + it = m_aliases.begin(); + it != m_aliases.end(); ++it) { + result.insert(it->first); } return result; } @@ -321,7 +317,7 @@ public: <<name<<"\""<<std::endl; // This is not thread-safe - assert(get_current_thread_id() == m_main_thread); + sanity_check(get_current_thread_id() == m_main_thread); // Skip if already in cache ClientCached *cc = NULL; @@ -362,8 +358,6 @@ public: scene::IMesh *node_mesh = NULL; - bool reenable_shaders = false; - if (need_rtt_mesh || need_wield_mesh) { u8 param1 = 0; if (f.param_type == CPT_LIGHT) @@ -372,11 +366,7 @@ public: /* Make a mesh from the node */ - if (g_settings->getBool("enable_shaders")) { - reenable_shaders = true; - g_settings->setBool("enable_shaders", false); - } - MeshMakeData mesh_make_data(gamedef); + MeshMakeData mesh_make_data(gamedef, false); u8 param2 = 0; if (f.param_type_2 == CPT2_WALLMOUNTED) param2 = 1; @@ -443,9 +433,6 @@ public: if (node_mesh) node_mesh->drop(); - - if (reenable_shaders) - g_settings->setBool("enable_shaders",true); } // Put in cache @@ -553,7 +540,7 @@ public: verbosestream<<"ItemDefManager: registering \""<<def.name<<"\""<<std::endl; // Ensure that the "" item (the hand) always has ToolCapabilities if(def.name == "") - assert(def.tool_capabilities != NULL); + FATAL_ERROR_IF(!def.tool_capabilities, "Hand does not have ToolCapabilities"); if(m_item_definitions.count(def.name) == 0) m_item_definitions[def.name] = new ItemDefinition(def); @@ -581,22 +568,24 @@ public: writeU8(os, 0); // version u16 count = m_item_definitions.size(); writeU16(os, count); - for(std::map<std::string, ItemDefinition*>::const_iterator - i = m_item_definitions.begin(); - i != m_item_definitions.end(); i++) - { - ItemDefinition *def = i->second; + + for (std::map<std::string, ItemDefinition *>::const_iterator + it = m_item_definitions.begin(); + it != m_item_definitions.end(); ++it) { + ItemDefinition *def = it->second; // Serialize ItemDefinition and write wrapped in a string std::ostringstream tmp_os(std::ios::binary); def->serialize(tmp_os, protocol_version); - os<<serializeString(tmp_os.str()); + os << serializeString(tmp_os.str()); } + writeU16(os, m_aliases.size()); - for(std::map<std::string, std::string>::const_iterator - i = m_aliases.begin(); i != m_aliases.end(); i++) - { - os<<serializeString(i->first); - os<<serializeString(i->second); + + for (StringMap::const_iterator + it = m_aliases.begin(); + it != m_aliases.end(); ++it) { + os << serializeString(it->first); + os << serializeString(it->second); } } void deSerialize(std::istream &is) @@ -643,7 +632,7 @@ private: // Key is name std::map<std::string, ItemDefinition*> m_item_definitions; // Aliases - std::map<std::string, std::string> m_aliases; + StringMap m_aliases; #ifndef SERVER // The id of the thread that is allowed to use irrlicht directly threadid_t m_main_thread; diff --git a/src/json/CMakeLists.txt b/src/json/CMakeLists.txt index 5887d523a..9056e4b6d 100644 --- a/src/json/CMakeLists.txt +++ b/src/json/CMakeLists.txt @@ -1,14 +1,7 @@ -if( UNIX ) - set(json_SRCS jsoncpp.cpp) - set(json_platform_LIBS "") -else( UNIX ) - set(json_SRCS jsoncpp.cpp) - set(json_platform_LIBS "") -endif( UNIX ) +if(MSVC) + set(CMAKE_CXX_FLAGS_RELEASE "/MT /O2 /Ob2 /D NDEBUG") +endif() -add_library(jsoncpp ${json_SRCS}) +add_library(jsoncpp jsoncpp.cpp) +target_link_libraries(jsoncpp) -target_link_libraries( - jsoncpp - ${json_platform_LIBS} -) diff --git a/src/jthread/CMakeLists.txt b/src/jthread/CMakeLists.txt index a581a3b02..cebb35caa 100644 --- a/src/jthread/CMakeLists.txt +++ b/src/jthread/CMakeLists.txt @@ -1,15 +1,14 @@ -if( UNIX ) - set(JTHREAD_SRCS - ${CMAKE_CURRENT_SOURCE_DIR}/pthread/jmutex.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/pthread/jthread.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/pthread/jsemaphore.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/pthread/jevent.cpp - PARENT_SCOPE) -else( UNIX ) - set(JTHREAD_SRCS - ${CMAKE_CURRENT_SOURCE_DIR}/win32/jmutex.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/win32/jthread.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/win32/jsemaphore.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/win32/jevent.cpp - PARENT_SCOPE) -endif( UNIX ) +if(UNIX) + set(THREAD_SYS_DIR pthread) +else() + set(THREAD_SYS_DIR win32) +endif() + +set(SRC_PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/${THREAD_SYS_DIR}) +set(JTHREAD_SRCS + ${SRC_PREFIX}/jmutex.cpp + ${SRC_PREFIX}/jthread.cpp + ${SRC_PREFIX}/jsemaphore.cpp + ${SRC_PREFIX}/jevent.cpp + PARENT_SCOPE) + diff --git a/src/keycode.cpp b/src/keycode.cpp index c5f102b44..990dee339 100644 --- a/src/keycode.cpp +++ b/src/keycode.cpp @@ -18,12 +18,11 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "keycode.h" -#include "main.h" // For g_settings #include "exceptions.h" #include "settings.h" #include "log.h" -#include "hex.h" #include "debug.h" +#include "util/hex.h" class UnknownKeycode : public BaseException { @@ -257,13 +256,18 @@ KeyPress::KeyPress() : KeyPress::KeyPress(const char *name) { - if (strlen(name) > 4) { + if (name[0] == 0) { + Key = irr::KEY_KEY_CODES_COUNT; + Char = L'\0'; + return; + } else if (strlen(name) > 4) { try { Key = keyname_to_keycode(name); m_name = name; if (strlen(name) > 8 && strncmp(name, "KEY_KEY_", 8) == 0) { int chars_read = mbtowc(&Char, name + 8, 1); - assert (chars_read == 1 && "unexpected multibyte character"); + + FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); } else Char = L'\0'; return; @@ -275,7 +279,8 @@ KeyPress::KeyPress(const char *name) try { Key = keyname_to_keycode(m_name.c_str()); int chars_read = mbtowc(&Char, name, 1); - assert (chars_read == 1 && "unexpected multibyte character"); + + FATAL_ERROR_IF(chars_read != 1, "Unexpected multibyte character"); return; } catch (UnknownKeycode &e) {}; } @@ -285,7 +290,7 @@ KeyPress::KeyPress(const char *name) Key = irr::KEY_KEY_CODES_COUNT; int mbtowc_ret = mbtowc(&Char, name, 1); - assert (mbtowc_ret == 1 && "unexpected multibyte character"); + FATAL_ERROR_IF(mbtowc_ret != 1, "Unexpected multibyte character"); m_name = name[0]; } diff --git a/src/light.cpp b/src/light.cpp index 08380a180..5dc01fcf0 100644 --- a/src/light.cpp +++ b/src/light.cpp @@ -88,7 +88,7 @@ void set_light_table(float gamma) 0 }; - gamma = rangelim(gamma, 1.1, 3.0); + gamma = rangelim(gamma, 1.0, 3.0); float brightness = brightness_step; diff --git a/src/localplayer.cpp b/src/localplayer.cpp index 69d4ec7ef..77e7a9e16 100644 --- a/src/localplayer.cpp +++ b/src/localplayer.cpp @@ -19,7 +19,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "localplayer.h" -#include "main.h" // For g_settings #include "event.h" #include "collision.h" #include "gamedef.h" @@ -43,23 +42,24 @@ LocalPlayer::LocalPlayer(IGameDef *gamedef, const char *name): last_pitch(0), last_yaw(0), last_keyPressed(0), - eye_offset_first(v3f(0,0,0)), - eye_offset_third(v3f(0,0,0)), last_animation(NO_ANIM), hotbar_image(""), hotbar_selected_image(""), light_color(255,255,255,255), m_sneak_node(32767,32767,32767), m_sneak_node_exists(false), + m_need_to_get_new_sneak_node(true), + m_sneak_node_bb_ymax(0), m_old_node_below(32767,32767,32767), m_old_node_below_type("air"), - m_need_to_get_new_sneak_node(true), m_can_jump(false), m_cao(NULL) { // Initialize hp to 0, so that no hearts will be shown if server // doesn't support health points hp = 0; + eye_offset_first = v3f(0,0,0); + eye_offset_third = v3f(0,0,0); } LocalPlayer::~LocalPlayer() @@ -67,7 +67,7 @@ LocalPlayer::~LocalPlayer() } void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, - std::list<CollisionInfo> *collision_info) + std::vector<CollisionInfo> *collision_info) { Map *map = &env->getMap(); INodeDefManager *nodemgr = m_gamedef->ndef(); @@ -87,9 +87,8 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, bool noclip = m_gamedef->checkLocalPrivilege("noclip") && g_settings->getBool("noclip"); bool free_move = noclip && fly_allowed && g_settings->getBool("free_move"); - if(free_move) - { - position += m_speed * dtime; + if (free_move) { + position += m_speed * dtime; setPosition(position); m_sneak_node_exists = false; return; @@ -172,7 +171,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, f32 d = 0.15*BS; // This should always apply, otherwise there are glitches - assert(d > pos_max_d); + sanity_check(d > pos_max_d); // Maximum distance over border for sneaking f32 sneak_max = BS*0.4; @@ -181,25 +180,26 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, If sneaking, keep in range from the last walked node and don't fall off from it */ - if(control.sneak && m_sneak_node_exists && + if (control.sneak && m_sneak_node_exists && !(fly_allowed && g_settings->getBool("free_move")) && !in_liquid && - physics_override_sneak) - { - f32 maxd = 0.5*BS + sneak_max; + physics_override_sneak) { + f32 maxd = 0.5 * 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) - { - f32 min_y = lwn_f.Y + 0.5*BS; - if(position.Y < min_y) - { - position.Y = min_y; - - if(m_speed.Y < 0) - m_speed.Y = 0; - } + if (!is_climbing) { + // Move up if necessary + f32 new_y = (lwn_f.Y - 0.5 * BS) + m_sneak_node_bb_ymax; + if (position.Y < new_y) + position.Y = new_y; + /* + Collision seems broken, since player is sinking when + 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; } } @@ -232,27 +232,28 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, player is sneaking from, if any. If the node from under the player has been removed, the player falls. */ - v3s16 current_node = floatToInt(position - v3f(0,BS/2,0), BS); - if(m_sneak_node_exists && - nodemgr->get(map->getNodeNoEx(m_old_node_below)).name == "air" && - m_old_node_below_type != "air") - { + f32 position_y_mod = 0.05 * BS; + if (m_sneak_node_bb_ymax > 0) + position_y_mod = m_sneak_node_bb_ymax - position_y_mod; + v3s16 current_node = floatToInt(position - v3f(0, position_y_mod, 0), BS); + if (m_sneak_node_exists && + nodemgr->get(map->getNodeNoEx(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->getNodeNoEx(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) - { - v3s16 pos_i_bottom = floatToInt(position - v3f(0,BS/2,0), BS); + + 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); v2f player_p2df(position.X, position.Z); - f32 min_distance_f = 100000.0*BS; + f32 min_distance_f = 100000.0 * BS; // If already seeking from some node, compare to it. /*if(m_sneak_node_exists) { @@ -300,11 +301,24 @@ void LocalPlayer::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.0 * BS * 0.9); 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); + std::vector<aabb3f> nodeboxes = n.getCollisionBoxes(nodemgr); + for (std::vector<aabb3f>::iterator it = nodeboxes.begin(); + it != nodeboxes.end(); ++it) { + aabb3f box = *it; + if (box.MaxEdge.Y > cb_max) + cb_max = box.MaxEdge.Y; + } + m_sneak_node_bb_ymax = cb_max; + } + /* If sneaking, the player's collision box can be in air, so this has to be set explicitly @@ -321,26 +335,15 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, /* Report collisions */ - bool bouncy_jump = false; + // Dont report if flying - if(collision_info && !(g_settings->getBool("free_move") && fly_allowed)) - { - for(size_t i=0; i<result.collisions.size(); i++){ + if(collision_info && !(g_settings->getBool("free_move") && fly_allowed)) { + for(size_t i=0; i<result.collisions.size(); i++) { const CollisionInfo &info = result.collisions[i]; collision_info->push_back(info); - if(info.new_speed.Y - info.old_speed.Y > 0.1*BS && - info.bouncy) - bouncy_jump = true; } } - if(bouncy_jump && control.jump){ - m_speed.Y += movement_speed_jump*BS; - touching_ground = false; - MtEvent *e = new SimpleTriggerEvent("PlayerJump"); - m_gamedef->event()->put(e); - } - if(!touching_ground_was && touching_ground){ MtEvent *e = new SimpleTriggerEvent("PlayerRegainGround"); m_gamedef->event()->put(e); @@ -374,6 +377,19 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, m_can_jump = touching_ground && !in_liquid; if(itemgroup_get(f.groups, "disable_jump")) m_can_jump = false; + // 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) { + float jumpspeed = movement_speed_jump * physics_override_jump; + if (m_speed.Y > 1) { + // Reduce boost when speed already is high + m_speed.Y += jumpspeed / (1 + (m_speed.Y / 16 )); + } else { + m_speed.Y += jumpspeed; + } + setSpeed(m_speed); + m_can_jump = false; + } } void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d) @@ -410,11 +426,12 @@ void LocalPlayer::applyControl(float dtime) // 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 && !g_settings->getBool("aux1_descends"); bool continuous_forward = g_settings->getBool("continuous_forward"); + bool always_fly_fast = g_settings->getBool("always_fly_fast"); // Whether superspeed mode is used or not bool superspeed = false; - if(g_settings->getBool("always_fly_fast") && free_move && fast_move) + if (always_fly_fast && free_move && fast_move) superspeed = true; // Old descend control @@ -472,7 +489,7 @@ void LocalPlayer::applyControl(float dtime) if(free_move) { // In free movement mode, sneak descends - if(fast_move && (control.aux1 || g_settings->getBool("always_fly_fast"))) + if (fast_move && (control.aux1 || always_fly_fast)) speedV.Y = -movement_speed_fast; else speedV.Y = -movement_speed_walk; @@ -519,11 +536,9 @@ void LocalPlayer::applyControl(float dtime) } if(control.jump) { - if(free_move) - { - if(g_settings->getBool("aux1_descends") || g_settings->getBool("always_fly_fast")) - { - if(fast_move) + if (free_move) { + if (g_settings->getBool("aux1_descends") || always_fly_fast) { + if (fast_move) speedV.Y = movement_speed_fast; else speedV.Y = movement_speed_walk; diff --git a/src/localplayer.h b/src/localplayer.h index 16b66716d..40a7f089e 100644 --- a/src/localplayer.h +++ b/src/localplayer.h @@ -48,7 +48,7 @@ public: void move(f32 dtime, Environment *env, f32 pos_max_d); void move(f32 dtime, Environment *env, f32 pos_max_d, - std::list<CollisionInfo> *collision_info); + std::vector<CollisionInfo> *collision_info); void applyControl(float dtime); @@ -62,8 +62,6 @@ public: unsigned int last_keyPressed; float camera_impact; - v3f eye_offset_first; - v3f eye_offset_third; int last_animation; float last_animation_speed; @@ -78,7 +76,7 @@ public: } void setCAO(GenericCAO* toset) { - assert( m_cao == NULL ); + assert( m_cao == NULL ); // Pre-condition m_cao = toset; } @@ -87,12 +85,15 @@ private: v3s16 m_sneak_node; // Whether the player is allowed to sneak bool m_sneak_node_exists; + // Whether recalculation of the sneak node is needed + bool m_need_to_get_new_sneak_node; + // Stores the max player uplift by m_sneak_node and is updated + // when m_need_to_get_new_sneak_node == true + f32 m_sneak_node_bb_ymax; // Node below player, used to determine whether it has been removed, // and its old type v3s16 m_old_node_below; std::string m_old_node_below_type; - // Whether recalculation of the sneak node is needed - bool m_need_to_get_new_sneak_node; bool m_can_jump; GenericCAO* m_cao; diff --git a/src/log.cpp b/src/log.cpp index b3b3f3f1b..e6d80db34 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -24,13 +24,28 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <sstream> #include <algorithm> #include "threads.h" +#include "jthread/jmutexautolock.h" #include "debug.h" #include "gettime.h" #include "porting.h" #include "config.h" +// Connection +std::ostream *dout_con_ptr = &dummyout; +std::ostream *derr_con_ptr = &verbosestream; + +// Server +std::ostream *dout_server_ptr = &infostream; +std::ostream *derr_server_ptr = &errorstream; + +#ifndef SERVER +// Client +std::ostream *dout_client_ptr = &infostream; +std::ostream *derr_client_ptr = &errorstream; +#endif + #ifdef __ANDROID__ -unsigned int android_log_level_mapping[] { +unsigned int android_log_level_mapping[] = { /* LMT_ERROR */ ANDROID_LOG_ERROR, /* LMT_ACTION */ ANDROID_LOG_WARN, /* LMT_INFO */ ANDROID_LOG_INFO, @@ -38,7 +53,7 @@ unsigned int android_log_level_mapping[] { }; #endif -std::list<ILogOutput*> log_outputs[LMT_NUM_VALUES]; +std::vector<ILogOutput*> log_outputs[LMT_NUM_VALUES]; std::map<threadid_t, std::string> log_threadnames; JMutex log_threadnamemutex; @@ -62,7 +77,7 @@ void log_add_output_all_levs(ILogOutput *out) void log_remove_output(ILogOutput *out) { for(int i=0; i<LMT_NUM_VALUES; i++){ - std::list<ILogOutput*>::iterator it = + std::vector<ILogOutput*>::iterator it = std::find(log_outputs[i].begin(), log_outputs[i].end(), out); if(it != log_outputs[i].end()) log_outputs[i].erase(it); @@ -71,33 +86,29 @@ void log_remove_output(ILogOutput *out) void log_set_lev_silence(enum LogMessageLevel lev, bool silence) { - log_threadnamemutex.Lock(); + JMutexAutoLock lock(log_threadnamemutex); - for (std::list<ILogOutput *>::iterator - it = log_outputs[lev].begin(); - it != log_outputs[lev].end(); - ++it) { + for (std::vector<ILogOutput *>::iterator it = log_outputs[lev].begin(); + it != log_outputs[lev].end(); ++it) { ILogOutput *out = *it; out->silence = silence; } - - log_threadnamemutex.Unlock(); } void log_register_thread(const std::string &name) { threadid_t id = get_current_thread_id(); - log_threadnamemutex.Lock(); + JMutexAutoLock lock(log_threadnamemutex); + log_threadnames[id] = name; - log_threadnamemutex.Unlock(); } void log_deregister_thread() { threadid_t id = get_current_thread_id(); - log_threadnamemutex.Lock(); + JMutexAutoLock lock(log_threadnamemutex); + log_threadnames.erase(id); - log_threadnamemutex.Unlock(); } static std::string get_lev_string(enum LogMessageLevel lev) @@ -119,7 +130,7 @@ static std::string get_lev_string(enum LogMessageLevel lev) void log_printline(enum LogMessageLevel lev, const std::string &text) { - log_threadnamemutex.Lock(); + JMutexAutoLock lock(log_threadnamemutex); std::string threadname = "(unknown thread)"; std::map<threadid_t, std::string>::const_iterator i; i = log_threadnames.find(get_current_thread_id()); @@ -127,9 +138,10 @@ void log_printline(enum LogMessageLevel lev, const std::string &text) threadname = i->second; std::string levelname = get_lev_string(lev); std::ostringstream os(std::ios_base::binary); - os<<getTimestamp()<<": "<<levelname<<"["<<threadname<<"]: "<<text; - for(std::list<ILogOutput*>::iterator i = log_outputs[lev].begin(); - i != log_outputs[lev].end(); i++){ + os << getTimestamp() << ": " << levelname << "["<<threadname<<"]: " << text; + + for(std::vector<ILogOutput*>::iterator i = log_outputs[lev].begin(); + i != log_outputs[lev].end(); i++) { ILogOutput *out = *i; if (out->silence) continue; @@ -138,7 +150,6 @@ void log_printline(enum LogMessageLevel lev, const std::string &text) out->printLog(os.str(), lev); out->printLog(lev, text); } - log_threadnamemutex.Unlock(); } class Logbuf : public std::streambuf @@ -81,5 +81,22 @@ extern bool log_trace_level_enabled; #define TRACESTREAM(x){ if(log_trace_level_enabled) verbosestream x; } #define TRACEDO(x){ if(log_trace_level_enabled){ x ;} } +extern std::ostream *dout_con_ptr; +extern std::ostream *derr_con_ptr; +extern std::ostream *dout_server_ptr; +extern std::ostream *derr_server_ptr; +#define dout_con (*dout_con_ptr) +#define derr_con (*derr_con_ptr) +#define dout_server (*dout_server_ptr) +#define derr_server (*derr_server_ptr) + +#ifndef SERVER +extern std::ostream *dout_client_ptr; +extern std::ostream *derr_client_ptr; +#define dout_client (*dout_client_ptr) +#define derr_client (*derr_client_ptr) + +#endif + #endif diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt index 36e271889..119dd6302 100644 --- a/src/lua/CMakeLists.txt +++ b/src/lua/CMakeLists.txt @@ -1,10 +1,12 @@ -# -# Lua 5.1.x -# cmake_minimum_required(VERSION 2.4 FATAL_ERROR) project(lua C) +set(LUA_VERSION_MAJOR 5) +set(LUA_VERSION_MINOR 1) +set(LUA_VERSION_PATCH 4) +set(LUA_VERSION "${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}.${LUA_VERSION_PATCH}") + set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(COMMON_CFLAGS) @@ -16,9 +18,7 @@ if(APPLE) set(DEFAULT_DLOPEN ON) # use this on Mac OS X 10.3- option(LUA_USE_MACOSX "Mac OS X 10.3-" OFF) -elseif(CYGWIN) - set(DEFAULT_POSIX TRUE) -elseif(UNIX) +elseif(UNIX OR CYGWIN) set(DEFAULT_POSIX TRUE) elseif(WIN32) set(LUA_WIN TRUE) @@ -32,54 +32,18 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(DEFAULT_DLOPEN ON) endif() -if(WIN32) - #set(BUILD_STATIC OFF) - set(BUILD_STATIC ON) -else() - #option(BUILD_STATIC "build static library" ON) - set(BUILD_STATIC ON) -endif() - -if(DEFAULT_DLOPEN) - option(LUA_USE_DLOPEN "Enable dlopen support." ON) -else() - option(LUA_USE_DLOPEN "Enable dlopen support." OFF) -endif() -mark_as_advanced(LUA_USE_DLOPEN) - -if(DEFAULT_POSIX) -else() -endif() - -if(DEFAULT_ANSI) - option(LUA_ANSI "Disable non-ansi features." ON) -else() - option(LUA_ANSI "Disable non-ansi features." OFF) -endif() -mark_as_advanced(LUA_ANSI) - -# -# Lua version -# -set(LUA_VERSION_MAJOR 5) -set(LUA_VERSION_MINOR 1) -set(LUA_VERSION_PATCH 4) -set(LUA_VERSION - "${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}.${LUA_VERSION_PATCH}") -set(LUA_SOVERSION - "${LUA_VERSION_MAJOR}") - -# -# libs & cflags -# -set(COMMON_LDFLAGS "${COMMON_LDFLAGS}") - # For "Mac OS X 10.3-" if(LUA_USE_MACOSX) set(COMMON_CFLAGS "${COMMON_CFLAGS} -DLUA_USE_MACOSX") set(LUA_USE_DLOPEN FALSE) endif(LUA_USE_MACOSX) +option(LUA_USE_DLOPEN "Enable dlopen support." ${DEFAULT_DLOPEN}) +mark_as_advanced(LUA_USE_DLOPEN) + +option(LUA_ANSI "Disable non-ANSI features." ${DEFAULT_ANSI}) +mark_as_advanced(LUA_ANSI) + if(LUA_USE_DLOPEN) set(COMMON_CFLAGS "${COMMON_CFLAGS} -DLUA_USE_DLOPEN") if(NOT APPLE) @@ -87,18 +51,19 @@ if(LUA_USE_DLOPEN) endif(NOT APPLE) endif(LUA_USE_DLOPEN) +if(DEFAULT_POSIX) + set(COMMON_CFLAGS "${COMMON_CFLAGS} -DLUA_USE_POSIX") +endif(DEFAULT_POSIX) + if(LUA_ANSI) set(COMMON_CFLAGS "${COMMON_CFLAGS} -DLUA_ANSI") endif(LUA_ANSI) -# # COMMON_CFLAGS has no effect without this line -# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_CFLAGS}") -# -# standard flags to use for each build type. -# + +# Standard flags to use for each build type. if(CMAKE_COMPILER_IS_GNUCC) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -Wall -Wextra -Wshadow -W -pedantic -std=gnu99") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2") @@ -107,8 +72,6 @@ if(CMAKE_COMPILER_IS_GNUCC) set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_WITHDEBINFO} -O2 -g") endif(CMAKE_COMPILER_IS_GNUCC) -# -# sub-folders -# -ADD_SUBDIRECTORY(src build) + +add_subdirectory(src build) diff --git a/src/lua/src/CMakeLists.txt b/src/lua/src/CMakeLists.txt index 8fdc7e58b..8f6cc1213 100644 --- a/src/lua/src/CMakeLists.txt +++ b/src/lua/src/CMakeLists.txt @@ -39,11 +39,9 @@ set(LUA_LIB_HEADERS ) include_directories(${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR}) + ${CMAKE_CURRENT_BINARY_DIR}) -# # Lua library. -# add_library(lua STATIC ${LUA_CORE_SRC}) target_link_libraries(lua ${LIBS}) set(LUA_STATIC_LIB lua) @@ -51,13 +49,6 @@ set(LUA_LIBS lua) set_target_properties(${LUA_LIBS} PROPERTIES VERSION ${LUA_VERSION} - SOVERSION ${LUA_SOVERSION} CLEAN_DIRECT_OUTPUT 1 ) -# Install library -#install(TARGETS ${LUA_LIBS} -# RUNTIME DESTINATION bin -# LIBRARY DESTINATION lib -# ARCHIVE DESTINATION lib) - diff --git a/src/lua/src/lauxlib.c b/src/lua/src/lauxlib.c index 10f14e2c0..be41ebcd3 100644 --- a/src/lua/src/lauxlib.c +++ b/src/lua/src/lauxlib.c @@ -574,7 +574,8 @@ LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) { lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */ if (lf.f == NULL) return errfile(L, "reopen", fnameindex); /* skip eventual `#!...' */ - while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) ; + while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) + {} lf.extraline = 0; } ungetc(c, lf.f); diff --git a/src/main.cpp b/src/main.cpp index bbf88e80d..1d73b4025 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,19 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifdef NDEBUG - /*#ifdef _WIN32 - #pragma message ("Disabling unit tests") - #else - #warning "Disabling unit tests" - #endif*/ - // Disable unit tests - #define ENABLE_TESTS 0 -#else - // Enable unit tests - #define ENABLE_TESTS 1 -#endif - #ifdef _MSC_VER #ifndef SERVER // Dedicated server isn't linked with Irrlicht #pragma comment(lib, "Irrlicht.lib") @@ -42,101 +29,39 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlicht.h" // createDevice -#include "main.h" #include "mainmenumanager.h" -#include <iostream> -#include <fstream> -#include <locale.h> #include "irrlichttypes_extrabloated.h" #include "debug.h" -#include "test.h" -#include "clouds.h" +#include "unittest/test.h" #include "server.h" -#include "constants.h" -#include "porting.h" -#include "gettime.h" #include "filesys.h" -#include "config.h" #include "version.h" #include "guiMainMenu.h" #include "game.h" -#include "keycode.h" -#include "tile.h" -#include "chat.h" #include "defaultsettings.h" #include "gettext.h" -#include "settings.h" -#include "profiler.h" #include "log.h" -#include "mods.h" -#include "util/string.h" -#include "subgame.h" #include "quicktune.h" -#include "serverlist.h" #include "httpfetch.h" #include "guiEngine.h" +#include "map.h" #include "mapsector.h" -#include "player.h" #include "fontengine.h" - -#include "database-sqlite3.h" -#ifdef USE_LEVELDB -#include "database-leveldb.h" -#endif - -#if USE_REDIS -#include "database-redis.h" +#include "gameparams.h" +#include "database.h" +#ifndef SERVER +#include "client/clientlauncher.h" #endif #ifdef HAVE_TOUCHSCREENGUI #include "touchscreengui.h" #endif -/* - Settings. - These are loaded from the config file. -*/ -static Settings main_settings; -Settings *g_settings = &main_settings; -std::string g_settings_path; - -// Global profiler -Profiler main_profiler; -Profiler *g_profiler = &main_profiler; - -// Menu clouds are created later -Clouds *g_menuclouds = 0; -irr::scene::ISceneManager *g_menucloudsmgr = 0; - -/* - Debug streams -*/ - -// Connection -std::ostream *dout_con_ptr = &dummyout; -std::ostream *derr_con_ptr = &verbosestream; - -// Server -std::ostream *dout_server_ptr = &infostream; -std::ostream *derr_server_ptr = &errorstream; - -// Client -std::ostream *dout_client_ptr = &infostream; -std::ostream *derr_client_ptr = &errorstream; - #define DEBUGFILE "debug.txt" #define DEFAULT_SERVER_PORT 30000 typedef std::map<std::string, ValueSpec> OptionList; -struct GameParams { - u16 socket_port; - std::string world_path; - SubgameSpec game_spec; - bool is_dedicated_server; - int log_level; -}; - /********************************************************************** * Private functions **********************************************************************/ @@ -174,36 +99,10 @@ static bool get_game_from_cmdline(GameParams *game_params, const Settings &cmd_a static bool determine_subgame(GameParams *game_params); static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args); -static bool migrate_database(const GameParams &game_params, const Settings &cmd_args, - Server *server); - -#ifndef SERVER -static bool print_video_modes(); -static void speed_tests(); -#endif +static bool migrate_database(const GameParams &game_params, const Settings &cmd_args); /**********************************************************************/ -#ifndef SERVER -/* - Random stuff -*/ - -/* mainmenumanager.h */ - -gui::IGUIEnvironment* guienv = NULL; -gui::IGUIStaticText *guiroot = NULL; -MainMenuManager g_menumgr; - -bool noMenuActive() -{ - return (g_menumgr.menuCount() == 0); -} - -// Passed to menus to allow disconnecting and exiting -MainGameCallback *g_gamecallback = NULL; -#endif - /* gettime.h implementation */ @@ -221,61 +120,6 @@ u32 getTime(TimePrecision prec) return porting::getTime(prec); } -#else - -// A small helper class -class TimeGetter -{ -public: - virtual u32 getTime(TimePrecision prec) = 0; -}; - -// A precise irrlicht one -class IrrlichtTimeGetter: public TimeGetter -{ -public: - IrrlichtTimeGetter(IrrlichtDevice *device): - m_device(device) - {} - u32 getTime(TimePrecision prec) - { - if (prec == PRECISION_MILLI) { - if (m_device == NULL) - return 0; - return m_device->getTimer()->getRealTime(); - } else { - return porting::getTime(prec); - } - } -private: - IrrlichtDevice *m_device; -}; -// Not so precise one which works without irrlicht -class SimpleTimeGetter: public TimeGetter -{ -public: - u32 getTime(TimePrecision prec) - { - return porting::getTime(prec); - } -}; - -// A pointer to a global instance of the time getter -// TODO: why? -TimeGetter *g_timegetter = NULL; - -u32 getTimeMs() -{ - if (g_timegetter == NULL) - return 0; - return g_timegetter->getTime(PRECISION_MILLI); -} - -u32 getTime(TimePrecision prec) { - if (g_timegetter == NULL) - return 0; - return g_timegetter->getTime(prec); -} #endif class StderrLogOutput: public ILogOutput @@ -298,482 +142,6 @@ public: } } main_dstream_no_stderr_log_out; -#ifndef SERVER - -/* - Event handler for Irrlicht - - NOTE: Everything possible should be moved out from here, - probably to InputHandler and the_game -*/ - -class MyEventReceiver : public IEventReceiver -{ -public: - // This is the one method that we have to implement - virtual bool OnEvent(const SEvent& event) - { - /* - React to nothing here if a menu is active - */ - if (noMenuActive() == false) { -#ifdef HAVE_TOUCHSCREENGUI - if (m_touchscreengui != 0) { - m_touchscreengui->Toggle(false); - } -#endif - return g_menumgr.preprocessEvent(event); - } - - // Remember whether each key is down or up - if (event.EventType == irr::EET_KEY_INPUT_EVENT) { - if (event.KeyInput.PressedDown) { - keyIsDown.set(event.KeyInput); - keyWasDown.set(event.KeyInput); - } else { - keyIsDown.unset(event.KeyInput); - } - } - -#ifdef HAVE_TOUCHSCREENGUI - // case of touchscreengui we have to handle different events - if ((m_touchscreengui != 0) && - (event.EventType == irr::EET_TOUCH_INPUT_EVENT)) { - m_touchscreengui->translateEvent(event); - return true; - } -#endif - // handle mouse events - if (event.EventType == irr::EET_MOUSE_INPUT_EVENT) { - if (noMenuActive() == false) { - left_active = false; - middle_active = false; - right_active = false; - } else { - left_active = event.MouseInput.isLeftPressed(); - middle_active = event.MouseInput.isMiddlePressed(); - right_active = event.MouseInput.isRightPressed(); - - if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { - leftclicked = true; - } - if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN) { - rightclicked = true; - } - if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { - leftreleased = true; - } - if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP) { - rightreleased = true; - } - if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) { - mouse_wheel += event.MouseInput.Wheel; - } - } - } - if (event.EventType == irr::EET_LOG_TEXT_EVENT) { - dstream << std::string("Irrlicht log: ") + std::string(event.LogEvent.Text) - << std::endl; - return true; - } - /* always return false in order to continue processing events */ - return false; - } - - bool IsKeyDown(const KeyPress &keyCode) const - { - return keyIsDown[keyCode]; - } - - // Checks whether a key was down and resets the state - bool WasKeyDown(const KeyPress &keyCode) - { - bool b = keyWasDown[keyCode]; - if (b) - keyWasDown.unset(keyCode); - return b; - } - - s32 getMouseWheel() - { - s32 a = mouse_wheel; - mouse_wheel = 0; - return a; - } - - void clearInput() - { - keyIsDown.clear(); - keyWasDown.clear(); - - leftclicked = false; - rightclicked = false; - leftreleased = false; - rightreleased = false; - - left_active = false; - middle_active = false; - right_active = false; - - mouse_wheel = 0; - } - - MyEventReceiver() - { - clearInput(); -#ifdef HAVE_TOUCHSCREENGUI - m_touchscreengui = NULL; -#endif - } - - bool leftclicked; - bool rightclicked; - bool leftreleased; - bool rightreleased; - - bool left_active; - bool middle_active; - bool right_active; - - s32 mouse_wheel; - -#ifdef HAVE_TOUCHSCREENGUI - TouchScreenGUI* m_touchscreengui; -#endif - -private: - // The current state of keys - KeyList keyIsDown; - // Whether a key has been pressed or not - KeyList keyWasDown; -}; - -/* - Separated input handler -*/ - -class RealInputHandler : public InputHandler -{ -public: - RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver): - m_device(device), - m_receiver(receiver), - m_mousepos(0,0) - { - } - virtual bool isKeyDown(const KeyPress &keyCode) - { - return m_receiver->IsKeyDown(keyCode); - } - virtual bool wasKeyDown(const KeyPress &keyCode) - { - return m_receiver->WasKeyDown(keyCode); - } - virtual v2s32 getMousePos() - { - if (m_device->getCursorControl()) { - return m_device->getCursorControl()->getPosition(); - } - else { - return m_mousepos; - } - } - virtual void setMousePos(s32 x, s32 y) - { - if (m_device->getCursorControl()) { - m_device->getCursorControl()->setPosition(x, y); - } - else { - m_mousepos = v2s32(x,y); - } - } - - virtual bool getLeftState() - { - return m_receiver->left_active; - } - virtual bool getRightState() - { - return m_receiver->right_active; - } - - virtual bool getLeftClicked() - { - return m_receiver->leftclicked; - } - virtual bool getRightClicked() - { - return m_receiver->rightclicked; - } - virtual void resetLeftClicked() - { - m_receiver->leftclicked = false; - } - virtual void resetRightClicked() - { - m_receiver->rightclicked = false; - } - - virtual bool getLeftReleased() - { - return m_receiver->leftreleased; - } - virtual bool getRightReleased() - { - return m_receiver->rightreleased; - } - virtual void resetLeftReleased() - { - m_receiver->leftreleased = false; - } - virtual void resetRightReleased() - { - m_receiver->rightreleased = false; - } - - virtual s32 getMouseWheel() - { - return m_receiver->getMouseWheel(); - } - - void clear() - { - m_receiver->clearInput(); - } -private: - IrrlichtDevice *m_device; - MyEventReceiver *m_receiver; - v2s32 m_mousepos; -}; - -class RandomInputHandler : public InputHandler -{ -public: - RandomInputHandler() - { - leftdown = false; - rightdown = false; - leftclicked = false; - rightclicked = false; - leftreleased = false; - rightreleased = false; - keydown.clear(); - } - virtual bool isKeyDown(const KeyPress &keyCode) - { - return keydown[keyCode]; - } - virtual bool wasKeyDown(const KeyPress &keyCode) - { - return false; - } - virtual v2s32 getMousePos() - { - return mousepos; - } - virtual void setMousePos(s32 x, s32 y) - { - mousepos = v2s32(x, y); - } - - virtual bool getLeftState() - { - return leftdown; - } - virtual bool getRightState() - { - return rightdown; - } - - virtual bool getLeftClicked() - { - return leftclicked; - } - virtual bool getRightClicked() - { - return rightclicked; - } - virtual void resetLeftClicked() - { - leftclicked = false; - } - virtual void resetRightClicked() - { - rightclicked = false; - } - - virtual bool getLeftReleased() - { - return leftreleased; - } - virtual bool getRightReleased() - { - return rightreleased; - } - virtual void resetLeftReleased() - { - leftreleased = false; - } - virtual void resetRightReleased() - { - rightreleased = false; - } - - virtual s32 getMouseWheel() - { - return 0; - } - - virtual void step(float dtime) - { - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 40); - keydown.toggle(getKeySetting("keymap_jump")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 40); - keydown.toggle(getKeySetting("keymap_special1")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 40); - keydown.toggle(getKeySetting("keymap_forward")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 40); - keydown.toggle(getKeySetting("keymap_left")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 20); - mousespeed = v2s32(Rand(-20, 20), Rand(-15, 20)); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 30); - leftdown = !leftdown; - if (leftdown) - leftclicked = true; - if (!leftdown) - leftreleased = true; - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if (counter1 < 0.0) { - counter1 = 0.1 * Rand(1, 15); - rightdown = !rightdown; - if (rightdown) - rightclicked = true; - if (!rightdown) - rightreleased = true; - } - } - mousepos += mousespeed; - } - - s32 Rand(s32 min, s32 max) - { - return (myrand()%(max-min+1))+min; - } -private: - KeyList keydown; - v2s32 mousepos; - v2s32 mousespeed; - bool leftdown; - bool rightdown; - bool leftclicked; - bool rightclicked; - bool leftreleased; - bool rightreleased; -}; - - -class ClientLauncher -{ -public: - ClientLauncher() : - list_video_modes(false), - skip_main_menu(false), - use_freetype(false), - random_input(false), - address(""), - playername(""), - password(""), - device(NULL), - input(NULL), - receiver(NULL), - skin(NULL), - font(NULL), - simple_singleplayer_mode(false), - current_playername("inv£lid"), - current_password(""), - current_address("does-not-exist"), - current_port(0) - {} - - ~ClientLauncher(); - - bool run(GameParams &game_params, const Settings &cmd_args); - -protected: - void init_args(GameParams &game_params, const Settings &cmd_args); - bool init_engine(int log_level); - - bool launch_game(std::wstring *error_message, GameParams &game_params, - const Settings &cmd_args); - - void main_menu(MainMenuData *menudata); - bool create_engine_device(int log_level); - - bool list_video_modes; - bool skip_main_menu; - bool use_freetype; - bool random_input; - std::string address; - std::string playername; - std::string password; - IrrlichtDevice *device; - InputHandler *input; - MyEventReceiver *receiver; - gui::IGUISkin *skin; - gui::IGUIFont *font; - scene::ISceneManager *smgr; - SubgameSpec gamespec; - WorldSpec worldspec; - bool simple_singleplayer_mode; - - // These are set up based on the menu and other things - // TODO: Are these required since there's already playername, password, etc - std::string current_playername; - std::string current_password; - std::string current_address; - int current_port; -}; - -#endif // !SERVER - static OptionList allowed_options; int main(int argc, char *argv[]) @@ -836,9 +204,9 @@ int main(int argc, char *argv[]) #ifndef __ANDROID__ // Run unit tests - if ((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false) - || cmd_args.getFlag("enable-unittests") == true) { + if (cmd_args.getFlag("run-unittests")) { run_tests(); + return 0; } #endif @@ -851,7 +219,7 @@ int main(int argc, char *argv[]) if (!game_configure(&game_params, cmd_args)) return 1; - assert(game_params.world_path != ""); + sanity_check(game_params.world_path != ""); infostream << "Using commanded world path [" << game_params.world_path << "]" << std::endl; @@ -909,10 +277,8 @@ static void set_allowed_options(OptionList *allowed_options) _("Load configuration from specified file")))); allowed_options->insert(std::make_pair("port", ValueSpec(VALUETYPE_STRING, _("Set network port (UDP)")))); - allowed_options->insert(std::make_pair("disable-unittests", ValueSpec(VALUETYPE_FLAG, - _("Disable unit tests")))); - allowed_options->insert(std::make_pair("enable-unittests", ValueSpec(VALUETYPE_FLAG, - _("Enable unit tests")))); + allowed_options->insert(std::make_pair("run-unittests", ValueSpec(VALUETYPE_FLAG, + _("Run the unit tests and exit")))); 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, @@ -980,13 +346,11 @@ static void print_allowed_options(const OptionList &allowed_options) static void print_version() { -#ifdef SERVER - dstream << "minetestserver " << minetest_version_hash << std::endl; -#else - dstream << "Minetest " << minetest_version_hash << std::endl; + dstream << PROJECT_NAME_C " " << g_version_hash << std::endl; +#ifndef SERVER dstream << "Using Irrlicht " << IRRLICHT_SDK_VERSION << std::endl; #endif - dstream << "Build info: " << minetest_build_info << std::endl; + dstream << "Build info: " << g_build_info << std::endl; } static void list_game_ids() @@ -1123,13 +487,13 @@ static void startup_message() infostream << PROJECT_NAME << " " << _("with") << " SER_FMT_VER_HIGHEST_READ=" << (int)SER_FMT_VER_HIGHEST_READ << ", " - << minetest_build_info << std::endl; + << g_build_info << std::endl; } static bool read_config_file(const Settings &cmd_args) { // Path of configuration file in use - assert(g_settings_path == ""); // Sanity check + sanity_check(g_settings_path == ""); // Sanity check if (cmd_args.exists("config")) { bool r = g_settings->readConfigFile(cmd_args.get("config").c_str()); @@ -1328,7 +692,7 @@ static bool auto_select_world(GameParams *game_params) << world_path << "]" << std::endl; } - assert(world_path != ""); + assert(world_path != ""); // Post-condition game_params->world_path = world_path; return true; } @@ -1384,7 +748,7 @@ static bool determine_subgame(GameParams *game_params) { SubgameSpec gamespec; - assert(game_params->world_path != ""); // pre-condition + assert(game_params->world_path != ""); // Pre-condition verbosestream << _("Determining gameid/gamespec") << std::endl; // If world doesn't exist @@ -1466,14 +830,14 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & return false; } + // Database migration + if (cmd_args.exists("migrate")) + return migrate_database(game_params, cmd_args); + // Create server Server server(game_params.world_path, game_params.game_spec, false, bind_addr.isIPv6()); - // Database migration - if (cmd_args.exists("migrate")) - return migrate_database(game_params, cmd_args, &server); - server.start(bind_addr); // Run server @@ -1483,79 +847,63 @@ static bool run_dedicated_server(const GameParams &game_params, const Settings & return true; } -static bool migrate_database(const GameParams &game_params, const Settings &cmd_args, - Server *server) +static bool migrate_database(const GameParams &game_params, const Settings &cmd_args) { + std::string migrate_to = cmd_args.get("migrate"); Settings world_mt; - bool success = world_mt.readConfigFile((game_params.world_path - + DIR_DELIM + "world.mt").c_str()); - if (!success) { - errorstream << "Cannot read world.mt" << std::endl; + std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; + if (!world_mt.readConfigFile(world_mt_path.c_str())) { + errorstream << "Cannot read world.mt!" << std::endl; return false; } - if (!world_mt.exists("backend")) { - errorstream << "Please specify your current backend in world.mt file:" - << std::endl << " backend = {sqlite3|leveldb|redis|dummy}" - << std::endl; + errorstream << "Please specify your current backend in world.mt:" + << std::endl + << " backend = {sqlite3|leveldb|redis|dummy}" + << std::endl; return false; } - std::string backend = world_mt.get("backend"); - Database *new_db; - std::string migrate_to = cmd_args.get("migrate"); - if (backend == migrate_to) { - errorstream << "Cannot migrate: new backend is same as the old one" - << std::endl; + errorstream << "Cannot migrate: new backend is same" + << " as the old one" << std::endl; return false; } + Database *old_db = ServerMap::createDatabase(backend, game_params.world_path, world_mt), + *new_db = ServerMap::createDatabase(migrate_to, game_params.world_path, world_mt); - if (migrate_to == "sqlite3") - new_db = new Database_SQLite3(&(ServerMap&)server->getMap(), - game_params.world_path); -#if USE_LEVELDB - else if (migrate_to == "leveldb") - new_db = new Database_LevelDB(&(ServerMap&)server->getMap(), - game_params.world_path); -#endif -#if USE_REDIS - else if (migrate_to == "redis") - new_db = new Database_Redis(&(ServerMap&)server->getMap(), - game_params.world_path); -#endif - else { - errorstream << "Migration to " << migrate_to << " is not supported" - << std::endl; - return false; - } + u32 count = 0; + time_t last_update_time = 0; + bool &kill = *porting::signal_handler_killstatus(); - std::list<v3s16> blocks; - ServerMap &old_map = ((ServerMap&)server->getMap()); - old_map.listAllLoadableBlocks(blocks); - int count = 0; + std::vector<v3s16> blocks; + old_db->listAllLoadableBlocks(blocks); new_db->beginSave(); - for (std::list<v3s16>::iterator i = blocks.begin(); i != blocks.end(); i++) { - MapBlock *block = old_map.loadBlock(*i); - if (!block) { - errorstream << "Failed to load block " << PP(*i) << ", skipping it."; + for (std::vector<v3s16>::const_iterator it = blocks.begin(); it != blocks.end(); ++it) { + if (kill) return false; + + const std::string &data = old_db->loadBlock(*it); + if (!data.empty()) { + new_db->saveBlock(*it, data); } else { - old_map.saveBlock(block, new_db); - MapSector *sector = old_map.getSectorNoGenerate(v2s16(i->X, i->Z)); - sector->deleteBlock(block); + errorstream << "Failed to load block " << PP(*it) << ", skipping it." << std::endl; + } + if (++count % 0xFF == 0 && time(NULL) - last_update_time >= 1) { + std::cerr << " Migrated " << count << " blocks, " + << (100.0 * count / blocks.size()) << "% completed.\r"; + new_db->endSave(); + new_db->beginSave(); + last_update_time = time(NULL); } - ++count; - if (count % 500 == 0) - actionstream << "Migrated " << count << " blocks " - << (100.0 * count / blocks.size()) << "% completed" << std::endl; } + std::cerr << std::endl; new_db->endSave(); + delete old_db; delete new_db; actionstream << "Successfully migrated " << count << " blocks" << std::endl; world_mt.set("backend", migrate_to); - if (!world_mt.updateConfigFile( - (game_params.world_path+ DIR_DELIM + "world.mt").c_str())) + if (!world_mt.updateConfigFile(world_mt_path.c_str())) errorstream << "Failed to update world.mt!" << std::endl; else actionstream << "world.mt updated" << std::endl; @@ -1563,671 +911,3 @@ static bool migrate_database(const GameParams &game_params, const Settings &cmd_ return true; } - -/***************************************************************************** - * Client - *****************************************************************************/ -#ifndef SERVER - -ClientLauncher::~ClientLauncher() -{ - if (receiver) - delete receiver; - - if (input) - delete input; - - if (g_fontengine) - delete g_fontengine; - - if (device) - device->drop(); -} - - -bool ClientLauncher::run(GameParams &game_params, const Settings &cmd_args) -{ - init_args(game_params, cmd_args); - - // List video modes if requested - if (list_video_modes) - return print_video_modes(); - - if (!init_engine(game_params.log_level)) { - errorstream << "Could not initialize game engine." << std::endl; - return false; - } - - // Speed tests (done after irrlicht is loaded to get timer) - if (cmd_args.getFlag("speedtests")) { - dstream << "Running speed tests" << std::endl; - speed_tests(); - return true; - } - - video::IVideoDriver *video_driver = device->getVideoDriver(); - if (video_driver == NULL) { - errorstream << "Could not initialize video driver." << std::endl; - return false; - } - - porting::setXorgClassHint(video_driver->getExposedVideoData(), "Minetest"); - - /* - This changes the minimum allowed number of vertices in a VBO. - Default is 500. - */ - //driver->setMinHardwareBufferVertexCount(50); - - // Create time getter - g_timegetter = new IrrlichtTimeGetter(device); - - // Create game callback for menus - g_gamecallback = new MainGameCallback(device); - - device->setResizable(true); - - if (random_input) - input = new RandomInputHandler(); - else - input = new RealInputHandler(device, receiver); - - smgr = device->getSceneManager(); - - guienv = device->getGUIEnvironment(); - skin = guienv->getSkin(); - skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255, 255, 255, 255)); - skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255, 0, 0, 0)); - skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255, 0, 0, 0)); - skin->setColor(gui::EGDC_HIGH_LIGHT, video::SColor(255, 70, 100, 50)); - skin->setColor(gui::EGDC_HIGH_LIGHT_TEXT, video::SColor(255, 255, 255, 255)); - - g_fontengine = new FontEngine(g_settings, guienv); - assert(g_fontengine != NULL); - -#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2 - // Irrlicht 1.8 input colours - skin->setColor(gui::EGDC_EDITABLE, video::SColor(255, 128, 128, 128)); - skin->setColor(gui::EGDC_FOCUSED_EDITABLE, video::SColor(255, 96, 134, 49)); -#endif - - // Create the menu clouds - if (!g_menucloudsmgr) - g_menucloudsmgr = smgr->createNewSceneManager(); - if (!g_menuclouds) - g_menuclouds = new Clouds(g_menucloudsmgr->getRootSceneNode(), - g_menucloudsmgr, -1, rand(), 100); - g_menuclouds->update(v2f(0, 0), video::SColor(255, 200, 200, 255)); - scene::ICameraSceneNode* camera; - camera = g_menucloudsmgr->addCameraSceneNode(0, - v3f(0, 0, 0), v3f(0, 60, 100)); - camera->setFarValue(10000); - - /* - GUI stuff - */ - - ChatBackend chat_backend; - - // If an error occurs, this is set to something by menu(). - // It is then displayed before the menu shows on the next call to menu() - std::wstring error_message = L""; - - bool first_loop = true; - - /* - Menu-game loop - */ - bool retval = true; - bool *kill = porting::signal_handler_killstatus(); - - while (device->run() && !*kill && !g_gamecallback->shutdown_requested) - { - // Set the window caption - const wchar_t *text = wgettext("Main Menu"); - device->setWindowCaption((std::wstring(L"Minetest [") + text + L"]").c_str()); - delete[] text; - - try { // This is used for catching disconnects - - guienv->clear(); - - /* - We need some kind of a root node to be able to add - custom gui elements directly on the screen. - Otherwise they won't be automatically drawn. - */ - guiroot = guienv->addStaticText(L"", core::rect<s32>(0, 0, 10000, 10000)); - - bool game_has_run = launch_game(&error_message, game_params, cmd_args); - - // If skip_main_menu, we only want to startup once - if (skip_main_menu && !first_loop) - break; - - first_loop = false; - - if (!game_has_run) { - if (skip_main_menu) - break; - else - continue; - } - - // Break out of menu-game loop to shut down cleanly - if (!device->run() || *kill) { - if (g_settings_path != "") - g_settings->updateConfigFile(g_settings_path.c_str()); - break; - } - - if (current_playername.length() > PLAYERNAME_SIZE-1) { - error_message = wgettext("Player name too long."); - playername = current_playername.substr(0, PLAYERNAME_SIZE-1); - g_settings->set("name", playername); - continue; - } - - device->getVideoDriver()->setTextureCreationFlag( - video::ETCF_CREATE_MIP_MAPS, g_settings->getBool("mip_map")); - -#ifdef HAVE_TOUCHSCREENGUI - receiver->m_touchscreengui = new TouchScreenGUI(device, receiver); - g_touchscreengui = receiver->m_touchscreengui; -#endif - the_game( - kill, - random_input, - input, - device, - worldspec.path, - current_playername, - current_password, - current_address, - current_port, - error_message, - chat_backend, - gamespec, - simple_singleplayer_mode - ); - smgr->clear(); - -#ifdef HAVE_TOUCHSCREENGUI - delete g_touchscreengui; - g_touchscreengui = NULL; - receiver->m_touchscreengui = NULL; -#endif - - } //try - catch (con::PeerNotFoundException &e) { - error_message = wgettext("Connection error (timed out?)"); - errorstream << wide_to_narrow(error_message) << std::endl; - } - -#ifdef NDEBUG - catch (std::exception &e) { - std::string narrow_message = "Some exception: \""; - narrow_message += e.what(); - narrow_message += "\""; - errorstream << narrow_message << std::endl; - error_message = narrow_to_wide(narrow_message); - } -#endif - - // If no main menu, show error and exit - if (skip_main_menu) { - if (error_message != L"") { - verbosestream << "error_message = " - << wide_to_narrow(error_message) << std::endl; - retval = false; - } - break; - } - } // Menu-game loop - - g_menuclouds->drop(); - g_menucloudsmgr->drop(); - - return retval; -} - -void ClientLauncher::init_args(GameParams &game_params, const Settings &cmd_args) -{ - - skip_main_menu = cmd_args.getFlag("go"); - - // FIXME: This is confusing (but correct) - - /* If world_path is set then override it unless skipping the main menu using - * the --go command line param. Else, give preference to the address - * supplied on the command line - */ - address = g_settings->get("address"); - if (game_params.world_path != "" && !skip_main_menu) - address = ""; - else if (cmd_args.exists("address")) - address = cmd_args.get("address"); - - playername = g_settings->get("name"); - if (cmd_args.exists("name")) - playername = cmd_args.get("name"); - - list_video_modes = cmd_args.getFlag("videomodes"); - - use_freetype = g_settings->getBool("freetype"); - - random_input = g_settings->getBool("random_input") - || cmd_args.getFlag("random-input"); -} - -bool ClientLauncher::init_engine(int log_level) -{ - receiver = new MyEventReceiver(); - create_engine_device(log_level); - return device != NULL; -} - -bool ClientLauncher::launch_game(std::wstring *error_message, - GameParams &game_params, const Settings &cmd_args) -{ - // Initialize menu data - MainMenuData menudata; - menudata.address = address; - menudata.name = playername; - menudata.port = itos(game_params.socket_port); - menudata.errormessage = wide_to_narrow(*error_message); - - *error_message = L""; - - if (cmd_args.exists("password")) - menudata.password = cmd_args.get("password"); - - menudata.enable_public = g_settings->getBool("server_announce"); - - // If a world was commanded, append and select it - if (game_params.world_path != "") { - worldspec.gameid = getWorldGameId(game_params.world_path, true); - worldspec.name = _("[--world parameter]"); - - if (worldspec.gameid == "") { // Create new - worldspec.gameid = g_settings->get("default_game"); - worldspec.name += " [new]"; - } - worldspec.path = game_params.world_path; - } - - /* Show the GUI menu - */ - if (!skip_main_menu) { - main_menu(&menudata); - - // Skip further loading if there was an exit signal. - if (*porting::signal_handler_killstatus()) - return false; - - address = menudata.address; - int newport = stoi(menudata.port); - if (newport != 0) - game_params.socket_port = newport; - - simple_singleplayer_mode = menudata.simple_singleplayer_mode; - - std::vector<WorldSpec> worldspecs = getAvailableWorlds(); - - if (menudata.selected_world >= 0 - && menudata.selected_world < (int)worldspecs.size()) { - g_settings->set("selected_world_path", - worldspecs[menudata.selected_world].path); - worldspec = worldspecs[menudata.selected_world]; - } - } - - if (menudata.errormessage != "") { - /* The calling function will pass this back into this function upon the - * next iteration (if any) causing it to be displayed by the GUI - */ - *error_message = narrow_to_wide(menudata.errormessage); - return false; - } - - if (menudata.name == "") - menudata.name = std::string("Guest") + itos(myrand_range(1000, 9999)); - else - playername = menudata.name; - - password = translatePassword(playername, narrow_to_wide(menudata.password)); - - g_settings->set("name", playername); - - current_playername = playername; - current_password = password; - current_address = address; - current_port = game_params.socket_port; - - // If using simple singleplayer mode, override - if (simple_singleplayer_mode) { - assert(skip_main_menu == false); - current_playername = "singleplayer"; - current_password = ""; - current_address = ""; - current_port = myrand_range(49152, 65535); - } else if (address != "") { - ServerListSpec server; - server["name"] = menudata.servername; - server["address"] = menudata.address; - server["port"] = menudata.port; - server["description"] = menudata.serverdescription; - ServerList::insert(server); - } - - infostream << "Selected world: " << worldspec.name - << " [" << worldspec.path << "]" << std::endl; - - if (current_address == "") { // If local game - if (worldspec.path == "") { - *error_message = wgettext("No world selected and no address " - "provided. Nothing to do."); - errorstream << wide_to_narrow(*error_message) << std::endl; - return false; - } - - if (!fs::PathExists(worldspec.path)) { - *error_message = wgettext("Provided world path doesn't exist: ") - + narrow_to_wide(worldspec.path); - errorstream << wide_to_narrow(*error_message) << std::endl; - return false; - } - - // Load gamespec for required game - gamespec = findWorldSubgame(worldspec.path); - if (!gamespec.isValid() && !game_params.game_spec.isValid()) { - *error_message = wgettext("Could not find or load game \"") - + narrow_to_wide(worldspec.gameid) + L"\""; - errorstream << wide_to_narrow(*error_message) << std::endl; - return false; - } - - if (porting::signal_handler_killstatus()) - return true; - - if (game_params.game_spec.isValid() && - game_params.game_spec.id != worldspec.gameid) { - errorstream << "WARNING: Overriding gamespec from \"" - << worldspec.gameid << "\" to \"" - << game_params.game_spec.id << "\"" << std::endl; - gamespec = game_params.game_spec; - } - - if (!gamespec.isValid()) { - *error_message = wgettext("Invalid gamespec."); - *error_message += L" (world_gameid=" - + narrow_to_wide(worldspec.gameid) + L")"; - errorstream << wide_to_narrow(*error_message) << std::endl; - return false; - } - } - - return true; -} - -void ClientLauncher::main_menu(MainMenuData *menudata) -{ - bool *kill = porting::signal_handler_killstatus(); - video::IVideoDriver *driver = device->getVideoDriver(); - - infostream << "Waiting for other menus" << std::endl; - while (device->run() && *kill == false) { - if (noMenuActive()) - break; - driver->beginScene(true, true, video::SColor(255, 128, 128, 128)); - guienv->drawAll(); - driver->endScene(); - // On some computers framerate doesn't seem to be automatically limited - sleep_ms(25); - } - infostream << "Waited for other menus" << std::endl; - - // Cursor can be non-visible when coming from the game -#ifndef ANDROID - device->getCursorControl()->setVisible(true); -#endif - - /* show main menu */ - GUIEngine mymenu(device, guiroot, &g_menumgr, smgr, menudata, *kill); - - smgr->clear(); /* leave scene manager in a clean state */ -} - -bool ClientLauncher::create_engine_device(int log_level) -{ - static const irr::ELOG_LEVEL irr_log_level[5] = { - ELL_NONE, - ELL_ERROR, - ELL_WARNING, - ELL_INFORMATION, -#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) - ELL_INFORMATION -#else - ELL_DEBUG -#endif - }; - - // Resolution selection - bool fullscreen = g_settings->getBool("fullscreen"); - u16 screenW = g_settings->getU16("screenW"); - u16 screenH = g_settings->getU16("screenH"); - - // bpp, fsaa, vsync - bool vsync = g_settings->getBool("vsync"); - u16 bits = g_settings->getU16("fullscreen_bpp"); - u16 fsaa = g_settings->getU16("fsaa"); - - // Determine driver - video::E_DRIVER_TYPE driverType = video::EDT_OPENGL; - std::string driverstring = g_settings->get("video_driver"); - std::vector<video::E_DRIVER_TYPE> drivers - = porting::getSupportedVideoDrivers(); - u32 i; - for (i = 0; i != drivers.size(); i++) { - if (!strcasecmp(driverstring.c_str(), - porting::getVideoDriverName(drivers[i]))) { - driverType = drivers[i]; - break; - } - } - if (i == drivers.size()) { - errorstream << "Invalid video_driver specified; " - "defaulting to opengl" << std::endl; - } - - SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); - params.DriverType = driverType; - params.WindowSize = core::dimension2d<u32>(screenW, screenH); - params.Bits = bits; - params.AntiAlias = fsaa; - params.Fullscreen = fullscreen; - params.Stencilbuffer = false; - params.Vsync = vsync; - params.EventReceiver = receiver; - params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); -#ifdef __ANDROID__ - params.PrivateData = porting::app_global; - params.OGLES2ShaderPath = std::string(porting::path_user + DIR_DELIM + - "media" + DIR_DELIM + "Shaders" + DIR_DELIM).c_str(); -#endif - - device = createDeviceEx(params); - - if (device) { - // Map our log level to irrlicht engine one. - ILogger* irr_logger = device->getLogger(); - irr_logger->setLogLevel(irr_log_level[log_level]); - - porting::initIrrlicht(device); - } - - return device != NULL; -} - -// Misc functions - -static bool print_video_modes() -{ - IrrlichtDevice *nulldevice; - - bool vsync = g_settings->getBool("vsync"); - u16 fsaa = g_settings->getU16("fsaa"); - MyEventReceiver* receiver = new MyEventReceiver(); - - SIrrlichtCreationParameters params = SIrrlichtCreationParameters(); - params.DriverType = video::EDT_NULL; - params.WindowSize = core::dimension2d<u32>(640, 480); - params.Bits = 24; - params.AntiAlias = fsaa; - params.Fullscreen = false; - params.Stencilbuffer = false; - params.Vsync = vsync; - params.EventReceiver = receiver; - params.HighPrecisionFPU = g_settings->getBool("high_precision_fpu"); - - nulldevice = createDeviceEx(params); - - if (nulldevice == NULL) { - delete receiver; - return false; - } - - dstream << _("Available video modes (WxHxD):") << std::endl; - - video::IVideoModeList *videomode_list = nulldevice->getVideoModeList(); - - if (videomode_list != NULL) { - s32 videomode_count = videomode_list->getVideoModeCount(); - core::dimension2d<u32> videomode_res; - s32 videomode_depth; - for (s32 i = 0; i < videomode_count; ++i) { - videomode_res = videomode_list->getVideoModeResolution(i); - videomode_depth = videomode_list->getVideoModeDepth(i); - dstream << videomode_res.Width << "x" << videomode_res.Height - << "x" << videomode_depth << std::endl; - } - - dstream << _("Active video mode (WxHxD):") << std::endl; - videomode_res = videomode_list->getDesktopResolution(); - videomode_depth = videomode_list->getDesktopDepth(); - dstream << videomode_res.Width << "x" << videomode_res.Height - << "x" << videomode_depth << std::endl; - - } - - nulldevice->drop(); - delete receiver; - - return videomode_list != NULL; -} - -#endif // !SERVER - -/***************************************************************************** - * Performance tests - *****************************************************************************/ -#ifndef SERVER -static void speed_tests() -{ - // volatile to avoid some potential compiler optimisations - volatile static s16 temp16; - volatile static f32 tempf; - static v3f tempv3f1; - static v3f tempv3f2; - static std::string tempstring; - static std::string tempstring2; - - tempv3f1 = v3f(); - tempv3f2 = v3f(); - tempstring = std::string(); - tempstring2 = std::string(); - - { - infostream << "The following test should take around 20ms." << std::endl; - TimeTaker timer("Testing std::string speed"); - const u32 jj = 10000; - for (u32 j = 0; j < jj; j++) { - tempstring = ""; - tempstring2 = ""; - const u32 ii = 10; - for (u32 i = 0; i < ii; i++) { - tempstring2 += "asd"; - } - for (u32 i = 0; i < ii+1; i++) { - tempstring += "asd"; - if (tempstring == tempstring2) - break; - } - } - } - - infostream << "All of the following tests should take around 100ms each." - << std::endl; - - { - TimeTaker timer("Testing floating-point conversion speed"); - tempf = 0.001; - for (u32 i = 0; i < 4000000; i++) { - temp16 += tempf; - tempf += 0.001; - } - } - - { - TimeTaker timer("Testing floating-point vector speed"); - - tempv3f1 = v3f(1, 2, 3); - tempv3f2 = v3f(4, 5, 6); - for (u32 i = 0; i < 10000000; i++) { - tempf += tempv3f1.dotProduct(tempv3f2); - tempv3f2 += v3f(7, 8, 9); - } - } - - { - TimeTaker timer("Testing std::map speed"); - - std::map<v2s16, f32> map1; - tempf = -324; - const s16 ii = 300; - for (s16 y = 0; y < ii; y++) { - for (s16 x = 0; x < ii; x++) { - map1[v2s16(x, y)] = tempf; - tempf += 1; - } - } - for (s16 y = ii - 1; y >= 0; y--) { - for (s16 x = 0; x < ii; x++) { - tempf = map1[v2s16(x, y)]; - } - } - } - - { - infostream << "Around 5000/ms should do well here." << std::endl; - TimeTaker timer("Testing mutex speed"); - - JMutex m; - u32 n = 0; - u32 i = 0; - do { - n += 10000; - for (; i < n; i++) { - m.Lock(); - m.Unlock(); - } - } - // Do at least 10ms - while(timer.getTimerTime() < 10); - - u32 dtime = timer.stop(); - u32 per_ms = n / dtime; - infostream << "Done. " << dtime << "ms, " << per_ms << "/ms" << std::endl; - } -} -#endif diff --git a/src/main.h b/src/main.h deleted file mode 100644 index 191b41887..000000000 --- a/src/main.h +++ /dev/null @@ -1,61 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#ifndef MAIN_HEADER -#define MAIN_HEADER - -#include <string> - -// Settings -class Settings; -extern Settings *g_settings; -extern std::string g_settings_path; - -// Global profiler -class Profiler; -extern Profiler *g_profiler; - -// Menu clouds -class Clouds; -extern Clouds *g_menuclouds; - -// Scene manager used for menu clouds -namespace irr{namespace scene{class ISceneManager;}} -extern irr::scene::ISceneManager *g_menucloudsmgr; - -// Debug streams - -#include <fstream> - -extern std::ostream *dout_con_ptr; -extern std::ostream *derr_con_ptr; -extern std::ostream *dout_client_ptr; -extern std::ostream *derr_client_ptr; -extern std::ostream *dout_server_ptr; -extern std::ostream *derr_server_ptr; - -#define dout_con (*dout_con_ptr) -#define derr_con (*derr_con_ptr) -#define dout_client (*dout_client_ptr) -#define derr_client (*derr_client_ptr) -#define dout_server (*dout_server_ptr) -#define derr_server (*derr_server_ptr) - -#endif - diff --git a/src/mainmenumanager.h b/src/mainmenumanager.h index b14ca56e5..6f8aa9137 100644 --- a/src/mainmenumanager.h +++ b/src/mainmenumanager.h @@ -39,7 +39,7 @@ public: virtual void signalKeyConfigChange() = 0; }; -extern gui::IGUIEnvironment* guienv; +extern gui::IGUIEnvironment *guienv; extern gui::IGUIStaticText *guiroot; // Handler for the modal menus diff --git a/src/map.cpp b/src/map.cpp index efa53ca08..76a558d43 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "mapsector.h" #include "mapblock.h" -#include "main.h" #include "filesys.h" #include "voxel.h" #include "porting.h" @@ -44,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "database-dummy.h" #include "database-sqlite3.h" #include <deque> +#include <queue> #if USE_LEVELDB #include "database-leveldb.h" #endif @@ -53,22 +53,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" -/* - SQLite format specification: - - Initially only replaces sectors/ and sectors2/ - - If map.sqlite does not exist in the save dir - or the block was not found in the database - the map will try to load from sectors folder. - In either case, map.sqlite will be created - and all future saves will save there. - - Structure of map.sqlite: - Tables: - blocks - (PK) INT pos - BLOB data -*/ /* Map @@ -783,8 +767,7 @@ void Map::updateLighting(enum LightBank bank, } else { - // Invalid lighting bank - assert(0); + assert("Invalid lighting bank" == NULL); } /*infostream<<"Bottom for sunlight-propagated block (" @@ -799,7 +782,7 @@ void Map::updateLighting(enum LightBank bank, } catch(InvalidPositionException &e) { - assert(0); + FATAL_ERROR("Invalid position"); } } @@ -1236,7 +1219,7 @@ void Map::removeNodeAndUpdate(v3s16 p, n.setLight(LIGHTBANK_DAY, 0, ndef); setNode(p, n); } else { - assert(0); + FATAL_ERROR("Invalid position"); } } @@ -1417,71 +1400,140 @@ bool Map::getDayNightDiff(v3s16 blockpos) return false; } +struct TimeOrderedMapBlock { + MapSector *sect; + MapBlock *block; + + TimeOrderedMapBlock(MapSector *sect, MapBlock *block) : + sect(sect), + block(block) + {} + + bool operator<(const TimeOrderedMapBlock &b) const + { + return block->getUsageTimer() < b.block->getUsageTimer(); + }; +}; + /* Updates usage timers */ -void Map::timerUpdate(float dtime, float unload_timeout, - std::list<v3s16> *unloaded_blocks) +void Map::timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, + std::vector<v3s16> *unloaded_blocks) { bool save_before_unloading = (mapType() == MAPTYPE_SERVER); // Profile modified reasons Profiler modprofiler; - std::list<v2s16> sector_deletion_queue; + std::vector<v2s16> sector_deletion_queue; u32 deleted_blocks_count = 0; u32 saved_blocks_count = 0; u32 block_count_all = 0; beginSave(); - for(std::map<v2s16, MapSector*>::iterator si = m_sectors.begin(); - si != m_sectors.end(); ++si) - { - MapSector *sector = si->second; - bool all_blocks_deleted = true; + // If there is no practical limit, we spare creation of mapblock_queue + if (max_loaded_blocks == (u32)-1) { + for (std::map<v2s16, MapSector*>::iterator si = m_sectors.begin(); + si != m_sectors.end(); ++si) { + MapSector *sector = si->second; - std::list<MapBlock*> blocks; - sector->getBlocks(blocks); + bool all_blocks_deleted = true; - for(std::list<MapBlock*>::iterator i = blocks.begin(); - i != blocks.end(); ++i) - { - MapBlock *block = (*i); + MapBlockVect blocks; + sector->getBlocks(blocks); - block->incrementUsageTimer(dtime); + for (MapBlockVect::iterator i = blocks.begin(); + i != blocks.end(); ++i) { + MapBlock *block = (*i); - if(block->refGet() == 0 && block->getUsageTimer() > unload_timeout) - { - v3s16 p = block->getPos(); + block->incrementUsageTimer(dtime); - // Save if modified - if (block->getModified() != MOD_STATE_CLEAN && save_before_unloading) - { - modprofiler.add(block->getModifiedReason(), 1); - if (!saveBlock(block)) - continue; - saved_blocks_count++; - } + if (block->refGet() == 0 + && block->getUsageTimer() > unload_timeout) { + v3s16 p = block->getPos(); - // Delete from memory - sector->deleteBlock(block); + // Save if modified + if (block->getModified() != MOD_STATE_CLEAN + && save_before_unloading) { + modprofiler.add(block->getModifiedReasonString(), 1); + if (!saveBlock(block)) + continue; + saved_blocks_count++; + } + + // Delete from memory + sector->deleteBlock(block); - if(unloaded_blocks) - unloaded_blocks->push_back(p); + if (unloaded_blocks) + unloaded_blocks->push_back(p); - deleted_blocks_count++; + deleted_blocks_count++; + } else { + all_blocks_deleted = false; + block_count_all++; + } } - else - { - all_blocks_deleted = false; - block_count_all++; + + if (all_blocks_deleted) { + sector_deletion_queue.push_back(si->first); } } + } else { + std::priority_queue<TimeOrderedMapBlock> mapblock_queue; + for (std::map<v2s16, MapSector*>::iterator si = m_sectors.begin(); + si != m_sectors.end(); ++si) { + MapSector *sector = si->second; - if(all_blocks_deleted) - { - sector_deletion_queue.push_back(si->first); + MapBlockVect blocks; + sector->getBlocks(blocks); + + for(MapBlockVect::iterator i = blocks.begin(); + i != blocks.end(); ++i) { + MapBlock *block = (*i); + + block->incrementUsageTimer(dtime); + mapblock_queue.push(TimeOrderedMapBlock(sector, block)); + } + } + block_count_all = mapblock_queue.size(); + // Delete old blocks, and blocks over the limit from the memory + while (!mapblock_queue.empty() && (mapblock_queue.size() > max_loaded_blocks + || mapblock_queue.top().block->getUsageTimer() > unload_timeout)) { + TimeOrderedMapBlock b = mapblock_queue.top(); + mapblock_queue.pop(); + + MapBlock *block = b.block; + + if (block->refGet() != 0) + continue; + + v3s16 p = block->getPos(); + + // Save if modified + if (block->getModified() != MOD_STATE_CLEAN && save_before_unloading) { + modprofiler.add(block->getModifiedReasonString(), 1); + if (!saveBlock(block)) + continue; + saved_blocks_count++; + } + + // Delete from memory + b.sect->deleteBlock(block); + + if (unloaded_blocks) + unloaded_blocks->push_back(p); + + deleted_blocks_count++; + block_count_all--; + } + // Delete empty sectors + for (std::map<v2s16, MapSector*>::iterator si = m_sectors.begin(); + si != m_sectors.end(); ++si) { + if (si->second->empty()) { + sector_deletion_queue.push_back(si->first); + } } } endSave(); @@ -1506,16 +1558,15 @@ void Map::timerUpdate(float dtime, float unload_timeout, } } -void Map::unloadUnreferencedBlocks(std::list<v3s16> *unloaded_blocks) +void Map::unloadUnreferencedBlocks(std::vector<v3s16> *unloaded_blocks) { - timerUpdate(0.0, -1.0, unloaded_blocks); + timerUpdate(0.0, -1.0, 0, unloaded_blocks); } -void Map::deleteSectors(std::list<v2s16> &list) +void Map::deleteSectors(std::vector<v2s16> §orList) { - for(std::list<v2s16>::iterator j = list.begin(); - j != list.end(); ++j) - { + for(std::vector<v2s16>::iterator j = sectorList.begin(); + j != sectorList.end(); ++j) { MapSector *sector = m_sectors[*j]; // If sector is in sector cache, remove it from there if(m_sector_cache == sector) @@ -1526,63 +1577,6 @@ void Map::deleteSectors(std::list<v2s16> &list) } } -#if 0 -void Map::unloadUnusedData(float timeout, - core::list<v3s16> *deleted_blocks) -{ - core::list<v2s16> sector_deletion_queue; - u32 deleted_blocks_count = 0; - u32 saved_blocks_count = 0; - - core::map<v2s16, MapSector*>::Iterator si = m_sectors.getIterator(); - for(; si.atEnd() == false; si++) - { - MapSector *sector = si.getNode()->getValue(); - - bool all_blocks_deleted = true; - - core::list<MapBlock*> blocks; - sector->getBlocks(blocks); - for(core::list<MapBlock*>::Iterator i = blocks.begin(); - i != blocks.end(); i++) - { - MapBlock *block = (*i); - - if(block->getUsageTimer() > timeout) - { - // Save if modified - if(block->getModified() != MOD_STATE_CLEAN) - { - saveBlock(block); - saved_blocks_count++; - } - // Delete from memory - sector->deleteBlock(block); - deleted_blocks_count++; - } - else - { - all_blocks_deleted = false; - } - } - - if(all_blocks_deleted) - { - sector_deletion_queue.push_back(si.getNode()->getKey()); - } - } - - deleteSectors(sector_deletion_queue); - - infostream<<"Map: Unloaded "<<deleted_blocks_count<<" blocks from memory" - <<", of which "<<saved_blocks_count<<" were wr." - <<std::endl; - - //return sector_deletion_queue.getSize(); - //return deleted_blocks_count; -} -#endif - void Map::PrintInfo(std::ostream &out) { out<<"Map: "; @@ -1972,6 +1966,47 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks) } } +std::vector<v3s16> Map::findNodesWithMetadata(v3s16 p1, v3s16 p2) +{ + std::vector<v3s16> positions_with_meta; + + sortBoxVerticies(p1, p2); + v3s16 bpmin = getNodeBlockPos(p1); + v3s16 bpmax = getNodeBlockPos(p2); + + VoxelArea area(p1, p2); + + for (s16 z = bpmin.Z; z <= bpmax.Z; z++) + for (s16 y = bpmin.Y; y <= bpmax.Y; y++) + for (s16 x = bpmin.X; x <= bpmax.X; x++) { + v3s16 blockpos(x, y, z); + + MapBlock *block = getBlockNoCreateNoEx(blockpos); + if (!block) { + verbosestream << "Map::getNodeMetadata(): Need to emerge " + << PP(blockpos) << std::endl; + block = emergeBlock(blockpos, false); + } + if (!block) { + infostream << "WARNING: Map::getNodeMetadata(): Block not found" + << std::endl; + continue; + } + + v3s16 p_base = blockpos * MAP_BLOCKSIZE; + std::vector<v3s16> keys = block->m_node_metadata.getAllKeys(); + for (size_t i = 0; i != keys.size(); i++) { + v3s16 p(keys[i] + p_base); + if (!area.contains(p)) + continue; + + positions_with_meta.push_back(p); + } + } + + return positions_with_meta; +} + NodeMetadata *Map::getNodeMetadata(v3s16 p) { v3s16 blockpos = getNodeBlockPos(p); @@ -2095,25 +2130,13 @@ ServerMap::ServerMap(std::string savedir, IGameDef *gamedef, EmergeManager *emer bool succeeded = conf.readConfigFile(conf_path.c_str()); if (!succeeded || !conf.exists("backend")) { // fall back to sqlite3 - dbase = new Database_SQLite3(this, savedir); conf.set("backend", "sqlite3"); - } else { - std::string backend = conf.get("backend"); - if (backend == "dummy") - dbase = new Database_Dummy(this); - else if (backend == "sqlite3") - dbase = new Database_SQLite3(this, savedir); - #if USE_LEVELDB - else if (backend == "leveldb") - dbase = new Database_LevelDB(this, savedir); - #endif - #if USE_REDIS - else if (backend == "redis") - dbase = new Database_Redis(this, savedir); - #endif - else - throw BaseException("Unknown map backend"); } + std::string backend = conf.get("backend"); + dbase = createDatabase(backend, savedir, conf); + + if (!conf.updateConfigFile(conf_path.c_str())) + errorstream << "ServerMap::ServerMap(): Failed to update world.mt!" << std::endl; m_savedir = savedir; m_map_saving_enabled = false; @@ -2272,7 +2295,7 @@ bool ServerMap::initBlockMake(BlockMakeData *data, v3s16 blockpos) v2s16 sectorpos(x, z); // Sector metadata is loaded from disk if not already loaded. ServerMapSector *sector = createSector(sectorpos); - assert(sector); + FATAL_ERROR_IF(sector == NULL, "createSector() failed"); (void) sector; for(s16 y=blockpos_min.Y-extra_borders.Y; @@ -2465,7 +2488,7 @@ void ServerMap::finishBlockMake(BlockMakeData *data, Set block as modified */ block->raiseModified(MOD_STATE_WRITE_NEEDED, - "finishBlockMake expireDayNightDiff"); + MOD_REASON_EXPIRE_DAYNIGHTDIFF); } /* @@ -2555,10 +2578,12 @@ ServerMapSector * ServerMap::createSector(v2s16 p2d) /* Do not create over-limit */ - if(p2d.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p2d.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p2d.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p2d.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE) + const static u16 map_gen_limit = MYMIN(MAX_MAP_GENERATION_LIMIT, + g_settings->getU16("map_generation_limit")); + if(p2d.X < -map_gen_limit / MAP_BLOCKSIZE + || p2d.X > map_gen_limit / MAP_BLOCKSIZE + || p2d.Y < -map_gen_limit / MAP_BLOCKSIZE + || p2d.Y > map_gen_limit / MAP_BLOCKSIZE) throw InvalidPositionException("createSector(): pos. over limit"); /* @@ -2702,12 +2727,7 @@ MapBlock * ServerMap::createBlock(v3s16 p) /* Do not create over-limit */ - if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE) + if (blockpos_over_limit(p)) throw InvalidPositionException("createBlock(): pos. over limit"); v2s16 p2d(p.X, p.Z); @@ -2720,7 +2740,7 @@ MapBlock * ServerMap::createBlock(v3s16 p) lighting on blocks for them. */ ServerMapSector *sector; - try{ + try { sector = (ServerMapSector*)createSector(p2d); assert(sector->getId() == MAPSECTOR_SERVER); } @@ -2892,7 +2912,8 @@ plan_b: } bool ServerMap::loadFromFolders() { - if(!dbase->Initialized() && !fs::PathExists(m_savedir + DIR_DELIM + "map.sqlite")) // ? + if (!dbase->initialized() && + !fs::PathExists(m_savedir + DIR_DELIM + "map.sqlite")) return true; return false; } @@ -2914,14 +2935,14 @@ std::string ServerMap::getSectorDir(v2s16 pos, int layout) { case 1: snprintf(cc, 9, "%.4x%.4x", - (unsigned int)pos.X&0xffff, - (unsigned int)pos.Y&0xffff); + (unsigned int) pos.X & 0xffff, + (unsigned int) pos.Y & 0xffff); return m_savedir + DIR_DELIM + "sectors" + DIR_DELIM + cc; case 2: - snprintf(cc, 9, "%.3x" DIR_DELIM "%.3x", - (unsigned int)pos.X&0xfff, - (unsigned int)pos.Y&0xfff); + snprintf(cc, 9, (std::string("%.3x") + DIR_DELIM + "%.3x").c_str(), + (unsigned int) pos.X & 0xfff, + (unsigned int) pos.Y & 0xfff); return m_savedir + DIR_DELIM + "sectors2" + DIR_DELIM + cc; default: @@ -2945,16 +2966,17 @@ v2s16 ServerMap::getSectorPos(std::string dirname) { // New layout fs::RemoveLastPathComponent(dirname, &component, 2); - r = sscanf(component.c_str(), "%3x" DIR_DELIM "%3x", &x, &y); + r = sscanf(component.c_str(), (std::string("%3x") + DIR_DELIM + "%3x").c_str(), &x, &y); // Sign-extend the 12 bit values up to 16 bits... - if(x&0x800) x|=0xF000; - if(y&0x800) y|=0xF000; + if(x & 0x800) x |= 0xF000; + if(y & 0x800) y |= 0xF000; } else { - assert(false); + r = -1; } - assert(r == 2); + + FATAL_ERROR_IF(r != 2, "getSectorPos()"); v2s16 pos((s16)x, (s16)y); return pos; } @@ -2983,8 +3005,7 @@ std::string ServerMap::getBlockFilename(v3s16 p) void ServerMap::save(ModifiedState save_level) { DSTACK(__FUNCTION_NAME); - if(m_map_saving_enabled == false) - { + if(m_map_saving_enabled == false) { infostream<<"WARNING: Not saving map, saving disabled."<<std::endl; return; } @@ -2993,8 +3014,7 @@ void ServerMap::save(ModifiedState save_level) infostream<<"ServerMap: Saving whole map, this can take time." <<std::endl; - if(m_map_metadata_changed || save_level == MOD_STATE_CLEAN) - { + if(m_map_metadata_changed || save_level == MOD_STATE_CLEAN) { saveMapMeta(); } @@ -3009,35 +3029,32 @@ void ServerMap::save(ModifiedState save_level) bool save_started = false; for(std::map<v2s16, MapSector*>::iterator i = m_sectors.begin(); - i != m_sectors.end(); ++i) - { + i != m_sectors.end(); ++i) { ServerMapSector *sector = (ServerMapSector*)i->second; assert(sector->getId() == MAPSECTOR_SERVER); - if(sector->differs_from_disk || save_level == MOD_STATE_CLEAN) - { + if(sector->differs_from_disk || save_level == MOD_STATE_CLEAN) { saveSectorMeta(sector); sector_meta_count++; } - std::list<MapBlock*> blocks; + + MapBlockVect blocks; sector->getBlocks(blocks); - for(std::list<MapBlock*>::iterator j = blocks.begin(); - j != blocks.end(); ++j) - { + for(MapBlockVect::iterator j = blocks.begin(); + j != blocks.end(); ++j) { MapBlock *block = *j; block_count_all++; - if(block->getModified() >= (u32)save_level) - { + if(block->getModified() >= (u32)save_level) { // Lazy beginSave() - if(!save_started){ + if(!save_started) { beginSave(); save_started = true; } - modprofiler.add(block->getModifiedReason(), 1); + modprofiler.add(block->getModifiedReasonString(), 1); saveBlock(block); block_count++; @@ -3050,6 +3067,7 @@ void ServerMap::save(ModifiedState save_level) } } } + if(save_started) endSave(); @@ -3057,8 +3075,7 @@ void ServerMap::save(ModifiedState save_level) Only print if something happened or saved whole map */ if(save_level == MOD_STATE_CLEAN || sector_meta_count != 0 - || block_count != 0) - { + || block_count != 0) { infostream<<"ServerMap: Written: " <<sector_meta_count<<" sector metadata files, " <<block_count<<" block files" @@ -3070,30 +3087,28 @@ void ServerMap::save(ModifiedState save_level) } } -void ServerMap::listAllLoadableBlocks(std::list<v3s16> &dst) +void ServerMap::listAllLoadableBlocks(std::vector<v3s16> &dst) { - if(loadFromFolders()){ - errorstream<<"Map::listAllLoadableBlocks(): Result will be missing " - <<"all blocks that are stored in flat files"<<std::endl; + if (loadFromFolders()) { + errorstream << "Map::listAllLoadableBlocks(): Result will be missing " + << "all blocks that are stored in flat files." << std::endl; } dbase->listAllLoadableBlocks(dst); } -void ServerMap::listAllLoadedBlocks(std::list<v3s16> &dst) +void ServerMap::listAllLoadedBlocks(std::vector<v3s16> &dst) { for(std::map<v2s16, MapSector*>::iterator si = m_sectors.begin(); si != m_sectors.end(); ++si) { MapSector *sector = si->second; - std::list<MapBlock*> blocks; + MapBlockVect blocks; sector->getBlocks(blocks); - for(std::list<MapBlock*>::iterator i = blocks.begin(); - i != blocks.end(); ++i) - { - MapBlock *block = (*i); - v3s16 p = block->getPos(); + for(MapBlockVect::iterator i = blocks.begin(); + i != blocks.end(); ++i) { + v3s16 p = (*i)->getPos(); dst.push_back(p); } } @@ -3103,26 +3118,20 @@ void ServerMap::saveMapMeta() { DSTACK(__FUNCTION_NAME); - /*infostream<<"ServerMap::saveMapMeta(): " - <<"seed="<<m_seed - <<std::endl;*/ - createDirs(m_savedir); - std::string fullpath = m_savedir + DIR_DELIM "map_meta.txt"; - std::ostringstream ss(std::ios_base::binary); - - Settings params; + std::string fullpath = m_savedir + DIR_DELIM + "map_meta.txt"; + std::ostringstream oss(std::ios_base::binary); + Settings conf; - m_emerge->saveParamsToSettings(¶ms); - params.writeLines(ss); + m_emerge->params.save(conf); + conf.writeLines(oss); - ss<<"[end_of_params]\n"; + oss << "[end_of_params]\n"; - if(!fs::safeWriteToFile(fullpath, ss.str())) - { - infostream<<"ERROR: ServerMap::saveMapMeta(): " - <<"could not write "<<fullpath<<std::endl; + if(!fs::safeWriteToFile(fullpath, oss.str())) { + errorstream << "ServerMap::saveMapMeta(): " + << "could not write " << fullpath << std::endl; throw FileNotGoodException("Cannot save chunk metadata"); } @@ -3133,24 +3142,22 @@ void ServerMap::loadMapMeta() { DSTACK(__FUNCTION_NAME); - Settings params; - std::string fullpath = m_savedir + DIR_DELIM "map_meta.txt"; + Settings conf; + std::string fullpath = m_savedir + DIR_DELIM + "map_meta.txt"; - if (fs::PathExists(fullpath)) { - std::ifstream is(fullpath.c_str(), std::ios_base::binary); - if (!is.good()) { - errorstream << "ServerMap::loadMapMeta(): " - "could not open " << fullpath << std::endl; - throw FileNotGoodException("Cannot open map metadata"); - } + std::ifstream is(fullpath.c_str(), std::ios_base::binary); + if (!is.good()) { + errorstream << "ServerMap::loadMapMeta(): " + "could not open " << fullpath << std::endl; + throw FileNotGoodException("Cannot open map metadata"); + } - if (!params.parseConfigLines(is, "[end_of_params]")) { - throw SerializationError("ServerMap::loadMapMeta(): " + if (!conf.parseConfigLines(is, "[end_of_params]")) { + throw SerializationError("ServerMap::loadMapMeta(): " "[end_of_params] not found!"); - } } - m_emerge->loadParamsFromSettings(¶ms); + m_emerge->params.load(conf); verbosestream << "ServerMap::loadMapMeta(): seed=" << m_emerge->params.seed << std::endl; @@ -3331,6 +3338,24 @@ bool ServerMap::loadSectorFull(v2s16 p2d) } #endif +Database *ServerMap::createDatabase(const std::string &name, const std::string &savedir, Settings &conf) +{ + if (name == "sqlite3") + return new Database_SQLite3(savedir); + if (name == "dummy") + return new Database_Dummy(); + #if USE_LEVELDB + else if (name == "leveldb") + return new Database_LevelDB(savedir); + #endif + #if USE_REDIS + else if (name == "redis") + return new Database_Redis(conf); + #endif + else + throw BaseException(std::string("Database backend ") + name + " not supported."); +} + void ServerMap::beginSave() { dbase->beginSave(); @@ -3370,7 +3395,7 @@ bool ServerMap::saveBlock(MapBlock *block, Database *db) std::string data = o.str(); bool ret = db->saveBlock(p3d, data); - if(ret) { + if (ret) { // We just wrote it to the disk so clear modified flag block->resetModified(); } @@ -3382,7 +3407,7 @@ void ServerMap::loadBlock(std::string sectordir, std::string blockfile, { DSTACK(__FUNCTION_NAME); - std::string fullpath = sectordir+DIR_DELIM+blockfile; + std::string fullpath = sectordir + DIR_DELIM + blockfile; try { std::ifstream is(fullpath.c_str(), std::ios_base::binary); @@ -3448,7 +3473,7 @@ void ServerMap::loadBlock(std::string sectordir, std::string blockfile, <<"what()="<<e.what() <<std::endl; // Ignoring. A new one will be generated. - assert(0); + abort(); // TODO: Backup file; name is in fullpath. } @@ -3518,7 +3543,6 @@ void ServerMap::loadBlock(std::string *blob, v3s16 p3d, MapSector *sector, bool <<"(ignore_world_load_errors)"<<std::endl; } else { throw SerializationError("Invalid block data in database"); - //assert(0); } } } @@ -3584,7 +3608,7 @@ MapBlock* ServerMap::loadBlock(v3s16 blockpos) */ std::string blockfilename = getBlockFilename(blockpos); - if(fs::PathExists(sectordir+DIR_DELIM+blockfilename) == false) + if(fs::PathExists(sectordir + DIR_DELIM + blockfilename) == false) return NULL; /* @@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/container.h" #include "nodetimer.h" +class Settings; class Database; class ClientMap; class MapSector; @@ -262,43 +263,33 @@ public: //bool updateChangedVisibleArea(); // Call these before and after saving of many blocks - virtual void beginSave() {return;}; - virtual void endSave() {return;}; + virtual void beginSave() { return; } + virtual void endSave() { return; } - virtual void save(ModifiedState save_level){assert(0);}; + virtual void save(ModifiedState save_level) { FATAL_ERROR("FIXME"); } // Server implements these. // Client leaves them as no-op. - virtual bool saveBlock(MapBlock *block) { return false; }; - virtual bool deleteBlock(v3s16 blockpos) { return false; }; + virtual bool saveBlock(MapBlock *block) { return false; } + virtual bool deleteBlock(v3s16 blockpos) { return false; } /* Updates usage timers and unloads unused blocks and sectors. Saves modified blocks before unloading on MAPTYPE_SERVER. */ - void timerUpdate(float dtime, float unload_timeout, - std::list<v3s16> *unloaded_blocks=NULL); + void timerUpdate(float dtime, float unload_timeout, u32 max_loaded_blocks, + std::vector<v3s16> *unloaded_blocks=NULL); /* Unloads all blocks with a zero refCount(). Saves modified blocks before unloading on MAPTYPE_SERVER. */ - void unloadUnreferencedBlocks(std::list<v3s16> *unloaded_blocks=NULL); + void unloadUnreferencedBlocks(std::vector<v3s16> *unloaded_blocks=NULL); // Deletes sectors and their blocks from memory // Takes cache into account // If deleted sector is in sector cache, clears cache - void deleteSectors(std::list<v2s16> &list); - -#if 0 - /* - Unload unused data - = flush changed to disk and delete from memory, if usage timer of - block is more than timeout - */ - void unloadUnusedData(float timeout, - core::list<v3s16> *deleted_blocks=NULL); -#endif + void deleteSectors(std::vector<v2s16> &list); // For debug printing. Prints "Map: ", "ServerMap: " or "ClientMap: " virtual void PrintInfo(std::ostream &out); @@ -310,7 +301,8 @@ public: These are basically coordinate wrappers to MapBlock */ - NodeMetadata* getNodeMetadata(v3s16 p); + std::vector<v3s16> findNodesWithMetadata(v3s16 p1, v3s16 p2); + NodeMetadata *getNodeMetadata(v3s16 p); /** * Sets metadata for a node. @@ -455,6 +447,7 @@ public: /* Database functions */ + static Database *createDatabase(const std::string &name, const std::string &savedir, Settings &conf); // Verify we can read/write to the database void verifyDatabase(); @@ -466,8 +459,8 @@ public: void endSave(); void save(ModifiedState save_level); - void listAllLoadableBlocks(std::list<v3s16> &dst); - void listAllLoadedBlocks(std::list<v3s16> &dst); + void listAllLoadableBlocks(std::vector<v3s16> &dst); + void listAllLoadedBlocks(std::vector<v3s16> &dst); // Saves map seed and possibly other stuff void saveMapMeta(); void loadMapMeta(); @@ -491,8 +484,8 @@ public: // Returns true if sector now resides in memory //bool deFlushSector(v2s16 p2d); - bool saveBlock(MapBlock *block, Database *db); bool saveBlock(MapBlock *block); + static bool saveBlock(MapBlock *block, Database *db); // This will generate a sector with getSector if not found. void loadBlock(std::string sectordir, std::string blockfile, MapSector *sector, bool save_after_load=false); MapBlock* loadBlock(v3s16 p); diff --git a/src/mapblock.cpp b/src/mapblock.cpp index ecd9a016b..43057f3a5 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -38,6 +38,30 @@ with this program; if not, write to the Free Software Foundation, Inc., #define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")" +static const char *modified_reason_strings[] = { + "initial", + "reallocate", + "setIsUnderground", + "setLightingExpired", + "setGenerated", + "setNode", + "setNodeNoCheck", + "setTimestamp", + "NodeMetaRef::reportMetadataChange", + "clearAllObjects", + "Timestamp expired (step)", + "addActiveObjectRaw", + "removeRemovedObjects/remove", + "removeRemovedObjects/deactivate", + "Stored list cleared in activateObjects due to overflow", + "deactivateFarObjects: Static data moved in", + "deactivateFarObjects: Static data moved out", + "deactivateFarObjects: Static data changed considerably", + "finishBlockMake: expireDayNightDiff", + "unknown", +}; + + /* MapBlock */ @@ -45,10 +69,10 @@ with this program; if not, write to the Free Software Foundation, Inc., MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy): m_parent(parent), m_pos(pos), + m_pos_relative(pos * MAP_BLOCKSIZE), m_gamedef(gamedef), m_modified(MOD_STATE_WRITE_NEEDED), - m_modified_reason("initial"), - m_modified_reason_too_long(false), + m_modified_reason(MOD_REASON_INITIAL), is_underground(false), m_lighting_expired(true), m_day_night_differs(false), @@ -109,7 +133,28 @@ MapNode MapBlock::getNodeParent(v3s16 p, bool *is_valid_position) } if (is_valid_position) *is_valid_position = true; - return data[p.Z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + p.Y*MAP_BLOCKSIZE + p.X]; + return data[p.Z * zstride + p.Y * ystride + p.X]; +} + +std::string MapBlock::getModifiedReasonString() +{ + std::string reason; + + const u32 ubound = MYMIN(sizeof(m_modified_reason) * CHAR_BIT, + ARRLEN(modified_reason_strings)); + + for (u32 i = 0; i != ubound; i++) { + if ((m_modified_reason & (1 << i)) == 0) + continue; + + reason += modified_reason_strings[i]; + reason += ", "; + } + + if (reason.length() > 2) + reason.resize(reason.length() - 2); + + return reason; } /* @@ -330,47 +375,42 @@ void MapBlock::copyFrom(VoxelManipulator &dst) void MapBlock::actuallyUpdateDayNightDiff() { INodeDefManager *nodemgr = m_gamedef->ndef(); + // Running this function un-expires m_day_night_differs m_day_night_differs_expired = false; - if(data == NULL) - { + if (data == NULL) { m_day_night_differs = false; return; } - bool differs = false; + bool differs; /* Check if any lighting value differs */ - for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++) - { + for (u32 i = 0; i < nodecount; i++) { MapNode &n = data[i]; - if(n.getLight(LIGHTBANK_DAY, nodemgr) != n.getLight(LIGHTBANK_NIGHT, nodemgr)) - { - differs = true; + + differs = !n.isLightDayNightEq(nodemgr); + if (differs) break; - } } /* If some lighting values differ, check if the whole thing is - just air. If it is, differ = false + just air. If it is just air, differs = false */ - if(differs) - { + if (differs) { bool only_air = true; - for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++) - { + for (u32 i = 0; i < nodecount; i++) { MapNode &n = data[i]; - if(n.getContent() != CONTENT_AIR) - { + if (n.getContent() != CONTENT_AIR) { only_air = false; break; } } - if(only_air) + if (only_air) differs = false; } @@ -426,16 +466,15 @@ s16 MapBlock::getGroundLevel(v2s16 p2d) // sure we can handle all content ids. But it's absolutely worth it as it's // a speedup of 4 for one of the major time consuming functions on storing // mapblocks. -static content_t getBlockNodeIdMapping_mapping[USHRT_MAX]; +static content_t getBlockNodeIdMapping_mapping[USHRT_MAX + 1]; static void getBlockNodeIdMapping(NameIdMapping *nimap, MapNode *nodes, INodeDefManager *nodedef) { - memset(getBlockNodeIdMapping_mapping, 0xFF, USHRT_MAX * sizeof(content_t)); + memset(getBlockNodeIdMapping_mapping, 0xFF, (USHRT_MAX + 1) * sizeof(content_t)); std::set<content_t> unknown_contents; content_t id_counter = 0; - for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++) - { + for (u32 i = 0; i < MapBlock::nodecount; i++) { content_t global_id = nodes[i].getContent(); content_t id = CONTENT_IGNORE; @@ -480,8 +519,7 @@ static void correctBlockNodeIds(const NameIdMapping *nimap, MapNode *nodes, // correct ids. std::set<content_t> unnamed_contents; std::set<std::string> unallocatable_contents; - for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++) - { + for (u32 i = 0; i < MapBlock::nodecount; i++) { content_t local_id = nodes[i].getContent(); std::string name; bool found = nimap->getName(local_id, name); @@ -526,7 +564,7 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) throw SerializationError("ERROR: Not writing dummy block."); } - assert(version >= SER_FMT_CLIENT_VER_LOWEST); + FATAL_ERROR_IF(version < SER_FMT_CLIENT_VER_LOWEST, "Serialize version error"); // First byte u8 flags = 0; @@ -544,7 +582,6 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk) Bulk node data */ NameIdMapping nimap; - u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; if(disk) { MapNode *tmp_nodes = new MapNode[nodecount]; @@ -644,7 +681,6 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk) */ TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos()) <<": Bulk node data"<<std::endl); - u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; u8 content_width = readU8(is); u8 params_width = readU8(is); if(content_width != 1 && content_width != 2) @@ -747,8 +783,6 @@ void MapBlock::deSerializeNetworkSpecific(std::istream &is) void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk) { - u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; - // Initialize default flags is_underground = false; m_day_night_differs = false; diff --git a/src/mapblock.h b/src/mapblock.h index e7d7798b8..334136b92 100644 --- a/src/mapblock.h +++ b/src/mapblock.h @@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodetimer.h" #include "modifiedstate.h" #include "util/numeric.h" // getContainerPos +#include "settings.h" class Map; class NodeMetadataList; @@ -97,21 +98,46 @@ public: }; #endif -/* - MapBlock itself -*/ +//// +//// MapBlock modified reason flags +//// + +#define MOD_REASON_INITIAL (1 << 0) +#define MOD_REASON_REALLOCATE (1 << 1) +#define MOD_REASON_SET_IS_UNDERGROUND (1 << 2) +#define MOD_REASON_SET_LIGHTING_EXPIRED (1 << 3) +#define MOD_REASON_SET_GENERATED (1 << 4) +#define MOD_REASON_SET_NODE (1 << 5) +#define MOD_REASON_SET_NODE_NO_CHECK (1 << 6) +#define MOD_REASON_SET_TIMESTAMP (1 << 7) +#define MOD_REASON_REPORT_META_CHANGE (1 << 8) +#define MOD_REASON_CLEAR_ALL_OBJECTS (1 << 9) +#define MOD_REASON_BLOCK_EXPIRED (1 << 10) +#define MOD_REASON_ADD_ACTIVE_OBJECT_RAW (1 << 11) +#define MOD_REASON_REMOVE_OBJECTS_REMOVE (1 << 12) +#define MOD_REASON_REMOVE_OBJECTS_DEACTIVATE (1 << 13) +#define MOD_REASON_TOO_MANY_OBJECTS (1 << 14) +#define MOD_REASON_STATIC_DATA_ADDED (1 << 15) +#define MOD_REASON_STATIC_DATA_REMOVED (1 << 16) +#define MOD_REASON_STATIC_DATA_CHANGED (1 << 17) +#define MOD_REASON_EXPIRE_DAYNIGHTDIFF (1 << 18) +#define MOD_REASON_UNKNOWN (1 << 19) + +//// +//// MapBlock itself +//// class MapBlock /*: public NodeContainer*/ { public: MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy=false); ~MapBlock(); - + /*virtual u16 nodeContainerId() const { return NODECONTAINER_ID_MAPBLOCK; }*/ - + Map * getParent() { return m_parent; @@ -119,150 +145,124 @@ public: void reallocate() { - if(data != NULL) - delete[] data; - u32 l = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE; - data = new MapNode[l]; - for(u32 i=0; i<l; i++){ - //data[i] = MapNode(); + delete[] data; + data = new MapNode[nodecount]; + for (u32 i = 0; i < nodecount; i++) data[i] = MapNode(CONTENT_IGNORE); - } - raiseModified(MOD_STATE_WRITE_NEEDED, "reallocate"); - } - /* - Flags - */ - - bool isDummy() - { - return (data == NULL); + raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_REALLOCATE); } - void unDummify() - { - assert(isDummy()); - reallocate(); - } - - // m_modified methods - void raiseModified(u32 mod, const std::string &reason="unknown") - { - if(mod > m_modified){ - m_modified = mod; - m_modified_reason = reason; - m_modified_reason_too_long = false; - if(m_modified >= MOD_STATE_WRITE_AT_UNLOAD){ - m_disk_timestamp = m_timestamp; - } - } else if(mod == m_modified){ - if(!m_modified_reason_too_long){ - if(m_modified_reason.size() < 40) - m_modified_reason += ", " + reason; - else{ - m_modified_reason += "..."; - m_modified_reason_too_long = true; - } - } - } - } - void raiseModified(u32 mod, const char *reason) + //// + //// Modification tracking methods + //// + void raiseModified(u32 mod, u32 reason=MOD_REASON_UNKNOWN) { - if (mod > m_modified){ + if (mod > m_modified) { m_modified = mod; m_modified_reason = reason; - m_modified_reason_too_long = false; - - if (m_modified >= MOD_STATE_WRITE_AT_UNLOAD){ + if (m_modified >= MOD_STATE_WRITE_AT_UNLOAD) m_disk_timestamp = m_timestamp; - } - } - else if (mod == m_modified){ - if (!m_modified_reason_too_long){ - if (m_modified_reason.size() < 40) - m_modified_reason += ", " + std::string(reason); - else{ - m_modified_reason += "..."; - m_modified_reason_too_long = true; - } - } + } else if (mod == m_modified) { + m_modified_reason |= reason; } } - u32 getModified() + inline u32 getModified() { return m_modified; } - std::string getModifiedReason() + + inline u32 getModifiedReason() { return m_modified_reason; } - void resetModified() + + std::string getModifiedReasonString(); + + inline void resetModified() { m_modified = MOD_STATE_CLEAN; - m_modified_reason = "none"; - m_modified_reason_too_long = false; + m_modified_reason = 0; } - + + //// + //// Flags + //// + + inline bool isDummy() + { + return (data == NULL); + } + + inline void unDummify() + { + assert(isDummy()); // Pre-condition + reallocate(); + } + // is_underground getter/setter - bool getIsUnderground() + inline bool getIsUnderground() { return is_underground; } - void setIsUnderground(bool a_is_underground) + + inline void setIsUnderground(bool a_is_underground) { is_underground = a_is_underground; - raiseModified(MOD_STATE_WRITE_NEEDED, "setIsUnderground"); + raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_IS_UNDERGROUND); } - void setLightingExpired(bool expired) + inline void setLightingExpired(bool expired) { - if(expired != m_lighting_expired){ + if (expired != m_lighting_expired){ m_lighting_expired = expired; - raiseModified(MOD_STATE_WRITE_NEEDED, "setLightingExpired"); + raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_LIGHTING_EXPIRED); } } - bool getLightingExpired() + + inline bool getLightingExpired() { return m_lighting_expired; } - bool isGenerated() + inline bool isGenerated() { return m_generated; } - void setGenerated(bool b) + + inline void setGenerated(bool b) { - if(b != m_generated){ - raiseModified(MOD_STATE_WRITE_NEEDED, "setGenerated"); + if (b != m_generated) { + raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_GENERATED); m_generated = b; } } - bool isValid() + inline bool isValid() { - if(m_lighting_expired) + if (m_lighting_expired) return false; - if(data == NULL) + if (data == NULL) return false; return true; } - /* - Position stuff - */ + //// + //// Position stuff + //// - v3s16 getPos() + inline v3s16 getPos() { return m_pos; } - - v3s16 getPosRelative() + + inline v3s16 getPosRelative() { - return m_pos * MAP_BLOCKSIZE; + return m_pos_relative; } - - core::aabbox3d<s16> getBox() + + inline core::aabbox3d<s16> getBox() { return core::aabbox3d<s16>(getPosRelative(), getPosRelative() @@ -270,140 +270,135 @@ public: - v3s16(1,1,1)); } - /* - Regular MapNode get-setters - */ - - bool isValidPosition(s16 x, s16 y, s16 z) + //// + //// Regular MapNode get-setters + //// + + inline bool isValidPosition(s16 x, s16 y, s16 z) { return data != NULL - && x >= 0 && x < MAP_BLOCKSIZE - && y >= 0 && y < MAP_BLOCKSIZE - && z >= 0 && z < MAP_BLOCKSIZE; + && x >= 0 && x < MAP_BLOCKSIZE + && y >= 0 && y < MAP_BLOCKSIZE + && z >= 0 && z < MAP_BLOCKSIZE; } - bool isValidPosition(v3s16 p) + inline bool isValidPosition(v3s16 p) { return isValidPosition(p.X, p.Y, p.Z); } - MapNode getNode(s16 x, s16 y, s16 z, bool *valid_position) + inline MapNode getNode(s16 x, s16 y, s16 z, bool *valid_position) { *valid_position = isValidPosition(x, y, z); if (!*valid_position) return MapNode(CONTENT_IGNORE); - return data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x]; + return data[z * zstride + y * ystride + x]; } - - MapNode getNode(v3s16 p, bool *valid_position) + + inline MapNode getNode(v3s16 p, bool *valid_position) { return getNode(p.X, p.Y, p.Z, valid_position); } - - MapNode getNodeNoEx(v3s16 p) + + inline MapNode getNodeNoEx(v3s16 p) { bool is_valid; MapNode node = getNode(p.X, p.Y, p.Z, &is_valid); return is_valid ? node : MapNode(CONTENT_IGNORE); } - - void setNode(s16 x, s16 y, s16 z, MapNode & n) + + inline void setNode(s16 x, s16 y, s16 z, MapNode & n) { - if(data == NULL) + if (!isValidPosition(x, y, z)) throw InvalidPositionException(); - if(x < 0 || x >= MAP_BLOCKSIZE) throw InvalidPositionException(); - if(y < 0 || y >= MAP_BLOCKSIZE) throw InvalidPositionException(); - if(z < 0 || z >= MAP_BLOCKSIZE) throw InvalidPositionException(); - data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x] = n; - raiseModified(MOD_STATE_WRITE_NEEDED, "setNode"); + + data[z * zstride + y * ystride + x] = n; + raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_NODE); } - - void setNode(v3s16 p, MapNode & n) + + inline void setNode(v3s16 p, MapNode & n) { setNode(p.X, p.Y, p.Z, n); } - /* - Non-checking variants of the above - */ + //// + //// Non-checking variants of the above + //// - MapNode getNodeNoCheck(s16 x, s16 y, s16 z, bool *valid_position) + inline MapNode getNodeNoCheck(s16 x, s16 y, s16 z, bool *valid_position) { *valid_position = data != NULL; - if(!valid_position) + if (!valid_position) return MapNode(CONTENT_IGNORE); - return data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x]; + return data[z * zstride + y * ystride + x]; } - - MapNode getNodeNoCheck(v3s16 p, bool *valid_position) + + inline MapNode getNodeNoCheck(v3s16 p, bool *valid_position) { return getNodeNoCheck(p.X, p.Y, p.Z, valid_position); } - - void setNodeNoCheck(s16 x, s16 y, s16 z, MapNode & n) + + inline void setNodeNoCheck(s16 x, s16 y, s16 z, MapNode & n) { - if(data == NULL) + if (data == NULL) throw InvalidPositionException(); - data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x] = n; - raiseModified(MOD_STATE_WRITE_NEEDED, "setNodeNoCheck"); + + data[z * zstride + y * ystride + x] = n; + raiseModified(MOD_STATE_WRITE_NEEDED, MOD_REASON_SET_NODE_NO_CHECK); } - - void setNodeNoCheck(v3s16 p, MapNode & n) + + inline void setNodeNoCheck(v3s16 p, MapNode & n) { setNodeNoCheck(p.X, p.Y, p.Z, n); } - /* - These functions consult the parent container if the position - is not valid on this MapBlock. - */ + // These functions consult the parent container if the position + // is not valid on this MapBlock. bool isValidPositionParent(v3s16 p); MapNode getNodeParent(v3s16 p, bool *is_valid_position = NULL); void setNodeParent(v3s16 p, MapNode & n); - void drawbox(s16 x0, s16 y0, s16 z0, s16 w, s16 h, s16 d, MapNode node) + inline void drawbox(s16 x0, s16 y0, s16 z0, s16 w, s16 h, s16 d, MapNode node) { - for(u16 z=0; z<d; z++) - for(u16 y=0; y<h; y++) - for(u16 x=0; x<w; x++) - setNode(x0+x, y0+y, z0+z, node); + for (u16 z = 0; z < d; z++) + for (u16 y = 0; y < h; y++) + for (u16 x = 0; x < w; x++) + setNode(x0 + x, y0 + y, z0 + z, node); } // See comments in mapblock.cpp - bool propagateSunlight(std::set<v3s16> & light_sources, - bool remove_light=false, bool *black_air_left=NULL); - + bool propagateSunlight(std::set<v3s16> &light_sources, + bool remove_light=false, bool *black_air_left=NULL); + // Copies data to VoxelManipulator to getPosRelative() void copyTo(VoxelManipulator &dst); + // Copies data from VoxelManipulator getPosRelative() void copyFrom(VoxelManipulator &dst); - /* - Update day-night lighting difference flag. - Sets m_day_night_differs to appropriate value. - These methods don't care about neighboring blocks. - */ + // Update day-night lighting difference flag. + // Sets m_day_night_differs to appropriate value. + // These methods don't care about neighboring blocks. void actuallyUpdateDayNightDiff(); - /* - Call this to schedule what the previous function does to be done - when the value is actually needed. - */ + + // Call this to schedule what the previous function does to be done + // when the value is actually needed. void expireDayNightDiff(); - bool getDayNightDiff() + inline bool getDayNightDiff() { - if(m_day_night_differs_expired) + if (m_day_night_differs_expired) actuallyUpdateDayNightDiff(); return m_day_night_differs; } - /* - Miscellaneous stuff - */ - + //// + //// Miscellaneous stuff + //// + /* Tries to measure ground level. Return value: @@ -414,84 +409,99 @@ public: */ s16 getGroundLevel(v2s16 p2d); - /* - Timestamp (see m_timestamp) - NOTE: BLOCK_TIMESTAMP_UNDEFINED=0xffffffff means there is no timestamp. - */ - void setTimestamp(u32 time) + //// + //// Timestamp (see m_timestamp) + //// + + // NOTE: BLOCK_TIMESTAMP_UNDEFINED=0xffffffff means there is no timestamp. + + inline void setTimestamp(u32 time) { m_timestamp = time; - raiseModified(MOD_STATE_WRITE_AT_UNLOAD, "setTimestamp"); + raiseModified(MOD_STATE_WRITE_AT_UNLOAD, MOD_REASON_SET_TIMESTAMP); } - void setTimestampNoChangedFlag(u32 time) + + inline void setTimestampNoChangedFlag(u32 time) { m_timestamp = time; } - u32 getTimestamp() + + inline u32 getTimestamp() { return m_timestamp; } - u32 getDiskTimestamp() + + inline u32 getDiskTimestamp() { return m_disk_timestamp; } - - /* - See m_usage_timer - */ - void resetUsageTimer() + + //// + //// Usage timer (see m_usage_timer) + //// + + inline void resetUsageTimer() { m_usage_timer = 0; } - void incrementUsageTimer(float dtime) + + inline void incrementUsageTimer(float dtime) { m_usage_timer += dtime; } - float getUsageTimer() + + inline float getUsageTimer() { return m_usage_timer; } - /* - See m_refcount - */ - void refGrab() + //// + //// Reference counting (see m_refcount) + //// + + inline void refGrab() { m_refcount++; } - void refDrop() + + inline void refDrop() { m_refcount--; } - int refGet() + + inline int refGet() { return m_refcount; } - - /* - Node Timers - */ - // Get timer - NodeTimer getNodeTimer(v3s16 p){ + + //// + //// Node Timers + //// + + inline NodeTimer getNodeTimer(v3s16 p) + { return m_node_timers.get(p); } - // Deletes timer - void removeNodeTimer(v3s16 p){ + + inline void removeNodeTimer(v3s16 p) + { m_node_timers.remove(p); } - // Deletes old timer and sets a new one - void setNodeTimer(v3s16 p, NodeTimer t){ + + inline void setNodeTimer(v3s16 p, NodeTimer t) + { m_node_timers.set(p,t); } - // Deletes all timers - void clearNodeTimers(){ + + inline void clearNodeTimers() + { m_node_timers.clear(); } - /* - Serialization - */ - + //// + //// Serialization + /// + // These don't write or read version by itself // Set disk to true for on-disk format, false for over-the-network format // Precondition: version >= SER_FMT_CLIENT_VER_LOWEST @@ -514,16 +524,15 @@ private: Used only internally, because changes can't be tracked */ - MapNode & getNodeRef(s16 x, s16 y, s16 z) + inline MapNode &getNodeRef(s16 x, s16 y, s16 z) { - if(data == NULL) + if (!isValidPosition(x, y, z)) throw InvalidPositionException(); - if(x < 0 || x >= MAP_BLOCKSIZE) throw InvalidPositionException(); - if(y < 0 || y >= MAP_BLOCKSIZE) throw InvalidPositionException(); - if(z < 0 || z >= MAP_BLOCKSIZE) throw InvalidPositionException(); - return data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x]; + + return data[z * zstride + y * ystride + x]; } - MapNode & getNodeRef(v3s16 &p) + + inline MapNode &getNodeRef(v3s16 &p) { return getNodeRef(p.X, p.Y, p.Z); } @@ -536,11 +545,16 @@ public: #ifndef SERVER // Only on client MapBlockMesh *mesh; #endif - + NodeMetadataList m_node_metadata; NodeTimerList m_node_timers; StaticObjectList m_static_objects; + static const u32 ystride = MAP_BLOCKSIZE; + static const u32 zstride = MAP_BLOCKSIZE * MAP_BLOCKSIZE; + + static const u32 nodecount = MAP_BLOCKSIZE * MAP_BLOCKSIZE * MAP_BLOCKSIZE; + private: /* Private member variables @@ -551,13 +565,21 @@ private: // Position in blocks on parent v3s16 m_pos; + /* This is the precalculated m_pos_relative value + * This caches the value, improving performance by removing 3 s16 multiplications + * at runtime on each getPosRelative call + * For a 5 minutes runtime with valgrind this removes 3 * 19M s16 multiplications + * The gain can be estimated in Release Build to 3 * 100M multiply operations for 5 mins + */ + v3s16 m_pos_relative; + IGameDef *m_gamedef; - + /* If NULL, block is a dummy block. Dummy blocks are used for caching not-found-on-disk blocks. */ - MapNode * data; + MapNode *data; /* - On the server, this is used for telling whether the @@ -565,8 +587,7 @@ private: - On the client, this is used for nothing. */ u32 m_modified; - std::string m_modified_reason; - bool m_modified_reason_too_long; + u32 m_modified_reason; /* When propagating sunlight and the above block doesn't exist, @@ -586,13 +607,13 @@ private: If this is true, lighting might be wrong or right. */ bool m_lighting_expired; - + // Whether day and night lighting differs bool m_day_night_differs; bool m_day_night_differs_expired; bool m_generated; - + /* When block is removed from active blocks, this is set to gametime. Value BLOCK_TIMESTAMP_UNDEFINED=0xffffffff means there is no timestamp. @@ -614,15 +635,18 @@ private: int m_refcount; }; +typedef std::vector<MapBlock*> MapBlockVect; + inline bool blockpos_over_limit(v3s16 p) { - return - (p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE - || p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE); + const static u16 map_gen_limit = MYMIN(MAX_MAP_GENERATION_LIMIT, + g_settings->getU16("map_generation_limit")); + return (p.X < -map_gen_limit / MAP_BLOCKSIZE + || p.X > map_gen_limit / MAP_BLOCKSIZE + || p.Y < -map_gen_limit / MAP_BLOCKSIZE + || p.Y > map_gen_limit / MAP_BLOCKSIZE + || p.Z < -map_gen_limit / MAP_BLOCKSIZE + || p.Z > map_gen_limit / MAP_BLOCKSIZE); } /* @@ -659,4 +683,3 @@ inline void getNodeSectorPosWithOffset(const v2s16 &p, v2s16 &block, v2s16 &offs std::string analyze_block(MapBlock *block); #endif - diff --git a/src/mapblock_mesh.cpp b/src/mapblock_mesh.cpp index cf311acba..33597b2fc 100644 --- a/src/mapblock_mesh.cpp +++ b/src/mapblock_mesh.cpp @@ -21,16 +21,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "light.h" #include "mapblock.h" #include "map.h" -#include "main.h" // for g_profiler #include "profiler.h" #include "nodedef.h" #include "gamedef.h" #include "mesh.h" +#include "minimap.h" #include "content_mapblock.h" #include "noise.h" #include "shader.h" #include "settings.h" #include "util/directiontables.h" +#include <IMeshManipulator.h> static void applyFacesShading(video::SColor& color, float factor) { @@ -42,7 +43,7 @@ static void applyFacesShading(video::SColor& color, float factor) MeshMakeData */ -MeshMakeData::MeshMakeData(IGameDef *gamedef): +MeshMakeData::MeshMakeData(IGameDef *gamedef, bool use_shaders): m_vmanip(), m_blockpos(-1337,-1337,-1337), m_crack_pos_relative(-1337, -1337, -1337), @@ -50,7 +51,8 @@ MeshMakeData::MeshMakeData(IGameDef *gamedef): m_smooth_lighting(false), m_show_hud(false), m_highlight_mesh_color(255, 255, 255, 255), - m_gamedef(gamedef) + m_gamedef(gamedef), + m_use_shaders(use_shaders) {} void MeshMakeData::fill(MapBlock *block) @@ -247,7 +249,7 @@ static u16 getSmoothLightCombined(v3s16 p, MeshMakeData *data) for (u32 i = 0; i < 8; i++) { - MapNode n = data->m_vmanip.getNodeNoEx(p - dirs8[i]); + const MapNode &n = data->m_vmanip.getNodeRefUnsafeCheckFlags(p - dirs8[i]); // if it's CONTENT_IGNORE we can't do any light calculations if (n.getContent() == CONTENT_IGNORE) { @@ -437,8 +439,6 @@ struct FastFace static void makeFastFace(TileSpec tile, u16 li0, u16 li1, u16 li2, u16 li3, v3f p, v3s16 dir, v3f scale, u8 light_source, std::vector<FastFace> &dest) { - FastFace face; - // Position is at the center of the cube. v3f pos = p * BS; @@ -589,6 +589,10 @@ static void makeFastFace(TileSpec tile, u16 li0, u16 li1, u16 li2, u16 li3, u8 alpha = tile.alpha; + dest.push_back(FastFace()); + + FastFace& face = *dest.rbegin(); + face.vertices[0] = video::S3DVertex(vertex_pos[0], normal, MapBlock_LightColor(alpha, li0, light_source), core::vector2d<f32>(x0+w*abs_scale, y0+h)); @@ -603,7 +607,6 @@ static void makeFastFace(TileSpec tile, u16 li0, u16 li1, u16 li2, u16 li3, core::vector2d<f32>(x0+w*abs_scale, y0)); face.tile = tile; - dest.push_back(face); } /* @@ -744,8 +747,8 @@ TileSpec getNodeTile(MapNode mn, v3s16 p, v3s16 dir, MeshMakeData *data) static void getTileInfo( // Input: MeshMakeData *data, - v3s16 p, - v3s16 face_dir, + const v3s16 &p, + const v3s16 &face_dir, // Output: bool &makes_face, v3s16 &p_corrected, @@ -759,14 +762,20 @@ static void getTileInfo( INodeDefManager *ndef = data->m_gamedef->ndef(); v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; - MapNode n0 = vmanip.getNodeNoEx(blockpos_nodes + p); + MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p); // Don't even try to get n1 if n0 is already CONTENT_IGNORE - if (n0.getContent() == CONTENT_IGNORE ) { + if (n0.getContent() == CONTENT_IGNORE) { + makes_face = false; + return; + } + + const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir); + + if (n1.getContent() == CONTENT_IGNORE) { makes_face = false; return; } - MapNode n1 = vmanip.getNodeNoEx(blockpos_nodes + p + face_dir); // This is hackish bool equivalent = false; @@ -1020,7 +1029,10 @@ static void updateAllFastFaceRows(MeshMakeData *data, MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): m_mesh(new scene::SMesh()), + m_minimap_mapblock(NULL), m_gamedef(data->m_gamedef), + m_tsrc(m_gamedef->getTextureSource()), + m_shdrsrc(m_gamedef->getShaderSource()), m_animation_force_timer(0), // force initial animation m_last_crack(-1), m_crack_materials(), @@ -1028,14 +1040,21 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): m_last_daynight_ratio((u32) -1), m_daynight_diffs() { - m_enable_shaders = g_settings->getBool("enable_shaders"); + m_enable_shaders = data->m_use_shaders; m_enable_highlighting = g_settings->getBool("enable_node_highlighting"); + if (g_settings->getBool("enable_minimap")) { + m_minimap_mapblock = new MinimapMapblock; + m_minimap_mapblock->getMinimapNodes( + &data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE); + } + // 4-21ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) // 24-155ms for MAP_BLOCKSIZE=32 (NOTE: probably outdated) //TimeTaker timer1("MapBlockMesh()"); std::vector<FastFace> fastfaces_new; + fastfaces_new.reserve(512); /* We are including the faces of the trailing edges of the block. @@ -1102,8 +1121,6 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): /* Convert MeshCollector to SMesh */ - ITextureSource *tsrc = m_gamedef->tsrc(); - IShaderSource *shdrsrc = m_gamedef->getShaderSource(); for(u32 i = 0; i < collector.prebuffers.size(); i++) { @@ -1115,13 +1132,13 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): { // Find the texture name plus ^[crack:N: std::ostringstream os(std::ios::binary); - os<<tsrc->getTextureName(p.tile.texture_id)<<"^[crack"; + os<<m_tsrc->getTextureName(p.tile.texture_id)<<"^[crack"; if(p.tile.material_flags & MATERIAL_FLAG_CRACK_OVERLAY) os<<"o"; // use ^[cracko os<<":"<<(u32)p.tile.animation_frame_count<<":"; m_crack_materials.insert(std::make_pair(i, os.str())); // Replace tile texture with the cracked one - p.tile.texture = tsrc->getTexture( + p.tile.texture = m_tsrc->getTextureForMesh( os.str()+"0", &p.tile.texture_id); } @@ -1146,24 +1163,25 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): } if(m_enable_highlighting && p.tile.material_flags & MATERIAL_FLAG_HIGHLIGHTED) - m_highlighted_materials.push_back(i); + m_highlighted_materials.push_back(i); for(u32 j = 0; j < p.vertices.size(); j++) { + video::S3DVertexTangents *vertex = &p.vertices[j]; // Note applyFacesShading second parameter is precalculated sqrt // value for speed improvement // Skip it for lightsources and top faces. - video::SColor &vc = p.vertices[j].Color; + video::SColor &vc = vertex->Color; if (!vc.getBlue()) { - if (p.vertices[j].Normal.Y < -0.5) { + if (vertex->Normal.Y < -0.5) { applyFacesShading (vc, 0.447213); - } else if (p.vertices[j].Normal.X > 0.5) { + } else if (vertex->Normal.X > 0.5) { applyFacesShading (vc, 0.670820); - } else if (p.vertices[j].Normal.X < -0.5) { + } else if (vertex->Normal.X < -0.5) { applyFacesShading (vc, 0.670820); - } else if (p.vertices[j].Normal.Z > 0.5) { + } else if (vertex->Normal.Z > 0.5) { applyFacesShading (vc, 0.836660); - } else if (p.vertices[j].Normal.Z < -0.5) { + } else if (vertex->Normal.Z < -0.5) { applyFacesShading (vc, 0.836660); } } @@ -1191,33 +1209,28 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): material.MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; } else { if (m_enable_shaders) { - material.MaterialType = shdrsrc->getShaderInfo(p.tile.shader_id).material; + material.MaterialType = m_shdrsrc->getShaderInfo(p.tile.shader_id).material; p.tile.applyMaterialOptionsWithShaders(material); if (p.tile.normal_texture) { material.setTexture(1, p.tile.normal_texture); - material.setTexture(2, tsrc->getTexture("enable_img.png")); - } else { - material.setTexture(2, tsrc->getTexture("disable_img.png")); } + material.setTexture(2, p.tile.flags_texture); } else { p.tile.applyMaterialOptions(material); } } - // Create meshbuffer - // This is a "Standard MeshBuffer", - // it's a typedeffed CMeshBuffer<video::S3DVertex> - scene::SMeshBuffer *buf = new scene::SMeshBuffer(); - // Set material - buf->Material = material; - // Add to mesh - m_mesh->addMeshBuffer(buf); - // Mesh grabbed it - buf->drop(); - buf->append(&p.vertices[0], p.vertices.size(), - &p.indices[0], p.indices.size()); - } - + // Create meshbuffer + scene::SMeshBufferTangents *buf = new scene::SMeshBufferTangents(); + // Set material + buf->Material = material; + // Add to mesh + m_mesh->addMeshBuffer(buf); + // Mesh grabbed it + buf->drop(); + buf->append(&p.vertices[0], p.vertices.size(), + &p.indices[0], p.indices.size()); +} m_camera_offset = camera_offset; /* @@ -1226,6 +1239,11 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): translateMesh(m_mesh, intToFloat(data->m_blockpos * MAP_BLOCKSIZE - camera_offset, BS)); + if (m_enable_shaders) { + scene::IMeshManipulator* meshmanip = m_gamedef->getSceneManager()->getMeshManipulator(); + meshmanip->recalculateTangents(m_mesh, true, false, false); + } + if(m_mesh) { #if 0 @@ -1260,11 +1278,11 @@ MapBlockMesh::~MapBlockMesh() { m_mesh->drop(); m_mesh = NULL; + delete m_minimap_mapblock; } bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_ratio) { - if(!m_has_animation) { m_animation_force_timer = 100000; @@ -1284,12 +1302,11 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat std::string basename = i->second; // Create new texture name from original - ITextureSource *tsrc = m_gamedef->getTextureSource(); std::ostringstream os; os<<basename<<crack; u32 new_texture_id = 0; video::ITexture *new_texture = - tsrc->getTexture(os.str(), &new_texture_id); + m_tsrc->getTextureForMesh(os.str(), &new_texture_id); buf->getMaterial().setTexture(0, new_texture); // If the current material is also animated, @@ -1325,17 +1342,14 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat m_animation_frames[i->first] = frame; scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first); - ITextureSource *tsrc = m_gamedef->getTextureSource(); FrameSpec animation_frame = tile.frames[frame]; buf->getMaterial().setTexture(0, animation_frame.texture); if (m_enable_shaders) { if (animation_frame.normal_texture) { buf->getMaterial().setTexture(1, animation_frame.normal_texture); - buf->getMaterial().setTexture(2, tsrc->getTexture("enable_img.png")); - } else { - buf->getMaterial().setTexture(2, tsrc->getTexture("disable_img.png")); } + buf->getMaterial().setTexture(2, animation_frame.flags_texture); } } @@ -1347,16 +1361,14 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat i != m_daynight_diffs.end(); i++) { scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(i->first); - video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices(); + video::S3DVertexTangents *vertices = (video::S3DVertexTangents *)buf->getVertices(); for(std::map<u32, std::pair<u8, u8 > >::iterator j = i->second.begin(); j != i->second.end(); j++) { - u32 vertexIndex = j->first; u8 day = j->second.first; u8 night = j->second.second; - finalColorBlend(vertices[vertexIndex].Color, - day, night, daynight_ratio); + finalColorBlend(vertices[j->first].Color, day, night, daynight_ratio); } } m_last_daynight_ratio = daynight_ratio; @@ -1365,7 +1377,7 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat // Node highlighting if (m_enable_highlighting) { u8 day = m_highlight_mesh_color.getRed(); - u8 night = m_highlight_mesh_color.getGreen(); + u8 night = m_highlight_mesh_color.getGreen(); video::SColor hc; finalColorBlend(hc, day, night, daynight_ratio); float sin_r = 0.07 * sin(1.5 * time); @@ -1380,7 +1392,7 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat i != m_highlighted_materials.end(); i++) { scene::IMeshBuffer *buf = m_mesh->getMeshBuffer(*i); - video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices(); + video::S3DVertexTangents *vertices = (video::S3DVertexTangents*)buf->getVertices(); for (u32 j = 0; j < buf->getVertexCount() ;j++) vertices[j].Color = hc; } @@ -1405,42 +1417,40 @@ void MeshCollector::append(const TileSpec &tile, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices) { - if(numIndices > 65535) - { + if (numIndices > 65535) { dstream<<"FIXME: MeshCollector::append() called with numIndices="<<numIndices<<" (limit 65535)"<<std::endl; return; } PreMeshBuffer *p = NULL; - for(u32 i=0; i<prebuffers.size(); i++) - { + for (u32 i = 0; i < prebuffers.size(); i++) { PreMeshBuffer &pp = prebuffers[i]; - if(pp.tile != tile) + if (pp.tile != tile) continue; - if(pp.indices.size() + numIndices > 65535) + if (pp.indices.size() + numIndices > 65535) continue; p = &pp; break; } - if(p == NULL) - { + if (p == NULL) { PreMeshBuffer pp; pp.tile = tile; prebuffers.push_back(pp); - p = &prebuffers[prebuffers.size()-1]; + p = &prebuffers[prebuffers.size() - 1]; } u32 vertex_count = p->vertices.size(); - for(u32 i=0; i<numIndices; i++) - { + for (u32 i = 0; i < numIndices; i++) { u32 j = indices[i] + vertex_count; p->indices.push_back(j); } - for(u32 i=0; i<numVertices; i++) - { - p->vertices.push_back(vertices[i]); + + for (u32 i = 0; i < numVertices; i++) { + video::S3DVertexTangents vert(vertices[i].Pos, vertices[i].Normal, + vertices[i].Color, vertices[i].TCoords); + p->vertices.push_back(vert); } } @@ -1453,15 +1463,13 @@ void MeshCollector::append(const TileSpec &tile, const u16 *indices, u32 numIndices, v3f pos, video::SColor c) { - if(numIndices > 65535) - { + if (numIndices > 65535) { dstream<<"FIXME: MeshCollector::append() called with numIndices="<<numIndices<<" (limit 65535)"<<std::endl; return; } PreMeshBuffer *p = NULL; - for(u32 i=0; i<prebuffers.size(); i++) - { + for (u32 i = 0; i < prebuffers.size(); i++) { PreMeshBuffer &pp = prebuffers[i]; if(pp.tile != tile) continue; @@ -1472,25 +1480,22 @@ void MeshCollector::append(const TileSpec &tile, break; } - if(p == NULL) - { + if (p == NULL) { PreMeshBuffer pp; pp.tile = tile; prebuffers.push_back(pp); - p = &prebuffers[prebuffers.size()-1]; + p = &prebuffers[prebuffers.size() - 1]; } u32 vertex_count = p->vertices.size(); - for(u32 i=0; i<numIndices; i++) - { + for (u32 i = 0; i < numIndices; i++) { u32 j = indices[i] + vertex_count; p->indices.push_back(j); } - for(u32 i=0; i<numVertices; i++) - { - video::S3DVertex vert = vertices[i]; - vert.Pos += pos; - vert.Color = c; + + for (u32 i = 0; i < numVertices; i++) { + video::S3DVertexTangents vert(vertices[i].Pos + pos, vertices[i].Normal, + c, vertices[i].TCoords); p->vertices.push_back(vert); } } diff --git a/src/mapblock_mesh.h b/src/mapblock_mesh.h index be56d4c58..8e994ec6b 100644 --- a/src/mapblock_mesh.h +++ b/src/mapblock_mesh.h @@ -21,11 +21,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MAPBLOCK_MESH_HEADER #include "irrlichttypes_extrabloated.h" -#include "tile.h" +#include "client/tile.h" #include "voxel.h" #include <map> class IGameDef; +class IShaderSource; /* Mesh making stuff @@ -33,6 +34,7 @@ class IGameDef; class MapBlock; +struct MinimapMapblock; struct MeshMakeData { @@ -45,8 +47,9 @@ struct MeshMakeData video::SColor m_highlight_mesh_color; IGameDef *m_gamedef; + bool m_use_shaders; - MeshMakeData(IGameDef *gamedef); + MeshMakeData(IGameDef *gamedef, bool use_shaders); /* Copy central data directly from block, and other data from @@ -101,11 +104,18 @@ public: // Returns true if anything has been changed. bool animate(bool faraway, float time, int crack, u32 daynight_ratio); - scene::SMesh* getMesh() + scene::SMesh *getMesh() { return m_mesh; } + MinimapMapblock *moveMinimapMapblock() + { + MinimapMapblock *p = m_minimap_mapblock; + m_minimap_mapblock = NULL; + return p; + } + bool isAnimationForced() const { return m_animation_force_timer == 0; @@ -121,7 +131,10 @@ public: private: scene::SMesh *m_mesh; + MinimapMapblock *m_minimap_mapblock; IGameDef *m_gamedef; + ITextureSource *m_tsrc; + IShaderSource *m_shdrsrc; bool m_enable_shaders; bool m_enable_highlighting; @@ -164,13 +177,12 @@ struct PreMeshBuffer { TileSpec tile; std::vector<u16> indices; - std::vector<video::S3DVertex> vertices; + std::vector<video::S3DVertexTangents> vertices; }; struct MeshCollector { std::vector<PreMeshBuffer> prebuffers; - void append(const TileSpec &material, const video::S3DVertex *vertices, u32 numVertices, const u16 *indices, u32 numIndices); diff --git a/src/mapchunk.h b/src/mapchunk.h deleted file mode 100644 index a70de8711..000000000 --- a/src/mapchunk.h +++ /dev/null @@ -1,77 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#ifndef MAPCHUNK_HEADER -#define MAPCHUNK_HEADER - -/* - TODO: Remove -*/ - -#if 0 -/* - MapChunk contains map-generation-time metadata for an area of - some MapSectors. (something like 16x16) -*/ - -// These should fit in 8 bits, as they are saved as such. -enum{ - GENERATED_FULLY = 0, - GENERATED_PARTLY = 10, - GENERATED_NOT = 20 -}; - -class MapChunk -{ -public: - MapChunk(): - m_generation_level(GENERATED_NOT), - m_modified(true) - { - } - - /* - Generation level. Possible values: - GENERATED_FULLY = 0 = fully generated - GENERATED_PARTLY = partly generated - GENERATED_NOT = not generated - */ - u16 getGenLevel(){ return m_generation_level; } - void setGenLevel(u16 lev){ m_generation_level = lev; } - - void serialize(std::ostream &os, u8 version) - { - os.write((char*)&m_generation_level, 1); - } - void deSerialize(std::istream &is, u8 version) - { - is.read((char*)&m_generation_level, 1); - } - - bool isModified(){ return m_modified; } - void setModified(bool modified){ m_modified = modified; } - -private: - u8 m_generation_level; - bool m_modified; -}; -#endif - -#endif - diff --git a/src/mapgen.cpp b/src/mapgen.cpp index ba1b16d6a..f8e9477c5 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -1,6 +1,7 @@ /* Minetest -Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2010-2015 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -30,17 +31,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "emerge.h" #include "content_mapnode.h" // For content_mapnode_get_new_name #include "voxelalgorithms.h" +#include "porting.h" #include "profiler.h" -#include "settings.h" // For g_settings -#include "main.h" // For g_profiler +#include "settings.h" #include "treegen.h" #include "serialization.h" #include "util/serialize.h" +#include "util/numeric.h" #include "filesys.h" #include "log.h" -const char *GenElementManager::ELEMENT_TITLE = "element"; - FlagDesc flagdesc_mapgen[] = { {"trees", MG_TREES}, {"caves", MG_CAVES}, @@ -64,35 +64,40 @@ FlagDesc flagdesc_gennotify[] = { /////////////////////////////////////////////////////////////////////////////// + Mapgen::Mapgen() { - generating = false; - id = -1; - seed = 0; - water_level = 0; - flags = 0; - - vm = NULL; - ndef = NULL; - heightmap = NULL; - biomemap = NULL; + generating = false; + id = -1; + seed = 0; + water_level = 0; + flags = 0; + + vm = NULL; + ndef = NULL; + heightmap = NULL; + biomemap = NULL; + heatmap = NULL; + humidmap = NULL; } Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge) : gennotify(emerge->gen_notify_on, &emerge->gen_notify_on_deco_ids) { - generating = false; - id = mapgenid; - seed = (int)params->seed; - water_level = params->water_level; - flags = params->flags; - csize = v3s16(1, 1, 1) * (params->chunksize * MAP_BLOCKSIZE); + generating = false; + id = mapgenid; + seed = (int)params->seed; + water_level = params->water_level; + flags = params->flags; + csize = v3s16(1, 1, 1) * (params->chunksize * MAP_BLOCKSIZE); vm = NULL; ndef = NULL; heightmap = NULL; biomemap = NULL; + heatmap = NULL; + humidmap = NULL; } @@ -138,6 +143,7 @@ s16 Mapgen::findGroundLevelFull(v2s16 p2d) } +// Returns -MAX_MAP_GENERATION_LIMIT if not found s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax) { v3s16 em = vm->m_area.getExtent(); @@ -151,7 +157,7 @@ s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax) vm->m_area.add_y(em, i, -1); } - return y; + return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT; } @@ -166,12 +172,6 @@ void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax) for (s16 x = nmin.X; x <= nmax.X; x++, index++) { s16 y = findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y); - // if the values found are out of range, trust the old heightmap - if (y == nmax.Y && heightmap[index] > nmax.Y) - continue; - if (y == nmin.Y - 1 && heightmap[index] < nmin.Y) - continue; - heightmap[index] = y; } } @@ -342,30 +342,6 @@ void Mapgen::spreadLight(v3s16 nmin, v3s16 nmax) -void Mapgen::calcLightingOld(v3s16 nmin, v3s16 nmax) -{ - enum LightBank banks[2] = {LIGHTBANK_DAY, LIGHTBANK_NIGHT}; - VoxelArea a(nmin, nmax); - bool block_is_underground = (water_level > nmax.Y); - bool sunlight = !block_is_underground; - - ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG); - - for (int i = 0; i < 2; i++) { - enum LightBank bank = banks[i]; - std::set<v3s16> light_sources; - std::map<v3s16, u8> unlight_from; - - voxalgo::clearLightAndCollectSources(*vm, a, bank, ndef, - light_sources, unlight_from); - voxalgo::propagateSunlight(*vm, a, sunlight, light_sources, ndef); - - vm->unspreadLight(bank, unlight_from, light_sources, ndef); - vm->spreadLight(bank, light_sources, ndef); - } -} - - /////////////////////////////////////////////////////////////////////////////// GenerateNotifier::GenerateNotifier() @@ -432,84 +408,46 @@ void GenerateNotifier::getEvents( m_notify_events.clear(); } - /////////////////////////////////////////////////////////////////////////////// - -GenElementManager::GenElementManager(IGameDef *gamedef) -{ - m_ndef = gamedef->getNodeDefManager(); -} - - -GenElementManager::~GenElementManager() -{ - for (size_t i = 0; i != m_elements.size(); i++) - delete m_elements[i]; -} - - -u32 GenElementManager::add(GenElement *elem) -{ - size_t nelem = m_elements.size(); - - for (size_t i = 0; i != nelem; i++) { - if (m_elements[i] == NULL) { - elem->id = i; - m_elements[i] = elem; - return i; - } - } - - if (nelem >= this->ELEMENT_LIMIT) - return -1; - - elem->id = nelem; - m_elements.push_back(elem); - - verbosestream << "GenElementManager: added " << this->ELEMENT_TITLE - << " element '" << elem->name << "'" << std::endl; - - return nelem; -} - - -GenElement *GenElementManager::get(u32 id) -{ - return (id < m_elements.size()) ? m_elements[id] : NULL; -} - - -GenElement *GenElementManager::getByName(const std::string &name) -{ - for (size_t i = 0; i != m_elements.size(); i++) { - GenElement *elem = m_elements[i]; - if (elem && name == elem->name) - return elem; - } - - return NULL; -} - - -GenElement *GenElementManager::update(u32 id, GenElement *elem) -{ - if (id >= m_elements.size()) - return NULL; - - GenElement *old_elem = m_elements[id]; - m_elements[id] = elem; - return old_elem; -} - - -GenElement *GenElementManager::remove(u32 id) +void MapgenParams::load(const Settings &settings) { - return update(id, NULL); + std::string seed_str; + const char *seed_name = (&settings == g_settings) ? "fixed_map_seed" : "seed"; + + if (settings.getNoEx(seed_name, seed_str) && !seed_str.empty()) + seed = read_seed(seed_str.c_str()); + else + myrand_bytes(&seed, sizeof(seed)); + + settings.getNoEx("mg_name", mg_name); + settings.getS16NoEx("water_level", water_level); + settings.getS16NoEx("chunksize", chunksize); + settings.getFlagStrNoEx("mg_flags", flags, flagdesc_mapgen); + settings.getNoiseParams("mg_biome_np_heat", np_biome_heat); + settings.getNoiseParams("mg_biome_np_heat_blend", np_biome_heat_blend); + settings.getNoiseParams("mg_biome_np_humidity", np_biome_humidity); + settings.getNoiseParams("mg_biome_np_humidity_blend", np_biome_humidity_blend); + + delete sparams; + sparams = EmergeManager::createMapgenParams(mg_name); + if (sparams) + sparams->readParams(&settings); } -void GenElementManager::clear() +void MapgenParams::save(Settings &settings) const { - m_elements.clear(); + settings.set("mg_name", mg_name); + settings.setU64("seed", seed); + settings.setS16("water_level", water_level); + settings.setS16("chunksize", chunksize); + settings.setFlagStr("mg_flags", flags, flagdesc_mapgen, (u32)-1); + settings.setNoiseParams("mg_biome_np_heat", np_biome_heat); + settings.setNoiseParams("mg_biome_np_heat_blend", np_biome_heat_blend); + settings.setNoiseParams("mg_biome_np_humidity", np_biome_humidity); + settings.setNoiseParams("mg_biome_np_humidity_blend", np_biome_humidity_blend); + + if (sparams) + sparams->writeParams(&settings); } diff --git a/src/mapgen.h b/src/mapgen.h index 5bbdd724d..46328ba92 100644 --- a/src/mapgen.h +++ b/src/mapgen.h @@ -70,6 +70,13 @@ enum GenNotifyType { NUM_GENNOTIFY_TYPES }; +// TODO(hmmmm/paramat): make stone type selection dynamic +enum MgStoneType { + STONE, + DESERT_STONE, + SANDSTONE, +}; + struct GenNotifyEvent { GenNotifyType type; v3s16 pos; @@ -95,8 +102,8 @@ private: }; struct MapgenSpecificParams { - virtual void readParams(Settings *settings) = 0; - virtual void writeParams(Settings *settings) = 0; + virtual void readParams(const Settings *settings) = 0; + virtual void writeParams(Settings *settings) const = 0; virtual ~MapgenSpecificParams() {} }; @@ -108,21 +115,27 @@ struct MapgenParams { u32 flags; NoiseParams np_biome_heat; + NoiseParams np_biome_heat_blend; NoiseParams np_biome_humidity; + NoiseParams np_biome_humidity_blend; MapgenSpecificParams *sparams; - MapgenParams() - { - mg_name = DEFAULT_MAPGEN; - seed = 0; - water_level = 1; - chunksize = 5; - flags = MG_TREES | MG_CAVES | MG_LIGHT; - sparams = NULL; - np_biome_heat = NoiseParams(50, 50, v3f(500.0, 500.0, 500.0), 5349, 3, 0.5, 2.0); - np_biome_humidity = NoiseParams(50, 50, v3f(500.0, 500.0, 500.0), 842, 3, 0.5, 2.0); - } + MapgenParams() : + mg_name(DEFAULT_MAPGEN), + chunksize(5), + seed(0), + water_level(1), + flags(MG_TREES | MG_CAVES | MG_LIGHT), + np_biome_heat(NoiseParams(50, 50, v3f(1000.0, 1000.0, 1000.0), 5349, 3, 0.5, 2.0)), + np_biome_heat_blend(NoiseParams(0, 1.5, v3f(8.0, 8.0, 8.0), 13, 2, 1.0, 2.0)), + np_biome_humidity(NoiseParams(50, 50, v3f(1000.0, 1000.0, 1000.0), 842, 3, 0.5, 2.0)), + np_biome_humidity_blend(NoiseParams(0, 1.5, v3f(8.0, 8.0, 8.0), 90003, 2, 1.0, 2.0)), + sparams(NULL) + {} + + void load(const Settings &settings); + void save(Settings &settings) const; }; class Mapgen { @@ -139,6 +152,8 @@ public: u32 blockseed; s16 *heightmap; u8 *biomemap; + float *heatmap; + float *humidmap; v3s16 csize; GenerateNotifier gennotify; @@ -164,8 +179,6 @@ public: void propagateSunlight(v3s16 nmin, v3s16 nmax); void spreadLight(v3s16 nmin, v3s16 nmax); - void calcLightingOld(v3s16 nmin, v3s16 nmax); - virtual void makeChunk(BlockMakeData *data) {} virtual int getGroundLevelAtPoint(v2s16 p) { return 0; } }; @@ -177,34 +190,4 @@ struct MapgenFactory { virtual ~MapgenFactory() {} }; -class GenElement { -public: - virtual ~GenElement() {} - u32 id; - std::string name; -}; - -class GenElementManager { -public: - static const char *ELEMENT_TITLE; - static const size_t ELEMENT_LIMIT = -1; - - GenElementManager(IGameDef *gamedef); - virtual ~GenElementManager(); - - virtual GenElement *create(int type) = 0; - - virtual u32 add(GenElement *elem); - virtual GenElement *get(u32 id); - virtual GenElement *update(u32 id, GenElement *elem); - virtual GenElement *remove(u32 id); - virtual void clear(); - - virtual GenElement *getByName(const std::string &name); - -protected: - INodeDefManager *m_ndef; - std::vector<GenElement *> m_elements; -}; - #endif diff --git a/src/mapgen_singlenode.cpp b/src/mapgen_singlenode.cpp index 5f81aba98..a8b84e849 100644 --- a/src/mapgen_singlenode.cpp +++ b/src/mapgen_singlenode.cpp @@ -24,21 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "nodedef.h" #include "voxelalgorithms.h" -#include "profiler.h" #include "emerge.h" -//////////////////////// Mapgen Singlenode parameter read/write - -void MapgenSinglenodeParams::readParams(Settings *settings) -{ -} - - -void MapgenSinglenodeParams::writeParams(Settings *settings) -{ -} - -/////////////////////////////////////////////////////////////////////////////// MapgenSinglenode::MapgenSinglenode(int mapgenid, MapgenParams *params, EmergeManager *emerge) @@ -62,6 +49,7 @@ MapgenSinglenode::~MapgenSinglenode() void MapgenSinglenode::makeChunk(BlockMakeData *data) { + // Pre-conditions assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && diff --git a/src/mapgen_singlenode.h b/src/mapgen_singlenode.h index 9fd1d75b3..35f2ba385 100644 --- a/src/mapgen_singlenode.h +++ b/src/mapgen_singlenode.h @@ -27,8 +27,8 @@ struct MapgenSinglenodeParams : public MapgenSpecificParams { MapgenSinglenodeParams() {} ~MapgenSinglenodeParams() {} - void readParams(Settings *settings); - void writeParams(Settings *settings); + void readParams(const Settings *settings) {} + void writeParams(Settings *settings) const {} }; class MapgenSinglenode : public Mapgen { diff --git a/src/mapgen_v5.cpp b/src/mapgen_v5.cpp index f7efc4e18..5b842a99e 100644 --- a/src/mapgen_v5.cpp +++ b/src/mapgen_v5.cpp @@ -27,9 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_sao.h" #include "nodedef.h" #include "voxelalgorithms.h" -#include "profiler.h" #include "settings.h" // For g_settings -#include "main.h" // For g_profiler #include "emerge.h" #include "dungeongen.h" #include "cavegen.h" @@ -42,7 +40,6 @@ with this program; if not, write to the Free Software Foundation, Inc., FlagDesc flagdesc_mapgen_v5[] = { - {"blobs", MGV5_BLOBS}, {NULL, 0} }; @@ -60,6 +57,8 @@ MapgenV5::MapgenV5(int mapgenid, MapgenParams *params, EmergeManager *emerge) this->biomemap = new u8[csize.X * csize.Z]; this->heightmap = new s16[csize.X * csize.Z]; + this->heatmap = NULL; + this->humidmap = NULL; MapgenV5Params *sp = (MapgenV5Params *)params->sparams; this->spflags = sp->spflags; @@ -70,42 +69,42 @@ MapgenV5::MapgenV5(int mapgenid, MapgenParams *params, EmergeManager *emerge) noise_height = new Noise(&sp->np_height, seed, csize.X, csize.Z); // 3D terrain noise - noise_cave1 = new Noise(&sp->np_cave1, seed, csize.X, csize.Y + 2, csize.Z); - noise_cave2 = new Noise(&sp->np_cave2, seed, csize.X, csize.Y + 2, csize.Z); - noise_ground = new Noise(&sp->np_ground, seed, csize.X, csize.Y + 2, csize.Z); - noise_crumble = new Noise(&sp->np_crumble, seed, csize.X, csize.Y + 2, csize.Z); - noise_wetness = new Noise(&sp->np_wetness, seed, csize.X, csize.Y + 2, csize.Z); + noise_cave1 = new Noise(&sp->np_cave1, seed, csize.X, csize.Y + 2, csize.Z); + noise_cave2 = new Noise(&sp->np_cave2, seed, csize.X, csize.Y + 2, csize.Z); + noise_ground = new Noise(&sp->np_ground, seed, csize.X, csize.Y + 2, csize.Z); // Biome noise - noise_heat = new Noise(¶ms->np_biome_heat, seed, csize.X, csize.Z); - noise_humidity = new Noise(¶ms->np_biome_humidity, seed, csize.X, csize.Z); + noise_heat = new Noise(¶ms->np_biome_heat, seed, csize.X, csize.Z); + noise_humidity = new Noise(¶ms->np_biome_humidity, seed, csize.X, csize.Z); + noise_heat_blend = new Noise(¶ms->np_biome_heat_blend, seed, csize.X, csize.Z); + noise_humidity_blend = new Noise(¶ms->np_biome_humidity_blend, seed, csize.X, csize.Z); //// Resolve nodes to be used INodeDefManager *ndef = emerge->ndef; - c_stone = ndef->getId("mapgen_stone"); - c_dirt = ndef->getId("mapgen_dirt"); - c_dirt_with_grass = ndef->getId("mapgen_dirt_with_grass"); - c_sand = ndef->getId("mapgen_sand"); - c_water_source = ndef->getId("mapgen_water_source"); - c_lava_source = ndef->getId("mapgen_lava_source"); - c_gravel = ndef->getId("mapgen_gravel"); - c_cobble = ndef->getId("mapgen_cobble"); - c_ice = ndef->getId("default:ice"); - c_mossycobble = ndef->getId("mapgen_mossycobble"); - c_sandbrick = ndef->getId("mapgen_sandstonebrick"); - c_stair_cobble = ndef->getId("mapgen_stair_cobble"); - c_stair_sandstone = ndef->getId("mapgen_stair_sandstone"); + c_stone = ndef->getId("mapgen_stone"); + c_water_source = ndef->getId("mapgen_water_source"); + c_lava_source = ndef->getId("mapgen_lava_source"); + c_desert_stone = ndef->getId("mapgen_desert_stone"); + c_ice = ndef->getId("mapgen_ice"); + c_sandstone = ndef->getId("mapgen_sandstone"); + + c_cobble = ndef->getId("mapgen_cobble"); + c_stair_cobble = ndef->getId("mapgen_stair_cobble"); + c_mossycobble = ndef->getId("mapgen_mossycobble"); + c_sandstonebrick = ndef->getId("mapgen_sandstonebrick"); + c_stair_sandstonebrick = ndef->getId("mapgen_stair_sandstonebrick"); + if (c_ice == CONTENT_IGNORE) c_ice = CONTENT_AIR; if (c_mossycobble == CONTENT_IGNORE) c_mossycobble = c_cobble; - if (c_sandbrick == CONTENT_IGNORE) - c_sandbrick = c_desert_stone; if (c_stair_cobble == CONTENT_IGNORE) c_stair_cobble = c_cobble; - if (c_stair_sandstone == CONTENT_IGNORE) - c_stair_sandstone = c_sandbrick; + if (c_sandstonebrick == CONTENT_IGNORE) + c_sandstonebrick = c_sandstone; + if (c_stair_sandstonebrick == CONTENT_IGNORE) + c_stair_sandstonebrick = c_sandstone; } @@ -117,11 +116,11 @@ MapgenV5::~MapgenV5() delete noise_cave1; delete noise_cave2; delete noise_ground; - delete noise_crumble; - delete noise_wetness; delete noise_heat; delete noise_humidity; + delete noise_heat_blend; + delete noise_humidity_blend; delete[] heightmap; delete[] biomemap; @@ -130,7 +129,7 @@ MapgenV5::~MapgenV5() MapgenV5Params::MapgenV5Params() { - spflags = MGV5_BLOBS; + spflags = 0; np_filler_depth = NoiseParams(0, 1, v3f(150, 150, 150), 261, 4, 0.7, 2.0); np_factor = NoiseParams(0, 1, v3f(250, 250, 250), 920381, 3, 0.45, 2.0); @@ -138,21 +137,14 @@ MapgenV5Params::MapgenV5Params() np_cave1 = NoiseParams(0, 12, v3f(50, 50, 50), 52534, 4, 0.5, 2.0); np_cave2 = NoiseParams(0, 12, v3f(50, 50, 50), 10325, 4, 0.5, 2.0); np_ground = NoiseParams(0, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED); - np_crumble = NoiseParams(0, 1, v3f(20, 20, 20), 34413, 3, 1.3, 2.0, NOISE_FLAG_EASED); - np_wetness = NoiseParams(0, 1, v3f(40, 40, 40), 32474, 4, 1.1, 2.0); } -// Scaling the output of the noise function affects the overdrive of the -// contour function, which affects the shape of the output considerably. - -// Two original MT 0.3 parameters for non-eased noise: - //#define CAVE_NOISE_SCALE 12.0 -//#define CAVE_NOISE_THRESHOLD (1.5/CAVE_NOISE_SCALE) +//#define CAVE_NOISE_THRESHOLD (1.5/CAVE_NOISE_SCALE) = 0.125 -void MapgenV5Params::readParams(Settings *settings) +void MapgenV5Params::readParams(const Settings *settings) { settings->getFlagStrNoEx("mgv5_spflags", spflags, flagdesc_mapgen_v5); @@ -162,12 +154,10 @@ void MapgenV5Params::readParams(Settings *settings) settings->getNoiseParams("mgv5_np_cave1", np_cave1); settings->getNoiseParams("mgv5_np_cave2", np_cave2); settings->getNoiseParams("mgv5_np_ground", np_ground); - settings->getNoiseParams("mgv5_np_crumble", np_crumble); - settings->getNoiseParams("mgv5_np_wetness", np_wetness); } -void MapgenV5Params::writeParams(Settings *settings) +void MapgenV5Params::writeParams(Settings *settings) const { settings->setFlagStr("mgv5_spflags", spflags, flagdesc_mapgen_v5, (u32)-1); @@ -177,8 +167,6 @@ void MapgenV5Params::writeParams(Settings *settings) settings->setNoiseParams("mgv5_np_cave1", np_cave1); settings->setNoiseParams("mgv5_np_cave2", np_cave2); settings->setNoiseParams("mgv5_np_ground", np_ground); - settings->setNoiseParams("mgv5_np_crumble", np_crumble); - settings->setNoiseParams("mgv5_np_wetness", np_wetness); } @@ -187,23 +175,20 @@ int MapgenV5::getGroundLevelAtPoint(v2s16 p) //TimeTaker t("getGroundLevelAtPoint", NULL, PRECISION_MICRO); float f = 0.55 + NoisePerlin2D(&noise_factor->np, p.X, p.Y, seed); - if(f < 0.01) + if (f < 0.01) f = 0.01; - else if(f >= 1.0) + else if (f >= 1.0) f *= 1.6; - float h = water_level + NoisePerlin2D(&noise_height->np, p.X, p.Y, seed); + float h = NoisePerlin2D(&noise_height->np, p.X, p.Y, seed); s16 search_top = water_level + 15; s16 search_base = water_level; - // Use these 2 lines instead for a slower search returning highest ground level: - //s16 search_top = h + f * noise_ground->np->octaves * noise_ground->np->scale; - //s16 search_base = h - f * noise_ground->np->octaves * noise_ground->np->scale; s16 level = -31000; for (s16 y = search_top; y >= search_base; y--) { float n_ground = NoisePerlin3D(&noise_ground->np, p.X, y, p.Y, seed); - if(n_ground * f > y - h) { - if(y >= search_top - 7) + if (n_ground * f > y - h) { + if (y >= search_top - 7) break; else level = y; @@ -218,6 +203,7 @@ int MapgenV5::getGroundLevelAtPoint(v2s16 p) void MapgenV5::makeChunk(BlockMakeData *data) { + // Pre-conditions assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && @@ -247,33 +233,68 @@ void MapgenV5::makeChunk(BlockMakeData *data) // Generate base terrain s16 stone_surface_max_y = generateBaseTerrain(); + + // Create heightmap updateHeightmap(node_min, node_max); - // Calculate biomes + // Create biomemap at heightmap surface bmgr->calcBiomes(csize.X, csize.Z, noise_heat->result, noise_humidity->result, heightmap, biomemap); // Actually place the biome-specific nodes - generateBiomes(); + MgStoneType stone_type = generateBiomes(noise_heat->result, noise_humidity->result); // Generate caves if ((flags & MG_CAVES) && (stone_surface_max_y >= node_min.Y)) - generateCaves(); + generateCaves(stone_surface_max_y); // Generate dungeons and desert temples if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) { - DungeonGen dgen(this, NULL); + DungeonParams dp; + + dp.np_rarity = nparams_dungeon_rarity; + dp.np_density = nparams_dungeon_density; + dp.np_wetness = nparams_dungeon_wetness; + dp.c_water = c_water_source; + if (stone_type == STONE) { + dp.c_cobble = c_cobble; + dp.c_moss = c_mossycobble; + dp.c_stair = c_stair_cobble; + + dp.diagonal_dirs = false; + dp.mossratio = 3.0; + dp.holesize = v3s16(1, 2, 1); + dp.roomsize = v3s16(0, 0, 0); + dp.notifytype = GENNOTIFY_DUNGEON; + } else if (stone_type == DESERT_STONE) { + dp.c_cobble = c_desert_stone; + dp.c_moss = c_desert_stone; + dp.c_stair = c_desert_stone; + + dp.diagonal_dirs = true; + dp.mossratio = 0.0; + dp.holesize = v3s16(2, 3, 2); + dp.roomsize = v3s16(2, 5, 2); + dp.notifytype = GENNOTIFY_TEMPLE; + } else if (stone_type == SANDSTONE) { + dp.c_cobble = c_sandstonebrick; + dp.c_moss = c_sandstonebrick; + dp.c_stair = c_sandstonebrick; + + dp.diagonal_dirs = false; + dp.mossratio = 0.0; + dp.holesize = v3s16(2, 2, 2); + dp.roomsize = v3s16(2, 0, 2); + dp.notifytype = GENNOTIFY_DUNGEON; + } + + DungeonGen dgen(this, &dp); dgen.generate(blockseed, full_node_min, full_node_max); } // Generate the registered decorations m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max); - // Generate underground dirt, sand, gravel and lava blobs - if (spflags & MGV5_BLOBS) { - generateBlobs(); - } - // Generate the registered ores m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); @@ -311,23 +332,23 @@ void MapgenV5::calculateNoise() noise_cave2->perlinMap3D(x, y, z); } - if (spflags & MGV5_BLOBS) { - noise_crumble->perlinMap3D(x, y, z); - noise_wetness->perlinMap3D(x, y, z); - } + noise_filler_depth->perlinMap2D(x, z); + noise_heat->perlinMap2D(x, z); + noise_humidity->perlinMap2D(x, z); + noise_heat_blend->perlinMap2D(x, z); + noise_humidity_blend->perlinMap2D(x, z); - if (node_max.Y >= water_level) { - noise_filler_depth->perlinMap2D(x, z); - noise_heat->perlinMap2D(x, z); - noise_humidity->perlinMap2D(x, z); + for (s32 i = 0; i < csize.X * csize.Z; i++) { + noise_heat->result[i] += noise_heat_blend->result[i]; + noise_humidity->result[i] += noise_humidity_blend->result[i]; } + heatmap = noise_heat->result; + humidmap = noise_humidity->result; //printf("calculateNoise: %dus\n", t.stop()); } -// Two original MT 0.3 functions: - //bool is_cave(u32 index) { // double d1 = contour(noise_cave1->result[index]); // double d2 = contour(noise_cave2->result[index]); @@ -349,24 +370,24 @@ int MapgenV5::generateBaseTerrain() { u32 index = 0; u32 index2d = 0; - int stone_surface_max_y = -MAP_GENERATION_LIMIT; + int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; - for(s16 z=node_min.Z; z<=node_max.Z; z++) { - for(s16 y=node_min.Y - 1; y<=node_max.Y + 1; y++) { + 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 i = vm->m_area.index(node_min.X, y, z); - for(s16 x=node_min.X; x<=node_max.X; x++, i++, index++, index2d++) { - if(vm->m_data[i].getContent() != CONTENT_IGNORE) + for (s16 x=node_min.X; x<=node_max.X; x++, i++, index++, index2d++) { + if (vm->m_data[i].getContent() != CONTENT_IGNORE) continue; float f = 0.55 + noise_factor->result[index2d]; - if(f < 0.01) + if (f < 0.01) f = 0.01; - else if(f >= 1.0) + else if (f >= 1.0) f *= 1.6; - float h = water_level + noise_height->result[index2d]; + float h = noise_height->result[index2d]; - if(noise_ground->result[index] * f < y - h) { - if(y <= water_level) + if (noise_ground->result[index] * f < y - h) { + if (y <= water_level) vm->m_data[i] = MapNode(c_water_source); else vm->m_data[i] = MapNode(CONTENT_AIR); @@ -376,156 +397,142 @@ int MapgenV5::generateBaseTerrain() stone_surface_max_y = y; } } - index2d = index2d - ystride; + index2d -= ystride; } - index2d = index2d + ystride; + index2d += ystride; } return stone_surface_max_y; } -void MapgenV5::generateBiomes() +MgStoneType MapgenV5::generateBiomes(float *heat_map, float *humidity_map) { - if (node_max.Y < water_level) - return; - - MapNode n_air(CONTENT_AIR); - MapNode n_stone(c_stone); - MapNode n_water(c_water_source); - v3s16 em = vm->m_area.getExtent(); u32 index = 0; + MgStoneType stone_type = STONE; for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, index++) { - Biome *biome = (Biome *)bmgr->get(biomemap[index]); - s16 dfiller = biome->depth_filler + noise_filler_depth->result[index]; - s16 y0_top = biome->depth_top; - s16 y0_filler = biome->depth_top + dfiller; - s16 shore_max = water_level + biome->height_shore; - s16 depth_water_top = biome->depth_water_top; - - s16 nplaced = 0; - u32 i = vm->m_area.index(x, node_max.Y, z); - - content_t c_above = vm->m_data[i + em.X].getContent(); - bool have_air = c_above == CONTENT_AIR; + Biome *biome = NULL; + u16 depth_top = 0; + u16 base_filler = 0; + u16 depth_water_top = 0; + u32 vi = vm->m_area.index(x, node_max.Y, z); + + // Check node at base of mapchunk above, either a node of a previously + // generated mapchunk or if not, a node of overgenerated base terrain. + content_t c_above = vm->m_data[vi + em.X].getContent(); + bool air_above = c_above == CONTENT_AIR; + bool water_above = c_above == c_water_source; + + // If there is air or water above enable top/filler placement, otherwise force + // nplaced to stone level by setting a number exceeding any possible filler depth. + u16 nplaced = (air_above || water_above) ? 0 : (u16)-1; for (s16 y = node_max.Y; y >= node_min.Y; y--) { - content_t c = vm->m_data[i].getContent(); - - if (c == c_stone && have_air) { - content_t c_below = vm->m_data[i - em.X].getContent(); - - if (c_below != CONTENT_AIR) { - if (nplaced < y0_top) { - if(y < water_level) - vm->m_data[i] = MapNode(biome->c_underwater); - else if(y <= shore_max) - vm->m_data[i] = MapNode(biome->c_shore_top); - else - vm->m_data[i] = MapNode(biome->c_top); - nplaced++; - } else if (nplaced < y0_filler && nplaced >= y0_top) { - if(y < water_level) - vm->m_data[i] = MapNode(biome->c_underwater); - else if(y <= shore_max) - vm->m_data[i] = MapNode(biome->c_shore_filler); - else - vm->m_data[i] = MapNode(biome->c_filler); - nplaced++; - } else if (c == c_stone) { - have_air = false; - nplaced = 0; - vm->m_data[i] = MapNode(biome->c_stone); - } else { - have_air = false; - nplaced = 0; - } - } else if (c == c_stone) { - have_air = false; - nplaced = 0; - vm->m_data[i] = MapNode(biome->c_stone); + content_t c = vm->m_data[vi].getContent(); + + // Biome is recalculated each time an upper surface is detected while + // working down a column. The selected biome then remains in effect for + // all nodes below until the next surface and biome recalculation. + // Biome is recalculated: + // 1. At the surface of stone below air or water. + // 2. At the surface of water below air. + // 3. When stone or water is detected but biome has not yet been calculated. + if ((c == c_stone && (air_above || water_above || !biome)) || + (c == c_water_source && (air_above || !biome))) { + biome = bmgr->getBiome(heat_map[index], humidity_map[index], y); + depth_top = biome->depth_top; + base_filler = MYMAX(depth_top + biome->depth_filler + + noise_filler_depth->result[index], 0); + depth_water_top = biome->depth_water_top; + + // Detect stone type for dungeons during every biome calculation. + // This is more efficient than detecting per-node and will not + // miss any desert stone or sandstone biomes. + if (biome->c_stone == c_desert_stone) + stone_type = DESERT_STONE; + else if (biome->c_stone == c_sandstone) + stone_type = SANDSTONE; + } + + if (c == c_stone) { + content_t c_below = vm->m_data[vi - em.X].getContent(); + + // If the node below isn't solid, make this node stone, so that + // any top/filler nodes above are structurally supported. + // This is done by aborting the cycle of top/filler placement + // immediately by forcing nplaced to stone level. + if (c_below == CONTENT_AIR || c_below == c_water_source) + nplaced = (u16)-1; + + if (nplaced < depth_top) { + vm->m_data[vi] = MapNode(biome->c_top); + nplaced++; + } else if (nplaced < base_filler) { + vm->m_data[vi] = MapNode(biome->c_filler); + nplaced++; + } else { + vm->m_data[vi] = MapNode(biome->c_stone); } - } else if (c == c_stone) { - have_air = false; - nplaced = 0; - vm->m_data[i] = MapNode(biome->c_stone); + + air_above = false; + water_above = false; } else if (c == c_water_source) { - have_air = true; - nplaced = 0; - if(y > water_level - depth_water_top) - vm->m_data[i] = MapNode(biome->c_water_top); - else - vm->m_data[i] = MapNode(biome->c_water); + vm->m_data[vi] = MapNode((y > (s32)(water_level - depth_water_top)) ? + biome->c_water_top : biome->c_water); + nplaced = 0; // Enable top/filler placement for next surface + air_above = false; + water_above = true; } else if (c == CONTENT_AIR) { - have_air = true; - nplaced = 0; + nplaced = 0; // Enable top/filler placement for next surface + air_above = true; + water_above = false; + } else { // Possible various nodes overgenerated from neighbouring mapchunks + nplaced = (u16)-1; // Disable top/filler placement + air_above = false; + water_above = false; } - vm->m_area.add_y(em, i, -1); + vm->m_area.add_y(em, vi, -1); } } + + return stone_type; } -void MapgenV5::generateCaves() +void MapgenV5::generateCaves(int max_stone_y) { - u32 index = 0; - u32 index2d = 0; + if (max_stone_y >= node_min.Y) { + u32 index = 0; - for(s16 z=node_min.Z; z<=node_max.Z; z++) { - for(s16 y=node_min.Y - 1; y<=node_max.Y + 1; y++) { + 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 i = vm->m_area.index(node_min.X, y, z); - for(s16 x=node_min.X; x<=node_max.X; x++, i++, index++, index2d++) { - Biome *biome = (Biome *)bmgr->get(biomemap[index2d]); - content_t c = vm->m_data[i].getContent(); - if(c == CONTENT_AIR - || (y <= water_level - && c != biome->c_stone - && c != c_stone)) - continue; - + for (s16 x = node_min.X; x <= node_max.X; x++, i++, index++) { float d1 = contour(noise_cave1->result[index]); float d2 = contour(noise_cave2->result[index]); - if(d1*d2 > 0.125) + if (d1*d2 > 0.125) { + content_t c = vm->m_data[i].getContent(); + if (!ndef->get(c).is_ground_content || c == CONTENT_AIR) + continue; + vm->m_data[i] = MapNode(CONTENT_AIR); + } } - index2d = index2d - ystride; } - index2d = index2d + ystride; } -} + if (node_max.Y > LARGE_CAVE_DEPTH) + return; -void MapgenV5::generateBlobs() -{ - u32 index = 0; - - 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 i = vm->m_area.index(node_min.X, y, z); - for(s16 x=node_min.X; x<=node_max.X; x++, i++, index++) { - content_t c = vm->m_data[i].getContent(); - if(c != c_stone) - continue; - - if(noise_crumble->result[index] > 1.3) { - if(noise_wetness->result[index] > 0.0) - vm->m_data[i] = MapNode(c_dirt); - else - vm->m_data[i] = MapNode(c_sand); - } else if(noise_crumble->result[index] > 0.7) { - if(noise_wetness->result[index] < -0.6) - vm->m_data[i] = MapNode(c_gravel); - } else if(noise_crumble->result[index] < -3.5 + - MYMIN(0.1 * - sqrt((float)MYMAX(0, -y)), 1.5)) { - vm->m_data[i] = MapNode(c_lava_source); - } - } - } + PseudoRandom ps(blockseed + 21343); + u32 bruises_count = (ps.range(1, 4) == 1) ? ps.range(1, 2) : 0; + for (u32 i = 0; i < bruises_count; i++) { + CaveV5 cave(this, &ps); + cave.makeCave(node_min, node_max, max_stone_y); } } @@ -540,14 +547,31 @@ void MapgenV5::dustTopNodes() for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, index++) { - Biome *biome = (Biome *)bmgr->get(biomemap[index]); + Biome *biome = (Biome *)bmgr->getRaw(biomemap[index]); if (biome->c_dust == CONTENT_IGNORE) continue; - s16 y = node_max.Y + 1; - u32 vi = vm->m_area.index(x, y, z); - for (; y >= node_min.Y; y--) { + u32 vi = vm->m_area.index(x, full_node_max.Y, z); + content_t c_full_max = vm->m_data[vi].getContent(); + s16 y_start; + + if (c_full_max == CONTENT_AIR) { + y_start = full_node_max.Y - 1; + } else if (c_full_max == CONTENT_IGNORE) { + vi = vm->m_area.index(x, node_max.Y + 1, z); + content_t c_max = vm->m_data[vi].getContent(); + + if (c_max == CONTENT_AIR) + y_start = node_max.Y; + else + continue; + } else { + continue; + } + + vi = vm->m_area.index(x, y_start, z); + for (s16 y = y_start; y >= node_min.Y - 1; y--) { if (vm->m_data[vi].getContent() != CONTENT_AIR) break; @@ -555,14 +579,9 @@ void MapgenV5::dustTopNodes() } content_t c = vm->m_data[vi].getContent(); - if (!ndef->get(c).buildable_to && c != CONTENT_IGNORE - && c != biome->c_dust) { - if (y == node_max.Y + 1) - continue; - + if (!ndef->get(c).buildable_to && c != CONTENT_IGNORE && c != biome->c_dust) { vm->m_area.add_y(em, vi, 1); vm->m_data[vi] = MapNode(biome->c_dust); } } } - diff --git a/src/mapgen_v5.h b/src/mapgen_v5.h index 1949bf5db..a6fdc2b2b 100644 --- a/src/mapgen_v5.h +++ b/src/mapgen_v5.h @@ -22,8 +22,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen.h" -/////////////////// Mapgen V5 flags -#define MGV5_BLOBS 0x01 +#define LARGE_CAVE_DEPTH -256 + +class BiomeManager; extern FlagDesc flagdesc_mapgen_v5[]; @@ -36,14 +37,12 @@ struct MapgenV5Params : public MapgenSpecificParams { NoiseParams np_cave1; NoiseParams np_cave2; NoiseParams np_ground; - NoiseParams np_crumble; - NoiseParams np_wetness; MapgenV5Params(); ~MapgenV5Params() {} - void readParams(Settings *settings); - void writeParams(Settings *settings); + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; }; @@ -67,26 +66,24 @@ public: Noise *noise_cave1; Noise *noise_cave2; Noise *noise_ground; - Noise *noise_crumble; - Noise *noise_wetness; + Noise *noise_heat; Noise *noise_humidity; + Noise *noise_heat_blend; + Noise *noise_humidity_blend; content_t c_stone; - content_t c_dirt; - content_t c_dirt_with_grass; - content_t c_sand; content_t c_water_source; content_t c_lava_source; + content_t c_desert_stone; content_t c_ice; - content_t c_gravel; + content_t c_sandstone; + content_t c_cobble; - content_t c_desert_sand; - content_t c_desert_stone; - content_t c_mossycobble; - content_t c_sandbrick; content_t c_stair_cobble; - content_t c_stair_sandstone; + content_t c_mossycobble; + content_t c_sandstonebrick; + content_t c_stair_sandstonebrick; MapgenV5(int mapgenid, MapgenParams *params, EmergeManager *emerge); ~MapgenV5(); @@ -95,9 +92,8 @@ public: int getGroundLevelAtPoint(v2s16 p); void calculateNoise(); int generateBaseTerrain(); - void generateBiomes(); - void generateCaves(); - void generateBlobs(); + MgStoneType generateBiomes(float *heat_map, float *humidity_map); + void generateCaves(int max_stone_y); void dustTopNodes(); }; diff --git a/src/mapgen_v6.cpp b/src/mapgen_v6.cpp index 95cdbd279..9e34aac2d 100644 --- a/src/mapgen_v6.cpp +++ b/src/mapgen_v6.cpp @@ -28,9 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "content_mapnode.h" // For content_mapnode_get_new_name #include "voxelalgorithms.h" -#include "profiler.h" #include "settings.h" // For g_settings -#include "main.h" // For g_profiler #include "emerge.h" #include "dungeongen.h" #include "cavegen.h" @@ -43,6 +41,7 @@ FlagDesc flagdesc_mapgen_v6[] = { {"jungles", MGV6_JUNGLES}, {"biomeblend", MGV6_BIOMEBLEND}, {"mudflow", MGV6_MUDFLOW}, + {"snowbiomes", MGV6_SNOWBIOMES}, {NULL, 0} }; @@ -55,6 +54,8 @@ MapgenV6::MapgenV6(int mapgenid, MapgenParams *params, EmergeManager *emerge) this->m_emerge = emerge; this->ystride = csize.X; //////fix this + this->heightmap = new s16[csize.X * csize.Z]; + MapgenV6Params *sp = (MapgenV6Params *)params->sparams; this->spflags = sp->spflags; this->freq_desert = sp->freq_desert; @@ -72,7 +73,10 @@ MapgenV6::MapgenV6(int mapgenid, MapgenParams *params, EmergeManager *emerge) noise_height_select = new Noise(&sp->np_height_select, seed, csize.X, csize.Y); noise_mud = new Noise(&sp->np_mud, seed, csize.X, csize.Y); noise_beach = new Noise(&sp->np_beach, seed, csize.X, csize.Y); - noise_biome = new Noise(&sp->np_biome, seed, csize.X, csize.Y); + noise_biome = new Noise(&sp->np_biome, seed, + csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE); + noise_humidity = new Noise(&sp->np_humidity, seed, + csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE); //// Resolve nodes to be used INodeDefManager *ndef = emerge->ndef; @@ -84,25 +88,33 @@ MapgenV6::MapgenV6(int mapgenid, MapgenParams *params, EmergeManager *emerge) c_water_source = ndef->getId("mapgen_water_source"); c_lava_source = ndef->getId("mapgen_lava_source"); c_gravel = ndef->getId("mapgen_gravel"); - c_cobble = ndef->getId("mapgen_cobble"); - c_desert_sand = ndef->getId("mapgen_desert_sand"); c_desert_stone = ndef->getId("mapgen_desert_stone"); - c_mossycobble = ndef->getId("mapgen_mossycobble"); - c_sandbrick = ndef->getId("mapgen_sandstonebrick"); + c_desert_sand = ndef->getId("mapgen_desert_sand"); + c_dirt_with_snow = ndef->getId("mapgen_dirt_with_snow"); + c_snow = ndef->getId("mapgen_snow"); + c_snowblock = ndef->getId("mapgen_snowblock"); + c_ice = ndef->getId("mapgen_ice"); + + c_cobble = ndef->getId("mapgen_cobble"); c_stair_cobble = ndef->getId("mapgen_stair_cobble"); - c_stair_sandstone = ndef->getId("mapgen_stair_sandstone"); + c_mossycobble = ndef->getId("mapgen_mossycobble"); + if (c_desert_sand == CONTENT_IGNORE) c_desert_sand = c_sand; if (c_desert_stone == CONTENT_IGNORE) c_desert_stone = c_stone; if (c_mossycobble == CONTENT_IGNORE) c_mossycobble = c_cobble; - if (c_sandbrick == CONTENT_IGNORE) - c_sandbrick = c_desert_stone; if (c_stair_cobble == CONTENT_IGNORE) c_stair_cobble = c_cobble; - if (c_stair_sandstone == CONTENT_IGNORE) - c_stair_sandstone = c_sandbrick; + if (c_dirt_with_snow == CONTENT_IGNORE) + c_dirt_with_snow = c_dirt_with_grass; + if (c_snow == CONTENT_IGNORE) + c_snow = CONTENT_AIR; + if (c_snowblock == CONTENT_IGNORE) + c_snowblock = c_dirt_with_grass; + if (c_ice == CONTENT_IGNORE) + c_ice = c_water_source; } @@ -115,6 +127,9 @@ MapgenV6::~MapgenV6() delete noise_mud; delete noise_beach; delete noise_biome; + delete noise_humidity; + + delete[] heightmap; } @@ -124,21 +139,21 @@ MapgenV6Params::MapgenV6Params() freq_desert = 0.45; freq_beach = 0.15; - np_terrain_base = NoiseParams(-4, 20.0, v3f(250.0, 250.0, 250.0), 82341, 5, 0.6, 2.0); - np_terrain_higher = NoiseParams(20, 16.0, v3f(500.0, 500.0, 500.0), 85039, 5, 0.6, 2.0); - np_steepness = NoiseParams(0.85,0.5, v3f(125.0, 125.0, 125.0), -932, 5, 0.7, 2.0); - np_height_select = NoiseParams(0, 1.0, v3f(250.0, 250.0, 250.0), 4213, 5, 0.69, 2.0); - np_mud = NoiseParams(4, 2.0, v3f(200.0, 200.0, 200.0), 91013, 3, 0.55, 2.0); - np_beach = NoiseParams(0, 1.0, v3f(250.0, 250.0, 250.0), 59420, 3, 0.50, 2.0); - np_biome = NoiseParams(0, 1.0, v3f(250.0, 250.0, 250.0), 9130, 3, 0.50, 2.0); - np_cave = NoiseParams(6, 6.0, v3f(250.0, 250.0, 250.0), 34329, 3, 0.50, 2.0); - np_humidity = NoiseParams(0.5, 0.5, v3f(500.0, 500.0, 500.0), 72384, 4, 0.66, 2.0); - np_trees = NoiseParams(0, 1.0, v3f(125.0, 125.0, 125.0), 2, 4, 0.66, 2.0); - np_apple_trees = NoiseParams(0, 1.0, v3f(100.0, 100.0, 100.0), 342902, 3, 0.45, 2.0); + np_terrain_base = NoiseParams(-4, 20.0, v3f(250.0, 250.0, 250.0), 82341, 5, 0.6, 2.0); + np_terrain_higher = NoiseParams(20, 16.0, v3f(500.0, 500.0, 500.0), 85039, 5, 0.6, 2.0); + np_steepness = NoiseParams(0.85, 0.5, v3f(125.0, 125.0, 125.0), -932, 5, 0.7, 2.0); + np_height_select = NoiseParams(0, 1.0, v3f(250.0, 250.0, 250.0), 4213, 5, 0.69, 2.0); + np_mud = NoiseParams(4, 2.0, v3f(200.0, 200.0, 200.0), 91013, 3, 0.55, 2.0); + np_beach = NoiseParams(0, 1.0, v3f(250.0, 250.0, 250.0), 59420, 3, 0.50, 2.0); + np_biome = NoiseParams(0, 1.0, v3f(500.0, 500.0, 500.0), 9130, 3, 0.50, 2.0); + np_cave = NoiseParams(6, 6.0, v3f(250.0, 250.0, 250.0), 34329, 3, 0.50, 2.0); + np_humidity = NoiseParams(0.5, 0.5, v3f(500.0, 500.0, 500.0), 72384, 3, 0.50, 2.0); + np_trees = NoiseParams(0, 1.0, v3f(125.0, 125.0, 125.0), 2, 4, 0.66, 2.0); + np_apple_trees = NoiseParams(0, 1.0, v3f(100.0, 100.0, 100.0), 342902, 3, 0.45, 2.0); } -void MapgenV6Params::readParams(Settings *settings) +void MapgenV6Params::readParams(const Settings *settings) { settings->getFlagStrNoEx("mgv6_spflags", spflags, flagdesc_mapgen_v6); settings->getFloatNoEx("mgv6_freq_desert", freq_desert); @@ -158,7 +173,7 @@ void MapgenV6Params::readParams(Settings *settings) } -void MapgenV6Params::writeParams(Settings *settings) +void MapgenV6Params::writeParams(Settings *settings) const { settings->setFlagStr("mgv6_spflags", spflags, flagdesc_mapgen_v6, (u32)-1); settings->setFloat("mgv6_freq_desert", freq_desert); @@ -191,10 +206,8 @@ s16 MapgenV6::find_stone_level(v2s16 p2d) s16 y; for (y = y_nodes_max; y >= y_nodes_min; y--) { - MapNode &n = vm->m_data[i]; - content_t c = n.getContent(); - if (c != CONTENT_IGNORE && ( - c == c_stone || c == c_desert_stone)) + content_t c = vm->m_data[i].getContent(); + if (c != CONTENT_IGNORE && (c == c_stone || c == c_desert_stone)) break; vm->m_area.add_y(em, i, -1); @@ -211,7 +224,7 @@ bool MapgenV6::block_is_underground(u64 seed, v3s16 blockpos) // Nah, this is just a heuristic, just return something s16 minimum_groundlevel = water_level; - if(blockpos.Y*MAP_BLOCKSIZE + MAP_BLOCKSIZE <= minimum_groundlevel) + if(blockpos.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE <= minimum_groundlevel) return true; else return false; @@ -263,7 +276,7 @@ float MapgenV6::baseTerrainLevelFromNoise(v2s16 p) p.X, 0.5, p.Y, 0.5, seed); return baseTerrainLevel(terrain_base, terrain_higher, - steepness, height_select); + steepness, height_select); } @@ -285,7 +298,7 @@ float MapgenV6::baseTerrainLevelFromMap(int index) float height_select = noise_height_select->result[index]; return baseTerrainLevel(terrain_base, terrain_higher, - steepness, height_select); + steepness, height_select); } @@ -319,7 +332,8 @@ bool MapgenV6::getHaveBeach(v2s16 p) BiomeV6Type MapgenV6::getBiome(v2s16 p) { - int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); + int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE) + + (p.X - full_node_min.X); return getBiome(index, p); } @@ -331,7 +345,9 @@ float MapgenV6::getHumidity(v2s16 p) seed+72384, 4, 0.66); noise = (noise + 1.0)/2.0;*/ - float noise = NoisePerlin2D(np_humidity, p.X, p.Y, seed); + int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE) + + (p.X - full_node_min.X); + float noise = noise_humidity->result[index]; if (noise < 0.0) noise = 0.0; @@ -352,7 +368,7 @@ float MapgenV6::getTreeAmount(v2s16 p) if (noise < zeroval) return 0; else - return 0.04 * (noise-zeroval) / (1.0-zeroval); + return 0.04 * (noise - zeroval) / (1.0 - zeroval); } @@ -401,22 +417,44 @@ BiomeV6Type MapgenV6::getBiome(int index, v2s16 p) seed+9130, 3, 0.50);*/ float d = noise_biome->result[index]; - if (d > freq_desert) - return BT_DESERT; - - if ((spflags & MGV6_BIOMEBLEND) && - (d > freq_desert - 0.10) && - ((noise2d(p.X, p.Y, seed) + 1.0) > (freq_desert - d) * 20.0)) - return BT_DESERT; - - return BT_NORMAL; + float h = noise_humidity->result[index]; + + if (spflags & MGV6_SNOWBIOMES) { + float blend = (spflags & MGV6_BIOMEBLEND) ? noise2d(p.X, p.Y, seed) / 40 : 0; + + if (d > FREQ_HOT + blend) { + if (h > FREQ_JUNGLE + blend) + return BT_JUNGLE; + else + return BT_DESERT; + } else if (d < FREQ_SNOW + blend) { + if (h > FREQ_TAIGA + blend) + return BT_TAIGA; + else + return BT_TUNDRA; + } else { + return BT_NORMAL; + } + } else { + if (d > freq_desert) + return BT_DESERT; + + if ((spflags & MGV6_BIOMEBLEND) && (d > freq_desert - 0.10) && + ((noise2d(p.X, p.Y, seed) + 1.0) > (freq_desert - d) * 20.0)) + return BT_DESERT; + + if ((spflags & MGV6_JUNGLES) && h > 0.75) + return BT_JUNGLE; + else + return BT_NORMAL; + } } u32 MapgenV6::get_blockseed(u64 seed, v3s16 p) { - s32 x=p.X, y=p.Y, z=p.Z; - return (u32)(seed%0x100000000ULL) + z*38134234 + y*42123 + x*23; + s32 x = p.X, y = p.Y, z = p.Z; + return (u32)(seed % 0x100000000ULL) + z * 38134234 + y * 42123 + x * 23; } @@ -424,6 +462,7 @@ u32 MapgenV6::get_blockseed(u64 seed, v3s16 p) void MapgenV6::makeChunk(BlockMakeData *data) { + // Pre-conditions assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && @@ -442,14 +481,14 @@ void MapgenV6::makeChunk(BlockMakeData *data) v3s16 blockpos_max = data->blockpos_max; // Area of central chunk - node_min = blockpos_min*MAP_BLOCKSIZE; - node_max = (blockpos_max+v3s16(1,1,1))*MAP_BLOCKSIZE-v3s16(1,1,1); + node_min = blockpos_min * MAP_BLOCKSIZE; + node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1); // Full allocated area - full_node_min = (blockpos_min-1)*MAP_BLOCKSIZE; - full_node_max = (blockpos_max+2)*MAP_BLOCKSIZE-v3s16(1,1,1); + full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE; + full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1); - central_area_size = node_max - node_min + v3s16(1,1,1); + central_area_size = node_max - node_min + v3s16(1, 1, 1); assert(central_area_size.X == central_area_size.Z); int volume_blocks = (blockpos_max.X - blockpos_min.X + 1) @@ -472,7 +511,8 @@ void MapgenV6::makeChunk(BlockMakeData *data) // Generate general ground level to full area stone_surface_max_y = generateGround(); - generateExperimental(); + // Create initial heightmap to limit caves + updateHeightmap(node_min, node_max); const s16 max_spread_amount = MAP_BLOCKSIZE; // Limit dirt flow area by 1 because mud is flown into neighbors. @@ -489,15 +529,15 @@ void MapgenV6::makeChunk(BlockMakeData *data) // Add mud to the central chunk addMud(); - // Add blobs of dirt and gravel underground - addDirtGravelBlobs(); - // Flow mud away from steep edges if (spflags & MGV6_MUDFLOW) flowMud(mudflow_minpos, mudflow_maxpos); } + // Update heightmap after mudflow + updateHeightmap(node_min, node_max); + // Add dungeons if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) { DungeonParams dp; @@ -505,27 +545,27 @@ void MapgenV6::makeChunk(BlockMakeData *data) dp.np_rarity = nparams_dungeon_rarity; dp.np_density = nparams_dungeon_density; dp.np_wetness = nparams_dungeon_wetness; - dp.c_water = c_water_source; - if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_NORMAL) { - dp.c_cobble = c_cobble; - dp.c_moss = c_mossycobble; - dp.c_stair = c_stair_cobble; + dp.c_water = c_water_source; + if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) { + dp.c_cobble = c_desert_stone; + dp.c_moss = c_desert_stone; + dp.c_stair = c_desert_stone; - dp.diagonal_dirs = false; - dp.mossratio = 3.0; - dp.holesize = v3s16(1, 2, 1); - dp.roomsize = v3s16(0, 0, 0); - dp.notifytype = GENNOTIFY_DUNGEON; + dp.diagonal_dirs = true; + dp.mossratio = 0.0; + dp.holesize = v3s16(2, 3, 2); + dp.roomsize = v3s16(2, 5, 2); + dp.notifytype = GENNOTIFY_TEMPLE; } else { - dp.c_cobble = c_sandbrick; - dp.c_moss = c_sandbrick; // should make this 'cracked sandstone' later - dp.c_stair = c_stair_sandstone; + dp.c_cobble = c_cobble; + dp.c_moss = c_mossycobble; + dp.c_stair = c_stair_cobble; - dp.diagonal_dirs = true; - dp.mossratio = 0.0; - dp.holesize = v3s16(2, 3, 2); - dp.roomsize = v3s16(2, 5, 2); - dp.notifytype = GENNOTIFY_TEMPLE; + dp.diagonal_dirs = false; + dp.mossratio = 3.0; + dp.holesize = v3s16(1, 2, 1); + dp.roomsize = v3s16(0, 0, 0); + dp.notifytype = GENNOTIFY_DUNGEON; } DungeonGen dgen(this, &dp); @@ -535,7 +575,7 @@ void MapgenV6::makeChunk(BlockMakeData *data) // Add top and bottom side of water to transforming_liquid queue updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); - // Grow grass + // Add surface nodes growGrass(); // Generate some trees, and add grass, if a jungle @@ -560,6 +600,8 @@ void MapgenV6::calculateNoise() { int x = node_min.X; int z = node_min.Z; + int fx = full_node_min.X; + int fz = full_node_min.Z; if (!(flags & MG_FLAT)) { noise_terrain_base->perlinMap2D_PO(x, 0.5, z, 0.5); @@ -570,7 +612,11 @@ void MapgenV6::calculateNoise() } noise_beach->perlinMap2D_PO(x, 0.2, z, 0.7); - noise_biome->perlinMap2D_PO(x, 0.6, z, 0.2); + + noise_biome->perlinMap2D_PO(fx, 0.6, fz, 0.2); + noise_humidity->perlinMap2D_PO(fx, 0.0, fz, 0.0); + // Humidity map does not need range limiting 0 to 1, + // only humidity at point does } @@ -579,9 +625,10 @@ int MapgenV6::generateGround() //TimeTaker timer1("Generating ground level"); MapNode n_air(CONTENT_AIR), n_water_source(c_water_source); MapNode n_stone(c_stone), n_desert_stone(c_desert_stone); - int stone_surface_max_y = -MAP_GENERATION_LIMIT; - u32 index = 0; + MapNode n_ice(c_ice); + int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; + u32 index = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, index++) { // Surface height @@ -591,7 +638,7 @@ int MapgenV6::generateGround() if (surface_y > stone_surface_max_y) stone_surface_max_y = surface_y; - BiomeV6Type bt = getBiome(index, v2s16(x, z)); + BiomeV6Type bt = getBiome(v2s16(x, z)); // Fill ground with stone v3s16 em = vm->m_area.getExtent(); @@ -599,10 +646,13 @@ int MapgenV6::generateGround() for (s16 y = node_min.Y; y <= node_max.Y; y++) { if (vm->m_data[i].getContent() == CONTENT_IGNORE) { if (y <= surface_y) { - vm->m_data[i] = (y > water_level && bt == BT_DESERT) ? + vm->m_data[i] = (y >= DESERT_STONE_BASE + && bt == BT_DESERT) ? n_desert_stone : n_stone; } else if (y <= water_level) { - vm->m_data[i] = n_water_source; + vm->m_data[i] = (y >= ICE_BASE + && bt == BT_TUNDRA) ? + n_ice : n_water_source; } else { vm->m_data[i] = n_air; } @@ -636,7 +686,7 @@ void MapgenV6::addMud() if (surface_y == vm->m_area.MinEdge.Y - 1) continue; - BiomeV6Type bt = getBiome(index, v2s16(x, z)); + BiomeV6Type bt = getBiome(v2s16(x, z)); addnode = (bt == BT_DESERT) ? n_desert_sand : n_dirt; if (bt == BT_DESERT && surface_y + mud_add_amount <= water_level + 1) { @@ -644,25 +694,25 @@ void MapgenV6::addMud() } else if (mud_add_amount <= 0) { mud_add_amount = 1 - mud_add_amount; addnode = n_gravel; - } else if (bt == BT_NORMAL && getHaveBeach(index) && + } else if (bt != BT_DESERT && getHaveBeach(index) && surface_y + mud_add_amount <= water_level + 2) { addnode = n_sand; } - if (bt == BT_DESERT && surface_y > 20) + if ((bt == BT_DESERT || bt == BT_TUNDRA) && surface_y > 20) mud_add_amount = MYMAX(0, mud_add_amount - (surface_y - 20) / 5); - // If topmost node is grass, change it to mud. It might be if it was + /* If topmost node is grass, change it to mud. It might be if it was // flown to there from a neighboring chunk and then converted. u32 i = vm->m_area.index(x, surface_y, z); if (vm->m_data[i].getContent() == c_dirt_with_grass) - vm->m_data[i] = n_dirt; + vm->m_data[i] = n_dirt;*/ // Add mud on ground s16 mudcount = 0; v3s16 em = vm->m_area.getExtent(); s16 y_start = surface_y + 1; - i = vm->m_area.index(x, y_start, z); + u32 i = vm->m_area.index(x, y_start, z); for (s16 y = y_start; y <= node_max.Y; y++) { if (mudcount >= mud_add_amount) break; @@ -679,10 +729,10 @@ void MapgenV6::addMud() void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) { // 340ms @cs=8 - TimeTaker timer1("flow mud"); + //TimeTaker timer1("flow mud"); // Iterate a few times - for(s16 k = 0; k < 3; k++) { + for (s16 k = 0; k < 3; k++) { for (s16 z = mudflow_minpos; z <= mudflow_maxpos; z++) for (s16 x = mudflow_minpos; x <= mudflow_maxpos; x++) { // Invert coordinates every 2nd iteration @@ -698,18 +748,16 @@ void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) u32 i = vm->m_area.index(p2d.X, node_max.Y, p2d.Y); s16 y = node_max.Y; - while(y >= node_min.Y) - { + while (y >= node_min.Y) { - for(;; y--) - { + for (;; y--) { MapNode *n = NULL; // Find mud - for(; y >= node_min.Y; y--) { + 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) + n->getContent() == c_dirt_with_grass || + n->getContent() == c_gravel) break; vm->m_area.add_y(em, i, -1); @@ -721,8 +769,7 @@ void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) break; if (n->getContent() == c_dirt || - n->getContent() == c_dirt_with_grass) - { + n->getContent() == c_dirt_with_grass) { // Make it exactly mud n->setContent(c_dirt); @@ -731,20 +778,20 @@ void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) u32 i2 = i; vm->m_area.add_y(em, i2, -1); // Cancel if out of area - if(vm->m_area.contains(i2) == false) + if (vm->m_area.contains(i2) == false) continue; MapNode *n2 = &vm->m_data[i2]; if (n2->getContent() != c_dirt && - n2->getContent() != c_dirt_with_grass) + n2->getContent() != c_dirt_with_grass) continue; } } v3s16 dirs4[4] = { - v3s16(0,0,1), // back - v3s16(1,0,0), // right - v3s16(0,0,-1), // front - v3s16(-1,0,0), // left + v3s16(0, 0, 1), // back + v3s16(1, 0, 0), // right + v3s16(0, 0, -1), // front + v3s16(-1, 0, 0), // left }; // Check that upper is air or doesn't exist. @@ -752,11 +799,11 @@ void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) u32 i3 = i; vm->m_area.add_y(em, i3, 1); if (vm->m_area.contains(i3) == true && - ndef->get(vm->m_data[i3]).walkable) + ndef->get(vm->m_data[i3]).walkable) continue; // Drop mud on side - for(u32 di=0; di<4; di++) { + for(u32 di = 0; di < 4; di++) { v3s16 dirp = dirs4[di]; u32 i2 = i; // Move to side @@ -782,7 +829,7 @@ void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) n2 = &vm->m_data[i2]; // if out of known area if(vm->m_area.contains(i2) == false || - n2->getContent() == CONTENT_IGNORE) { + n2->getContent() == CONTENT_IGNORE) { dropped_to_unknown = true; break; } @@ -812,45 +859,6 @@ void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) } -void MapgenV6::addDirtGravelBlobs() -{ - if (getBiome(v2s16(node_min.X, node_min.Z)) != BT_NORMAL) - return; - - PseudoRandom pr(blockseed + 983); - for (int i = 0; i < volume_nodes/10/10/10; i++) { - bool only_fill_cave = (myrand_range(0,1) != 0); - v3s16 size( - pr.range(1, 8), - pr.range(1, 8), - pr.range(1, 8) - ); - v3s16 p0( - pr.range(node_min.X, node_max.X) - size.X / 2, - pr.range(node_min.Y, node_max.Y) - size.Y / 2, - pr.range(node_min.Z, node_max.Z) - size.Z / 2 - ); - - MapNode n1((p0.Y > -32 && !pr.range(0, 1)) ? c_dirt : c_gravel); - for (int z1 = 0; z1 < size.Z; z1++) - for (int y1 = 0; y1 < size.Y; y1++) - for (int x1 = 0; x1 < size.X; x1++) { - v3s16 p = p0 + v3s16(x1, y1, z1); - u32 i = vm->m_area.index(p); - if (!vm->m_area.contains(i)) - continue; - // Cancel if not stone and not cave air - if (vm->m_data[i].getContent() != c_stone && - !(vm->m_flags[i] & VMANIP_FLAG_CAVE)) - continue; - if (only_fill_cave && !(vm->m_flags[i] & VMANIP_FLAG_CAVE)) - continue; - vm->m_data[i] = n1; - } - } -} - - void MapgenV6::placeTreesAndJungleGrass() { //TimeTaker t("placeTrees"); @@ -890,28 +898,30 @@ void MapgenV6::placeTreesAndJungleGrass() node_min.Z + sidelen + sidelen * z0 - 1 ); - // Amount of trees, jungle area - u32 tree_count = area * getTreeAmount(p2d_center); + // Get biome at center position of part of division + BiomeV6Type bt = getBiome(p2d_center); - float humidity; - bool is_jungle = false; - if (spflags & MGV6_JUNGLES) { - humidity = getHumidity(p2d_center); - if (humidity > 0.75) { - is_jungle = true; + // Amount of trees + u32 tree_count; + if (bt == BT_JUNGLE || bt == BT_TAIGA || bt == BT_NORMAL) { + tree_count = area * getTreeAmount(p2d_center); + if (bt == BT_JUNGLE) tree_count *= 4; - } + } else { + tree_count = 0; } // Add jungle grass - if (is_jungle) { + if (bt == BT_JUNGLE) { + float humidity = getHumidity(p2d_center); u32 grass_count = 5 * humidity * tree_count; for (u32 i = 0; i < grass_count; i++) { s16 x = grassrandom.range(p2d_min.X, p2d_max.X); s16 z = grassrandom.range(p2d_min.Y, p2d_max.Y); - - s16 y = findGroundLevelFull(v2s16(x, z)); ////////////////optimize this! - if (y < water_level || y < node_min.Y || y > node_max.Y) + int mapindex = central_area_size.X * (z - node_min.Z) + + (x - node_min.X); + s16 y = heightmap[mapindex]; + if (y < water_level) continue; u32 vi = vm->m_area.index(x, y, z); @@ -927,29 +937,35 @@ void MapgenV6::placeTreesAndJungleGrass() for (u32 i = 0; i < tree_count; i++) { s16 x = myrand_range(p2d_min.X, p2d_max.X); s16 z = myrand_range(p2d_min.Y, p2d_max.Y); - s16 y = findGroundLevelFull(v2s16(x, z)); ////////////////////optimize this! + int mapindex = central_area_size.X * (z - node_min.Z) + + (x - node_min.X); + s16 y = heightmap[mapindex]; // Don't make a tree under water level // Don't make a tree so high that it doesn't fit - if(y < water_level || y > node_max.Y - 6) + if (y < water_level || y > node_max.Y - 6) continue; - v3s16 p(x,y,z); - // Trees grow only on mud and grass + v3s16 p(x, y, z); + // Trees grow only on mud and grass and snowblock { u32 i = vm->m_area.index(p); - MapNode *n = &vm->m_data[i]; - if (n->getContent() != c_dirt && - n->getContent() != c_dirt_with_grass) + content_t c = vm->m_data[i].getContent(); + if (c != c_dirt && + c != c_dirt_with_grass && + c != c_dirt_with_snow && + c != c_snowblock) continue; } p.Y++; // Make a tree - if (is_jungle) { + if (bt == BT_JUNGLE) { treegen::make_jungletree(*vm, p, ndef, myrand()); - } else { + } else if (bt == BT_TAIGA) { + treegen::make_pine_tree(*vm, p - v3s16(0, 1, 0), ndef, myrand()); + } else if (bt == BT_NORMAL) { bool is_apple_tree = (myrand_range(0, 3) == 0) && - getHaveAppleTree(v2s16(x, z)); + getHaveAppleTree(v2s16(x, z)); treegen::make_tree(*vm, p, is_apple_tree, ndef, myrand()); } } @@ -958,32 +974,54 @@ void MapgenV6::placeTreesAndJungleGrass() } -void MapgenV6::growGrass() +void MapgenV6::growGrass() // Add surface nodes { + MapNode n_dirt_with_grass(c_dirt_with_grass); + MapNode n_dirt_with_snow(c_dirt_with_snow); + MapNode n_snowblock(c_snowblock); + MapNode n_snow(c_snow); + v3s16 em = vm->m_area.getExtent(); + + u32 index = 0; for (s16 z = full_node_min.Z; z <= full_node_max.Z; z++) - for (s16 x = full_node_min.X; x <= full_node_max.X; x++) { + for (s16 x = full_node_min.X; x <= full_node_max.X; x++, index++) { // Find the lowest surface to which enough light ends up to make // grass grow. Basically just wait until not air and not leaves. s16 surface_y = 0; { - v3s16 em = vm->m_area.getExtent(); u32 i = vm->m_area.index(x, node_max.Y, z); s16 y; // Go to ground level for (y = node_max.Y; y >= full_node_min.Y; y--) { MapNode &n = vm->m_data[i]; if (ndef->get(n).param_type != CPT_LIGHT || - ndef->get(n).liquid_type != LIQUID_NONE) + ndef->get(n).liquid_type != LIQUID_NONE || + n.getContent() == c_ice) break; vm->m_area.add_y(em, i, -1); } surface_y = (y >= full_node_min.Y) ? y : full_node_min.Y; } + BiomeV6Type bt = getBiome(index, v2s16(x, z)); u32 i = vm->m_area.index(x, surface_y, z); - MapNode *n = &vm->m_data[i]; - if (n->getContent() == c_dirt && surface_y >= water_level - 20) - n->setContent(c_dirt_with_grass); + content_t c = vm->m_data[i].getContent(); + if (surface_y >= water_level - 20) { + if (bt == BT_TAIGA && c == c_dirt) { + vm->m_data[i] = n_snowblock; + vm->m_area.add_y(em, i, -1); + vm->m_data[i] = n_dirt_with_snow; + } else if (bt == BT_TUNDRA) { + if (c == c_dirt) { + vm->m_data[i] = n_dirt_with_snow; + } else if (c == c_stone && surface_y < node_max.Y) { + vm->m_area.add_y(em, i, 1); + vm->m_data[i] = n_snow; + } + } else if (c == c_dirt) { + vm->m_data[i] = n_dirt_with_grass; + } + } } } diff --git a/src/mapgen_v6.h b/src/mapgen_v6.h index 64aa2d87a..c71cf3c53 100644 --- a/src/mapgen_v6.h +++ b/src/mapgen_v6.h @@ -24,11 +24,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "noise.h" #define AVERAGE_MUD_AMOUNT 4 - -/////////////////// Mapgen V6 flags +#define DESERT_STONE_BASE -32 +#define ICE_BASE 0 +#define FREQ_HOT 0.4 +#define FREQ_SNOW -0.4 +#define FREQ_TAIGA 0.5 +#define FREQ_JUNGLE 0.5 + +//////////// Mapgen V6 flags #define MGV6_JUNGLES 0x01 #define MGV6_BIOMEBLEND 0x02 #define MGV6_MUDFLOW 0x04 +#define MGV6_SNOWBIOMES 0x08 extern FlagDesc flagdesc_mapgen_v6[]; @@ -37,9 +44,13 @@ extern FlagDesc flagdesc_mapgen_v6[]; enum BiomeV6Type { BT_NORMAL, - BT_DESERT + BT_DESERT, + BT_JUNGLE, + BT_TUNDRA, + BT_TAIGA, }; + struct MapgenV6Params : public MapgenSpecificParams { u32 spflags; float freq_desert; @@ -59,10 +70,11 @@ struct MapgenV6Params : public MapgenSpecificParams { MapgenV6Params(); ~MapgenV6Params() {} - void readParams(Settings *settings); - void writeParams(Settings *settings); + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; }; + class MapgenV6 : public Mapgen { public: EmergeManager *m_emerge; @@ -84,6 +96,7 @@ public: Noise *noise_mud; Noise *noise_beach; Noise *noise_biome; + Noise *noise_humidity; NoiseParams *np_cave; NoiseParams *np_humidity; NoiseParams *np_trees; @@ -98,14 +111,16 @@ public: content_t c_water_source; content_t c_lava_source; content_t c_gravel; - content_t c_cobble; - content_t c_desert_sand; content_t c_desert_stone; + content_t c_desert_sand; + content_t c_dirt_with_snow; + content_t c_snow; + content_t c_snowblock; + content_t c_ice; + content_t c_cobble; content_t c_mossycobble; - content_t c_sandbrick; content_t c_stair_cobble; - content_t c_stair_sandstone; MapgenV6(int mapgenid, MapgenParams *params, EmergeManager *emerge); ~MapgenV6(); @@ -139,13 +154,12 @@ public: int generateGround(); void addMud(); void flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos); - void addDirtGravelBlobs(); void growGrass(); void placeTreesAndJungleGrass(); virtual void generateCaves(int max_stone_y); - virtual void generateExperimental() {} }; + struct MapgenFactoryV6 : public MapgenFactory { Mapgen *createMapgen(int mgid, MapgenParams *params, EmergeManager *emerge) { @@ -158,4 +172,5 @@ struct MapgenFactoryV6 : public MapgenFactory { }; }; + #endif diff --git a/src/mapgen_v7.cpp b/src/mapgen_v7.cpp index a7b9076b3..9f612de81 100644 --- a/src/mapgen_v7.cpp +++ b/src/mapgen_v7.cpp @@ -27,9 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_sao.h" #include "nodedef.h" #include "voxelalgorithms.h" -#include "profiler.h" #include "settings.h" // For g_settings -#include "main.h" // For g_profiler #include "emerge.h" #include "dungeongen.h" #include "cavegen.h" @@ -58,10 +56,12 @@ MapgenV7::MapgenV7(int mapgenid, MapgenParams *params, EmergeManager *emerge) //// amount of elements to skip for the next index //// for noise/height/biome maps (not vmanip) this->ystride = csize.X; - this->zstride = csize.X * csize.Y; + this->zstride = csize.X * (csize.Y + 2); - this->biomemap = new u8[csize.X * csize.Z]; - this->heightmap = new s16[csize.X * csize.Z]; + this->biomemap = new u8[csize.X * csize.Z]; + this->heightmap = new s16[csize.X * csize.Z]; + this->heatmap = NULL; + this->humidmap = NULL; this->ridge_heightmap = new s16[csize.X * csize.Z]; MapgenV7Params *sp = (MapgenV7Params *)params->sparams; @@ -77,27 +77,43 @@ MapgenV7::MapgenV7(int mapgenid, MapgenParams *params, EmergeManager *emerge) noise_ridge_uwater = new Noise(&sp->np_ridge_uwater, seed, csize.X, csize.Z); //// 3d terrain noise - noise_mountain = new Noise(&sp->np_mountain, seed, csize.X, csize.Y, csize.Z); - noise_ridge = new Noise(&sp->np_ridge, seed, csize.X, csize.Y, csize.Z); - noise_cave1 = new Noise(&sp->np_cave1, seed, csize.X, csize.Y, csize.Z); - noise_cave2 = new Noise(&sp->np_cave2, seed, csize.X, csize.Y, csize.Z); + noise_mountain = new Noise(&sp->np_mountain, seed, csize.X, csize.Y + 2, csize.Z); + noise_ridge = new Noise(&sp->np_ridge, seed, csize.X, csize.Y + 2, csize.Z); + noise_cave1 = new Noise(&sp->np_cave1, seed, csize.X, csize.Y + 2, csize.Z); + noise_cave2 = new Noise(&sp->np_cave2, seed, csize.X, csize.Y + 2, csize.Z); //// Biome noise - noise_heat = new Noise(¶ms->np_biome_heat, seed, csize.X, csize.Z); - noise_humidity = new Noise(¶ms->np_biome_humidity, seed, csize.X, csize.Z); + noise_heat = new Noise(¶ms->np_biome_heat, seed, csize.X, csize.Z); + noise_humidity = new Noise(¶ms->np_biome_humidity, seed, csize.X, csize.Z); + noise_heat_blend = new Noise(¶ms->np_biome_heat_blend, seed, csize.X, csize.Z); + noise_humidity_blend = new Noise(¶ms->np_biome_humidity_blend, seed, csize.X, csize.Z); //// Resolve nodes to be used INodeDefManager *ndef = emerge->ndef; - c_stone = ndef->getId("mapgen_stone"); - c_dirt = ndef->getId("mapgen_dirt"); - c_dirt_with_grass = ndef->getId("mapgen_dirt_with_grass"); - c_sand = ndef->getId("mapgen_sand"); - c_water_source = ndef->getId("mapgen_water_source"); - c_lava_source = ndef->getId("mapgen_lava_source"); - c_ice = ndef->getId("default:ice"); + c_stone = ndef->getId("mapgen_stone"); + c_water_source = ndef->getId("mapgen_water_source"); + c_lava_source = ndef->getId("mapgen_lava_source"); + c_desert_stone = ndef->getId("mapgen_desert_stone"); + c_ice = ndef->getId("mapgen_ice"); + c_sandstone = ndef->getId("mapgen_sandstone"); + + c_cobble = ndef->getId("mapgen_cobble"); + c_stair_cobble = ndef->getId("mapgen_stair_cobble"); + c_mossycobble = ndef->getId("mapgen_mossycobble"); + c_sandstonebrick = ndef->getId("mapgen_sandstonebrick"); + c_stair_sandstonebrick = ndef->getId("mapgen_stair_sandstonebrick"); + if (c_ice == CONTENT_IGNORE) c_ice = CONTENT_AIR; + if (c_mossycobble == CONTENT_IGNORE) + c_mossycobble = c_cobble; + if (c_stair_cobble == CONTENT_IGNORE) + c_stair_cobble = c_cobble; + if (c_sandstonebrick == CONTENT_IGNORE) + c_sandstonebrick = c_sandstone; + if (c_stair_sandstonebrick == CONTENT_IGNORE) + c_stair_sandstonebrick = c_sandstone; } @@ -117,6 +133,8 @@ MapgenV7::~MapgenV7() delete noise_heat; delete noise_humidity; + delete noise_heat_blend; + delete noise_humidity_blend; delete[] ridge_heightmap; delete[] heightmap; @@ -128,21 +146,21 @@ MapgenV7Params::MapgenV7Params() { spflags = MGV7_MOUNTAINS | MGV7_RIDGES; - np_terrain_base = NoiseParams(4, 70, v3f(300, 300, 300), 82341, 6, 0.7, 2.0); - np_terrain_alt = NoiseParams(4, 25, v3f(600, 600, 600), 5934, 5, 0.6, 2.0); - np_terrain_persist = NoiseParams(0.6, 0.1, v3f(500, 500, 500), 539, 3, 0.6, 2.0); - np_height_select = NoiseParams(-0.5, 1, v3f(250, 250, 250), 4213, 5, 0.69, 2.0); - np_filler_depth = NoiseParams(0, 1.2, v3f(150, 150, 150), 261, 4, 0.7, 2.0); - np_mount_height = NoiseParams(100, 30, v3f(500, 500, 500), 72449, 4, 0.6, 2.0); - np_ridge_uwater = NoiseParams(0, 1, v3f(500, 500, 500), 85039, 4, 0.6, 2.0); - np_mountain = NoiseParams(-0.6, 1, v3f(250, 350, 250), 5333, 5, 0.68, 2.0); - np_ridge = NoiseParams(0, 1, v3f(100, 100, 100), 6467, 4, 0.75, 2.0); - np_cave1 = NoiseParams(0, 12, v3f(100, 100, 100), 52534, 4, 0.5, 2.0); - np_cave2 = NoiseParams(0, 12, v3f(100, 100, 100), 10325, 4, 0.5, 2.0); + np_terrain_base = NoiseParams(4, 70, v3f(600, 600, 600), 82341, 5, 0.6, 2.0); + np_terrain_alt = NoiseParams(4, 25, v3f(600, 600, 600), 5934, 5, 0.6, 2.0); + np_terrain_persist = NoiseParams(0.6, 0.1, v3f(2000, 2000, 2000), 539, 3, 0.6, 2.0); + np_height_select = NoiseParams(-12, 24, v3f(500, 500, 500), 4213, 6, 0.7, 2.0); + np_filler_depth = NoiseParams(0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0); + np_mount_height = NoiseParams(256, 112, v3f(1000, 1000, 1000), 72449, 3, 0.6, 2.0); + np_ridge_uwater = NoiseParams(0, 1, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0); + np_mountain = NoiseParams(-0.6, 1, v3f(250, 350, 250), 5333, 5, 0.63, 2.0); + np_ridge = NoiseParams(0, 1, v3f(100, 100, 100), 6467, 4, 0.75, 2.0); + np_cave1 = NoiseParams(0, 12, v3f(100, 100, 100), 52534, 4, 0.5, 2.0); + np_cave2 = NoiseParams(0, 12, v3f(100, 100, 100), 10325, 4, 0.5, 2.0); } -void MapgenV7Params::readParams(Settings *settings) +void MapgenV7Params::readParams(const Settings *settings) { settings->getFlagStrNoEx("mgv7_spflags", spflags, flagdesc_mapgen_v7); @@ -160,7 +178,7 @@ void MapgenV7Params::readParams(Settings *settings) } -void MapgenV7Params::writeParams(Settings *settings) +void MapgenV7Params::writeParams(Settings *settings) const { settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7, (u32)-1); @@ -210,14 +228,15 @@ int MapgenV7::getGroundLevelAtPoint(v2s16 p) void MapgenV7::makeChunk(BlockMakeData *data) { + // Pre-conditions assert(data->vmanip); assert(data->nodedef); assert(data->blockpos_requested.X >= data->blockpos_min.X && - data->blockpos_requested.Y >= data->blockpos_min.Y && - data->blockpos_requested.Z >= data->blockpos_min.Z); + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); assert(data->blockpos_requested.X <= data->blockpos_max.X && - data->blockpos_requested.Y <= data->blockpos_max.Y && - data->blockpos_requested.Z <= data->blockpos_max.Z); + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); this->generating = true; this->vm = data->vmanip; @@ -239,20 +258,59 @@ void MapgenV7::makeChunk(BlockMakeData *data) // Generate base terrain, mountains, and ridges with initial heightmaps s16 stone_surface_max_y = generateTerrain(); + // Create heightmap updateHeightmap(node_min, node_max); - // Calculate biomes + // Create biomemap at heightmap surface bmgr->calcBiomes(csize.X, csize.Z, noise_heat->result, noise_humidity->result, heightmap, biomemap); - // Actually place the biome-specific nodes and what not - generateBiomes(); + // Actually place the biome-specific nodes + MgStoneType stone_type = generateBiomes(noise_heat->result, noise_humidity->result); if (flags & MG_CAVES) generateCaves(stone_surface_max_y); if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) { - DungeonGen dgen(this, NULL); + DungeonParams dp; + + dp.np_rarity = nparams_dungeon_rarity; + dp.np_density = nparams_dungeon_density; + dp.np_wetness = nparams_dungeon_wetness; + dp.c_water = c_water_source; + if (stone_type == STONE) { + dp.c_cobble = c_cobble; + dp.c_moss = c_mossycobble; + dp.c_stair = c_stair_cobble; + + dp.diagonal_dirs = false; + dp.mossratio = 3.0; + dp.holesize = v3s16(1, 2, 1); + dp.roomsize = v3s16(0, 0, 0); + dp.notifytype = GENNOTIFY_DUNGEON; + } else if (stone_type == DESERT_STONE) { + dp.c_cobble = c_desert_stone; + dp.c_moss = c_desert_stone; + dp.c_stair = c_desert_stone; + + dp.diagonal_dirs = true; + dp.mossratio = 0.0; + dp.holesize = v3s16(2, 3, 2); + dp.roomsize = v3s16(2, 5, 2); + dp.notifytype = GENNOTIFY_TEMPLE; + } else if (stone_type == SANDSTONE) { + dp.c_cobble = c_sandstonebrick; + dp.c_moss = c_sandstonebrick; + dp.c_stair = c_sandstonebrick; + + dp.diagonal_dirs = false; + dp.mossratio = 0.0; + dp.holesize = v3s16(2, 2, 2); + dp.roomsize = v3s16(2, 0, 2); + dp.notifytype = GENNOTIFY_DUNGEON; + } + + DungeonGen dgen(this, &dp); dgen.generate(blockseed, full_node_min, full_node_max); } @@ -270,7 +328,9 @@ void MapgenV7::makeChunk(BlockMakeData *data) updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); if (flags & MG_LIGHT) - calcLighting(node_min, node_max); + 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); @@ -282,7 +342,7 @@ void MapgenV7::calculateNoise() { //TimeTaker t("calculateNoise", NULL, PRECISION_MICRO); int x = node_min.X; - int y = node_min.Y; + int y = node_min.Y - 1; int z = node_min.Z; noise_terrain_persist->perlinMap2D(x, z); @@ -302,31 +362,38 @@ void MapgenV7::calculateNoise() noise_ridge_uwater->perlinMap2D(x, z); } - if ((spflags & MGV7_MOUNTAINS) && node_max.Y >= 0) { - noise_mountain->perlinMap3D(x, y, z); - noise_mount_height->perlinMap2D(x, z); - } + // Mountain noises are calculated in generateMountainTerrain() - if (node_max.Y >= water_level) { - noise_filler_depth->perlinMap2D(x, z); - noise_heat->perlinMap2D(x, z); - noise_humidity->perlinMap2D(x, z); + noise_filler_depth->perlinMap2D(x, z); + noise_heat->perlinMap2D(x, z); + noise_humidity->perlinMap2D(x, z); + noise_heat_blend->perlinMap2D(x, z); + noise_humidity_blend->perlinMap2D(x, z); + + for (s32 i = 0; i < csize.X * csize.Z; i++) { + noise_heat->result[i] += noise_heat_blend->result[i]; + noise_humidity->result[i] += noise_humidity_blend->result[i]; } + + heatmap = noise_heat->result; + humidmap = noise_humidity->result; //printf("calculateNoise: %dus\n", t.stop()); } Biome *MapgenV7::getBiomeAtPoint(v3s16 p) { - float heat = NoisePerlin2D(&noise_heat->np, p.X, p.Z, seed); - float humidity = NoisePerlin2D(&noise_humidity->np, p.X, p.Z, seed); + float heat = NoisePerlin2D(&noise_heat->np, p.X, p.Z, seed) + + NoisePerlin2D(&noise_heat_blend->np, p.X, p.Z, seed); + float humidity = NoisePerlin2D(&noise_humidity->np, p.X, p.Z, seed) + + NoisePerlin2D(&noise_humidity_blend->np, p.X, p.Z, seed); s16 groundlevel = baseTerrainLevelAtPoint(p.X, p.Z); return bmgr->getBiome(heat, humidity, groundlevel); } //needs to be updated -float MapgenV7::baseTerrainLevelAtPoint(int x, int z) +float MapgenV7::baseTerrainLevelAtPoint(s16 x, s16 z) { float hselect = NoisePerlin2D(&noise_height_select->np, x, z, seed); hselect = rangelim(hselect, 0.0, 1.0); @@ -359,19 +426,23 @@ float MapgenV7::baseTerrainLevelFromMap(int index) } -bool MapgenV7::getMountainTerrainAtPoint(int x, int y, int z) +bool MapgenV7::getMountainTerrainAtPoint(s16 x, s16 y, s16 z) { float mnt_h_n = NoisePerlin2D(&noise_mount_height->np, x, z, seed); + float density_gradient = -((float)y / mnt_h_n); float mnt_n = NoisePerlin3D(&noise_mountain->np, x, y, z, seed); - return mnt_n * mnt_h_n >= (float)y; + + return mnt_n + density_gradient >= 0.0; } -bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, int y) +bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y) { float mounthn = noise_mount_height->result[idx_xz]; + float density_gradient = -((float)y / mounthn); float mountn = noise_mountain->result[idx_xyz]; - return mountn * mounthn >= (float)y; + + return mountn + density_gradient >= 0.0; } @@ -412,26 +483,30 @@ void MapgenV7::carveRivers() { int MapgenV7::generateTerrain() { - int ymax = generateBaseTerrain(); + s16 stone_surface_min_y; + s16 stone_surface_max_y; + + generateBaseTerrain(&stone_surface_min_y, &stone_surface_max_y); - if (spflags & MGV7_MOUNTAINS) - ymax = generateMountainTerrain(ymax); + if ((spflags & MGV7_MOUNTAINS) && stone_surface_min_y < node_max.Y) + stone_surface_max_y = generateMountainTerrain(stone_surface_max_y); if (spflags & MGV7_RIDGES) generateRidgeTerrain(); - return ymax; + return stone_surface_max_y; } -int MapgenV7::generateBaseTerrain() +void MapgenV7::generateBaseTerrain(s16 *stone_surface_min_y, s16 *stone_surface_max_y) { MapNode n_air(CONTENT_AIR); MapNode n_stone(c_stone); MapNode n_water(c_water_source); - int stone_surface_max_y = -MAP_GENERATION_LIMIT; v3s16 em = vm->m_area.getExtent(); + s16 surface_min_y = MAX_MAP_GENERATION_LIMIT; + s16 surface_max_y = -MAX_MAP_GENERATION_LIMIT; u32 index = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) @@ -442,11 +517,14 @@ int MapgenV7::generateBaseTerrain() heightmap[index] = surface_y; ridge_heightmap[index] = surface_y; - if (surface_y > stone_surface_max_y) - stone_surface_max_y = surface_y; + if (surface_y < surface_min_y) + surface_min_y = surface_y; + + if (surface_y > surface_max_y) + surface_max_y = surface_y; - u32 i = vm->m_area.index(x, node_min.Y, z); - for (s16 y = node_min.Y; y <= node_max.Y; y++) { + u32 i = 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[i].getContent() == CONTENT_IGNORE) { if (y <= surface_y) vm->m_data[i] = n_stone; @@ -459,20 +537,21 @@ int MapgenV7::generateBaseTerrain() } } - return stone_surface_max_y; + *stone_surface_min_y = surface_min_y; + *stone_surface_max_y = surface_max_y; } -int MapgenV7::generateMountainTerrain(int ymax) +int MapgenV7::generateMountainTerrain(s16 ymax) { - if (node_max.Y < 0) - return ymax; + noise_mountain->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + noise_mount_height->perlinMap2D(node_min.X, node_min.Z); MapNode n_stone(c_stone); u32 j = 0; for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 y = node_min.Y; y <= node_max.Y; y++) { + 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++) { int index = (z - node_min.Z) * csize.X + (x - node_min.X); @@ -505,7 +584,7 @@ void MapgenV7::generateRidgeTerrain() float width = 0.2; // TODO: figure out acceptable perlin noise values for (s16 z = node_min.Z; z <= node_max.Z; z++) - for (s16 y = node_min.Y; y <= node_max.Y; y++) { + 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++, index++, vi++) { int j = (z - node_min.Z) * csize.X + (x - node_min.X); @@ -534,98 +613,100 @@ void MapgenV7::generateRidgeTerrain() } -void MapgenV7::generateBiomes() +MgStoneType MapgenV7::generateBiomes(float *heat_map, float *humidity_map) { - if (node_max.Y < water_level) - return; - - MapNode n_air(CONTENT_AIR); - MapNode n_stone(c_stone); - MapNode n_water(c_water_source); - v3s16 em = vm->m_area.getExtent(); u32 index = 0; + MgStoneType stone_type = STONE; for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, index++) { - Biome *biome = (Biome *)bmgr->get(biomemap[index]); - s16 dfiller = biome->depth_filler + noise_filler_depth->result[index]; - s16 y0_top = biome->depth_top; - s16 y0_filler = biome->depth_top + dfiller; - s16 shore_max = water_level + biome->height_shore; - s16 depth_water_top = biome->depth_water_top; - - s16 nplaced = 0; - u32 i = vm->m_area.index(x, node_max.Y, z); - - content_t c_above = vm->m_data[i + em.X].getContent(); - bool have_air = c_above == CONTENT_AIR; + Biome *biome = NULL; + u16 depth_top = 0; + u16 base_filler = 0; + u16 depth_water_top = 0; + u32 vi = vm->m_area.index(x, node_max.Y, z); + + // Check node at base of mapchunk above, either a node of a previously + // generated mapchunk or if not, a node of overgenerated base terrain. + content_t c_above = vm->m_data[vi + em.X].getContent(); + bool air_above = c_above == CONTENT_AIR; + bool water_above = c_above == c_water_source; + + // If there is air or water above enable top/filler placement, otherwise force + // nplaced to stone level by setting a number exceeding any possible filler depth. + u16 nplaced = (air_above || water_above) ? 0 : (u16)-1; for (s16 y = node_max.Y; y >= node_min.Y; y--) { - content_t c = vm->m_data[i].getContent(); + content_t c = vm->m_data[vi].getContent(); - // It could be the case that the elevation is equal to the chunk - // boundary, but the chunk above has not been generated yet - if (y == node_max.Y && c_above == CONTENT_IGNORE && - y == heightmap[index] && c == c_stone) { - int j = (z - node_min.Z) * zstride + - (y - node_min.Y) * ystride + - (x - node_min.X); - have_air = !getMountainTerrainFromMap(j, index, y); + // Biome is recalculated each time an upper surface is detected while + // working down a column. The selected biome then remains in effect for + // all nodes below until the next surface and biome recalculation. + // Biome is recalculated: + // 1. At the surface of stone below air or water. + // 2. At the surface of water below air. + // 3. When stone or water is detected but biome has not yet been calculated. + if ((c == c_stone && (air_above || water_above || !biome)) || + (c == c_water_source && (air_above || !biome))) { + biome = bmgr->getBiome(heat_map[index], humidity_map[index], y); + depth_top = biome->depth_top; + base_filler = MYMAX(depth_top + biome->depth_filler + + noise_filler_depth->result[index], 0); + depth_water_top = biome->depth_water_top; + + // Detect stone type for dungeons during every biome calculation. + // This is more efficient than detecting per-node and will not + // miss any desert stone or sandstone biomes. + if (biome->c_stone == c_desert_stone) + stone_type = DESERT_STONE; + else if (biome->c_stone == c_sandstone) + stone_type = SANDSTONE; } - if (c == c_stone && have_air) { - content_t c_below = vm->m_data[i - em.X].getContent(); - - if (c_below != CONTENT_AIR) { - if (nplaced < y0_top) { - if(y < water_level) - vm->m_data[i] = MapNode(biome->c_underwater); - else if(y <= shore_max) - vm->m_data[i] = MapNode(biome->c_shore_top); - else - vm->m_data[i] = MapNode(biome->c_top); - nplaced++; - } else if (nplaced < y0_filler && nplaced >= y0_top) { - if(y < water_level) - vm->m_data[i] = MapNode(biome->c_underwater); - else if(y <= shore_max) - vm->m_data[i] = MapNode(biome->c_shore_filler); - else - vm->m_data[i] = MapNode(biome->c_filler); - nplaced++; - } else if (c == c_stone) { - have_air = false; - nplaced = 0; - vm->m_data[i] = MapNode(biome->c_stone); - } else { - have_air = false; - nplaced = 0; - } - } else if (c == c_stone) { - have_air = false; - nplaced = 0; - vm->m_data[i] = MapNode(biome->c_stone); + if (c == c_stone) { + content_t c_below = vm->m_data[vi - em.X].getContent(); + + // If the node below isn't solid, make this node stone, so that + // any top/filler nodes above are structurally supported. + // This is done by aborting the cycle of top/filler placement + // immediately by forcing nplaced to stone level. + if (c_below == CONTENT_AIR || c_below == c_water_source) + nplaced = (u16)-1; + + if (nplaced < depth_top) { + vm->m_data[vi] = MapNode(biome->c_top); + nplaced++; + } else if (nplaced < base_filler) { + vm->m_data[vi] = MapNode(biome->c_filler); + nplaced++; + } else { + vm->m_data[vi] = MapNode(biome->c_stone); } - } else if (c == c_stone) { - have_air = false; - nplaced = 0; - vm->m_data[i] = MapNode(biome->c_stone); + + air_above = false; + water_above = false; } else if (c == c_water_source) { - have_air = true; - nplaced = 0; - if(y > water_level - depth_water_top) - vm->m_data[i] = MapNode(biome->c_water_top); - else - vm->m_data[i] = MapNode(biome->c_water); + vm->m_data[vi] = MapNode((y > (s32)(water_level - depth_water_top)) ? + biome->c_water_top : biome->c_water); + nplaced = 0; // Enable top/filler placement for next surface + air_above = false; + water_above = true; } else if (c == CONTENT_AIR) { - have_air = true; - nplaced = 0; + nplaced = 0; // Enable top/filler placement for next surface + air_above = true; + water_above = false; + } else { // Possible various nodes overgenerated from neighbouring mapchunks + nplaced = (u16)-1; // Disable top/filler placement + air_above = false; + water_above = false; } - vm->m_area.add_y(em, i, -1); + vm->m_area.add_y(em, vi, -1); } } + + return stone_type; } @@ -639,14 +720,31 @@ void MapgenV7::dustTopNodes() for (s16 z = node_min.Z; z <= node_max.Z; z++) for (s16 x = node_min.X; x <= node_max.X; x++, index++) { - Biome *biome = (Biome *)bmgr->get(biomemap[index]); + Biome *biome = (Biome *)bmgr->getRaw(biomemap[index]); if (biome->c_dust == CONTENT_IGNORE) continue; - s16 y = node_max.Y; - u32 vi = vm->m_area.index(x, y, z); - for (; y >= node_min.Y; y--) { + u32 vi = vm->m_area.index(x, full_node_max.Y, z); + content_t c_full_max = vm->m_data[vi].getContent(); + s16 y_start; + + if (c_full_max == CONTENT_AIR) { + y_start = full_node_max.Y - 1; + } else if (c_full_max == CONTENT_IGNORE) { + vi = vm->m_area.index(x, node_max.Y + 1, z); + content_t c_max = vm->m_data[vi].getContent(); + + if (c_max == CONTENT_AIR) + y_start = node_max.Y; + else + continue; + } else { + continue; + } + + vi = vm->m_area.index(x, y_start, z); + for (s16 y = y_start; y >= node_min.Y - 1; y--) { if (vm->m_data[vi].getContent() != CONTENT_AIR) break; @@ -654,10 +752,7 @@ void MapgenV7::dustTopNodes() } content_t c = vm->m_data[vi].getContent(); - if (!ndef->get(c).buildable_to && c != CONTENT_IGNORE) { - if (y == node_max.Y) - continue; - + if (!ndef->get(c).buildable_to && c != CONTENT_IGNORE && c != biome->c_dust) { vm->m_area.add_y(em, vi, 1); vm->m_data[vi] = MapNode(biome->c_dust); } @@ -761,39 +856,32 @@ void MapgenV7::addTopNodes() #endif -void MapgenV7::generateCaves(int max_stone_y) +void MapgenV7::generateCaves(s16 max_stone_y) { if (max_stone_y >= node_min.Y) { u32 index = 0; - u32 index2d = 0; - - for (s16 z = node_min.Z; z <= node_max.Z; z++) { - for (s16 y = node_min.Y; y <= node_max.Y; y++) { - u32 i = vm->m_area.index(node_min.X, y, z); - for (s16 x = node_min.X; x <= node_max.X; - x++, i++, index++, index2d++) { - Biome *biome = (Biome *)bmgr->get(biomemap[index2d]); + + 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 i = vm->m_area.index(node_min.X, y, z); + for (s16 x = node_min.X; x <= node_max.X; x++, i++, index++) { + float d1 = contour(noise_cave1->result[index]); + float d2 = contour(noise_cave2->result[index]); + if (d1 * d2 > 0.3) { content_t c = vm->m_data[i].getContent(); - if (c == CONTENT_AIR || (y <= water_level && - c != biome->c_stone && c != c_stone)) + if (!ndef->get(c).is_ground_content || c == CONTENT_AIR) continue; - float d1 = contour(noise_cave1->result[index]); - float d2 = contour(noise_cave2->result[index]); - if (d1 * d2 > 0.3) - vm->m_data[i] = MapNode(CONTENT_AIR); + vm->m_data[i] = MapNode(CONTENT_AIR); } - index2d -= ystride; } - index2d += ystride; } } PseudoRandom ps(blockseed + 21343); - u32 bruises_count = (ps.range(1, 5) == 1) ? ps.range(1, 2) : 0; + u32 bruises_count = (ps.range(1, 4) == 1) ? ps.range(1, 2) : 0; for (u32 i = 0; i < bruises_count; i++) { - CaveV7 cave(this, &ps, true); + CaveV7 cave(this, &ps); cave.makeCave(node_min, node_max, max_stone_y); } } - diff --git a/src/mapgen_v7.h b/src/mapgen_v7.h index bcf362ac9..c0cfa8c77 100644 --- a/src/mapgen_v7.h +++ b/src/mapgen_v7.h @@ -48,8 +48,8 @@ struct MapgenV7Params : public MapgenSpecificParams { MapgenV7Params(); ~MapgenV7Params() {} - void readParams(Settings *settings); - void writeParams(Settings *settings); + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; }; class MapgenV7 : public Mapgen { @@ -82,18 +82,21 @@ public: Noise *noise_heat; Noise *noise_humidity; + Noise *noise_heat_blend; + Noise *noise_humidity_blend; content_t c_stone; - content_t c_dirt; - content_t c_dirt_with_grass; - content_t c_sand; content_t c_water_source; content_t c_lava_source; + content_t c_desert_stone; content_t c_ice; - content_t c_gravel; + content_t c_sandstone; + content_t c_cobble; - content_t c_desert_sand; - content_t c_desert_stone; + content_t c_stair_cobble; + content_t c_mossycobble; + content_t c_sandstonebrick; + content_t c_stair_sandstonebrick; MapgenV7(int mapgenid, MapgenParams *params, EmergeManager *emerge); ~MapgenV7(); @@ -102,24 +105,24 @@ public: int getGroundLevelAtPoint(v2s16 p); Biome *getBiomeAtPoint(v3s16 p); - float baseTerrainLevelAtPoint(int x, int z); + float baseTerrainLevelAtPoint(s16 x, s16 z); float baseTerrainLevelFromMap(int index); - bool getMountainTerrainAtPoint(int x, int y, int z); - bool getMountainTerrainFromMap(int idx_xyz, int idx_xz, int y); + bool getMountainTerrainAtPoint(s16 x, s16 y, s16 z); + bool getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y); void calculateNoise(); virtual int generateTerrain(); - int generateBaseTerrain(); - int generateMountainTerrain(int ymax); + void generateBaseTerrain(s16 *stone_surface_min_y, s16 *stone_surface_max_y); + int generateMountainTerrain(s16 ymax); void generateRidgeTerrain(); - void generateBiomes(); + MgStoneType generateBiomes(float *heat_map, float *humidity_map); void dustTopNodes(); //void addTopNodes(); - void generateCaves(int max_stone_y); + void generateCaves(s16 max_stone_y); }; struct MapgenFactoryV7 : public MapgenFactory { diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 056b94054..fe9686f0d 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "mapnode.h" #include "porting.h" -#include "main.h" // For g_settings #include "nodedef.h" #include "content_mapnode.h" // For mapnode_translate_*_internal #include "serialization.h" // For ser_ver_supported @@ -71,7 +70,23 @@ void MapNode::setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr param1 |= (a_light & 0x0f)<<4; } else - assert(0); + assert("Invalid light bank" == NULL); +} + +bool MapNode::isLightDayNightEq(INodeDefManager *nodemgr) const +{ + const ContentFeatures &f = nodemgr->get(*this); + bool isEqual; + + if (f.param_type == CPT_LIGHT) { + u8 day = MYMAX(f.light_source, param1 & 0x0f); + u8 night = MYMAX(f.light_source, (param1 >> 4) & 0x0f); + isEqual = day == night; + } else { + isEqual = true; + } + + return isEqual; } u8 MapNode::getLight(enum LightBank bank, INodeDefManager *nodemgr) const @@ -88,7 +103,7 @@ u8 MapNode::getLight(enum LightBank bank, INodeDefManager *nodemgr) const return MYMAX(f.light_source, light); } -u8 MapNode::getLightNoChecks(enum LightBank bank, const ContentFeatures *f) +u8 MapNode::getLightNoChecks(enum LightBank bank, const ContentFeatures *f) const { return MYMAX(f->light_source, bank == LIGHTBANK_DAY ? param1 & 0x0f : (param1 >> 4) & 0x0f); @@ -148,6 +163,9 @@ void MapNode::rotateAlongYAxis(INodeDefManager *nodemgr, Rotation rot) { ContentParamType2 cpt2 = nodemgr->get(*this).param_type_2; if (cpt2 == CPT2_FACEDIR) { + if (param2 >= 4) + return; + u8 newrot = param2 & 3; param2 &= ~3; param2 |= (newrot + rot) & 3; @@ -500,8 +518,8 @@ void MapNode::serializeBulk(std::ostream &os, int version, if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); - assert(content_width == 2); - assert(params_width == 2); + sanity_check(content_width == 2); + sanity_check(params_width == 2); // Can't do this anymore; we have 16-bit dynamically allocated node IDs // in memory; conversion just won't work in this direction. @@ -547,9 +565,10 @@ void MapNode::deSerializeBulk(std::istream &is, int version, if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapNode format not supported"); - assert(version >= 22); - assert(content_width == 1 || content_width == 2); - assert(params_width == 2); + if (version < 22 + || (content_width != 1 && content_width != 2) + || params_width != 2) + FATAL_ERROR("Deserialize bulk node data error"); // Uncompress or read data u32 len = nodecount * (content_width + params_width); diff --git a/src/mapnode.h b/src/mapnode.h index 82c53e7d4..7cc25c60c 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -192,6 +192,14 @@ struct MapNode } void setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr); + + /** + * Check if the light value for night differs from the light value for day. + * + * @return If the light values are equal, returns true; otherwise false + */ + bool isLightDayNightEq(INodeDefManager *nodemgr) const; + u8 getLight(enum LightBank bank, INodeDefManager *nodemgr) const; /** @@ -209,7 +217,7 @@ struct MapNode * @pre f != NULL * @pre f->param_type == CPT_LIGHT */ - u8 getLightNoChecks(LightBank bank, const ContentFeatures *f); + u8 getLightNoChecks(LightBank bank, const ContentFeatures *f) const; bool getLightBanks(u8 &lightday, u8 &lightnight, INodeDefManager *nodemgr) const; diff --git a/src/mapsector.cpp b/src/mapsector.cpp index 0d40a659d..9ce3c8eb3 100644 --- a/src/mapsector.cpp +++ b/src/mapsector.cpp @@ -59,7 +59,7 @@ MapBlock * MapSector::getBlockBuffered(s16 y) if(m_block_cache != NULL && y == m_block_cache_y){ return m_block_cache; } - + // If block doesn't exist, return NULL std::map<s16, MapBlock*>::iterator n = m_blocks.find(y); if(n == m_blocks.end()) @@ -70,11 +70,11 @@ MapBlock * MapSector::getBlockBuffered(s16 y) else{ block = n->second; } - + // Cache the last result m_block_cache_y = y; m_block_cache = block; - + return block; } @@ -85,19 +85,19 @@ MapBlock * MapSector::getBlockNoCreateNoEx(s16 y) MapBlock * MapSector::createBlankBlockNoInsert(s16 y) { - assert(getBlockBuffered(y) == NULL); + assert(getBlockBuffered(y) == NULL); // Pre-condition v3s16 blockpos_map(m_pos.X, y, m_pos.Y); - + MapBlock *block = new MapBlock(m_parent, blockpos_map, m_gamedef); - + return block; } MapBlock * MapSector::createBlankBlock(s16 y) { MapBlock *block = createBlankBlockNoInsert(y); - + m_blocks[y] = block; return block; @@ -114,7 +114,7 @@ void MapSector::insertBlock(MapBlock *block) v2s16 p2d(block->getPos().X, block->getPos().Z); assert(p2d == m_pos); - + // Insert into container m_blocks[block_y] = block; } @@ -125,7 +125,7 @@ void MapSector::deleteBlock(MapBlock *block) // Clear from cache m_block_cache = NULL; - + // Remove from container m_blocks.erase(block_y); @@ -133,7 +133,7 @@ void MapSector::deleteBlock(MapBlock *block) delete block; } -void MapSector::getBlocks(std::list<MapBlock*> &dest) +void MapSector::getBlocks(MapBlockVect &dest) { for(std::map<s16, MapBlock*>::iterator bi = m_blocks.begin(); bi != m_blocks.end(); ++bi) @@ -142,6 +142,11 @@ void MapSector::getBlocks(std::list<MapBlock*> &dest) } } +bool MapSector::empty() +{ + return m_blocks.empty(); +} + /* ServerMapSector */ @@ -159,18 +164,18 @@ void ServerMapSector::serialize(std::ostream &os, u8 version) { if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapSector format not supported"); - + /* [0] u8 serialization version + heightmap data */ - + // Server has both of these, no need to support not having them. //assert(m_objects != NULL); // Write version os.write((char*)&version, 1); - + /* Add stuff here, if needed */ @@ -193,18 +198,18 @@ ServerMapSector* ServerMapSector::deSerialize( /* Read stuff */ - + // Read version u8 version = SER_FMT_VER_INVALID; is.read((char*)&version, 1); - + if(!ser_ver_supported(version)) throw VersionMismatchException("ERROR: MapSector format not supported"); - + /* Add necessary reading stuff here */ - + /* Get or create sector */ diff --git a/src/mapsector.h b/src/mapsector.h index dac4ee8d6..4c1ce86a3 100644 --- a/src/mapsector.h +++ b/src/mapsector.h @@ -22,11 +22,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include "irr_v2d.h" +#include "mapblock.h" #include <ostream> #include <map> -#include <list> +#include <vector> -class MapBlock; class Map; class IGameDef; @@ -40,7 +40,7 @@ class IGameDef; class MapSector { public: - + MapSector(Map *parent, v2s16 pos, IGameDef *gamedef); virtual ~MapSector(); @@ -58,16 +58,18 @@ public: MapBlock * createBlankBlock(s16 y); void insertBlock(MapBlock *block); - + void deleteBlock(MapBlock *block); - - void getBlocks(std::list<MapBlock*> &dest); - + + void getBlocks(MapBlockVect &dest); + + bool empty(); + // Always false at the moment, because sector contains no metadata. bool differs_from_disk; protected: - + // The pile of MapBlocks std::map<s16, MapBlock*> m_blocks; @@ -76,12 +78,12 @@ protected: v2s16 m_pos; IGameDef *m_gamedef; - + // Last-used block is cached here for quicker access. - // Be sure to set this to NULL when the cached block is deleted + // Be sure to set this to NULL when the cached block is deleted MapBlock *m_block_cache; s16 m_block_cache_y; - + /* Private methods */ @@ -94,7 +96,7 @@ class ServerMapSector : public MapSector public: ServerMapSector(Map *parent, v2s16 pos, IGameDef *gamedef); ~ServerMapSector(); - + u32 getId() const { return MAPSECTOR_SERVER; @@ -106,7 +108,7 @@ public: */ void serialize(std::ostream &os, u8 version); - + static ServerMapSector* deSerialize( std::istream &is, Map *parent, @@ -114,7 +116,7 @@ public: std::map<v2s16, MapSector*> & sectors, IGameDef *gamedef ); - + private: }; @@ -124,7 +126,7 @@ class ClientMapSector : public MapSector public: ClientMapSector(Map *parent, v2s16 pos, IGameDef *gamedef); ~ClientMapSector(); - + u32 getId() const { return MAPSECTOR_CLIENT; @@ -133,6 +135,6 @@ public: private: }; #endif - + #endif diff --git a/src/mesh.cpp b/src/mesh.cpp index e021e4c92..dab1575f3 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -33,6 +33,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MY_ETLM_READ_ONLY video::ETLM_READ_ONLY #endif +static void applyFacesShading(video::SColor& color, float factor) +{ + color.setRed(core::clamp(core::round32(color.getRed()*factor), 0, 255)); + color.setGreen(core::clamp(core::round32(color.getGreen()*factor), 0, 255)); + color.setBlue(core::clamp(core::round32(color.getBlue()*factor), 0, 255)); +} + scene::IAnimatedMesh* createCubeMesh(v3f scale) { video::SColor c(255,255,255,255); @@ -94,26 +101,25 @@ scene::IAnimatedMesh* createCubeMesh(v3f scale) void scaleMesh(scene::IMesh *mesh, v3f scale) { - if(mesh == NULL) + if (mesh == NULL) return; core::aabbox3d<f32> bbox; - bbox.reset(0,0,0); + bbox.reset(0, 0, 0); - u16 mc = mesh->getMeshBufferCount(); - for(u16 j=0; j<mc; j++) - { + u32 mc = mesh->getMeshBufferCount(); + for (u32 j = 0; j < mc; j++) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices(); - u16 vc = buf->getVertexCount(); - for(u16 i=0; i<vc; i++) - { - vertices[i].Pos *= scale; - } + 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 *= scale; + buf->recalculateBoundingBox(); // calculate total bounding box - if(j == 0) + if (j == 0) bbox = buf->getBoundingBox(); else bbox.addInternalBox(buf->getBoundingBox()); @@ -123,26 +129,25 @@ void scaleMesh(scene::IMesh *mesh, v3f scale) void translateMesh(scene::IMesh *mesh, v3f vec) { - if(mesh == NULL) + if (mesh == NULL) return; core::aabbox3d<f32> bbox; - bbox.reset(0,0,0); + bbox.reset(0, 0, 0); - u16 mc = mesh->getMeshBufferCount(); - for(u16 j=0; j<mc; j++) - { + u32 mc = mesh->getMeshBufferCount(); + for (u32 j = 0; j < mc; j++) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices(); - u16 vc = buf->getVertexCount(); - for(u16 i=0; i<vc; i++) - { - vertices[i].Pos += vec; - } + 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 += vec; + buf->recalculateBoundingBox(); // calculate total bounding box - if(j == 0) + if (j == 0) bbox = buf->getBoundingBox(); else bbox.addInternalBox(buf->getBoundingBox()); @@ -150,20 +155,48 @@ void translateMesh(scene::IMesh *mesh, v3f vec) mesh->setBoundingBox(bbox); } + void setMeshColor(scene::IMesh *mesh, const video::SColor &color) { - if(mesh == NULL) + if (mesh == NULL) return; - - u16 mc = mesh->getMeshBufferCount(); - for(u16 j=0; j<mc; j++) - { + + u32 mc = mesh->getMeshBufferCount(); + for (u32 j = 0; j < mc; j++) { scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); - video::S3DVertex *vertices = (video::S3DVertex*)buf->getVertices(); - u16 vc = buf->getVertexCount(); - for(u16 i=0; i<vc; i++) - { - vertices[i].Color = color; + 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))->Color = color; + } +} + +void shadeMeshFaces(scene::IMesh *mesh) +{ + if (mesh == NULL) + return; + + u32 mc = mesh->getMeshBufferCount(); + for (u32 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); + video::SColor &vc = vertex->Color; + if (vertex->Normal.Y < -0.5) { + applyFacesShading (vc, 0.447213); + } else if (vertex->Normal.Z > 0.5) { + applyFacesShading (vc, 0.670820); + } else if (vertex->Normal.Z < -0.5) { + applyFacesShading (vc, 0.670820); + } else if (vertex->Normal.X > 0.5) { + applyFacesShading (vc, 0.836660); + } else if (vertex->Normal.X < -0.5) { + applyFacesShading (vc, 0.836660); + } } } } diff --git a/src/mesh.h b/src/mesh.h index 761842b0d..ec109e9e9 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -49,6 +49,12 @@ void translateMesh(scene::IMesh *mesh, v3f vec); void setMeshColor(scene::IMesh *mesh, const video::SColor &color); /* + Shade mesh faces according to their normals +*/ + +void shadeMeshFaces(scene::IMesh *mesh); + +/* Set the color of all vertices in the mesh. For each vertex, determine the largest absolute entry in the normal vector, and choose one of colorX, colorY or diff --git a/src/mg_biome.cpp b/src/mg_biome.cpp index 0d17ae5ed..055ce0198 100644 --- a/src/mg_biome.cpp +++ b/src/mg_biome.cpp @@ -18,49 +18,46 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "mg_biome.h" +#include "mg_decoration.h" +#include "emerge.h" #include "gamedef.h" #include "nodedef.h" #include "map.h" //for MMVManip #include "log.h" #include "util/numeric.h" -#include "main.h" #include "util/mathconstants.h" #include "porting.h" -const char *BiomeManager::ELEMENT_TITLE = "biome"; - /////////////////////////////////////////////////////////////////////////////// + BiomeManager::BiomeManager(IGameDef *gamedef) : - GenElementManager(gamedef) + ObjDefManager(gamedef, OBJDEF_BIOME) { + m_gamedef = gamedef; + // Create default biome to be used in case none exist Biome *b = new Biome; - b->id = 0; b->name = "Default"; b->flags = 0; b->depth_top = 0; - b->depth_filler = 0; - b->height_shore = 0; + b->depth_filler = -MAX_MAP_GENERATION_LIMIT; b->depth_water_top = 0; - b->y_min = -MAP_GENERATION_LIMIT; - b->y_max = MAP_GENERATION_LIMIT; + b->y_min = -MAX_MAP_GENERATION_LIMIT; + b->y_max = MAX_MAP_GENERATION_LIMIT; b->heat_point = 0.0; b->humidity_point = 0.0; - NodeResolveInfo *nri = new NodeResolveInfo(b); - nri->nodenames.push_back("air"); - nri->nodenames.push_back("air"); - nri->nodenames.push_back("air"); - nri->nodenames.push_back("air"); - nri->nodenames.push_back("air"); - nri->nodenames.push_back("mapgen_stone"); - nri->nodenames.push_back("mapgen_water_source"); - nri->nodenames.push_back("mapgen_water_source"); - nri->nodenames.push_back("air"); - m_ndef->pendNodeResolve(nri); + b->m_nodenames.push_back("mapgen_stone"); + b->m_nodenames.push_back("mapgen_stone"); + b->m_nodenames.push_back("mapgen_stone"); + b->m_nodenames.push_back("mapgen_water_source"); + b->m_nodenames.push_back("mapgen_water_source"); + b->m_nodenames.push_back("mapgen_river_water_source"); + b->m_nodenames.push_back("air"); + m_ndef->pendNodeResolve(b); add(b); } @@ -79,8 +76,10 @@ BiomeManager::~BiomeManager() void BiomeManager::calcBiomes(s16 sx, s16 sy, float *heat_map, float *humidity_map, s16 *height_map, u8 *biomeid_map) { - for (s32 i = 0; i != sx * sy; i++) - biomeid_map[i] = getBiome(heat_map[i], humidity_map[i], height_map[i])->id; + for (s32 i = 0; i != sx * sy; i++) { + Biome *biome = getBiome(heat_map[i], humidity_map[i], height_map[i]); + biomeid_map[i] = biome->index; + } } @@ -89,8 +88,8 @@ Biome *BiomeManager::getBiome(float heat, float humidity, s16 y) Biome *b, *biome_closest = NULL; float dist_min = FLT_MAX; - for (size_t i = 1; i < m_elements.size(); i++) { - b = (Biome *)m_elements[i]; + for (size_t i = 1; i < m_objects.size(); i++) { + b = (Biome *)m_objects[i]; if (!b || y > b->y_max || y < b->y_min) continue; @@ -104,34 +103,40 @@ Biome *BiomeManager::getBiome(float heat, float humidity, s16 y) } } - return biome_closest ? biome_closest : (Biome *)m_elements[0]; + return biome_closest ? biome_closest : (Biome *)m_objects[0]; } void BiomeManager::clear() { + EmergeManager *emerge = m_gamedef->getEmergeManager(); - for (size_t i = 1; i < m_elements.size(); i++) { - Biome *b = (Biome *)m_elements[i]; + // Remove all dangling references in Decorations + DecorationManager *decomgr = emerge->decomgr; + for (size_t i = 0; i != decomgr->getNumObjects(); i++) { + Decoration *deco = (Decoration *)decomgr->getRaw(i); + deco->biomes.clear(); + } + + // Don't delete the first biome + for (size_t i = 1; i < m_objects.size(); i++) { + Biome *b = (Biome *)m_objects[i]; delete b; } - m_elements.resize(1); + m_objects.resize(1); } /////////////////////////////////////////////////////////////////////////////// -void Biome::resolveNodeNames(NodeResolveInfo *nri) +void Biome::resolveNodeNames() { - m_ndef->getIdFromResolveInfo(nri, "mapgen_dirt_with_grass", CONTENT_AIR, c_top); - m_ndef->getIdFromResolveInfo(nri, "mapgen_dirt", CONTENT_AIR, c_filler); - m_ndef->getIdFromResolveInfo(nri, "mapgen_sand", CONTENT_AIR, c_shore_top); - m_ndef->getIdFromResolveInfo(nri, "mapgen_sand", CONTENT_AIR, c_shore_filler); - m_ndef->getIdFromResolveInfo(nri, "mapgen_sand", CONTENT_AIR, c_underwater); - m_ndef->getIdFromResolveInfo(nri, "mapgen_stone", CONTENT_AIR, c_stone); - m_ndef->getIdFromResolveInfo(nri, "mapgen_water_source", CONTENT_AIR, c_water_top); - m_ndef->getIdFromResolveInfo(nri, "mapgen_water_source", CONTENT_AIR, c_water); - m_ndef->getIdFromResolveInfo(nri, "air", CONTENT_IGNORE, c_dust); + getIdFromNrBacklog(&c_top, "mapgen_stone", CONTENT_AIR); + getIdFromNrBacklog(&c_filler, "mapgen_stone", CONTENT_AIR); + getIdFromNrBacklog(&c_stone, "mapgen_stone", CONTENT_AIR); + getIdFromNrBacklog(&c_water_top, "mapgen_water_source", CONTENT_AIR); + getIdFromNrBacklog(&c_water, "mapgen_water_source", CONTENT_AIR); + getIdFromNrBacklog(&c_river_water, "mapgen_river_water_source", CONTENT_AIR); + getIdFromNrBacklog(&c_dust, "air", CONTENT_IGNORE); } - diff --git a/src/mg_biome.h b/src/mg_biome.h index 3577960a9..8d519f808 100644 --- a/src/mg_biome.h +++ b/src/mg_biome.h @@ -20,36 +20,32 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef MG_BIOME_HEADER #define MG_BIOME_HEADER -#include "mapgen.h" - -struct NoiseParams; +#include "objdef.h" +#include "nodedef.h" enum BiomeType { - BIOME_TYPE_NORMAL, - BIOME_TYPE_LIQUID, - BIOME_TYPE_NETHER, - BIOME_TYPE_AETHER, - BIOME_TYPE_FLAT + BIOME_NORMAL, + BIOME_LIQUID, + BIOME_NETHER, + BIOME_AETHER, + BIOME_FLAT }; -class Biome : public GenElement, public NodeResolver { +class Biome : public ObjDef, public NodeResolver { public: u32 flags; content_t c_top; content_t c_filler; - content_t c_shore_top; - content_t c_shore_filler; - content_t c_underwater; content_t c_stone; content_t c_water_top; content_t c_water; + content_t c_river_water; content_t c_dust; s16 depth_top; s16 depth_filler; - s16 height_shore; s16 depth_water_top; s16 y_min; @@ -57,27 +53,34 @@ public: float heat_point; float humidity_point; - virtual void resolveNodeNames(NodeResolveInfo *nri); + virtual void resolveNodeNames(); }; -class BiomeManager : public GenElementManager { +class BiomeManager : public ObjDefManager { public: - static const char *ELEMENT_TITLE; - static const size_t ELEMENT_LIMIT = 0x100; + static const char *OBJECT_TITLE; BiomeManager(IGameDef *gamedef); - ~BiomeManager(); + virtual ~BiomeManager(); - Biome *create(int btt) + const char *getObjectTitle() const + { + return "biome"; + } + + static Biome *create(BiomeType type) { return new Biome; } - void clear(); + virtual void clear(); void calcBiomes(s16 sx, s16 sy, float *heat_map, float *humidity_map, s16 *height_map, u8 *biomeid_map); Biome *getBiome(float heat, float humidity, s16 y); + +private: + IGameDef *m_gamedef; }; #endif diff --git a/src/mg_decoration.cpp b/src/mg_decoration.cpp index a67c3cd8c..0d6693929 100644 --- a/src/mg_decoration.cpp +++ b/src/mg_decoration.cpp @@ -25,12 +25,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "util/numeric.h" -const char *DecorationManager::ELEMENT_TITLE = "decoration"; - FlagDesc flagdesc_deco[] = { {"place_center_x", DECO_PLACE_CENTER_X}, {"place_center_y", DECO_PLACE_CENTER_Y}, {"place_center_z", DECO_PLACE_CENTER_Z}, + {"force_placement", DECO_FORCE_PLACEMENT}, {NULL, 0} }; @@ -39,7 +38,7 @@ FlagDesc flagdesc_deco[] = { DecorationManager::DecorationManager(IGameDef *gamedef) : - GenElementManager(gamedef) + ObjDefManager(gamedef, OBJDEF_DECORATION) { } @@ -49,8 +48,8 @@ size_t DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed, { size_t nplaced = 0; - for (size_t i = 0; i != m_elements.size(); i++) { - Decoration *deco = (Decoration *)m_elements[i]; + for (size_t i = 0; i != m_objects.size(); i++) { + Decoration *deco = (Decoration *)m_objects[i]; if (!deco) continue; @@ -62,16 +61,6 @@ size_t DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed, } -void DecorationManager::clear() -{ - for (size_t i = 0; i < m_elements.size(); i++) { - Decoration *deco = (Decoration *)m_elements[i]; - delete deco; - } - m_elements.clear(); -} - - /////////////////////////////////////////////////////////////////////////////// @@ -89,9 +78,9 @@ Decoration::~Decoration() } -void Decoration::resolveNodeNames(NodeResolveInfo *nri) +void Decoration::resolveNodeNames() { - m_ndef->getIdsFromResolveInfo(nri, c_place_on); + getIdsFromNrBacklog(&c_place_on); } @@ -145,9 +134,7 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) y < y_min || y > y_max) continue; - int height = getHeight(); - int max_y = nmax.Y;// + MAP_BLOCKSIZE - 1; - if (y + 1 + height > max_y) { + if (y + getHeight() >= mg->vm->m_area.MaxEdge.Y) { continue; #if 0 printf("Decoration at (%d %d %d) cut off\n", x, y, z); @@ -168,8 +155,8 @@ size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) } v3s16 pos(x, y, z); - if (generate(mg->vm, &ps, max_y, pos)) - mg->gennotify.addEvent(GENNOTIFY_DECORATION, pos, id); + if (generate(mg->vm, &ps, pos)) + mg->gennotify.addEvent(GENNOTIFY_DECORATION, pos, index); } } @@ -234,11 +221,11 @@ void Decoration::placeCutoffs(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) /////////////////////////////////////////////////////////////////////////////// -void DecoSimple::resolveNodeNames(NodeResolveInfo *nri) +void DecoSimple::resolveNodeNames() { - Decoration::resolveNodeNames(nri); - m_ndef->getIdsFromResolveInfo(nri, c_decos); - m_ndef->getIdsFromResolveInfo(nri, c_spawnby); + Decoration::resolveNodeNames(); + getIdsFromNrBacklog(&c_decos); + getIdsFromNrBacklog(&c_spawnby); } @@ -259,7 +246,7 @@ bool DecoSimple::canPlaceDecoration(MMVManip *vm, v3s16 p) return true; int nneighs = 0; - v3s16 dirs[8] = { + v3s16 dirs[16] = { v3s16( 0, 0, 1), v3s16( 0, 0, -1), v3s16( 1, 0, 0), @@ -267,7 +254,16 @@ bool DecoSimple::canPlaceDecoration(MMVManip *vm, v3s16 p) v3s16( 1, 0, 1), v3s16(-1, 0, 1), v3s16(-1, 0, -1), - v3s16( 1, 0, -1) + v3s16( 1, 0, -1), + + v3s16( 0, 1, 1), + v3s16( 0, 1, -1), + v3s16( 1, 1, 0), + v3s16(-1, 1, 0), + v3s16( 1, 1, 1), + v3s16(-1, 1, 1), + v3s16(-1, 1, -1), + v3s16( 1, 1, -1) }; // Check a Moore neighborhood if there are enough spawnby nodes @@ -287,7 +283,7 @@ bool DecoSimple::canPlaceDecoration(MMVManip *vm, v3s16 p) } -size_t DecoSimple::generate(MMVManip *vm, PseudoRandom *pr, s16 max_y, v3s16 p) +size_t DecoSimple::generate(MMVManip *vm, PseudoRandom *pr, v3s16 p) { if (!canPlaceDecoration(vm, p)) return 0; @@ -297,8 +293,6 @@ size_t DecoSimple::generate(MMVManip *vm, PseudoRandom *pr, s16 max_y, v3s16 p) s16 height = (deco_height_max > 0) ? pr->range(deco_height, deco_height_max) : deco_height; - height = MYMIN(height, max_y - p.Y); - v3s16 em = vm->m_area.getExtent(); u32 vi = vm->m_area.index(p); for (int i = 0; i < height; i++) { @@ -324,16 +318,17 @@ int DecoSimple::getHeight() /////////////////////////////////////////////////////////////////////////////// -size_t DecoSchematic::generate(MMVManip *vm, PseudoRandom *pr, s16 max_y, v3s16 p) +DecoSchematic::DecoSchematic() { - if (flags & DECO_PLACE_CENTER_X) - p.X -= (schematic->size.X + 1) / 2; - if (flags & DECO_PLACE_CENTER_Y) - p.Y -= (schematic->size.Y + 1) / 2; - if (flags & DECO_PLACE_CENTER_Z) - p.Z -= (schematic->size.Z + 1) / 2; + schematic = NULL; +} + - if (!vm->m_area.contains(p)) +size_t DecoSchematic::generate(MMVManip *vm, PseudoRandom *pr, v3s16 p) +{ + // Schematic could have been unloaded but not the decoration + // In this case generate() does nothing (but doesn't *fail*) + if (schematic == NULL) return 0; u32 vi = vm->m_area.index(p); @@ -341,10 +336,19 @@ size_t DecoSchematic::generate(MMVManip *vm, PseudoRandom *pr, s16 max_y, v3s16 if (!CONTAINS(c_place_on, c)) return 0; + if (flags & DECO_PLACE_CENTER_X) + p.X -= (schematic->size.X - 1) / 2; + if (flags & DECO_PLACE_CENTER_Y) + p.Y -= (schematic->size.Y - 1) / 2; + if (flags & DECO_PLACE_CENTER_Z) + p.Z -= (schematic->size.Z - 1) / 2; + Rotation rot = (rotation == ROTATE_RAND) ? (Rotation)pr->range(ROTATE_0, ROTATE_270) : rotation; - schematic->blitToVManip(p, vm, rot, false, m_ndef); + bool force_placement = (flags & DECO_FORCE_PLACEMENT); + + schematic->blitToVManip(p, vm, rot, force_placement); return 1; } diff --git a/src/mg_decoration.h b/src/mg_decoration.h index ab4a9377b..056748918 100644 --- a/src/mg_decoration.h +++ b/src/mg_decoration.h @@ -21,9 +21,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MG_DECORATION_HEADER #include <set> -#include "mapgen.h" +#include "objdef.h" +#include "noise.h" +#include "nodedef.h" -struct NoiseParams; class Mapgen; class MMVManip; class PseudoRandom; @@ -35,10 +36,11 @@ enum DecorationType { DECO_LSYSTEM }; -#define DECO_PLACE_CENTER_X 0x01 -#define DECO_PLACE_CENTER_Y 0x02 -#define DECO_PLACE_CENTER_Z 0x04 -#define DECO_USE_NOISE 0x08 +#define DECO_PLACE_CENTER_X 0x01 +#define DECO_PLACE_CENTER_Y 0x02 +#define DECO_PLACE_CENTER_Z 0x04 +#define DECO_USE_NOISE 0x08 +#define DECO_FORCE_PLACEMENT 0x10 extern FlagDesc flagdesc_deco[]; @@ -58,9 +60,18 @@ struct CutoffData { }; #endif -class Decoration : public GenElement, public NodeResolver { +class Decoration : public ObjDef, public NodeResolver { public: - INodeDefManager *ndef; + Decoration(); + virtual ~Decoration(); + + virtual void resolveNodeNames(); + + size_t placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); + //size_t placeCutoffs(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); + + virtual size_t generate(MMVManip *vm, PseudoRandom *pr, v3s16 p) = 0; + virtual int getHeight() = 0; u32 flags; int mapseed; @@ -74,42 +85,32 @@ public: std::set<u8> biomes; //std::list<CutoffData> cutoffs; //JMutex cutoff_mutex; - - Decoration(); - virtual ~Decoration(); - - virtual void resolveNodeNames(NodeResolveInfo *nri); - - size_t placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); - //size_t placeCutoffs(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); - - virtual size_t generate(MMVManip *vm, PseudoRandom *pr, s16 max_y, v3s16 p) = 0; - virtual int getHeight() = 0; }; class DecoSimple : public Decoration { public: + virtual size_t generate(MMVManip *vm, PseudoRandom *pr, v3s16 p); + bool canPlaceDecoration(MMVManip *vm, v3s16 p); + virtual int getHeight(); + + virtual void resolveNodeNames(); + std::vector<content_t> c_decos; std::vector<content_t> c_spawnby; s16 deco_height; s16 deco_height_max; s16 nspawnby; - - virtual void resolveNodeNames(NodeResolveInfo *nri); - - bool canPlaceDecoration(MMVManip *vm, v3s16 p); - virtual size_t generate(MMVManip *vm, PseudoRandom *pr, s16 max_y, v3s16 p); - virtual int getHeight(); }; class DecoSchematic : public Decoration { public: - Rotation rotation; - Schematic *schematic; - std::string filename; + DecoSchematic(); - virtual size_t generate(MMVManip *vm, PseudoRandom *pr, s16 max_y, v3s16 p); + virtual size_t generate(MMVManip *vm, PseudoRandom *pr, v3s16 p); virtual int getHeight(); + + Rotation rotation; + Schematic *schematic; }; @@ -120,15 +121,17 @@ public: }; */ -class DecorationManager : public GenElementManager { +class DecorationManager : public ObjDefManager { public: - static const char *ELEMENT_TITLE; - static const size_t ELEMENT_LIMIT = 0x10000; - DecorationManager(IGameDef *gamedef); - ~DecorationManager() {} + virtual ~DecorationManager() {} - Decoration *create(int type) + const char *getObjectTitle() const + { + return "decoration"; + } + + static Decoration *create(DecorationType type) { switch (type) { case DECO_SIMPLE: @@ -142,8 +145,6 @@ public: } } - void clear(); - size_t placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); }; diff --git a/src/mg_ore.cpp b/src/mg_ore.cpp index c62f05860..a94d1d6d9 100644 --- a/src/mg_ore.cpp +++ b/src/mg_ore.cpp @@ -24,8 +24,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "log.h" -const char *OreManager::ELEMENT_TITLE = "ore"; - FlagDesc flagdesc_ore[] = { {"absheight", OREFLAG_ABSHEIGHT}, {NULL, 0} @@ -36,7 +34,7 @@ FlagDesc flagdesc_ore[] = { OreManager::OreManager(IGameDef *gamedef) : - GenElementManager(gamedef) + ObjDefManager(gamedef, OBJDEF_ORE) { } @@ -45,8 +43,8 @@ size_t OreManager::placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nma { size_t nplaced = 0; - for (size_t i = 0; i != m_elements.size(); i++) { - Ore *ore = (Ore *)m_elements[i]; + for (size_t i = 0; i != m_objects.size(); i++) { + Ore *ore = (Ore *)m_objects[i]; if (!ore) continue; @@ -60,11 +58,11 @@ size_t OreManager::placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nma void OreManager::clear() { - for (size_t i = 0; i < m_elements.size(); i++) { - Ore *ore = (Ore *)m_elements[i]; + for (size_t i = 0; i < m_objects.size(); i++) { + Ore *ore = (Ore *)m_objects[i]; delete ore; } - m_elements.clear(); + m_objects.clear(); } @@ -84,10 +82,10 @@ Ore::~Ore() } -void Ore::resolveNodeNames(NodeResolveInfo *nri) +void Ore::resolveNodeNames() { - m_ndef->getIdFromResolveInfo(nri, "", CONTENT_AIR, c_ore); - m_ndef->getIdsFromResolveInfo(nri, c_wherein); + getIdFromNrBacklog(&c_ore, "", CONTENT_AIR); + getIdsFromNrBacklog(&c_wherein); } @@ -114,7 +112,7 @@ size_t Ore::placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) nmin.Y = actual_ymin; nmax.Y = actual_ymax; - generate(mg->vm, mg->seed, blockseed, nmin, nmax); + generate(mg->vm, mg->seed, blockseed, nmin, nmax, mg->biomemap); return 1; } @@ -124,19 +122,20 @@ size_t Ore::placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax) + v3s16 nmin, v3s16 nmax, u8 *biomemap) { PseudoRandom pr(blockseed); MapNode n_ore(c_ore, 0, ore_param2); - int volume = (nmax.X - nmin.X + 1) * + u32 sizex = (nmax.X - nmin.X + 1); + u32 volume = (nmax.X - nmin.X + 1) * (nmax.Y - nmin.Y + 1) * (nmax.Z - nmin.Z + 1); - int csize = clust_size; - int orechance = (csize * csize * csize) / clust_num_ores; - int nclusters = volume / clust_scarcity; + u32 csize = clust_size; + u32 orechance = (csize * csize * csize) / clust_num_ores; + u32 nclusters = volume / clust_scarcity; - for (int i = 0; i != nclusters; i++) { + for (u32 i = 0; i != nclusters; i++) { int x0 = pr.range(nmin.X, nmax.X - csize + 1); int y0 = pr.range(nmin.Y, nmax.Y - csize + 1); int z0 = pr.range(nmin.Z, nmax.Z - csize + 1); @@ -145,9 +144,16 @@ void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed, (NoisePerlin3D(&np, x0, y0, z0, mapseed) < nthresh)) continue; - for (int z1 = 0; z1 != csize; z1++) - for (int y1 = 0; y1 != csize; y1++) - for (int x1 = 0; x1 != csize; x1++) { + if (biomemap && !biomes.empty()) { + u32 index = sizex * (z0 - nmin.Z) + (x0 - nmin.X); + std::set<u8>::iterator it = biomes.find(biomemap[index]); + if (it == biomes.end()) + continue; + } + + for (u32 z1 = 0; z1 != csize; z1++) + for (u32 y1 = 0; y1 != csize; y1++) + for (u32 x1 = 0; x1 != csize; x1++) { if (pr.range(1, orechance) != 1) continue; @@ -165,7 +171,7 @@ void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed, void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax) + v3s16 nmin, v3s16 nmax, u8 *biomemap) { PseudoRandom pr(blockseed + 4234); MapNode n_ore(c_ore, 0, ore_param2); @@ -183,11 +189,17 @@ void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed, size_t index = 0; for (int z = nmin.Z; z <= nmax.Z; z++) - for (int x = nmin.X; x <= nmax.X; x++) { - float noiseval = noise->result[index++]; + for (int x = nmin.X; x <= nmax.X; x++, index++) { + float noiseval = noise->result[index]; if (noiseval < nthresh) continue; + if (biomemap && !biomes.empty()) { + std::set<u8>::iterator it = biomes.find(biomemap[index]); + if (it == biomes.end()) + continue; + } + int height = max_height * (1. / pr.range(1, 3)); int y0 = y_start + np.scale * noiseval; //pr.range(1, 3) - 1; int y1 = y0 + height; @@ -207,32 +219,40 @@ void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed, /////////////////////////////////////////////////////////////////////////////// void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax) + v3s16 nmin, v3s16 nmax, u8 *biomemap) { PseudoRandom pr(blockseed + 2404); MapNode n_ore(c_ore, 0, ore_param2); - int volume = (nmax.X - nmin.X + 1) * + u32 sizex = (nmax.X - nmin.X + 1); + u32 volume = (nmax.X - nmin.X + 1) * (nmax.Y - nmin.Y + 1) * (nmax.Z - nmin.Z + 1); - int csize = clust_size; - int nblobs = volume / clust_scarcity; + u32 csize = clust_size; + u32 nblobs = volume / clust_scarcity; if (!noise) noise = new Noise(&np, mapseed, csize, csize, csize); - for (int i = 0; i != nblobs; i++) { + for (u32 i = 0; i != nblobs; i++) { int x0 = pr.range(nmin.X, nmax.X - csize + 1); int y0 = pr.range(nmin.Y, nmax.Y - csize + 1); int z0 = pr.range(nmin.Z, nmax.Z - csize + 1); + if (biomemap && !biomes.empty()) { + u32 bmapidx = sizex * (z0 - nmin.Z) + (x0 - nmin.X); + std::set<u8>::iterator it = biomes.find(biomemap[bmapidx]); + if (it == biomes.end()) + continue; + } + bool noise_generated = false; noise->seed = blockseed + i; size_t index = 0; - for (int z1 = 0; z1 != csize; z1++) - for (int y1 = 0; y1 != csize; y1++) - for (int x1 = 0; x1 != csize; x1++, index++) { + for (u32 z1 = 0; z1 != csize; z1++) + for (u32 y1 = 0; y1 != csize; y1++) + for (u32 x1 = 0; x1 != csize; x1++, index++) { u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1); if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) continue; @@ -276,11 +296,13 @@ OreVein::~OreVein() void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax) + v3s16 nmin, v3s16 nmax, u8 *biomemap) { PseudoRandom pr(blockseed + 520); MapNode n_ore(c_ore, 0, ore_param2); + u32 sizex = (nmax.X - nmin.X + 1); + if (!noise) { int sx = nmax.X - nmin.X + 1; int sy = nmax.Y - nmin.Y + 1; @@ -300,6 +322,13 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) continue; + if (biomemap && !biomes.empty()) { + u32 bmapidx = sizex * (z - nmin.Z) + (x - nmin.X); + std::set<u8>::iterator it = biomes.find(biomemap[bmapidx]); + if (it == biomes.end()) + continue; + } + // Same lazy generation optimization as in OreBlob if (!noise_generated) { noise_generated = true; @@ -308,7 +337,7 @@ void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, } // randval ranges from -1..1 - float randval = (float)pr.next() / (PSEUDORANDOM_MAX / 2) - 1.f; + float randval = (float)pr.next() / (pr.RANDOM_RANGE / 2) - 1.f; float noiseval = contour(noise->result[index]); float noiseval2 = contour(noise2->result[index]); if (noiseval * noiseval2 + randval * random_factor < nthresh) diff --git a/src/mg_ore.h b/src/mg_ore.h index 67ca9a849..ffe8cfe50 100644 --- a/src/mg_ore.h +++ b/src/mg_ore.h @@ -20,10 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef MG_ORE_HEADER #define MG_ORE_HEADER -#include "util/string.h" -#include "mapgen.h" +#include "objdef.h" +#include "noise.h" +#include "nodedef.h" -struct NoiseParams; class Noise; class Mapgen; class MMVManip; @@ -39,15 +39,15 @@ class MMVManip; enum OreType { - ORE_TYPE_SCATTER, - ORE_TYPE_SHEET, - ORE_TYPE_BLOB, - ORE_TYPE_VEIN, + ORE_SCATTER, + ORE_SHEET, + ORE_BLOB, + ORE_VEIN, }; extern FlagDesc flagdesc_ore[]; -class Ore : public GenElement, public NodeResolver { +class Ore : public ObjDef, public NodeResolver { public: static const bool NEEDS_NOISE = false; @@ -63,15 +63,16 @@ public: float nthresh; // threshhold for noise at which an ore is placed NoiseParams np; // noise for distribution of clusters (NULL for uniform scattering) Noise *noise; + std::set<u8> biomes; Ore(); virtual ~Ore(); - virtual void resolveNodeNames(NodeResolveInfo *nri); + virtual void resolveNodeNames(); size_t placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax) = 0; + v3s16 nmin, v3s16 nmax, u8 *biomemap) = 0; }; class OreScatter : public Ore { @@ -79,7 +80,7 @@ public: static const bool NEEDS_NOISE = false; virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax); + v3s16 nmin, v3s16 nmax, u8 *biomemap); }; class OreSheet : public Ore { @@ -87,7 +88,7 @@ public: static const bool NEEDS_NOISE = true; virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax); + v3s16 nmin, v3s16 nmax, u8 *biomemap); }; class OreBlob : public Ore { @@ -95,7 +96,7 @@ public: static const bool NEEDS_NOISE = true; virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax); + v3s16 nmin, v3s16 nmax, u8 *biomemap); }; class OreVein : public Ore { @@ -109,27 +110,29 @@ public: virtual ~OreVein(); virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, - v3s16 nmin, v3s16 nmax); + v3s16 nmin, v3s16 nmax, u8 *biomemap); }; -class OreManager : public GenElementManager { +class OreManager : public ObjDefManager { public: - static const char *ELEMENT_TITLE; - static const size_t ELEMENT_LIMIT = 0x10000; - OreManager(IGameDef *gamedef); - ~OreManager() {} + virtual ~OreManager() {} + + const char *getObjectTitle() const + { + return "ore"; + } - Ore *create(int type) + static Ore *create(OreType type) { switch (type) { - case ORE_TYPE_SCATTER: + case ORE_SCATTER: return new OreScatter; - case ORE_TYPE_SHEET: + case ORE_SHEET: return new OreSheet; - case ORE_TYPE_BLOB: + case ORE_BLOB: return new OreBlob; - case ORE_TYPE_VEIN: + case ORE_VEIN: return new OreVein; default: return NULL; diff --git a/src/mg_schematic.cpp b/src/mg_schematic.cpp index a3404e2dc..a5ffb20b8 100644 --- a/src/mg_schematic.cpp +++ b/src/mg_schematic.cpp @@ -18,8 +18,11 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include <fstream> +#include <typeinfo> #include "mg_schematic.h" +#include "gamedef.h" #include "mapgen.h" +#include "emerge.h" #include "map.h" #include "mapblock.h" #include "log.h" @@ -28,14 +31,34 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serialization.h" #include "filesys.h" -const char *SchematicManager::ELEMENT_TITLE = "schematic"; - /////////////////////////////////////////////////////////////////////////////// SchematicManager::SchematicManager(IGameDef *gamedef) : - GenElementManager(gamedef) + ObjDefManager(gamedef, OBJDEF_SCHEMATIC) { + m_gamedef = gamedef; +} + + +void SchematicManager::clear() +{ + EmergeManager *emerge = m_gamedef->getEmergeManager(); + + // Remove all dangling references in Decorations + DecorationManager *decomgr = emerge->decomgr; + for (size_t i = 0; i != decomgr->getNumObjects(); i++) { + Decoration *deco = (Decoration *)decomgr->getRaw(i); + + try { + DecoSchematic *dschem = dynamic_cast<DecoSchematic *>(deco); + if (dschem) + dschem->schematic = NULL; + } catch (std::bad_cast) { + } + } + + ObjDefManager::clear(); } @@ -58,34 +81,27 @@ Schematic::~Schematic() } -void Schematic::resolveNodeNames(NodeResolveInfo *nri) +void Schematic::resolveNodeNames() { - m_ndef->getIdsFromResolveInfo(nri, c_nodes); -} - - -void Schematic::updateContentIds() -{ - if (flags & SCHEM_CIDS_UPDATED) - return; - - flags |= SCHEM_CIDS_UPDATED; + getIdsFromNrBacklog(&c_nodes, true, CONTENT_AIR); size_t bufsize = size.X * size.Y * size.Z; - for (size_t i = 0; i != bufsize; i++) - schemdata[i].setContent(c_nodes[schemdata[i].getContent()]); + for (size_t i = 0; i != bufsize; i++) { + content_t c_original = schemdata[i].getContent(); + content_t c_new = c_nodes[c_original]; + schemdata[i].setContent(c_new); + } } -void Schematic::blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, - bool force_placement, INodeDefManager *ndef) +void Schematic::blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, bool force_place) { + sanity_check(m_ndef != NULL); + int xstride = 1; int ystride = size.X; int zstride = size.X * size.Y; - updateContentIds(); - s16 sx = size.X; s16 sy = size.Y; s16 sz = size.Z; @@ -117,8 +133,8 @@ void Schematic::blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, s16 y_map = p.Y; for (s16 y = 0; y != sy; y++) { - if (slice_probs[y] != MTSCHEM_PROB_ALWAYS && - myrand_range(1, 255) > slice_probs[y]) + if ((slice_probs[y] != MTSCHEM_PROB_ALWAYS) && + (slice_probs[y] <= myrand_range(1, MTSCHEM_PROB_ALWAYS))) continue; for (s16 z = 0; z != sz; z++) { @@ -131,24 +147,27 @@ void Schematic::blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, if (schemdata[i].getContent() == CONTENT_IGNORE) continue; - if (schemdata[i].param1 == MTSCHEM_PROB_NEVER) + u8 placement_prob = schemdata[i].param1 & MTSCHEM_PROB_MASK; + bool force_place_node = schemdata[i].param1 & MTSCHEM_FORCE_PLACE; + + if (placement_prob == MTSCHEM_PROB_NEVER) continue; - if (!force_placement) { + if (!force_place && !force_place_node) { content_t c = vm->m_data[vi].getContent(); if (c != CONTENT_AIR && c != CONTENT_IGNORE) continue; } - if (schemdata[i].param1 != MTSCHEM_PROB_ALWAYS && - myrand_range(1, 255) > schemdata[i].param1) + if ((placement_prob != MTSCHEM_PROB_ALWAYS) && + (placement_prob <= myrand_range(1, MTSCHEM_PROB_ALWAYS))) continue; vm->m_data[vi] = schemdata[i]; vm->m_data[vi].param1 = 0; if (rot) - vm->m_data[vi].rotateAlongYAxis(ndef, rot); + vm->m_data[vi].rotateAlongYAxis(m_ndef, rot); } } y_map++; @@ -156,10 +175,12 @@ void Schematic::blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, } -void Schematic::placeStructure(Map *map, v3s16 p, u32 flags, Rotation rot, - bool force_placement, INodeDefManager *ndef) +void Schematic::placeStructure(Map *map, v3s16 p, u32 flags, + Rotation rot, bool force_place) { - assert(schemdata != NULL); + assert(schemdata != NULL); // Pre-condition + sanity_check(m_ndef != NULL); + MMVManip *vm = new MMVManip(map); if (rot == ROTATE_RAND) @@ -179,7 +200,7 @@ void Schematic::placeStructure(Map *map, v3s16 p, u32 flags, Rotation rot, v3s16 bp2 = getNodeBlockPos(p + s - v3s16(1,1,1)); vm->initialEmerge(bp1, bp2); - blitToVManip(p, vm, rot, force_placement, ndef); + blitToVManip(p, vm, rot, force_place); std::map<v3s16, MapBlock *> lighting_modified_blocks; std::map<v3s16, MapBlock *> modified_blocks; @@ -200,111 +221,89 @@ void Schematic::placeStructure(Map *map, v3s16 p, u32 flags, Rotation rot, } -bool Schematic::loadSchematicFromFile(const char *filename, INodeDefManager *ndef, - std::map<std::string, std::string> &replace_names) +bool Schematic::deserializeFromMts(std::istream *is, + std::vector<std::string> *names) { + std::istream &ss = *is; content_t cignore = CONTENT_IGNORE; bool have_cignore = false; - std::ifstream is(filename, std::ios_base::binary); - - u32 signature = readU32(is); + //// Read signature + u32 signature = readU32(ss); if (signature != MTSCHEM_FILE_SIGNATURE) { - errorstream << "loadSchematicFile: invalid schematic " + errorstream << "Schematic::deserializeFromMts: invalid schematic " "file" << std::endl; return false; } - u16 version = readU16(is); + //// Read version + u16 version = readU16(ss); if (version > MTSCHEM_FILE_VER_HIGHEST_READ) { - errorstream << "loadSchematicFile: unsupported schematic " + errorstream << "Schematic::deserializeFromMts: unsupported schematic " "file version" << std::endl; return false; } - size = readV3S16(is); + //// Read size + size = readV3S16(ss); + //// Read Y-slice probability values delete []slice_probs; slice_probs = new u8[size.Y]; for (int y = 0; y != size.Y; y++) - slice_probs[y] = (version >= 3) ? readU8(is) : MTSCHEM_PROB_ALWAYS; - - NodeResolveInfo *nri = new NodeResolveInfo(this); + slice_probs[y] = (version >= 3) ? readU8(ss) : MTSCHEM_PROB_ALWAYS_OLD; - u16 nidmapcount = readU16(is); + //// Read node names + u16 nidmapcount = readU16(ss); for (int i = 0; i != nidmapcount; i++) { - std::string name = deSerializeString(is); + std::string name = deSerializeString(ss); + + // Instances of "ignore" from v1 are converted to air (and instances + // are fixed to have MTSCHEM_PROB_NEVER later on). if (name == "ignore") { name = "air"; cignore = i; have_cignore = true; } - std::map<std::string, std::string>::iterator it; - it = replace_names.find(name); - if (it != replace_names.end()) - name = it->second; - - nri->nodenames.push_back(name); + names->push_back(name); } - nri->nodelistinfo.push_back(NodeListInfo(nidmapcount, CONTENT_AIR)); - ndef->pendNodeResolve(nri); - + //// Read node data size_t nodecount = size.X * size.Y * size.Z; delete []schemdata; schemdata = new MapNode[nodecount]; - MapNode::deSerializeBulk(is, SER_FMT_VER_HIGHEST_READ, schemdata, + MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata, nodecount, 2, 2, true); - if (version == 1) { // fix up the probability values + // Fix probability values for nodes that were ignore; removed in v2 + if (version < 2) { for (size_t i = 0; i != nodecount; i++) { if (schemdata[i].param1 == 0) - schemdata[i].param1 = MTSCHEM_PROB_ALWAYS; + schemdata[i].param1 = MTSCHEM_PROB_ALWAYS_OLD; if (have_cignore && schemdata[i].getContent() == cignore) schemdata[i].param1 = MTSCHEM_PROB_NEVER; } } + // Fix probability values for probability range truncation introduced in v4 + if (version < 4) { + for (s16 y = 0; y != size.Y; y++) + slice_probs[y] >>= 1; + for (size_t i = 0; i != nodecount; i++) + schemdata[i].param1 >>= 1; + } + return true; } -/* - Minetest Schematic File Format - - All values are stored in big-endian byte order. - [u32] signature: 'MTSM' - [u16] version: 3 - [u16] size X - [u16] size Y - [u16] size Z - For each Y: - [u8] slice probability value - [Name-ID table] Name ID Mapping Table - [u16] name-id count - For each name-id mapping: - [u16] name length - [u8[]] name - ZLib deflated { - For each node in schematic: (for z, y, x) - [u16] content - For each node in schematic: - [u8] probability of occurance (param1) - For each node in schematic: - [u8] param2 - } - - Version changes: - 1 - Initial version - 2 - Fixed messy never/always place; 0 probability is now never, 0xFF is always - 3 - Added y-slice probabilities; this allows for variable height structures -*/ -void Schematic::saveSchematicToFile(const char *filename, INodeDefManager *ndef) +bool Schematic::serializeToMts(std::ostream *os, + const std::vector<std::string> &names) { - std::ostringstream ss(std::ios_base::binary); + std::ostream &ss = *os; writeU32(ss, MTSCHEM_FILE_SIGNATURE); // signature writeU16(ss, MTSCHEM_FILE_VER_HIGHEST_WRITE); // version @@ -313,45 +312,159 @@ void Schematic::saveSchematicToFile(const char *filename, INodeDefManager *ndef) for (int y = 0; y != size.Y; y++) // Y slice probabilities writeU8(ss, slice_probs[y]); - std::vector<content_t> usednodes; - int nodecount = size.X * size.Y * size.Z; - build_nnlist_and_update_ids(schemdata, nodecount, &usednodes); - - u16 numids = usednodes.size(); - writeU16(ss, numids); // name count - for (int i = 0; i != numids; i++) - ss << serializeString(ndef->get(usednodes[i]).name); // node names + writeU16(ss, names.size()); // name count + for (size_t i = 0; i != names.size(); i++) + ss << serializeString(names[i]); // node names // compressed bulk node data - MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE, schemdata, - nodecount, 2, 2, true); + MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE, + schemdata, size.X * size.Y * size.Z, 2, 2, true); - fs::safeWriteToFile(filename, ss.str()); + return true; } -void build_nnlist_and_update_ids(MapNode *nodes, u32 nodecount, - std::vector<content_t> *usednodes) +bool Schematic::serializeToLua(std::ostream *os, + const std::vector<std::string> &names, bool use_comments, u32 indent_spaces) { - std::map<content_t, content_t> nodeidmap; - content_t numids = 0; + std::ostream &ss = *os; + + std::string indent("\t"); + if (indent_spaces > 0) + indent.assign(indent_spaces, ' '); + + //// Write header + { + ss << "schematic = {" << std::endl; + ss << indent << "size = " + << "{x=" << size.X + << ", y=" << size.Y + << ", z=" << size.Z + << "}," << std::endl; + } - for (u32 i = 0; i != nodecount; i++) { - content_t id; - content_t c = nodes[i].getContent(); + //// Write y-slice probabilities + { + ss << indent << "yslice_prob = {" << std::endl; - std::map<content_t, content_t>::const_iterator it = nodeidmap.find(c); - if (it == nodeidmap.end()) { - id = numids; - numids++; + for (u16 y = 0; y != size.Y; y++) { + u8 probability = slice_probs[y] & MTSCHEM_PROB_MASK; - usednodes->push_back(c); - nodeidmap.insert(std::make_pair(c, id)); - } else { - id = it->second; + ss << indent << indent << "{" + << "ypos=" << y + << ", prob=" << (u16)probability * 2 + << "}," << std::endl; } - nodes[i].setContent(id); + + ss << indent << "}," << std::endl; + } + + //// Write node data + { + ss << indent << "data = {" << std::endl; + + u32 i = 0; + for (u16 z = 0; z != size.Z; z++) + for (u16 y = 0; y != size.Y; y++) { + if (use_comments) { + ss << std::endl + << indent << indent + << "-- z=" << z + << ", y=" << y << std::endl; + } + + for (u16 x = 0; x != size.X; x++, i++) { + u8 probability = schemdata[i].param1 & MTSCHEM_PROB_MASK; + bool force_place = schemdata[i].param1 & MTSCHEM_FORCE_PLACE; + + ss << indent << indent << "{" + << "name=\"" << names[schemdata[i].getContent()] + << "\", prob=" << (u16)probability * 2 + << ", param2=" << (u16)schemdata[i].param2; + + if (force_place) + ss << ", force_place=true"; + + ss << "}," << std::endl; + } + } + + ss << indent << "}," << std::endl; } + + ss << "}" << std::endl; + + return true; +} + + +bool Schematic::loadSchematicFromFile(const std::string &filename, + INodeDefManager *ndef, StringMap *replace_names) +{ + std::ifstream is(filename.c_str(), std::ios_base::binary); + if (!is.good()) { + errorstream << "Schematic::loadSchematicFile: unable to open file '" + << filename << "'" << std::endl; + return false; + } + + size_t origsize = m_nodenames.size(); + if (!deserializeFromMts(&is, &m_nodenames)) + return false; + + if (replace_names) { + for (size_t i = origsize; i != m_nodenames.size(); i++) { + std::string &name = m_nodenames[i]; + StringMap::iterator it = replace_names->find(name); + if (it != replace_names->end()) + name = it->second; + } + } + + m_nnlistsizes.push_back(m_nodenames.size() - origsize); + + if (ndef) + ndef->pendNodeResolve(this); + + return true; +} + + +bool Schematic::saveSchematicToFile(const std::string &filename, + INodeDefManager *ndef) +{ + MapNode *orig_schemdata = schemdata; + std::vector<std::string> ndef_nodenames; + std::vector<std::string> *names; + + if (m_resolve_done && ndef == NULL) + ndef = m_ndef; + + if (ndef) { + names = &ndef_nodenames; + + u32 volume = size.X * size.Y * size.Z; + schemdata = new MapNode[volume]; + for (u32 i = 0; i != volume; i++) + schemdata[i] = orig_schemdata[i]; + + generate_nodelist_and_update_ids(schemdata, volume, names, ndef); + } else { // otherwise, use the names we have on hand in the list + names = &m_nodenames; + } + + std::ostringstream os(std::ios_base::binary); + bool status = serializeToMts(&os, *names); + + if (ndef) { + delete []schemdata; + schemdata = orig_schemdata; + } + + if (!status) + return false; + + return fs::safeWriteToFile(filename, os.str()); } @@ -408,3 +521,28 @@ void Schematic::applyProbabilities(v3s16 p0, slice_probs[y] = (*splist)[i].second; } } + + +void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount, + std::vector<std::string> *usednodes, INodeDefManager *ndef) +{ + std::map<content_t, content_t> nodeidmap; + content_t numids = 0; + + for (size_t i = 0; i != nodecount; i++) { + content_t id; + content_t c = nodes[i].getContent(); + + std::map<content_t, content_t>::const_iterator it = nodeidmap.find(c); + if (it == nodeidmap.end()) { + id = numids; + numids++; + + usednodes->push_back(ndef->get(c).name); + nodeidmap.insert(std::make_pair(c, id)); + } else { + id = it->second; + } + nodes[i].setContent(id); + } +} diff --git a/src/mg_schematic.h b/src/mg_schematic.h index ad5afb15f..5c732648e 100644 --- a/src/mg_schematic.h +++ b/src/mg_schematic.h @@ -29,66 +29,119 @@ class Mapgen; class MMVManip; class PseudoRandom; class NodeResolver; +class IGameDef; -/////////////////// Schematic flags -#define SCHEM_CIDS_UPDATED 0x08 +/* + Minetest Schematic File Format + + All values are stored in big-endian byte order. + [u32] signature: 'MTSM' + [u16] version: 4 + [u16] size X + [u16] size Y + [u16] size Z + For each Y: + [u8] slice probability value + [Name-ID table] Name ID Mapping Table + [u16] name-id count + For each name-id mapping: + [u16] name length + [u8[]] name + ZLib deflated { + For each node in schematic: (for z, y, x) + [u16] content + For each node in schematic: + [u8] param1 + bit 0-6: probability + bit 7: specific node force placement + For each node in schematic: + [u8] param2 + } + Version changes: + 1 - Initial version + 2 - Fixed messy never/always place; 0 probability is now never, 0xFF is always + 3 - Added y-slice probabilities; this allows for variable height structures + 4 - Compressed range of node occurence prob., added per-node force placement bit +*/ +//// Schematic constants #define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM' -#define MTSCHEM_FILE_VER_HIGHEST_READ 3 -#define MTSCHEM_FILE_VER_HIGHEST_WRITE 3 +#define MTSCHEM_FILE_VER_HIGHEST_READ 4 +#define MTSCHEM_FILE_VER_HIGHEST_WRITE 4 -#define MTSCHEM_PROB_NEVER 0x00 -#define MTSCHEM_PROB_ALWAYS 0xFF +#define MTSCHEM_PROB_MASK 0x7F +#define MTSCHEM_PROB_NEVER 0x00 +#define MTSCHEM_PROB_ALWAYS 0x7F +#define MTSCHEM_PROB_ALWAYS_OLD 0xFF -class Schematic : public GenElement, public NodeResolver { -public: - std::vector<content_t> c_nodes; +#define MTSCHEM_FORCE_PLACE 0x80 - u32 flags; - v3s16 size; - MapNode *schemdata; - u8 *slice_probs; +enum SchematicType +{ + SCHEMATIC_NORMAL, +}; +enum SchematicFormatType { + SCHEM_FMT_HANDLE, + SCHEM_FMT_MTS, + SCHEM_FMT_LUA, +}; + +class Schematic : public ObjDef, public NodeResolver { +public: Schematic(); virtual ~Schematic(); - virtual void resolveNodeNames(NodeResolveInfo *nri); + virtual void resolveNodeNames(); - void updateContentIds(); + bool loadSchematicFromFile(const std::string &filename, INodeDefManager *ndef, + StringMap *replace_names=NULL); + bool saveSchematicToFile(const std::string &filename, INodeDefManager *ndef); + bool getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2); - void blitToVManip(v3s16 p, MMVManip *vm, - Rotation rot, bool force_placement, INodeDefManager *ndef); + bool deserializeFromMts(std::istream *is, std::vector<std::string> *names); + bool serializeToMts(std::ostream *os, const std::vector<std::string> &names); + bool serializeToLua(std::ostream *os, const std::vector<std::string> &names, + bool use_comments, u32 indent_spaces); - bool loadSchematicFromFile(const char *filename, INodeDefManager *ndef, - std::map<std::string, std::string> &replace_names); - void saveSchematicToFile(const char *filename, INodeDefManager *ndef); - bool getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2); + void blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, bool force_place); + void placeStructure(Map *map, v3s16 p, u32 flags, Rotation rot, bool force_place); - void placeStructure(Map *map, v3s16 p, u32 flags, - Rotation rot, bool force_placement, INodeDefManager *nef); void applyProbabilities(v3s16 p0, std::vector<std::pair<v3s16, u8> > *plist, std::vector<std::pair<s16, u8> > *splist); + + std::vector<content_t> c_nodes; + u32 flags; + v3s16 size; + MapNode *schemdata; + u8 *slice_probs; }; -class SchematicManager : public GenElementManager { +class SchematicManager : public ObjDefManager { public: - static const char *ELEMENT_TITLE; - static const size_t ELEMENT_LIMIT = 0x10000; - SchematicManager(IGameDef *gamedef); - ~SchematicManager() {} + virtual ~SchematicManager() {} + + virtual void clear(); - Schematic *create(int type) + const char *getObjectTitle() const + { + return "schematic"; + } + + static Schematic *create(SchematicType type) { return new Schematic; } -}; -void build_nnlist_and_update_ids(MapNode *nodes, u32 nodecount, - std::vector<content_t> *usednodes); +private: + IGameDef *m_gamedef; +}; +void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount, + std::vector<std::string> *usednodes, INodeDefManager *ndef); #endif diff --git a/src/minimap.cpp b/src/minimap.cpp new file mode 100644 index 000000000..d1fb3867d --- /dev/null +++ b/src/minimap.cpp @@ -0,0 +1,561 @@ +/* +Minetest +Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "minimap.h" +#include <math.h> +#include "logoutputbuffer.h" +#include "jthread/jmutexautolock.h" +#include "jthread/jsemaphore.h" +#include "clientmap.h" +#include "settings.h" +#include "nodedef.h" +#include "porting.h" +#include "util/numeric.h" +#include "util/string.h" + + +//// +//// MinimapUpdateThread +//// + +MinimapUpdateThread::~MinimapUpdateThread() +{ + for (std::map<v3s16, MinimapMapblock *>::iterator + it = m_blocks_cache.begin(); + it != m_blocks_cache.end(); ++it) { + delete it->second; + } + + for (std::deque<QueuedMinimapUpdate>::iterator + it = m_update_queue.begin(); + it != m_update_queue.end(); ++it) { + QueuedMinimapUpdate &q = *it; + delete q.data; + } +} + +bool MinimapUpdateThread::pushBlockUpdate(v3s16 pos, MinimapMapblock *data) +{ + JMutexAutoLock lock(m_queue_mutex); + + // Find if block is already in queue. + // If it is, update the data and quit. + for (std::deque<QueuedMinimapUpdate>::iterator + it = m_update_queue.begin(); + it != m_update_queue.end(); ++it) { + QueuedMinimapUpdate &q = *it; + if (q.pos == pos) { + delete q.data; + q.data = data; + return false; + } + } + + // Add the block + QueuedMinimapUpdate q; + q.pos = pos; + q.data = data; + m_update_queue.push_back(q); + + return true; +} + +bool MinimapUpdateThread::popBlockUpdate(QueuedMinimapUpdate *update) +{ + JMutexAutoLock lock(m_queue_mutex); + + if (m_update_queue.empty()) + return false; + + *update = m_update_queue.front(); + m_update_queue.pop_front(); + + return true; +} + +void MinimapUpdateThread::enqueueBlock(v3s16 pos, MinimapMapblock *data) +{ + pushBlockUpdate(pos, data); + deferUpdate(); +} + + +void MinimapUpdateThread::doUpdate() +{ + QueuedMinimapUpdate update; + + while (popBlockUpdate(&update)) { + if (update.data) { + // Swap two values in the map using single lookup + std::pair<std::map<v3s16, MinimapMapblock*>::iterator, bool> + result = m_blocks_cache.insert(std::make_pair(update.pos, update.data)); + if (result.second == false) { + delete result.first->second; + result.first->second = update.data; + } + } else { + std::map<v3s16, MinimapMapblock *>::iterator it; + it = m_blocks_cache.find(update.pos); + if (it != m_blocks_cache.end()) { + delete it->second; + m_blocks_cache.erase(it); + } + } + } + + if (data->map_invalidated && data->mode != MINIMAP_MODE_OFF) { + getMap(data->pos, data->map_size, data->scan_height, data->is_radar); + data->map_invalidated = false; + } +} + +MinimapPixel *MinimapUpdateThread::getMinimapPixel(v3s16 pos, + s16 scan_height, s16 *pixel_height) +{ + s16 height = scan_height - MAP_BLOCKSIZE; + v3s16 blockpos_max, blockpos_min, relpos; + + getNodeBlockPosWithOffset( + v3s16(pos.X, pos.Y - scan_height / 2, pos.Z), + blockpos_min, relpos); + getNodeBlockPosWithOffset( + v3s16(pos.X, pos.Y + scan_height / 2, pos.Z), + blockpos_max, relpos); + + for (s16 i = blockpos_max.Y; i > blockpos_min.Y - 1; i--) { + std::map<v3s16, MinimapMapblock *>::iterator it = + m_blocks_cache.find(v3s16(blockpos_max.X, i, blockpos_max.Z)); + if (it != m_blocks_cache.end()) { + MinimapMapblock *mmblock = it->second; + MinimapPixel *pixel = &mmblock->data[relpos.Z * MAP_BLOCKSIZE + relpos.X]; + if (pixel->id != CONTENT_AIR) { + *pixel_height = height + pixel->height; + return pixel; + } + } + + height -= MAP_BLOCKSIZE; + } + + return NULL; +} + +s16 MinimapUpdateThread::getAirCount(v3s16 pos, s16 height) +{ + s16 air_count = 0; + v3s16 blockpos_max, blockpos_min, relpos; + + getNodeBlockPosWithOffset( + v3s16(pos.X, pos.Y - height / 2, pos.Z), + blockpos_min, relpos); + getNodeBlockPosWithOffset( + v3s16(pos.X, pos.Y + height / 2, pos.Z), + blockpos_max, relpos); + + for (s16 i = blockpos_max.Y; i > blockpos_min.Y - 1; i--) { + std::map<v3s16, MinimapMapblock *>::iterator it = + m_blocks_cache.find(v3s16(blockpos_max.X, i, blockpos_max.Z)); + if (it != m_blocks_cache.end()) { + MinimapMapblock *mmblock = it->second; + MinimapPixel *pixel = &mmblock->data[relpos.Z * MAP_BLOCKSIZE + relpos.X]; + air_count += pixel->air_count; + } + } + + return air_count; +} + +void MinimapUpdateThread::getMap(v3s16 pos, s16 size, s16 height, bool is_radar) +{ + v3s16 p = v3s16(pos.X - size / 2, pos.Y, pos.Z - size / 2); + + for (s16 x = 0; x < size; x++) + for (s16 z = 0; z < size; z++) { + u16 id = CONTENT_AIR; + MinimapPixel *mmpixel = &data->minimap_scan[x + z * size]; + + if (!is_radar) { + s16 pixel_height = 0; + MinimapPixel *cached_pixel = + getMinimapPixel(v3s16(p.X + x, p.Y, p.Z + z), height, &pixel_height); + if (cached_pixel) { + id = cached_pixel->id; + mmpixel->height = pixel_height; + } + } else { + mmpixel->air_count = getAirCount(v3s16(p.X + x, p.Y, p.Z + z), height); + } + + mmpixel->id = id; + } +} + +//// +//// Mapper +//// + +Mapper::Mapper(IrrlichtDevice *device, Client *client) +{ + this->driver = device->getVideoDriver(); + this->m_tsrc = client->getTextureSource(); + this->m_shdrsrc = client->getShaderSource(); + this->m_ndef = client->getNodeDefManager(); + + // Initialize static settings + m_enable_shaders = g_settings->getBool("enable_shaders"); + m_surface_mode_scan_height = + g_settings->getBool("minimap_double_scan_height") ? 256 : 128; + + // Initialize minimap data + data = new MinimapData; + data->mode = MINIMAP_MODE_OFF; + data->is_radar = false; + data->map_invalidated = true; + data->heightmap_image = NULL; + data->minimap_image = NULL; + data->texture = NULL; + data->heightmap_texture = NULL; + data->minimap_shape_round = g_settings->getBool("minimap_shape_round"); + + // Get round minimap textures + data->minimap_mask_round = driver->createImage( + m_tsrc->getTexture("minimap_mask_round.png"), + core::position2d<s32>(0, 0), + core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + data->minimap_overlay_round = m_tsrc->getTexture("minimap_overlay_round.png"); + + // Get square minimap textures + data->minimap_mask_square = driver->createImage( + m_tsrc->getTexture("minimap_mask_square.png"), + core::position2d<s32>(0, 0), + core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + data->minimap_overlay_square = m_tsrc->getTexture("minimap_overlay_square.png"); + + // Create player marker texture + data->player_marker = m_tsrc->getTexture("player_marker.png"); + + // Create mesh buffer for minimap + m_meshbuffer = getMinimapMeshBuffer(); + + // Initialize and start thread + m_minimap_update_thread = new MinimapUpdateThread(); + m_minimap_update_thread->data = data; + m_minimap_update_thread->Start(); +} + +Mapper::~Mapper() +{ + m_minimap_update_thread->Stop(); + m_minimap_update_thread->Wait(); + + m_meshbuffer->drop(); + + data->minimap_mask_round->drop(); + data->minimap_mask_square->drop(); + + driver->removeTexture(data->texture); + driver->removeTexture(data->heightmap_texture); + driver->removeTexture(data->minimap_overlay_round); + driver->removeTexture(data->minimap_overlay_square); + + delete data; + delete m_minimap_update_thread; +} + +void Mapper::addBlock(v3s16 pos, MinimapMapblock *data) +{ + m_minimap_update_thread->enqueueBlock(pos, data); +} + +MinimapMode Mapper::getMinimapMode() +{ + return data->mode; +} + +void Mapper::toggleMinimapShape() +{ + JMutexAutoLock lock(m_mutex); + + data->minimap_shape_round = !data->minimap_shape_round; + g_settings->setBool("minimap_shape_round", data->minimap_shape_round); + m_minimap_update_thread->deferUpdate(); +} + +void Mapper::setMinimapMode(MinimapMode mode) +{ + static const MinimapModeDef modedefs[MINIMAP_MODE_COUNT] = { + {false, 0, 0}, + {false, m_surface_mode_scan_height, 256}, + {false, m_surface_mode_scan_height, 128}, + {false, m_surface_mode_scan_height, 64}, + {true, 32, 128}, + {true, 32, 64}, + {true, 32, 32} + }; + + if (mode >= MINIMAP_MODE_COUNT) + return; + + JMutexAutoLock lock(m_mutex); + + data->is_radar = modedefs[mode].is_radar; + data->scan_height = modedefs[mode].scan_height; + data->map_size = modedefs[mode].map_size; + data->mode = mode; + + m_minimap_update_thread->deferUpdate(); +} + +void Mapper::setPos(v3s16 pos) +{ + bool do_update = false; + + { + JMutexAutoLock lock(m_mutex); + + if (pos != data->old_pos) { + data->old_pos = data->pos; + data->pos = pos; + do_update = true; + } + } + + if (do_update) + m_minimap_update_thread->deferUpdate(); +} + +void Mapper::setAngle(f32 angle) +{ + m_angle = angle; +} + +void Mapper::blitMinimapPixelsToImageRadar(video::IImage *map_image) +{ + for (s16 x = 0; x < data->map_size; x++) + for (s16 z = 0; z < data->map_size; z++) { + MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; + + video::SColor c(240, 0, 0, 0); + if (mmpixel->air_count > 0) + c.setGreen(core::clamp(core::round32(32 + mmpixel->air_count * 8), 0, 255)); + + map_image->setPixel(x, data->map_size - z - 1, c); + } +} + +void Mapper::blitMinimapPixelsToImageSurface( + video::IImage *map_image, video::IImage *heightmap_image) +{ + for (s16 x = 0; x < data->map_size; x++) + for (s16 z = 0; z < data->map_size; z++) { + MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size]; + + video::SColor c = m_ndef->get(mmpixel->id).minimap_color; + c.setAlpha(240); + + map_image->setPixel(x, data->map_size - z - 1, c); + + u32 h = mmpixel->height; + heightmap_image->setPixel(x,data->map_size - z - 1, + video::SColor(255, h, h, h)); + } +} + +video::ITexture *Mapper::getMinimapTexture() +{ + // update minimap textures when new scan is ready + if (data->map_invalidated) + return data->texture; + + // create minimap and heightmap images in memory + core::dimension2d<u32> dim(data->map_size, data->map_size); + video::IImage *map_image = driver->createImage(video::ECF_A8R8G8B8, dim); + video::IImage *heightmap_image = driver->createImage(video::ECF_A8R8G8B8, dim); + video::IImage *minimap_image = driver->createImage(video::ECF_A8R8G8B8, + core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY)); + + // Blit MinimapPixels to images + if (data->is_radar) + blitMinimapPixelsToImageRadar(map_image); + else + blitMinimapPixelsToImageSurface(map_image, heightmap_image); + + map_image->copyToScaling(minimap_image); + map_image->drop(); + + video::IImage *minimap_mask = data->minimap_shape_round ? + data->minimap_mask_round : data->minimap_mask_square; + + if (minimap_mask) { + for (s16 y = 0; y < MINIMAP_MAX_SY; y++) + for (s16 x = 0; x < MINIMAP_MAX_SX; x++) { + video::SColor mask_col = minimap_mask->getPixel(x, y); + if (!mask_col.getAlpha()) + minimap_image->setPixel(x, y, video::SColor(0,0,0,0)); + } + } + + if (data->texture) + driver->removeTexture(data->texture); + if (data->heightmap_texture) + driver->removeTexture(data->heightmap_texture); + + data->texture = driver->addTexture("minimap__", minimap_image); + data->heightmap_texture = + driver->addTexture("minimap_heightmap__", heightmap_image); + minimap_image->drop(); + heightmap_image->drop(); + + data->map_invalidated = true; + + return data->texture; +} + +v3f Mapper::getYawVec() +{ + if (data->minimap_shape_round) { + return v3f( + cos(m_angle * core::DEGTORAD), + sin(m_angle * core::DEGTORAD), + 1.0); + } else { + return v3f(1.0, 0.0, 1.0); + } +} + +scene::SMeshBuffer *Mapper::getMinimapMeshBuffer() +{ + scene::SMeshBuffer *buf = new scene::SMeshBuffer(); + buf->Vertices.set_used(4); + buf->Indices.set_used(6); + video::SColor c(255, 255, 255, 255); + + buf->Vertices[0] = video::S3DVertex(-1, -1, 0, 0, 0, 1, c, 0, 1); + buf->Vertices[1] = video::S3DVertex(-1, 1, 0, 0, 0, 1, c, 0, 0); + buf->Vertices[2] = video::S3DVertex( 1, 1, 0, 0, 0, 1, c, 1, 0); + buf->Vertices[3] = video::S3DVertex( 1, -1, 0, 0, 0, 1, c, 1, 1); + + buf->Indices[0] = 0; + buf->Indices[1] = 1; + buf->Indices[2] = 2; + buf->Indices[3] = 2; + buf->Indices[4] = 3; + buf->Indices[5] = 0; + + return buf; +} + +void Mapper::drawMinimap() +{ + video::ITexture *minimap_texture = getMinimapTexture(); + if (!minimap_texture) + return; + + v2u32 screensize = porting::getWindowSize(); + const u32 size = 0.25 * screensize.Y; + + core::rect<s32> oldViewPort = driver->getViewPort(); + core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION); + core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW); + + driver->setViewPort(core::rect<s32>( + screensize.X - size - 10, 10, + screensize.X - 10, size + 10)); + driver->setTransform(video::ETS_PROJECTION, core::matrix4()); + driver->setTransform(video::ETS_VIEW, core::matrix4()); + + core::matrix4 matrix; + matrix.makeIdentity(); + + video::SMaterial &material = m_meshbuffer->getMaterial(); + material.setFlag(video::EMF_TRILINEAR_FILTER, true); + material.Lighting = false; + material.TextureLayer[0].Texture = minimap_texture; + material.TextureLayer[1].Texture = data->heightmap_texture; + + if (m_enable_shaders && !data->is_radar) { + u16 sid = m_shdrsrc->getShader("minimap_shader", 1, 1); + material.MaterialType = m_shdrsrc->getShaderInfo(sid).material; + } else { + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + } + + if (data->minimap_shape_round) + matrix.setRotationDegrees(core::vector3df(0, 0, 360 - m_angle)); + + // Draw minimap + driver->setTransform(video::ETS_WORLD, matrix); + driver->setMaterial(material); + driver->drawMeshBuffer(m_meshbuffer); + + // Draw overlay + video::ITexture *minimap_overlay = data->minimap_shape_round ? + data->minimap_overlay_round : data->minimap_overlay_square; + material.TextureLayer[0].Texture = minimap_overlay; + material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; + driver->setMaterial(material); + driver->drawMeshBuffer(m_meshbuffer); + + // If round minimap, draw player marker + if (!data->minimap_shape_round) { + matrix.setRotationDegrees(core::vector3df(0, 0, m_angle)); + material.TextureLayer[0].Texture = data->player_marker; + + driver->setTransform(video::ETS_WORLD, matrix); + driver->setMaterial(material); + driver->drawMeshBuffer(m_meshbuffer); + } + + // Reset transformations + driver->setTransform(video::ETS_VIEW, oldViewMat); + driver->setTransform(video::ETS_PROJECTION, oldProjMat); + driver->setViewPort(oldViewPort); +} + +//// +//// MinimapMapblock +//// + +void MinimapMapblock::getMinimapNodes(VoxelManipulator *vmanip, v3s16 pos) +{ + + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) { + s16 air_count = 0; + bool surface_found = false; + MinimapPixel *mmpixel = &data[z * MAP_BLOCKSIZE + x]; + + for (s16 y = MAP_BLOCKSIZE -1; y >= 0; y--) { + v3s16 p(x, y, z); + MapNode n = vmanip->getNodeNoEx(pos + p); + if (!surface_found && n.getContent() != CONTENT_AIR) { + mmpixel->height = y; + mmpixel->id = n.getContent(); + surface_found = true; + } else if (n.getContent() == CONTENT_AIR) { + air_count++; + } + } + + if (!surface_found) + mmpixel->id = CONTENT_AIR; + + mmpixel->air_count = air_count; + } +} diff --git a/src/minimap.h b/src/minimap.h new file mode 100644 index 000000000..628be7489 --- /dev/null +++ b/src/minimap.h @@ -0,0 +1,157 @@ +/* +Minetest +Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef MINIMAP_HEADER +#define MINIMAP_HEADER + +#include <map> +#include <string> +#include <vector> +#include "irrlichttypes_extrabloated.h" +#include "client.h" +#include "voxel.h" +#include "jthread/jmutex.h" +#include "jthread/jsemaphore.h" + +#define MINIMAP_MAX_SX 512 +#define MINIMAP_MAX_SY 512 + +enum MinimapMode { + MINIMAP_MODE_OFF, + MINIMAP_MODE_SURFACEx1, + MINIMAP_MODE_SURFACEx2, + MINIMAP_MODE_SURFACEx4, + MINIMAP_MODE_RADARx1, + MINIMAP_MODE_RADARx2, + MINIMAP_MODE_RADARx4, + MINIMAP_MODE_COUNT, +}; + +struct MinimapModeDef { + bool is_radar; + u16 scan_height; + u16 map_size; +}; + +struct MinimapPixel { + u16 id; + u16 height; + u16 air_count; + u16 light; +}; + +struct MinimapMapblock { + void getMinimapNodes(VoxelManipulator *vmanip, v3s16 pos); + + MinimapPixel data[MAP_BLOCKSIZE * MAP_BLOCKSIZE]; +}; + +struct MinimapData { + bool is_radar; + MinimapMode mode; + v3s16 pos; + v3s16 old_pos; + u16 scan_height; + u16 map_size; + MinimapPixel minimap_scan[MINIMAP_MAX_SX * MINIMAP_MAX_SY]; + bool map_invalidated; + bool minimap_shape_round; + video::IImage *minimap_image; + video::IImage *heightmap_image; + video::IImage *minimap_mask_round; + video::IImage *minimap_mask_square; + video::ITexture *texture; + video::ITexture *heightmap_texture; + video::ITexture *minimap_overlay_round; + video::ITexture *minimap_overlay_square; + video::ITexture *player_marker; +}; + +struct QueuedMinimapUpdate { + v3s16 pos; + MinimapMapblock *data; +}; + +class MinimapUpdateThread : public UpdateThread { +public: + virtual ~MinimapUpdateThread(); + + void getMap(v3s16 pos, s16 size, s16 height, bool radar); + MinimapPixel *getMinimapPixel(v3s16 pos, s16 height, s16 *pixel_height); + s16 getAirCount(v3s16 pos, s16 height); + video::SColor getColorFromId(u16 id); + + void enqueueBlock(v3s16 pos, MinimapMapblock *data); + + bool pushBlockUpdate(v3s16 pos, MinimapMapblock *data); + bool popBlockUpdate(QueuedMinimapUpdate *update); + + MinimapData *data; + +protected: + const char *getName() { return "MinimapUpdateThread"; } + virtual void doUpdate(); + +private: + JMutex m_queue_mutex; + std::deque<QueuedMinimapUpdate> m_update_queue; + std::map<v3s16, MinimapMapblock *> m_blocks_cache; +}; + +class Mapper { +public: + Mapper(IrrlichtDevice *device, Client *client); + ~Mapper(); + + void addBlock(v3s16 pos, MinimapMapblock *data); + + v3f getYawVec(); + MinimapMode getMinimapMode(); + + void setPos(v3s16 pos); + void setAngle(f32 angle); + void setMinimapMode(MinimapMode mode); + void toggleMinimapShape(); + + + video::ITexture *getMinimapTexture(); + + void blitMinimapPixelsToImageRadar(video::IImage *map_image); + void blitMinimapPixelsToImageSurface(video::IImage *map_image, + video::IImage *heightmap_image); + + scene::SMeshBuffer *getMinimapMeshBuffer(); + void drawMinimap(); + + video::IVideoDriver *driver; + MinimapData *data; + +private: + ITextureSource *m_tsrc; + IShaderSource *m_shdrsrc; + INodeDefManager *m_ndef; + MinimapUpdateThread *m_minimap_update_thread; + scene::SMeshBuffer *m_meshbuffer; + bool m_enable_shaders; + u16 m_surface_mode_scan_height; + f32 m_angle; + JMutex m_mutex; +}; + +#endif diff --git a/src/mods.cpp b/src/mods.cpp index 6126de7a1..a81dd4604 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -17,15 +17,15 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <cctype> +#include <fstream> #include "mods.h" -#include "main.h" #include "filesys.h" #include "strfnd.h" #include "log.h" #include "subgame.h" #include "settings.h" #include "strfnd.h" -#include <cctype> #include "convert_json.h" static bool parseDependsLine(std::istream &is, @@ -47,6 +47,11 @@ static bool parseDependsLine(std::istream &is, void parseModContents(ModSpec &spec) { // NOTE: this function works in mutual recursion with getModsInPath + Settings info; + info.readConfigFile((spec.path+DIR_DELIM+"mod.conf").c_str()); + + if (info.exists("name")) + spec.name = info.get("name"); spec.depends.clear(); spec.optdepends.clear(); diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt new file mode 100644 index 000000000..3805c323d --- /dev/null +++ b/src/network/CMakeLists.txt @@ -0,0 +1,16 @@ +set(common_network_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/connection.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/networkpacket.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/serverpackethandler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/serveropcodes.cpp + PARENT_SCOPE +) + +if (BUILD_CLIENT) + set(client_network_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/clientopcodes.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/clientpackethandler.cpp + PARENT_SCOPE + ) +endif() + diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp new file mode 100644 index 000000000..3364de8c5 --- /dev/null +++ b/src/network/clientopcodes.cpp @@ -0,0 +1,213 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2015 nerzhul, Loic Blot <loic.blot@unix-experience.fr> + +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 "clientopcodes.h" + +const static ToClientCommandHandler null_command_handler = {"TOCLIENT_NULL", TOCLIENT_STATE_ALL, &Client::handleCommand_Null}; + +const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = +{ + null_command_handler, // 0x00 (never use this) + null_command_handler, // 0x01 + { "TOCLIENT_HELLO", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_Hello }, // 0x02 + { "TOCLIENT_AUTH_ACCEPT", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_AuthAccept }, // 0x03 + { "TOCLIENT_ACCEPT_SUDO_MODE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_AcceptSudoMode}, // 0x04 + { "TOCLIENT_DENY_SUDO_MODE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DenySudoMode}, // 0x05 + null_command_handler, // 0x06 + null_command_handler, // 0x07 + null_command_handler, // 0x08 + null_command_handler, // 0x09 + { "TOCLIENT_ACCESS_DENIED", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_AccessDenied }, // 0x0A + null_command_handler, // 0x0B + null_command_handler, // 0x0C + null_command_handler, // 0x0D + null_command_handler, // 0x0E + null_command_handler, // 0x0F + { "TOCLIENT_INIT", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_InitLegacy }, // 0x10 + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + { "TOCLIENT_BLOCKDATA", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_BlockData }, // 0x20 + { "TOCLIENT_ADDNODE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_AddNode }, // 0x21 + { "TOCLIENT_REMOVENODE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_RemoveNode }, // 0x22 + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + { "TOCLIENT_INVENTORY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Inventory }, // 0x27 + null_command_handler, + { "TOCLIENT_TIME_OF_DAY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_TimeOfDay }, // 0x29 + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + { "TOCLIENT_CHAT_MESSAGE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ChatMessage }, // 0x30 + { "TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ActiveObjectRemoveAdd }, // 0x31 + { "TOCLIENT_ACTIVE_OBJECT_MESSAGES", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ActiveObjectMessages }, // 0x32 + { "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 + { "TOCLIENT_PLAYERITEM", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_PlayerItem }, // 0x36 + { "TOCLIENT_DEATHSCREEN", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeathScreen }, // 0x37 + { "TOCLIENT_MEDIA", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Media }, // 0x38 + { "TOCLIENT_TOOLDEF", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ToolDef }, // 0x39 + { "TOCLIENT_NODEDEF", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_NodeDef }, // 0x3a + { "TOCLIENT_CRAFTITEMDEF", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_CraftItemDef }, // 0x3b + { "TOCLIENT_ANNOUNCE_MEDIA", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_AnnounceMedia }, // 0x3c + { "TOCLIENT_ITEMDEF", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ItemDef }, // 0x3d + null_command_handler, + { "TOCLIENT_PLAY_SOUND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_PlaySound }, // 0x3f + { "TOCLIENT_STOP_SOUND", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_StopSound }, // 0x40 + { "TOCLIENT_PRIVILEGES", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Privileges }, // 0x41 + { "TOCLIENT_INVENTORY_FORMSPEC", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_InventoryFormSpec }, // 0x42 + { "TOCLIENT_DETACHED_INVENTORY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DetachedInventory }, // 0x43 + { "TOCLIENT_SHOW_FORMSPEC", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_ShowFormSpec }, // 0x44 + { "TOCLIENT_MOVEMENT", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Movement }, // 0x45 + { "TOCLIENT_SPAWN_PARTICLE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_SpawnParticle }, // 0x46 + { "TOCLIENT_ADD_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_AddParticleSpawner }, // 0x47 + { "TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeleteParticleSpawner }, // 0x48 + { "TOCLIENT_HUDADD", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudAdd }, // 0x49 + { "TOCLIENT_HUDRM", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudRemove }, // 0x4a + { "TOCLIENT_HUDCHANGE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudChange }, // 0x4b + { "TOCLIENT_HUD_SET_FLAGS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetFlags }, // 0x4c + { "TOCLIENT_HUD_SET_PARAM", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetParam }, // 0x4d + { "TOCLIENT_BREATH", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Breath }, // 0x4e + { "TOCLIENT_SET_SKY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudSetSky }, // 0x4f + { "TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_OverrideDayNightRatio }, // 0x50 + { "TOCLIENT_LOCAL_PLAYER_ANIMATIONS", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_LocalPlayerAnimations }, // 0x51 + { "TOCLIENT_EYE_OFFSET", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_EyeOffset }, // 0x52 + { "TOCLIENT_DELETE_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeleteParticleSpawner }, // 0x53 + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + null_command_handler, + { "TOCLIENT_SRP_BYTES_S_B", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_SrpBytesSandB }, // 0x60 +}; + +const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, false }; + +const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] = +{ + null_command_factory, // 0x00 + null_command_factory, // 0x01 + { "TOSERVER_INIT", 1, false }, // 0x02 + null_command_factory, // 0x03 + null_command_factory, // 0x04 + null_command_factory, // 0x05 + null_command_factory, // 0x06 + null_command_factory, // 0x07 + null_command_factory, // 0x08 + null_command_factory, // 0x09 + null_command_factory, // 0x0a + null_command_factory, // 0x0b + null_command_factory, // 0x0c + null_command_factory, // 0x0d + null_command_factory, // 0x0e + null_command_factory, // 0x0F + { "TOSERVER_INIT_LEGACY", 1, false }, // 0x10 + { "TOSERVER_INIT2", 1, true }, // 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 + null_command_factory, // 0x20 + null_command_factory, // 0x21 + null_command_factory, // 0x22 + { "TOSERVER_PLAYERPOS", 0, false }, // 0x23 + { "TOSERVER_GOTBLOCKS", 2, true }, // 0x24 + { "TOSERVER_DELETEDBLOCKS", 2, true }, // 0x25 + null_command_factory, // 0x26 + { "TOSERVER_CLICK_OBJECT", 0, false }, // 0x27 + { "TOSERVER_GROUND_ACTION", 0, false }, // 0x28 + { "TOSERVER_RELEASE", 0, false }, // 0x29 + null_command_factory, // 0x2a + null_command_factory, // 0x2b + null_command_factory, // 0x2c + null_command_factory, // 0x2d + null_command_factory, // 0x2e + null_command_factory, // 0x2f + { "TOSERVER_SIGNTEXT", 0, false }, // 0x30 + { "TOSERVER_INVENTORY_ACTION", 0, true }, // 0x31 + { "TOSERVER_CHAT_MESSAGE", 0, true }, // 0x32 + { "TOSERVER_SIGNNODETEXT", 0, false }, // 0x33 + { "TOSERVER_CLICK_ACTIVEOBJECT", 0, false }, // 0x34 + { "TOSERVER_DAMAGE", 0, true }, // 0x35 + { "TOSERVER_PASSWORD_LEGACY", 0, true }, // 0x36 + { "TOSERVER_PLAYERITEM", 0, true }, // 0x37 + { "TOSERVER_RESPAWN", 0, true }, // 0x38 + { "TOSERVER_INTERACT", 0, true }, // 0x39 + { "TOSERVER_REMOVED_SOUNDS", 1, true }, // 0x3a + { "TOSERVER_NODEMETA_FIELDS", 0, true }, // 0x3b + { "TOSERVER_INVENTORY_FIELDS", 0, true }, // 0x3c + null_command_factory, // 0x3d + null_command_factory, // 0x3e + null_command_factory, // 0x3f + { "TOSERVER_REQUEST_MEDIA", 1, true }, // 0x40 + { "TOSERVER_RECEIVED_MEDIA", 1, true }, // 0x41 + { "TOSERVER_BREATH", 0, true }, // 0x42 + { "TOSERVER_CLIENT_READY", 0, true }, // 0x43 + null_command_factory, // 0x44 + null_command_factory, // 0x45 + null_command_factory, // 0x46 + null_command_factory, // 0x47 + null_command_factory, // 0x48 + null_command_factory, // 0x49 + null_command_factory, // 0x4a + null_command_factory, // 0x4b + null_command_factory, // 0x4c + null_command_factory, // 0x4d + null_command_factory, // 0x4e + null_command_factory, // 0x4f + { "TOSERVER_FIRST_SRP", 1, true }, // 0x50 + { "TOSERVER_SRP_BYTES_A", 1, true }, // 0x51 + { "TOSERVER_SRP_BYTES_M", 1, true }, // 0x52 +}; diff --git a/src/network/clientopcodes.h b/src/network/clientopcodes.h new file mode 100644 index 000000000..9143865b8 --- /dev/null +++ b/src/network/clientopcodes.h @@ -0,0 +1,52 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2015 nerzhul, Loic Blot <loic.blot@unix-experience.fr> + +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. +*/ + +#ifndef CLIENTOPCODES_HEADER +#define CLIENTOPCODES_HEADER + +#include "client.h" +#include "networkprotocol.h" +#include "networkpacket.h" + +enum ToClientConnectionState { + TOCLIENT_STATE_NOT_CONNECTED, + TOCLIENT_STATE_CONNECTED, + TOCLIENT_STATE_ALL, +}; + +struct ToClientCommandHandler +{ + const char* name; + ToClientConnectionState state; + void (Client::*handler)(NetworkPacket* pkt); +}; + +struct ServerCommandFactory +{ + const char* name; + u16 channel; + bool reliable; +}; + +extern const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES]; + +extern const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES]; + +#endif diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp new file mode 100644 index 000000000..86091bc88 --- /dev/null +++ b/src/network/clientpackethandler.cpp @@ -0,0 +1,1211 @@ +/* +Minetest +Copyright (C) 2015 nerzhul, Loic Blot <loic.blot@unix-experience.fr> + +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 "client.h" + +#include "util/base64.h" +#include "clientmedia.h" +#include "log.h" +#include "map.h" +#include "mapsector.h" +#include "nodedef.h" +#include "serialization.h" +#include "server.h" +#include "strfnd.h" +#include "network/clientopcodes.h" +#include "util/serialize.h" +#include "util/srp.h" + +void Client::handleCommand_Deprecated(NetworkPacket* pkt) +{ + infostream << "Got deprecated command " + << toClientCommandTable[pkt->getCommand()].name << " from peer " + << pkt->getPeerId() << "!" << std::endl; +} + +void Client::handleCommand_Hello(NetworkPacket* pkt) +{ + if (pkt->getSize() < 1) + return; + + u8 serialization_ver; + u16 proto_ver; + u16 compression_mode; + u32 auth_mechs; + std::string username_legacy; // for case insensitivity + *pkt >> serialization_ver >> compression_mode >> proto_ver + >> auth_mechs >> username_legacy; + + // Chose an auth method we support + AuthMechanism chosen_auth_mechanism = choseAuthMech(auth_mechs); + + infostream << "Client: TOCLIENT_HELLO received with " + << "serialization_ver=" << (u32)serialization_ver + << ", auth_mechs=" << auth_mechs + << ", proto_ver=" << proto_ver + << ", compression_mode=" << compression_mode + << ". Doing auth with mech " << chosen_auth_mechanism << std::endl; + + if (!ser_ver_supported(serialization_ver)) { + infostream << "Client: TOCLIENT_HELLO: Server sent " + << "unsupported ser_fmt_ver"<< std::endl; + return; + } + + m_server_ser_ver = serialization_ver; + m_proto_ver = proto_ver; + + //TODO verify that username_legacy matches sent username, only + // differs in casing (make both uppercase and compare) + // This is only neccessary though when we actually want to add casing support + + if (m_chosen_auth_mech != AUTH_MECHANISM_NONE) { + // we recieved a TOCLIENT_HELLO while auth was already going on + errorstream << "Client: TOCLIENT_HELLO while auth was already going on" + << "(chosen_mech=" << m_chosen_auth_mech << ")." << std::endl; + if ((m_chosen_auth_mech == AUTH_MECHANISM_SRP) + || (m_chosen_auth_mech == AUTH_MECHANISM_LEGACY_PASSWORD)) { + srp_user_delete((SRPUser *) m_auth_data); + m_auth_data = 0; + } + } + + // Authenticate using that method, or abort if there wasn't any method found + if (chosen_auth_mechanism != AUTH_MECHANISM_NONE) { + startAuth(chosen_auth_mechanism); + } else { + m_chosen_auth_mech = AUTH_MECHANISM_NONE; + m_access_denied = true; + m_access_denied_reason = "Unknown"; + m_con.Disconnect(); + } + +} + +void Client::handleCommand_AuthAccept(NetworkPacket* pkt) +{ + deleteAuthData(); + + v3f playerpos; + *pkt >> playerpos >> m_map_seed >> m_recommended_send_interval + >> m_sudo_auth_methods; + + playerpos -= v3f(0, BS / 2, 0); + + // Set player position + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + player->setPosition(playerpos); + + infostream << "Client: received map seed: " << m_map_seed << std::endl; + infostream << "Client: received recommended send interval " + << m_recommended_send_interval<<std::endl; + + // Reply to server + NetworkPacket resp_pkt(TOSERVER_INIT2, 0); + Send(&resp_pkt); + + m_state = LC_Init; +} +void Client::handleCommand_AcceptSudoMode(NetworkPacket* pkt) +{ + deleteAuthData(); + + m_password = m_new_password; + + verbosestream << "Client: Recieved TOCLIENT_ACCEPT_SUDO_MODE." << std::endl; + + // send packet to actually set the password + startAuth(AUTH_MECHANISM_FIRST_SRP); + + // reset again + m_chosen_auth_mech = AUTH_MECHANISM_NONE; +} +void Client::handleCommand_DenySudoMode(NetworkPacket* pkt) +{ + m_chat_queue.push(L"Password change denied. Password NOT changed."); + // reset everything and be sad + deleteAuthData(); +} +void Client::handleCommand_InitLegacy(NetworkPacket* pkt) +{ + if (pkt->getSize() < 1) + return; + + u8 server_ser_ver; + *pkt >> server_ser_ver; + + infostream << "Client: TOCLIENT_INIT_LEGACY received with " + "server_ser_ver=" << ((int)server_ser_ver & 0xff) << std::endl; + + if (!ser_ver_supported(server_ser_ver)) { + infostream << "Client: TOCLIENT_INIT_LEGACY: Server sent " + << "unsupported ser_fmt_ver"<< std::endl; + return; + } + + m_server_ser_ver = server_ser_ver; + + // We can be totally wrong with this guess + // but we only need some value < 25. + m_proto_ver = 24; + + // Get player position + v3s16 playerpos_s16(0, BS * 2 + BS * 20, 0); + if (pkt->getSize() >= 1 + 6) { + *pkt >> playerpos_s16; + } + v3f playerpos_f = intToFloat(playerpos_s16, BS) - v3f(0, BS / 2, 0); + + + // Set player position + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + player->setPosition(playerpos_f); + + if (pkt->getSize() >= 1 + 6 + 8) { + // Get map seed + *pkt >> m_map_seed; + infostream << "Client: received map seed: " << m_map_seed << std::endl; + } + + if (pkt->getSize() >= 1 + 6 + 8 + 4) { + *pkt >> m_recommended_send_interval; + infostream << "Client: received recommended send interval " + << m_recommended_send_interval<<std::endl; + } + + // Reply to server + NetworkPacket resp_pkt(TOSERVER_INIT2, 0); + Send(&resp_pkt); + + m_state = LC_Init; +} + +void Client::handleCommand_AccessDenied(NetworkPacket* pkt) +{ + // The server didn't like our password. Note, this needs + // to be processed even if the serialisation format has + // not been agreed yet, the same as TOCLIENT_INIT. + m_access_denied = true; + m_access_denied_reason = "Unknown"; + + if (pkt->getCommand() == TOCLIENT_ACCESS_DENIED) { + if (pkt->getSize() < 1) + return; + + u8 denyCode = SERVER_ACCESSDENIED_UNEXPECTED_DATA; + *pkt >> denyCode; + if (denyCode == SERVER_ACCESSDENIED_SHUTDOWN || + denyCode == SERVER_ACCESSDENIED_CRASH) { + *pkt >> m_access_denied_reason; + if (m_access_denied_reason == "") { + m_access_denied_reason = accessDeniedStrings[denyCode]; + } + u8 reconnect; + *pkt >> reconnect; + m_access_denied_reconnect = reconnect & 1; + } else if (denyCode == SERVER_ACCESSDENIED_CUSTOM_STRING) { + *pkt >> m_access_denied_reason; + } else if (denyCode < SERVER_ACCESSDENIED_MAX) { + m_access_denied_reason = accessDeniedStrings[denyCode]; + } else { + // Allow us to add new error messages to the + // protocol without raising the protocol version, if we want to. + // Until then (which may be never), this is outside + // of the defined protocol. + *pkt >> m_access_denied_reason; + if (m_access_denied_reason == "") { + m_access_denied_reason = "Unknown"; + } + } + } + // 13/03/15 Legacy code from 0.4.12 and lesser. must stay 1 year + // for compat with old clients + else { + if (pkt->getSize() >= 2) { + std::wstring wide_reason; + *pkt >> wide_reason; + m_access_denied_reason = wide_to_utf8(wide_reason); + } + } +} + +void Client::handleCommand_RemoveNode(NetworkPacket* pkt) +{ + if (pkt->getSize() < 6) + return; + + v3s16 p; + *pkt >> p; + removeNode(p); +} + +void Client::handleCommand_AddNode(NetworkPacket* pkt) +{ + if (pkt->getSize() < 6 + MapNode::serializedLength(m_server_ser_ver)) + return; + + v3s16 p; + *pkt >> p; + + MapNode n; + n.deSerialize(pkt->getU8Ptr(6), m_server_ser_ver); + + bool remove_metadata = true; + u32 index = 6 + MapNode::serializedLength(m_server_ser_ver); + if ((pkt->getSize() >= index + 1) && pkt->getU8(index)) { + remove_metadata = false; + } + + addNode(p, n, remove_metadata); +} +void Client::handleCommand_BlockData(NetworkPacket* pkt) +{ + // Ignore too small packet + if (pkt->getSize() < 6) + return; + + v3s16 p; + *pkt >> p; + + std::string datastring(pkt->getString(6), pkt->getSize() - 6); + std::istringstream istr(datastring, std::ios_base::binary); + + MapSector *sector; + MapBlock *block; + + v2s16 p2d(p.X, p.Z); + sector = m_env.getMap().emergeSector(p2d); + + assert(sector->getPos() == p2d); + + block = sector->getBlockNoCreateNoEx(p.Y); + if (block) { + /* + Update an existing block + */ + block->deSerialize(istr, m_server_ser_ver, false); + block->deSerializeNetworkSpecific(istr); + } + else { + /* + Create a new block + */ + block = new MapBlock(&m_env.getMap(), p, this); + block->deSerialize(istr, m_server_ser_ver, false); + block->deSerializeNetworkSpecific(istr); + sector->insertBlock(block); + } + + if (m_localdb) { + ServerMap::saveBlock(block, m_localdb); + } + + /* + Add it to mesh update queue and set it to be acknowledged after update. + */ + addUpdateMeshTaskWithEdge(p, true); +} + +void Client::handleCommand_Inventory(NetworkPacket* pkt) +{ + if (pkt->getSize() < 1) + return; + + std::string datastring(pkt->getString(0), pkt->getSize()); + std::istringstream is(datastring, std::ios_base::binary); + + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + + player->inventory.deSerialize(is); + + m_inventory_updated = true; + + delete m_inventory_from_server; + m_inventory_from_server = new Inventory(player->inventory); + m_inventory_from_server_age = 0.0; +} + +void Client::handleCommand_TimeOfDay(NetworkPacket* pkt) +{ + if (pkt->getSize() < 2) + return; + + u16 time_of_day; + + *pkt >> time_of_day; + + time_of_day = time_of_day % 24000; + float time_speed = 0; + + if (pkt->getSize() >= 2 + 4) { + *pkt >> time_speed; + } + else { + // Old message; try to approximate speed of time by ourselves + float time_of_day_f = (float)time_of_day / 24000.0; + float tod_diff_f = 0; + + if (time_of_day_f < 0.2 && m_last_time_of_day_f > 0.8) + tod_diff_f = time_of_day_f - m_last_time_of_day_f + 1.0; + else + tod_diff_f = time_of_day_f - m_last_time_of_day_f; + + m_last_time_of_day_f = time_of_day_f; + float time_diff = m_time_of_day_update_timer; + m_time_of_day_update_timer = 0; + + if (m_time_of_day_set) { + time_speed = (3600.0 * 24.0) * tod_diff_f / time_diff; + infostream << "Client: Measured time_of_day speed (old format): " + << time_speed << " tod_diff_f=" << tod_diff_f + << " time_diff=" << time_diff << std::endl; + } + } + + // Update environment + m_env.setTimeOfDay(time_of_day); + m_env.setTimeOfDaySpeed(time_speed); + m_time_of_day_set = true; + + u32 dr = m_env.getDayNightRatio(); + infostream << "Client: time_of_day=" << time_of_day + << " time_speed=" << time_speed + << " dr=" << dr << std::endl; +} + +void Client::handleCommand_ChatMessage(NetworkPacket* pkt) +{ + /* + u16 command + u16 length + wstring message + */ + u16 len, read_wchar; + + *pkt >> len; + + std::wstring message; + for (u32 i = 0; i < len; i++) { + *pkt >> read_wchar; + message += (wchar_t)read_wchar; + } + + m_chat_queue.push(message); +} + +void Client::handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt) +{ + /* + u16 count of removed objects + for all removed objects { + u16 id + } + u16 count of added objects + for all added objects { + u16 id + u8 type + u32 initialization data length + string initialization data + } + */ + + try { + u8 type; + u16 removed_count, added_count, id; + + // Read removed objects + *pkt >> removed_count; + + for (u16 i = 0; i < removed_count; i++) { + *pkt >> id; + m_env.removeActiveObject(id); + } + + // Read added objects + *pkt >> added_count; + + for (u16 i = 0; i < added_count; i++) { + *pkt >> id >> type; + m_env.addActiveObject(id, type, pkt->readLongString()); + } + } catch (PacketError &e) { + infostream << "handleCommand_ActiveObjectRemoveAdd: " << e.what() + << ". The packet is unreliable, ignoring" << std::endl; + } +} + +void Client::handleCommand_ActiveObjectMessages(NetworkPacket* pkt) +{ + /* + for all objects + { + u16 id + u16 message length + string message + } + */ + std::string datastring(pkt->getString(0), pkt->getSize()); + std::istringstream is(datastring, std::ios_base::binary); + + try { + while (is.good()) { + u16 id = readU16(is); + if (!is.good()) + break; + + std::string message = deSerializeString(is); + + // Pass on to the environment + m_env.processActiveObjectMessage(id, message); + } + } catch (SerializationError &e) { + errorstream << "Client::handleCommand_ActiveObjectMessages: " + << "caught SerializationError: " << e.what() << std::endl; + } +} + +void Client::handleCommand_Movement(NetworkPacket* pkt) +{ + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + + float mad, maa, maf, msw, mscr, msf, mscl, msj, lf, lfs, ls, g; + + *pkt >> mad >> maa >> maf >> msw >> mscr >> msf >> mscl >> msj + >> lf >> lfs >> ls >> g; + + player->movement_acceleration_default = mad * BS; + player->movement_acceleration_air = maa * BS; + player->movement_acceleration_fast = maf * BS; + player->movement_speed_walk = msw * BS; + player->movement_speed_crouch = mscr * BS; + player->movement_speed_fast = msf * BS; + player->movement_speed_climb = mscl * BS; + player->movement_speed_jump = msj * BS; + player->movement_liquid_fluidity = lf * BS; + player->movement_liquid_fluidity_smooth = lfs * BS; + player->movement_liquid_sink = ls * BS; + player->movement_gravity = g * BS; +} + +void Client::handleCommand_HP(NetworkPacket* pkt) +{ + + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + + u8 oldhp = player->hp; + + u8 hp; + *pkt >> hp; + + player->hp = hp; + + if (hp < oldhp) { + // Add to ClientEvent queue + ClientEvent event; + event.type = CE_PLAYER_DAMAGE; + event.player_damage.amount = oldhp - hp; + m_client_event_queue.push(event); + } +} + +void Client::handleCommand_Breath(NetworkPacket* pkt) +{ + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + + u16 breath; + + *pkt >> breath; + + player->setBreath(breath); +} + +void Client::handleCommand_MovePlayer(NetworkPacket* pkt) +{ + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + + v3f pos; + f32 pitch, yaw; + + *pkt >> pos >> pitch >> yaw; + + player->setPosition(pos); + + infostream << "Client got TOCLIENT_MOVE_PLAYER" + << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")" + << " pitch=" << pitch + << " yaw=" << yaw + << std::endl; + + /* + Add to ClientEvent queue. + This has to be sent to the main program because otherwise + it would just force the pitch and yaw values to whatever + the camera points to. + */ + ClientEvent event; + event.type = CE_PLAYER_FORCE_MOVE; + event.player_force_move.pitch = pitch; + event.player_force_move.yaw = yaw; + m_client_event_queue.push(event); + + // Ignore damage for a few seconds, so that the player doesn't + // get damage from falling on ground + m_ignore_damage_timer = 3.0; +} + +void Client::handleCommand_PlayerItem(NetworkPacket* pkt) +{ + infostream << "Client: WARNING: Ignoring TOCLIENT_PLAYERITEM" << std::endl; +} + +void Client::handleCommand_DeathScreen(NetworkPacket* pkt) +{ + bool set_camera_point_target; + v3f camera_point_target; + + *pkt >> set_camera_point_target; + *pkt >> camera_point_target; + + ClientEvent event; + event.type = CE_DEATHSCREEN; + event.deathscreen.set_camera_point_target = set_camera_point_target; + event.deathscreen.camera_point_target_x = camera_point_target.X; + event.deathscreen.camera_point_target_y = camera_point_target.Y; + event.deathscreen.camera_point_target_z = camera_point_target.Z; + m_client_event_queue.push(event); +} + +void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) +{ + u16 num_files; + + *pkt >> num_files; + + infostream << "Client: Received media announcement: packet size: " + << pkt->getSize() << std::endl; + + if (m_media_downloader == NULL || + m_media_downloader->isStarted()) { + const char *problem = m_media_downloader ? + "we already saw another announcement" : + "all media has been received already"; + errorstream << "Client: Received media announcement but " + << problem << "! " + << " files=" << num_files + << " size=" << pkt->getSize() << std::endl; + return; + } + + // Mesh update thread must be stopped while + // updating content definitions + sanity_check(!m_mesh_update_thread.IsRunning()); + + for (u16 i = 0; i < num_files; i++) { + std::string name, sha1_base64; + + *pkt >> name >> sha1_base64; + + std::string sha1_raw = base64_decode(sha1_base64); + m_media_downloader->addFile(name, sha1_raw); + } + + std::vector<std::string> remote_media; + try { + std::string str; + + *pkt >> str; + + Strfnd sf(str); + while(!sf.atend()) { + std::string baseurl = trim(sf.next(",")); + if (baseurl != "") + m_media_downloader->addRemoteServer(baseurl); + } + } + catch(SerializationError& e) { + // not supported by server or turned off + } + + m_media_downloader->step(this); +} + +void Client::handleCommand_Media(NetworkPacket* pkt) +{ + /* + u16 command + u16 total number of file bunches + u16 index of this bunch + u32 number of files in this bunch + for each file { + u16 length of name + string name + u32 length of data + data + } + */ + u16 num_bunches; + u16 bunch_i; + u32 num_files; + + *pkt >> num_bunches >> bunch_i >> num_files; + + infostream << "Client: Received files: bunch " << bunch_i << "/" + << num_bunches << " files=" << num_files + << " size=" << pkt->getSize() << std::endl; + + if (num_files == 0) + return; + + if (m_media_downloader == NULL || + !m_media_downloader->isStarted()) { + const char *problem = m_media_downloader ? + "media has not been requested" : + "all media has been received already"; + errorstream << "Client: Received media but " + << problem << "! " + << " bunch " << bunch_i << "/" << num_bunches + << " files=" << num_files + << " size=" << pkt->getSize() << std::endl; + return; + } + + // Mesh update thread must be stopped while + // updating content definitions + sanity_check(!m_mesh_update_thread.IsRunning()); + + for (u32 i=0; i < num_files; i++) { + std::string name; + + *pkt >> name; + + std::string data = pkt->readLongString(); + + m_media_downloader->conventionalTransferDone( + name, data, this); + } +} + +void Client::handleCommand_ToolDef(NetworkPacket* pkt) +{ + infostream << "Client: WARNING: Ignoring TOCLIENT_TOOLDEF" << std::endl; +} + +void Client::handleCommand_NodeDef(NetworkPacket* pkt) +{ + infostream << "Client: Received node definitions: packet size: " + << pkt->getSize() << std::endl; + + // Mesh update thread must be stopped while + // updating content definitions + sanity_check(!m_mesh_update_thread.IsRunning()); + + // Decompress node definitions + std::string datastring(pkt->getString(0), pkt->getSize()); + std::istringstream is(datastring, std::ios_base::binary); + std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary); + std::ostringstream tmp_os; + decompressZlib(tmp_is, tmp_os); + + // Deserialize node definitions + std::istringstream tmp_is2(tmp_os.str()); + m_nodedef->deSerialize(tmp_is2); + m_nodedef_received = true; +} + +void Client::handleCommand_CraftItemDef(NetworkPacket* pkt) +{ + infostream << "Client: WARNING: Ignoring TOCLIENT_CRAFTITEMDEF" << std::endl; +} + +void Client::handleCommand_ItemDef(NetworkPacket* pkt) +{ + infostream << "Client: Received item definitions: packet size: " + << pkt->getSize() << std::endl; + + // Mesh update thread must be stopped while + // updating content definitions + sanity_check(!m_mesh_update_thread.IsRunning()); + + // Decompress item definitions + std::string datastring(pkt->getString(0), pkt->getSize()); + std::istringstream is(datastring, std::ios_base::binary); + std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary); + std::ostringstream tmp_os; + decompressZlib(tmp_is, tmp_os); + + // Deserialize node definitions + std::istringstream tmp_is2(tmp_os.str()); + m_itemdef->deSerialize(tmp_is2); + m_itemdef_received = true; +} + +void Client::handleCommand_PlaySound(NetworkPacket* pkt) +{ + s32 server_id; + std::string name; + float gain; + u8 type; // 0=local, 1=positional, 2=object + v3f pos; + u16 object_id; + bool loop; + + *pkt >> server_id >> name >> gain >> type >> pos >> object_id >> loop; + + // Start playing + int client_id = -1; + switch(type) { + case 0: // local + client_id = m_sound->playSound(name, loop, gain); + break; + case 1: // positional + client_id = m_sound->playSoundAt(name, loop, gain, pos); + break; + case 2: + { // object + ClientActiveObject *cao = m_env.getActiveObject(object_id); + if (cao) + pos = cao->getPosition(); + client_id = m_sound->playSoundAt(name, loop, gain, pos); + // TODO: Set up sound to move with object + break; + } + default: + break; + } + + if (client_id != -1) { + m_sounds_server_to_client[server_id] = client_id; + m_sounds_client_to_server[client_id] = server_id; + if (object_id != 0) + m_sounds_to_objects[client_id] = object_id; + } +} + +void Client::handleCommand_StopSound(NetworkPacket* pkt) +{ + s32 server_id; + + *pkt >> server_id; + + std::map<s32, int>::iterator i = + m_sounds_server_to_client.find(server_id); + + if (i != m_sounds_server_to_client.end()) { + int client_id = i->second; + m_sound->stopSound(client_id); + } +} + +void Client::handleCommand_Privileges(NetworkPacket* pkt) +{ + m_privileges.clear(); + infostream << "Client: Privileges updated: "; + u16 num_privileges; + + *pkt >> num_privileges; + + for (u16 i = 0; i < num_privileges; i++) { + std::string priv; + + *pkt >> priv; + + m_privileges.insert(priv); + infostream << priv << " "; + } + infostream << std::endl; +} + +void Client::handleCommand_InventoryFormSpec(NetworkPacket* pkt) +{ + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + + // Store formspec in LocalPlayer + player->inventory_formspec = pkt->readLongString(); +} + +void Client::handleCommand_DetachedInventory(NetworkPacket* pkt) +{ + std::string datastring(pkt->getString(0), pkt->getSize()); + std::istringstream is(datastring, std::ios_base::binary); + + std::string name = deSerializeString(is); + + infostream << "Client: Detached inventory update: \"" << name + << "\"" << std::endl; + + Inventory *inv = NULL; + if (m_detached_inventories.count(name) > 0) + inv = m_detached_inventories[name]; + else { + inv = new Inventory(m_itemdef); + m_detached_inventories[name] = inv; + } + inv->deSerialize(is); +} + +void Client::handleCommand_ShowFormSpec(NetworkPacket* pkt) +{ + std::string formspec = pkt->readLongString(); + std::string formname; + + *pkt >> formname; + + ClientEvent event; + event.type = CE_SHOW_FORMSPEC; + // pointer is required as event is a struct only! + // adding a std:string to a struct isn't possible + event.show_formspec.formspec = new std::string(formspec); + event.show_formspec.formname = new std::string(formname); + m_client_event_queue.push(event); +} + +void Client::handleCommand_SpawnParticle(NetworkPacket* pkt) +{ + std::string datastring(pkt->getString(0), pkt->getSize()); + std::istringstream is(datastring, std::ios_base::binary); + + v3f pos = readV3F1000(is); + v3f vel = readV3F1000(is); + v3f acc = readV3F1000(is); + float expirationtime = readF1000(is); + float size = readF1000(is); + bool collisiondetection = readU8(is); + std::string texture = deSerializeLongString(is); + bool vertical = false; + try { + vertical = readU8(is); + } catch (...) {} + + ClientEvent event; + event.type = CE_SPAWN_PARTICLE; + event.spawn_particle.pos = new v3f (pos); + event.spawn_particle.vel = new v3f (vel); + event.spawn_particle.acc = new v3f (acc); + event.spawn_particle.expirationtime = expirationtime; + event.spawn_particle.size = size; + event.spawn_particle.collisiondetection = collisiondetection; + event.spawn_particle.vertical = vertical; + event.spawn_particle.texture = new std::string(texture); + + m_client_event_queue.push(event); +} + +void Client::handleCommand_AddParticleSpawner(NetworkPacket* pkt) +{ + u16 amount; + float spawntime; + v3f minpos; + v3f maxpos; + v3f minvel; + v3f maxvel; + v3f minacc; + v3f maxacc; + float minexptime; + float maxexptime; + float minsize; + float maxsize; + bool collisiondetection; + u32 id; + + *pkt >> amount >> spawntime >> minpos >> maxpos >> minvel >> maxvel + >> minacc >> maxacc >> minexptime >> maxexptime >> minsize + >> maxsize >> collisiondetection; + + std::string texture = pkt->readLongString(); + + *pkt >> id; + + bool vertical = false; + try { + *pkt >> vertical; + } catch (...) {} + + ClientEvent event; + event.type = CE_ADD_PARTICLESPAWNER; + event.add_particlespawner.amount = amount; + event.add_particlespawner.spawntime = spawntime; + event.add_particlespawner.minpos = new v3f (minpos); + event.add_particlespawner.maxpos = new v3f (maxpos); + event.add_particlespawner.minvel = new v3f (minvel); + event.add_particlespawner.maxvel = new v3f (maxvel); + event.add_particlespawner.minacc = new v3f (minacc); + event.add_particlespawner.maxacc = new v3f (maxacc); + event.add_particlespawner.minexptime = minexptime; + event.add_particlespawner.maxexptime = maxexptime; + event.add_particlespawner.minsize = minsize; + event.add_particlespawner.maxsize = maxsize; + event.add_particlespawner.collisiondetection = collisiondetection; + event.add_particlespawner.vertical = vertical; + event.add_particlespawner.texture = new std::string(texture); + event.add_particlespawner.id = id; + + m_client_event_queue.push(event); +} + + +void Client::handleCommand_DeleteParticleSpawner(NetworkPacket* pkt) +{ + u16 legacy_id; + u32 id; + + // Modification set 13/03/15, 1 year of compat for protocol v24 + if (pkt->getCommand() == TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY) { + *pkt >> legacy_id; + } + else { + *pkt >> id; + } + + + ClientEvent event; + event.type = CE_DELETE_PARTICLESPAWNER; + event.delete_particlespawner.id = + (pkt->getCommand() == TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY ? (u32) legacy_id : id); + + m_client_event_queue.push(event); +} + +void Client::handleCommand_HudAdd(NetworkPacket* pkt) +{ + std::string datastring(pkt->getString(0), pkt->getSize()); + std::istringstream is(datastring, std::ios_base::binary); + + u32 id; + u8 type; + v2f pos; + std::string name; + v2f scale; + std::string text; + u32 number; + u32 item; + u32 dir; + v2f align; + v2f offset; + v3f world_pos; + v2s32 size; + + *pkt >> id >> type >> pos >> name >> scale >> text >> number >> item + >> dir >> align >> offset; + try { + *pkt >> world_pos; + } + catch(SerializationError &e) {}; + + try { + *pkt >> size; + } catch(SerializationError &e) {}; + + ClientEvent event; + event.type = CE_HUDADD; + event.hudadd.id = id; + event.hudadd.type = type; + event.hudadd.pos = new v2f(pos); + event.hudadd.name = new std::string(name); + event.hudadd.scale = new v2f(scale); + event.hudadd.text = new std::string(text); + event.hudadd.number = number; + event.hudadd.item = item; + event.hudadd.dir = dir; + event.hudadd.align = new v2f(align); + event.hudadd.offset = new v2f(offset); + event.hudadd.world_pos = new v3f(world_pos); + event.hudadd.size = new v2s32(size); + m_client_event_queue.push(event); +} + +void Client::handleCommand_HudRemove(NetworkPacket* pkt) +{ + u32 id; + + *pkt >> id; + + ClientEvent event; + event.type = CE_HUDRM; + event.hudrm.id = id; + m_client_event_queue.push(event); +} + +void Client::handleCommand_HudChange(NetworkPacket* pkt) +{ + std::string sdata; + v2f v2fdata; + v3f v3fdata; + u32 intdata = 0; + v2s32 v2s32data; + u32 id; + u8 stat; + + *pkt >> id >> stat; + + if (stat == HUD_STAT_POS || stat == HUD_STAT_SCALE || + stat == HUD_STAT_ALIGN || stat == HUD_STAT_OFFSET) + *pkt >> v2fdata; + else if (stat == HUD_STAT_NAME || stat == HUD_STAT_TEXT) + *pkt >> sdata; + else if (stat == HUD_STAT_WORLD_POS) + *pkt >> v3fdata; + else if (stat == HUD_STAT_SIZE ) + *pkt >> v2s32data; + else + *pkt >> intdata; + + ClientEvent event; + event.type = CE_HUDCHANGE; + event.hudchange.id = id; + event.hudchange.stat = (HudElementStat)stat; + event.hudchange.v2fdata = new v2f(v2fdata); + event.hudchange.v3fdata = new v3f(v3fdata); + event.hudchange.sdata = new std::string(sdata); + event.hudchange.data = intdata; + event.hudchange.v2s32data = new v2s32(v2s32data); + m_client_event_queue.push(event); +} + +void Client::handleCommand_HudSetFlags(NetworkPacket* pkt) +{ + u32 flags, mask; + + *pkt >> flags >> mask; + + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + + player->hud_flags &= ~mask; + player->hud_flags |= flags; +} + +void Client::handleCommand_HudSetParam(NetworkPacket* pkt) +{ + u16 param; std::string value; + + *pkt >> param >> value; + + Player *player = m_env.getLocalPlayer(); + assert(player != NULL); + + if (param == HUD_PARAM_HOTBAR_ITEMCOUNT && value.size() == 4) { + s32 hotbar_itemcount = readS32((u8*) value.c_str()); + if (hotbar_itemcount > 0 && hotbar_itemcount <= HUD_HOTBAR_ITEMCOUNT_MAX) + player->hud_hotbar_itemcount = hotbar_itemcount; + } + else if (param == HUD_PARAM_HOTBAR_IMAGE) { + ((LocalPlayer *) player)->hotbar_image = value; + } + else if (param == HUD_PARAM_HOTBAR_SELECTED_IMAGE) { + ((LocalPlayer *) player)->hotbar_selected_image = value; + } +} + +void Client::handleCommand_HudSetSky(NetworkPacket* pkt) +{ + std::string datastring(pkt->getString(0), pkt->getSize()); + std::istringstream is(datastring, std::ios_base::binary); + + video::SColor *bgcolor = new video::SColor(readARGB8(is)); + std::string *type = new std::string(deSerializeString(is)); + u16 count = readU16(is); + std::vector<std::string> *params = new std::vector<std::string>; + + for (size_t i = 0; i < count; i++) + params->push_back(deSerializeString(is)); + + ClientEvent event; + event.type = CE_SET_SKY; + event.set_sky.bgcolor = bgcolor; + event.set_sky.type = type; + event.set_sky.params = params; + m_client_event_queue.push(event); +} + +void Client::handleCommand_OverrideDayNightRatio(NetworkPacket* pkt) +{ + bool do_override; + u16 day_night_ratio_u; + + *pkt >> do_override >> day_night_ratio_u; + + float day_night_ratio_f = (float)day_night_ratio_u / 65536; + + ClientEvent event; + event.type = CE_OVERRIDE_DAY_NIGHT_RATIO; + event.override_day_night_ratio.do_override = do_override; + event.override_day_night_ratio.ratio_f = day_night_ratio_f; + m_client_event_queue.push(event); +} + +void Client::handleCommand_LocalPlayerAnimations(NetworkPacket* pkt) +{ + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player != NULL); + + *pkt >> player->local_animations[0]; + *pkt >> player->local_animations[1]; + *pkt >> player->local_animations[2]; + *pkt >> player->local_animations[3]; + *pkt >> player->local_animation_speed; +} + +void Client::handleCommand_EyeOffset(NetworkPacket* pkt) +{ + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player != NULL); + + *pkt >> player->eye_offset_first >> player->eye_offset_third; +} + +void Client::handleCommand_SrpBytesSandB(NetworkPacket* pkt) +{ + if ((m_chosen_auth_mech != AUTH_MECHANISM_LEGACY_PASSWORD) + && (m_chosen_auth_mech != AUTH_MECHANISM_SRP)) { + errorstream << "Client: Recieved SRP S_B login message," + << " but wasn't supposed to (chosen_mech=" + << m_chosen_auth_mech << ")." << std::endl; + return; + } + + char *bytes_M = 0; + size_t len_M = 0; + SRPUser *usr = (SRPUser *) m_auth_data; + std::string s; + std::string B; + *pkt >> s >> B; + + infostream << "Client: Recieved TOCLIENT_SRP_BYTES_S_B." << std::endl; + + srp_user_process_challenge(usr, (const unsigned char *) s.c_str(), s.size(), + (const unsigned char *) B.c_str(), B.size(), + (unsigned char **) &bytes_M, &len_M); + + if ( !bytes_M ) { + errorstream << "Client: SRP-6a S_B safety check violation!" << std::endl; + return; + } + + NetworkPacket resp_pkt(TOSERVER_SRP_BYTES_M, 0); + resp_pkt << std::string(bytes_M, len_M); + Send(&resp_pkt); +} diff --git a/src/connection.cpp b/src/network/connection.cpp index 2ee6d2c6e..7794ce10f 100644 --- a/src/connection.cpp +++ b/src/network/connection.cpp @@ -20,16 +20,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iomanip> #include <errno.h> #include "connection.h" -#include "main.h" #include "serialization.h" #include "log.h" #include "porting.h" +#include "network/networkpacket.h" #include "util/serialize.h" #include "util/numeric.h" #include "util/string.h" #include "settings.h" #include "profiler.h" -#include "main.h" // for profiling namespace con { @@ -134,7 +133,7 @@ std::list<SharedBuffer<u8> > makeSplitPacket( u16 chunk_count = 0; do{ end = start + maximum_data_size - 1; - if(end > data.getSize() - 1) + if (end > data.getSize() - 1) end = data.getSize() - 1; u32 payload_size = end - start + 1; @@ -173,7 +172,7 @@ std::list<SharedBuffer<u8> > makeAutoSplitPacket( { u32 original_header_size = 1; std::list<SharedBuffer<u8> > list; - if(data.getSize() + original_header_size > chunksize_max) + if (data.getSize() + original_header_size > chunksize_max) { list = makeSplitPacket(data, chunksize_max, split_seqnum); split_seqnum++; @@ -246,7 +245,7 @@ RPBSearchResult ReliablePacketBuffer::findPacket(u16 seqnum) u16 s = readU16(&(i->data[BASE_HEADER_SIZE+1])); /*dout_con<<"findPacket(): finding seqnum="<<seqnum <<", comparing to s="<<s<<std::endl;*/ - if(s == seqnum) + if (s == seqnum) break; } return i; @@ -258,7 +257,7 @@ RPBSearchResult ReliablePacketBuffer::notFound() bool ReliablePacketBuffer::getFirstSeqnum(u16& result) { JMutexAutoLock listlock(m_list_mutex); - if(m_list.empty()) + if (m_list.empty()) return false; BufferedPacket p = *m_list.begin(); result = readU16(&p.data[BASE_HEADER_SIZE+1]); @@ -268,7 +267,7 @@ bool ReliablePacketBuffer::getFirstSeqnum(u16& result) BufferedPacket ReliablePacketBuffer::popFirst() { JMutexAutoLock listlock(m_list_mutex); - if(m_list.empty()) + if (m_list.empty()) throw NotFoundException("Buffer is empty"); BufferedPacket p = *m_list.begin(); m_list.erase(m_list.begin()); @@ -286,7 +285,7 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) { JMutexAutoLock listlock(m_list_mutex); RPBSearchResult r = findPacket(seqnum); - if(r == notFound()){ + if (r == notFound()) { LOG(dout_con<<"Sequence number: " << seqnum << " not found in reliable buffer"<<std::endl); throw NotFoundException("seqnum not found in buffer"); @@ -313,20 +312,36 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum) void ReliablePacketBuffer::insert(BufferedPacket &p,u16 next_expected) { JMutexAutoLock listlock(m_list_mutex); - assert(p.data.getSize() >= BASE_HEADER_SIZE+3); - u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]); - assert(type == TYPE_RELIABLE); - u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); + if (p.data.getSize() < BASE_HEADER_SIZE + 3) { + errorstream << "ReliablePacketBuffer::insert(): Invalid data size for " + "reliable packet" << std::endl; + return; + } + u8 type = readU8(&p.data[BASE_HEADER_SIZE + 0]); + if (type != TYPE_RELIABLE) { + errorstream << "ReliablePacketBuffer::insert(): type is not reliable" + << std::endl; + return; + } + u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE + 1]); - assert(seqnum_in_window(seqnum,next_expected,MAX_RELIABLE_WINDOW_SIZE)); - assert(seqnum != next_expected); + if (!seqnum_in_window(seqnum, next_expected, MAX_RELIABLE_WINDOW_SIZE)) { + errorstream << "ReliablePacketBuffer::insert(): seqnum is outside of " + "expected window " << std::endl; + return; + } + if (seqnum == next_expected) { + errorstream << "ReliablePacketBuffer::insert(): seqnum is next expected" + << std::endl; + return; + } ++m_list_size; - assert(m_list_size <= SEQNUM_MAX+1); + sanity_check(m_list_size <= SEQNUM_MAX+1); // FIXME: Handle the error? // Find the right place for the packet and insert it there // If list is empty, just add it - if(m_list.empty()) + if (m_list.empty()) { m_list.push_back(p); m_oldest_non_answered_ack = seqnum; @@ -378,10 +393,6 @@ void ReliablePacketBuffer::insert(BufferedPacket &p,u16 next_expected) throw IncomingDataCorruption("duplicated packet isn't same as original one"); } - assert(readU16(&(i->data[BASE_HEADER_SIZE+1])) == seqnum); - assert(i->data.getSize() == p.data.getSize()); - assert(i->address == p.address); - /* nothing to do this seems to be a resent packet */ /* for paranoia reason data should be compared */ --m_list_size; @@ -417,7 +428,7 @@ std::list<BufferedPacket> ReliablePacketBuffer::getTimedOuts(float timeout, for(std::list<BufferedPacket>::iterator i = m_list.begin(); i != m_list.end(); ++i) { - if(i->time >= timeout) { + if (i->time >= timeout) { timed_outs.push_back(*i); //this packet will be sent right afterwards reset timeout here @@ -450,15 +461,23 @@ SharedBuffer<u8> IncomingSplitBuffer::insert(BufferedPacket &p, bool reliable) { JMutexAutoLock listlock(m_map_mutex); u32 headersize = BASE_HEADER_SIZE + 7; - assert(p.data.getSize() >= headersize); + if (p.data.getSize() < headersize) { + errorstream << "Invalid data size for split packet" << std::endl; + return SharedBuffer<u8>(); + } u8 type = readU8(&p.data[BASE_HEADER_SIZE+0]); - assert(type == TYPE_SPLIT); u16 seqnum = readU16(&p.data[BASE_HEADER_SIZE+1]); u16 chunk_count = readU16(&p.data[BASE_HEADER_SIZE+3]); u16 chunk_num = readU16(&p.data[BASE_HEADER_SIZE+5]); + if (type != TYPE_SPLIT) { + errorstream << "IncomingSplitBuffer::insert(): type is not split" + << std::endl; + return SharedBuffer<u8>(); + } + // Add if doesn't exist - if(m_buf.find(seqnum) == m_buf.end()) + if (m_buf.find(seqnum) == m_buf.end()) { IncomingSplitPacket *sp = new IncomingSplitPacket(); sp->chunk_count = chunk_count; @@ -469,11 +488,11 @@ SharedBuffer<u8> IncomingSplitBuffer::insert(BufferedPacket &p, bool reliable) IncomingSplitPacket *sp = m_buf[seqnum]; // TODO: These errors should be thrown or something? Dunno. - if(chunk_count != sp->chunk_count) + if (chunk_count != sp->chunk_count) LOG(derr_con<<"Connection: WARNING: chunk_count="<<chunk_count <<" != sp->chunk_count="<<sp->chunk_count <<std::endl); - if(reliable != sp->reliable) + if (reliable != sp->reliable) LOG(derr_con<<"Connection: WARNING: reliable="<<reliable <<" != sp->reliable="<<sp->reliable <<std::endl); @@ -481,7 +500,7 @@ SharedBuffer<u8> IncomingSplitBuffer::insert(BufferedPacket &p, bool reliable) // 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()) + if (sp->chunks.find(chunk_num) != sp->chunks.end()) return SharedBuffer<u8>(); // Cut chunk data out of packet @@ -493,7 +512,7 @@ SharedBuffer<u8> IncomingSplitBuffer::insert(BufferedPacket &p, bool reliable) sp->chunks[chunk_num] = chunkdata; // If not all chunks are received, return empty buffer - if(sp->allReceived() == false) + if (sp->allReceived() == false) return SharedBuffer<u8>(); // Calculate total size @@ -533,10 +552,10 @@ void IncomingSplitBuffer::removeUnreliableTimedOuts(float dtime, float timeout) { IncomingSplitPacket *p = i->second; // Reliable ones are not removed by timeout - if(p->reliable == true) + if (p->reliable == true) continue; p->time += dtime; - if(p->time >= timeout) + if (p->time >= timeout) remove_queue.push_back(i->first); } } @@ -609,7 +628,7 @@ void Channel::setNextSplitSeqNum(u16 seqnum) next_outgoing_split_seqnum = seqnum; } -u16 Channel::getOutgoingSequenceNumber(bool& successfull) +u16 Channel::getOutgoingSequenceNumber(bool& successful) { JMutexAutoLock internal(m_internal_mutex); u16 retval = next_outgoing_seqnum; @@ -629,7 +648,7 @@ u16 Channel::getOutgoingSequenceNumber(bool& successfull) // know about difference of two unsigned may be negative in general // but we already made sure it won't happen in this case if (((u16)(next_outgoing_seqnum - lowest_unacked_seqnumber)) > window_size) { - successfull = false; + successful = false; return 0; } } @@ -639,7 +658,7 @@ u16 Channel::getOutgoingSequenceNumber(bool& successfull) // but we already made sure it won't happen in this case if ((next_outgoing_seqnum + (u16)(SEQNUM_MAX - lowest_unacked_seqnumber)) > window_size) { - successfull = false; + successful = false; return 0; } } @@ -899,7 +918,7 @@ void Peer::DecUseCount() { { JMutexAutoLock lock(m_exclusive_access_mutex); - assert(m_usage > 0); + sanity_check(m_usage > 0); m_usage--; if (!((m_pending_deletion) && (m_usage == 0))) @@ -919,7 +938,7 @@ void Peer::RTTStatistics(float rtt, std::string profiler_id, m_rtt.max_rtt = rtt; /* do average calculation */ - if(m_rtt.avg_rtt < 0.0) + if (m_rtt.avg_rtt < 0.0) m_rtt.avg_rtt = rtt; else m_rtt.avg_rtt = m_rtt.avg_rtt * (num_samples/(num_samples-1)) + @@ -941,7 +960,7 @@ void Peer::RTTStatistics(float rtt, std::string profiler_id, if (jitter >= m_rtt.jitter_max) m_rtt.jitter_max = jitter; - if(m_rtt.jitter_avg < 0.0) + if (m_rtt.jitter_avg < 0.0) m_rtt.jitter_avg = jitter; else m_rtt.jitter_avg = m_rtt.jitter_avg * (num_samples/(num_samples-1)) + @@ -1027,9 +1046,9 @@ void UDPPeer::reportRTT(float rtt) RTTStatistics(rtt,"rudp",MAX_RELIABLE_WINDOW_SIZE*10); float timeout = getStat(AVG_RTT) * RESEND_TIMEOUT_FACTOR; - if(timeout < RESEND_TIMEOUT_MIN) + if (timeout < RESEND_TIMEOUT_MIN) timeout = RESEND_TIMEOUT_MIN; - if(timeout > RESEND_TIMEOUT_MAX) + if (timeout > RESEND_TIMEOUT_MAX) timeout = RESEND_TIMEOUT_MAX; JMutexAutoLock usage_lock(m_exclusive_access_mutex); @@ -1039,7 +1058,7 @@ void UDPPeer::reportRTT(float rtt) bool UDPPeer::Ping(float dtime,SharedBuffer<u8>& data) { m_ping_timer += dtime; - if(m_ping_timer >= PING_TIMEOUT) + if (m_ping_timer >= PING_TIMEOUT) { // Create and send PING packet writeU8(&data[0], TYPE_CONTROL); @@ -1059,18 +1078,15 @@ void UDPPeer::PutReliableSendCommand(ConnectionCommand &c, if ( channels[c.channelnum].queued_commands.empty() && /* don't queue more packets then window size */ (channels[c.channelnum].queued_reliables.size() - < (channels[c.channelnum].getWindowSize()/2))) - { + < (channels[c.channelnum].getWindowSize()/2))) { LOG(dout_con<<m_connection->getDesc() <<" processing reliable command for peer id: " << c.peer_id <<" data size: " << c.data.getSize() << std::endl); - if (!processReliableSendCommand(c,max_packet_size)) - { + if (!processReliableSendCommand(c,max_packet_size)) { channels[c.channelnum].queued_commands.push_back(c); } } - else - { + else { LOG(dout_con<<m_connection->getDesc() <<" Queueing reliable command for peer id: " << c.peer_id <<" data size: " << c.data.getSize() <<std::endl); @@ -1089,7 +1105,7 @@ bool UDPPeer::processReliableSendCommand( - BASE_HEADER_SIZE - RELIABLE_HEADER_SIZE; - assert(c.data.getSize() < MAX_RELIABLE_WINDOW_SIZE*512); + sanity_check(c.data.getSize() < MAX_RELIABLE_WINDOW_SIZE*512); std::list<SharedBuffer<u8> > originals; u16 split_sequence_number = channels[c.channelnum].readNextSplitSeqNum(); @@ -1105,7 +1121,7 @@ bool UDPPeer::processReliableSendCommand( bool have_sequence_number = true; bool have_initial_sequence_number = false; - Queue<BufferedPacket> toadd; + std::queue<BufferedPacket> toadd; volatile u16 initial_sequence_number = 0; for(std::list<SharedBuffer<u8> >::iterator i = originals.begin(); @@ -1130,22 +1146,23 @@ bool UDPPeer::processReliableSendCommand( m_connection->GetProtocolID(), m_connection->GetPeerID(), c.channelnum); - toadd.push_back(p); + toadd.push(p); } if (have_sequence_number) { volatile u16 pcount = 0; while(toadd.size() > 0) { - BufferedPacket p = toadd.pop_front(); + BufferedPacket p = toadd.front(); + toadd.pop(); // LOG(dout_con<<connection->getDesc() // << " queuing reliable packet for peer_id: " << c.peer_id // << " channel: " << (c.channelnum&0xFF) // << " seqnum: " << readU16(&p.data[BASE_HEADER_SIZE+1]) // << std::endl) - channels[c.channelnum].queued_reliables.push_back(p); + channels[c.channelnum].queued_reliables.push(p); pcount++; } - assert(channels[c.channelnum].queued_reliables.size() < 0xFFFF); + sanity_check(channels[c.channelnum].queued_reliables.size() < 0xFFFF); return true; } else { @@ -1157,13 +1174,13 @@ bool UDPPeer::processReliableSendCommand( } while(toadd.size() > 0) { /* remove packet */ - toadd.pop_front(); + toadd.pop(); bool successfully_put_back_sequence_number = channels[c.channelnum].putBackSequenceNumber( (initial_sequence_number+toadd.size() % (SEQNUM_MAX+1))); - assert(successfully_put_back_sequence_number); + FATAL_ERROR_IF(!successfully_put_back_sequence_number, "error"); } LOG(dout_con<<m_connection->getDesc() << " Windowsize exceeded on reliable sending " @@ -1185,24 +1202,26 @@ void UDPPeer::RunCommandQueues( unsigned int maxtransfer) { - for (unsigned int i = 0; i < CHANNEL_COUNT; i++) - { + for (unsigned int i = 0; i < CHANNEL_COUNT; i++) { unsigned int commands_processed = 0; if ((channels[i].queued_commands.size() > 0) && (channels[i].queued_reliables.size() < maxtransfer) && - (commands_processed < maxcommands)) - { + (commands_processed < maxcommands)) { try { - ConnectionCommand c = channels[i].queued_commands.pop_front(); - LOG(dout_con<<m_connection->getDesc() - <<" processing queued reliable command "<<std::endl); - if (!processReliableSendCommand(c,max_packet_size)) { - LOG(dout_con<<m_connection->getDesc() + ConnectionCommand c = channels[i].queued_commands.front(); + + LOG(dout_con << m_connection->getDesc() + << " processing queued reliable command " << std::endl); + + // Packet is processed, remove it from queue + if (processReliableSendCommand(c,max_packet_size)) { + channels[i].queued_commands.pop_front(); + } else { + LOG(dout_con << m_connection->getDesc() << " Failed to queue packets for peer_id: " << c.peer_id << ", delaying sending of " << c.data.getSize() << " bytes" << std::endl); - channels[i].queued_commands.push_front(c); } } catch (ItemNotFoundException &e) { @@ -1214,13 +1233,13 @@ void UDPPeer::RunCommandQueues( u16 UDPPeer::getNextSplitSequenceNumber(u8 channel) { - assert(channel < CHANNEL_COUNT); + assert(channel < CHANNEL_COUNT); // Pre-condition return channels[channel].readNextIncomingSeqNum(); } void UDPPeer::setNextSplitSequenceNumber(u8 channel, u16 seqnum) { - assert(channel < CHANNEL_COUNT); + assert(channel < CHANNEL_COUNT); // Pre-condition channels[channel].setNextSplitSeqNum(seqnum); } @@ -1228,7 +1247,7 @@ SharedBuffer<u8> UDPPeer::addSpiltPacket(u8 channel, BufferedPacket toadd, bool reliable) { - assert(channel < CHANNEL_COUNT); + assert(channel < CHANNEL_COUNT); // Pre-condition return channels[channel].incoming_splits.insert(toadd,reliable); } @@ -1330,12 +1349,10 @@ bool ConnectionSendThread::packetsQueued() if (dynamic_cast<UDPPeer*>(&peer) == 0) continue; - for(u16 i=0; i<CHANNEL_COUNT; i++) - { + for(u16 i=0; i < CHANNEL_COUNT; i++) { Channel *channel = &(dynamic_cast<UDPPeer*>(&peer))->channels[i]; - if (channel->queued_commands.size() > 0) - { + if (channel->queued_commands.size() > 0) { return true; } } @@ -1358,7 +1375,7 @@ void ConnectionSendThread::runTimeouts(float dtime) if (!peer) continue; - if(dynamic_cast<UDPPeer*>(&peer) == 0) + if (dynamic_cast<UDPPeer*>(&peer) == 0) continue; PROFILE(std::stringstream peerIdentifier); @@ -1371,7 +1388,7 @@ void ConnectionSendThread::runTimeouts(float dtime) /* Check peer timeout */ - if(peer->isTimedOut(m_timeout)) + if (peer->isTimedOut(m_timeout)) { infostream<<m_connection->getDesc() <<"RunTimeouts(): Peer "<<peer->id @@ -1477,7 +1494,7 @@ void ConnectionSendThread::rawSend(const BufferedPacket &packet) LOG(dout_con <<m_connection->getDesc() << " rawSend: " << packet.data.getSize() << " bytes sent" << std::endl); - } catch(SendFailedException &e){ + } catch(SendFailedException &e) { LOG(derr_con<<m_connection->getDesc() <<"Connection::rawSend(): SendFailedException: " <<packet.address.serializeString()<<std::endl); @@ -1498,7 +1515,6 @@ void ConnectionSendThread::sendAsPacketReliable(BufferedPacket& p, Channel* chan LOG(derr_con<<m_connection->getDesc() <<"WARNING: Going to send a reliable packet" <<" in outgoing buffer" <<std::endl); - //assert(0); } // Send the packet @@ -1509,16 +1525,16 @@ bool ConnectionSendThread::rawSendAsPacket(u16 peer_id, u8 channelnum, SharedBuffer<u8> data, bool reliable) { PeerHelper peer = m_connection->getPeerNoEx(peer_id); - if(!peer) { + if (!peer) { LOG(dout_con<<m_connection->getDesc() <<" INFO: dropped packet for non existent peer_id: " << peer_id << std::endl); - assert(reliable && "trying to send raw packet reliable but no peer found!"); + FATAL_ERROR_IF(!reliable, "Trying to send raw packet reliable but no peer found!"); return false; } Channel *channel = &(dynamic_cast<UDPPeer*>(&peer)->channels[channelnum]); - if(reliable) + if (reliable) { bool have_sequence_number_for_raw_packet = true; u16 seqnum = @@ -1551,7 +1567,7 @@ bool ConnectionSendThread::rawSendAsPacket(u16 peer_id, u8 channelnum, <<" INFO: queueing reliable packet for peer_id: " << peer_id <<" channel: " << channelnum <<" seqnum: " << seqnum << std::endl); - channel->queued_reliables.push_back(p); + channel->queued_reliables.push(p); return false; } } @@ -1584,9 +1600,9 @@ bool ConnectionSendThread::rawSendAsPacket(u16 peer_id, u8 channelnum, void ConnectionSendThread::processReliableCommand(ConnectionCommand &c) { - assert(c.reliable); + assert(c.reliable); // Pre-condition - switch(c.type){ + switch(c.type) { case CONNCMD_NONE: LOG(dout_con<<m_connection->getDesc() <<"UDP processing reliable CONNCMD_NONE"<<std::endl); @@ -1628,7 +1644,7 @@ void ConnectionSendThread::processReliableCommand(ConnectionCommand &c) case CONNCMD_CONNECT: case CONNCMD_DISCONNECT: case CONCMD_ACK: - assert("Got command that shouldn't be reliable as reliable command" == 0); + FATAL_ERROR("Got command that shouldn't be reliable as reliable command"); default: LOG(dout_con<<m_connection->getDesc() <<" Invalid reliable command type: " << c.type <<std::endl); @@ -1638,9 +1654,9 @@ void ConnectionSendThread::processReliableCommand(ConnectionCommand &c) void ConnectionSendThread::processNonReliableCommand(ConnectionCommand &c) { - assert(!c.reliable); + assert(!c.reliable); // Pre-condition - switch(c.type){ + switch(c.type) { case CONNCMD_NONE: LOG(dout_con<<m_connection->getDesc() <<" UDP processing CONNCMD_NONE"<<std::endl); @@ -1682,7 +1698,7 @@ void ConnectionSendThread::processNonReliableCommand(ConnectionCommand &c) sendAsPacket(c.peer_id,c.channelnum,c.data,true); return; case CONCMD_CREATE_PEER: - assert("Got command that should be reliable as unreliable command" == 0); + FATAL_ERROR("Got command that should be reliable as unreliable command"); default: LOG(dout_con<<m_connection->getDesc() <<" Invalid command type: " << c.type <<std::endl); @@ -1697,7 +1713,7 @@ void ConnectionSendThread::serve(Address bind_address) m_connection->m_udpSocket.Bind(bind_address); m_connection->SetPeerID(PEER_ID_SERVER); } - catch(SocketException &e){ + catch(SocketException &e) { // Create event ConnectionEvent ce; ce.bindFailed(); @@ -1728,8 +1744,8 @@ void ConnectionSendThread::connect(Address address) // Send a dummy packet to server with peer_id = PEER_ID_INEXISTENT m_connection->SetPeerID(PEER_ID_INEXISTENT); - SharedBuffer<u8> data(0); - m_connection->Send(PEER_ID_SERVER, 0, data, true); + NetworkPacket pkt(0,0); + m_connection->Send(PEER_ID_SERVER, 0, &pkt, true); } void ConnectionSendThread::disconnect() @@ -1779,10 +1795,10 @@ void ConnectionSendThread::disconnect_peer(u16 peer_id) void ConnectionSendThread::send(u16 peer_id, u8 channelnum, SharedBuffer<u8> data) { - assert(channelnum < CHANNEL_COUNT); + assert(channelnum < CHANNEL_COUNT); // Pre-condition PeerHelper peer = m_connection->getPeerNoEx(peer_id); - if(!peer) + if (!peer) { LOG(dout_con<<m_connection->getDesc()<<" peer: peer_id="<<peer_id << ">>>NOT<<< found on sending packet" @@ -1919,7 +1935,8 @@ void ConnectionSendThread::sendPackets(float dtime) < dynamic_cast<UDPPeer*>(&peer)->channels[i].getWindowSize())&& (peer->m_increment_packets_remaining > 0)) { - BufferedPacket p = dynamic_cast<UDPPeer*>(&peer)->channels[i].queued_reliables.pop_front(); + BufferedPacket p = dynamic_cast<UDPPeer*>(&peer)->channels[i].queued_reliables.front(); + dynamic_cast<UDPPeer*>(&peer)->channels[i].queued_reliables.pop(); Channel* channel = &(dynamic_cast<UDPPeer*>(&peer)->channels[i]); LOG(dout_con<<m_connection->getDesc() <<" INFO: sending a queued reliable packet " @@ -1942,13 +1959,14 @@ void ConnectionSendThread::sendPackets(float dtime) unsigned int initial_queuesize = m_outgoing_queue.size(); /* send non reliable packets*/ for(unsigned int i=0;i < initial_queuesize;i++) { - OutgoingPacket packet = m_outgoing_queue.pop_front(); + OutgoingPacket packet = m_outgoing_queue.front(); + m_outgoing_queue.pop(); - assert(!packet.reliable && - "reliable packets are not allowed in outgoing queue!"); + if (packet.reliable) + continue; PeerHelper peer = m_connection->getPeerNoEx(packet.peer_id); - if(!peer) { + if (!peer) { LOG(dout_con<<m_connection->getDesc() <<" Outgoing queue: peer_id="<<packet.peer_id << ">>>NOT<<< found on sending packet" @@ -1966,13 +1984,13 @@ void ConnectionSendThread::sendPackets(float dtime) } else if ( ( peer->m_increment_packets_remaining > 0) || - (StopRequested())){ + (StopRequested())) { rawSendAsPacket(packet.peer_id, packet.channelnum, packet.data, packet.reliable); peer->m_increment_packets_remaining--; } else { - m_outgoing_queue.push_back(packet); + m_outgoing_queue.push(packet); pending_unreliable[packet.peer_id] = true; } } @@ -1992,7 +2010,7 @@ void ConnectionSendThread::sendAsPacket(u16 peer_id, u8 channelnum, SharedBuffer<u8> data, bool ack) { OutgoingPacket packet(peer_id, channelnum, data, false, ack); - m_outgoing_queue.push_back(packet); + m_outgoing_queue.push(packet); } ConnectionReceiveThread::ConnectionReceiveThread(unsigned int max_packet_size) : @@ -2107,152 +2125,149 @@ void ConnectionReceiveThread::receive() /* first of all read packets from socket */ /* check for incoming data available */ while( (loop_count < 10) && - (m_connection->m_udpSocket.WaitData(50))) - { + (m_connection->m_udpSocket.WaitData(50))) { loop_count++; - try{ - if (packet_queued) - { - bool no_data_left = false; - u16 peer_id; - SharedBuffer<u8> resultdata; - while(!no_data_left) - { - try { - no_data_left = !getFromBuffers(peer_id, resultdata); - if (!no_data_left) { - ConnectionEvent e; - e.dataReceived(peer_id, resultdata); - m_connection->putEvent(e); + try { + if (packet_queued) { + bool data_left = true; + u16 peer_id; + SharedBuffer<u8> resultdata; + while(data_left) { + try { + data_left = getFromBuffers(peer_id, resultdata); + if (data_left) { + ConnectionEvent e; + e.dataReceived(peer_id, resultdata); + m_connection->putEvent(e); + } + } + catch(ProcessedSilentlyException &e) { + /* try reading again */ } } - catch(ProcessedSilentlyException &e) { - /* try reading again */ - } + packet_queued = false; } - packet_queued = false; - } - Address sender; - s32 received_size = m_connection->m_udpSocket.Receive(sender, *packetdata, packet_maxsize); + Address sender; + s32 received_size = m_connection->m_udpSocket.Receive(sender, *packetdata, packet_maxsize); - if ((received_size < 0) || - (received_size < BASE_HEADER_SIZE) || - (readU32(&packetdata[0]) != m_connection->GetProtocolID())) - { - LOG(derr_con<<m_connection->getDesc() - <<"Receive(): Invalid incoming packet, " - <<"size: " << received_size - <<", protocol: " - << ((received_size >= 4) ? readU32(&packetdata[0]) : -1) - << std::endl); - continue; - } + if ((received_size < BASE_HEADER_SIZE) || + (readU32(&packetdata[0]) != m_connection->GetProtocolID())) + { + LOG(derr_con<<m_connection->getDesc() + <<"Receive(): Invalid incoming packet, " + <<"size: " << received_size + <<", protocol: " + << ((received_size >= 4) ? readU32(&packetdata[0]) : -1) + << std::endl); + continue; + } - u16 peer_id = readPeerId(*packetdata); - u8 channelnum = readChannel(*packetdata); + u16 peer_id = readPeerId(*packetdata); + u8 channelnum = readChannel(*packetdata); - if(channelnum > CHANNEL_COUNT-1){ - LOG(derr_con<<m_connection->getDesc() - <<"Receive(): Invalid channel "<<channelnum<<std::endl); - throw InvalidIncomingDataException("Channel doesn't exist"); - } + if (channelnum > CHANNEL_COUNT-1) { + LOG(derr_con<<m_connection->getDesc() + <<"Receive(): Invalid channel "<<channelnum<<std::endl); + throw InvalidIncomingDataException("Channel doesn't exist"); + } - /* preserve original peer_id for later usage */ - u16 packet_peer_id = peer_id; + /* preserve original peer_id for later usage */ + u16 packet_peer_id = peer_id; - /* Try to identify peer by sender address (may happen on join) */ - if(peer_id == PEER_ID_INEXISTENT) - { - peer_id = m_connection->lookupPeer(sender); - } + /* Try to identify peer by sender address (may happen on join) */ + if (peer_id == PEER_ID_INEXISTENT) { + peer_id = m_connection->lookupPeer(sender); + } - /* The peer was not found in our lists. Add it. */ - if(peer_id == PEER_ID_INEXISTENT) - { - peer_id = m_connection->createPeer(sender, MTP_MINETEST_RELIABLE_UDP, 0); - } + /* The peer was not found in our lists. Add it. */ + if (peer_id == PEER_ID_INEXISTENT) { + peer_id = m_connection->createPeer(sender, MTP_MINETEST_RELIABLE_UDP, 0); + } - PeerHelper peer = m_connection->getPeerNoEx(peer_id); + PeerHelper peer = m_connection->getPeerNoEx(peer_id); - if (!peer) { - LOG(dout_con<<m_connection->getDesc() - <<" got packet from unknown peer_id: " - <<peer_id<<" Ignoring."<<std::endl); - continue; - } + if (!peer) { + LOG(dout_con<<m_connection->getDesc() + <<" got packet from unknown peer_id: " + <<peer_id<<" Ignoring."<<std::endl); + continue; + } - // Validate peer address + // Validate peer address - Address peer_address; + Address peer_address; - if (peer->getAddress(MTP_UDP, peer_address)) { - if (peer_address != sender) { - LOG(derr_con<<m_connection->getDesc() - <<m_connection->getDesc() - <<" Peer "<<peer_id<<" sending from different address." - " Ignoring."<<std::endl); - continue; + if (peer->getAddress(MTP_UDP, peer_address)) { + if (peer_address != sender) { + LOG(derr_con<<m_connection->getDesc() + <<m_connection->getDesc() + <<" Peer "<<peer_id<<" sending from different address." + " Ignoring."<<std::endl); + continue; + } } - } - else { - - bool invalid_address = true; - if (invalid_address) { - LOG(derr_con<<m_connection->getDesc() - <<m_connection->getDesc() - <<" Peer "<<peer_id<<" unknown." - " Ignoring."<<std::endl); - continue; + else { + + bool invalid_address = true; + if (invalid_address) { + LOG(derr_con<<m_connection->getDesc() + <<m_connection->getDesc() + <<" Peer "<<peer_id<<" unknown." + " Ignoring."<<std::endl); + continue; + } } - } - /* mark peer as seen with id */ - if (!(packet_peer_id == PEER_ID_INEXISTENT)) - peer->setSentWithID(); + /* mark peer as seen with id */ + if (!(packet_peer_id == PEER_ID_INEXISTENT)) + peer->setSentWithID(); - peer->ResetTimeout(); + peer->ResetTimeout(); - Channel *channel = 0; + Channel *channel = 0; - if (dynamic_cast<UDPPeer*>(&peer) != 0) - { - channel = &(dynamic_cast<UDPPeer*>(&peer)->channels[channelnum]); - } + if (dynamic_cast<UDPPeer*>(&peer) != 0) + { + channel = &(dynamic_cast<UDPPeer*>(&peer)->channels[channelnum]); + } - if (channel != 0) { - channel->UpdateBytesReceived(received_size); - } + if (channel != 0) { + channel->UpdateBytesReceived(received_size); + } - // Throw the received packet to channel->processPacket() + // Throw the received packet to channel->processPacket() - // Make a new SharedBuffer from the data without the base headers - SharedBuffer<u8> strippeddata(received_size - BASE_HEADER_SIZE); - memcpy(*strippeddata, &packetdata[BASE_HEADER_SIZE], - strippeddata.getSize()); + // Make a new SharedBuffer from the data without the base headers + SharedBuffer<u8> strippeddata(received_size - BASE_HEADER_SIZE); + memcpy(*strippeddata, &packetdata[BASE_HEADER_SIZE], + strippeddata.getSize()); - try{ - // Process it (the result is some data with no headers made by us) - SharedBuffer<u8> resultdata = processPacket - (channel, strippeddata, peer_id, channelnum, false); + try{ + // Process it (the result is some data with no headers made by us) + SharedBuffer<u8> resultdata = processPacket + (channel, strippeddata, peer_id, channelnum, false); - LOG(dout_con<<m_connection->getDesc() - <<" ProcessPacket from peer_id: " << peer_id - << ",channel: " << (channelnum & 0xFF) << ", returned " - << resultdata.getSize() << " bytes" <<std::endl); + LOG(dout_con<<m_connection->getDesc() + <<" ProcessPacket from peer_id: " << peer_id + << ",channel: " << (channelnum & 0xFF) << ", returned " + << resultdata.getSize() << " bytes" <<std::endl); - ConnectionEvent e; - e.dataReceived(peer_id, resultdata); - m_connection->putEvent(e); - }catch(ProcessedSilentlyException &e){ - }catch(ProcessedQueued &e){ - packet_queued = true; + ConnectionEvent e; + e.dataReceived(peer_id, resultdata); + m_connection->putEvent(e); + } + catch(ProcessedSilentlyException &e) { + } + catch(ProcessedQueued &e) { + packet_queued = true; + } + } + catch(InvalidIncomingDataException &e) { + } + catch(ProcessedSilentlyException &e) { } - }catch(InvalidIncomingDataException &e){ - } - catch(ProcessedSilentlyException &e){ - } } } @@ -2267,17 +2282,14 @@ bool ConnectionReceiveThread::getFromBuffers(u16 &peer_id, SharedBuffer<u8> &dst if (!peer) continue; - if(dynamic_cast<UDPPeer*>(&peer) == 0) + if (dynamic_cast<UDPPeer*>(&peer) == 0) continue; for(u16 i=0; i<CHANNEL_COUNT; i++) { Channel *channel = &(dynamic_cast<UDPPeer*>(&peer))->channels[i]; - SharedBuffer<u8> resultdata; - bool got = checkIncomingBuffers(channel, peer_id, resultdata); - if(got){ - dst = resultdata; + if (checkIncomingBuffers(channel, peer_id, dst)) { return true; } } @@ -2291,7 +2303,7 @@ bool ConnectionReceiveThread::checkIncomingBuffers(Channel *channel, u16 firstseqnum = 0; if (channel->incoming_reliables.getFirstSeqnum(firstseqnum)) { - if(firstseqnum == channel->readNextIncomingSeqNum()) + if (firstseqnum == channel->readNextIncomingSeqNum()) { BufferedPacket p = channel->incoming_reliables.popFirst(); peer_id = readPeerId(*p.data); @@ -2329,29 +2341,32 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, throw ProcessedSilentlyException("Peer not found (possible timeout)"); } - if(packetdata.getSize() < 1) + if (packetdata.getSize() < 1) throw InvalidIncomingDataException("packetdata.getSize() < 1"); u8 type = readU8(&(packetdata[0])); if (MAX_UDP_PEERS <= 65535 && peer_id >= MAX_UDP_PEERS) { - errorstream << "Something is wrong with peer_id" << std::endl; - assert(0); + std::string errmsg = "Invalid peer_id=" + itos(peer_id); + errorstream << errmsg << std::endl; + throw InvalidIncomingDataException(errmsg.c_str()); } - if(type == TYPE_CONTROL) + if (type == TYPE_CONTROL) { - if(packetdata.getSize() < 2) + if (packetdata.getSize() < 2) throw InvalidIncomingDataException("packetdata.getSize() < 2"); u8 controltype = readU8(&(packetdata[1])); - if(controltype == CONTROLTYPE_ACK) + if (controltype == CONTROLTYPE_ACK) { - assert(channel != 0); - if(packetdata.getSize() < 4) - throw InvalidIncomingDataException - ("packetdata.getSize() < 4 (ACK header size)"); + assert(channel != NULL); + + if (packetdata.getSize() < 4) { + throw InvalidIncomingDataException( + "packetdata.getSize() < 4 (ACK header size)"); + } u16 seqnum = readU16(&packetdata[2]); LOG(dout_con<<m_connection->getDesc() @@ -2394,7 +2409,7 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, m_connection->TriggerSend(); } } - catch(NotFoundException &e){ + catch(NotFoundException &e) { LOG(derr_con<<m_connection->getDesc() <<"WARNING: ACKed packet not " "in outgoing queue" @@ -2403,17 +2418,16 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, } throw ProcessedSilentlyException("Got an ACK"); } - else if(controltype == CONTROLTYPE_SET_PEER_ID) - { + else if (controltype == CONTROLTYPE_SET_PEER_ID) { // Got a packet to set our peer id - if(packetdata.getSize() < 4) + if (packetdata.getSize() < 4) throw InvalidIncomingDataException ("packetdata.getSize() < 4 (SET_PEER_ID header size)"); u16 peer_id_new = readU16(&packetdata[2]); LOG(dout_con<<m_connection->getDesc() <<"Got new peer id: "<<peer_id_new<<"... "<<std::endl); - if(m_connection->GetPeerID() != PEER_ID_INEXISTENT) + if (m_connection->GetPeerID() != PEER_ID_INEXISTENT) { LOG(derr_con<<m_connection->getDesc() <<"WARNING: Not changing" @@ -2435,21 +2449,21 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, throw ProcessedSilentlyException("Got a SET_PEER_ID"); } - else if(controltype == CONTROLTYPE_PING) + else if (controltype == CONTROLTYPE_PING) { // Just ignore it, the incoming data already reset // the timeout counter LOG(dout_con<<m_connection->getDesc()<<"PING"<<std::endl); throw ProcessedSilentlyException("Got a PING"); } - else if(controltype == CONTROLTYPE_DISCO) + else if (controltype == CONTROLTYPE_DISCO) { // Just ignore it, the incoming data already reset // the timeout counter LOG(dout_con<<m_connection->getDesc() <<"DISCO: Removing peer "<<(peer_id)<<std::endl); - if(m_connection->deletePeer(peer_id, false) == false) + if (m_connection->deletePeer(peer_id, false) == false) { derr_con<<m_connection->getDesc() <<"DISCO: Peer not found"<<std::endl; @@ -2457,7 +2471,7 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, throw ProcessedSilentlyException("Got a DISCO"); } - else if(controltype == CONTROLTYPE_ENABLE_BIG_SEND_WINDOW) + else if (controltype == CONTROLTYPE_ENABLE_BIG_SEND_WINDOW) { dynamic_cast<UDPPeer*>(&peer)->setNonLegacyPeer(); throw ProcessedSilentlyException("Got non legacy control"); @@ -2469,9 +2483,9 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, throw InvalidIncomingDataException("Invalid control type"); } } - else if(type == TYPE_ORIGINAL) + else if (type == TYPE_ORIGINAL) { - if(packetdata.getSize() <= ORIGINAL_HEADER_SIZE) + if (packetdata.getSize() <= ORIGINAL_HEADER_SIZE) throw InvalidIncomingDataException ("packetdata.getSize() <= ORIGINAL_HEADER_SIZE"); LOG(dout_con<<m_connection->getDesc() @@ -2482,7 +2496,7 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, memcpy(*payload, &(packetdata[ORIGINAL_HEADER_SIZE]), payload.getSize()); return payload; } - else if(type == TYPE_SPLIT) + else if (type == TYPE_SPLIT) { Address peer_address; @@ -2501,7 +2515,7 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, SharedBuffer<u8> data = peer->addSpiltPacket(channelnum,packet,reliable); - if(data.getSize() != 0) + if (data.getSize() != 0) { LOG(dout_con<<m_connection->getDesc() <<"RETURNING TYPE_SPLIT: Constructed full data, " @@ -2515,14 +2529,15 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, //TODO throw some error } } - else if(type == TYPE_RELIABLE) + else if (type == TYPE_RELIABLE) { - assert(channel != 0); + assert(channel != NULL); + // Recursive reliable packets not allowed - if(reliable) + if (reliable) throw InvalidIncomingDataException("Found nested reliable packets"); - if(packetdata.getSize() < RELIABLE_HEADER_SIZE) + if (packetdata.getSize() < RELIABLE_HEADER_SIZE) throw InvalidIncomingDataException ("packetdata.getSize() < RELIABLE_HEADER_SIZE"); @@ -2641,10 +2656,7 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, } // We should never get here. - // If you get here, add an exception or a return to some of the - // above conditionals. - assert(0); - throw BaseException("Error in Channel::ProcessPacket()"); + FATAL_ERROR("Invalid execution point"); } /* @@ -2652,30 +2664,6 @@ SharedBuffer<u8> ConnectionReceiveThread::processPacket(Channel *channel, */ Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout, - bool ipv6) : - m_udpSocket(ipv6), - m_command_queue(), - m_event_queue(), - m_peer_id(0), - m_protocol_id(protocol_id), - m_sendThread(max_packet_size, timeout), - m_receiveThread(max_packet_size), - m_info_mutex(), - m_bc_peerhandler(0), - m_bc_receive_timeout(0), - m_shutting_down(false), - m_next_remote_peer_id(2) -{ - m_udpSocket.setTimeoutMs(5); - - m_sendThread.setParent(this); - m_receiveThread.setParent(this); - - m_sendThread.Start(); - m_receiveThread.Start(); -} - -Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout, bool ipv6, PeerHandler *peerhandler) : m_udpSocket(ipv6), m_command_queue(), @@ -2730,7 +2718,7 @@ Connection::~Connection() /* Internal stuff */ void Connection::putEvent(ConnectionEvent &e) { - assert(e.type != CONNEVENT_NONE); + assert(e.type != CONNEVENT_NONE); // Pre-condition m_event_queue.push_back(e); } @@ -2739,12 +2727,12 @@ PeerHelper Connection::getPeer(u16 peer_id) JMutexAutoLock peerlock(m_peers_mutex); std::map<u16, Peer*>::iterator node = m_peers.find(peer_id); - if(node == m_peers.end()){ + if (node == m_peers.end()) { throw PeerNotFoundException("GetPeer: Peer not found (possible timeout)"); } // Error checking - assert(node->second->id == peer_id); + FATAL_ERROR_IF(node->second->id != peer_id, "Invalid peer id"); return PeerHelper(node->second); } @@ -2754,12 +2742,12 @@ PeerHelper Connection::getPeerNoEx(u16 peer_id) JMutexAutoLock peerlock(m_peers_mutex); std::map<u16, Peer*>::iterator node = m_peers.find(peer_id); - if(node == m_peers.end()){ + if (node == m_peers.end()) { return PeerHelper(NULL); } // Error checking - assert(node->second->id == peer_id); + FATAL_ERROR_IF(node->second->id != peer_id, "Invalid peer id"); return PeerHelper(node->second); } @@ -2773,7 +2761,7 @@ u16 Connection::lookupPeer(Address& sender) for(; j != m_peers.end(); ++j) { Peer *peer = j->second; - if(peer->isActive()) + if (peer->isActive()) continue; Address tocheck; @@ -2807,10 +2795,11 @@ bool Connection::deletePeer(u16 peer_id, bool timeout) /* lock list as short as possible */ { JMutexAutoLock peerlock(m_peers_mutex); - if(m_peers.find(peer_id) == m_peers.end()) + if (m_peers.find(peer_id) == m_peers.end()) return false; peer = m_peers[peer_id]; m_peers.erase(peer_id); + m_peer_ids.remove(peer_id); } Address peer_address; @@ -2828,21 +2817,11 @@ bool Connection::deletePeer(u16 peer_id, bool timeout) /* Interface */ -ConnectionEvent Connection::getEvent() -{ - if(m_event_queue.empty()){ - ConnectionEvent e; - e.type = CONNEVENT_NONE; - return e; - } - return m_event_queue.pop_frontNoEx(); -} - ConnectionEvent Connection::waitEvent(u32 timeout_ms) { - try{ + try { return m_event_queue.pop_front(timeout_ms); - } catch(ItemNotFoundException &ex){ + } catch(ItemNotFoundException &ex) { ConnectionEvent e; e.type = CONNEVENT_NONE; return e; @@ -2851,8 +2830,7 @@ ConnectionEvent Connection::waitEvent(u32 timeout_ms) void Connection::putCommand(ConnectionCommand &c) { - if (!m_shutting_down) - { + if (!m_shutting_down) { m_command_queue.push_back(c); m_sendThread.Trigger(); } @@ -2876,14 +2854,14 @@ bool Connection::Connected() { JMutexAutoLock peerlock(m_peers_mutex); - if(m_peers.size() != 1) + if (m_peers.size() != 1) return false; std::map<u16, Peer*>::iterator node = m_peers.find(PEER_ID_SERVER); - if(node == m_peers.end()) + if (node == m_peers.end()) return false; - if(m_peer_id == PEER_ID_INEXISTENT) + if (m_peer_id == PEER_ID_INEXISTENT) return false; return true; @@ -2896,30 +2874,36 @@ void Connection::Disconnect() putCommand(c); } -u32 Connection::Receive(u16 &peer_id, SharedBuffer<u8> &data) +void Connection::Receive(NetworkPacket* pkt) { - for(;;){ + for(;;) { ConnectionEvent e = waitEvent(m_bc_receive_timeout); - if(e.type != CONNEVENT_NONE) - LOG(dout_con<<getDesc()<<": Receive: got event: " - <<e.describe()<<std::endl); - switch(e.type){ + if (e.type != CONNEVENT_NONE) + LOG(dout_con << getDesc() << ": Receive: got event: " + << e.describe() << std::endl); + switch(e.type) { case CONNEVENT_NONE: throw NoIncomingDataException("No incoming data"); case CONNEVENT_DATA_RECEIVED: - peer_id = e.peer_id; - data = SharedBuffer<u8>(e.data); - return e.data.getSize(); + // Data size is lesser than command size, ignoring packet + if (e.data.getSize() < 2) { + continue; + } + + pkt->putRawPacket(*e.data, e.data.getSize(), e.peer_id); + return; case CONNEVENT_PEER_ADDED: { UDPPeer tmp(e.peer_id, e.address, this); - if(m_bc_peerhandler) + if (m_bc_peerhandler) m_bc_peerhandler->peerAdded(&tmp); - continue; } + continue; + } case CONNEVENT_PEER_REMOVED: { UDPPeer tmp(e.peer_id, e.address, this); - if(m_bc_peerhandler) + if (m_bc_peerhandler) m_bc_peerhandler->deletingPeer(&tmp, e.timeout); - continue; } + continue; + } case CONNEVENT_BIND_FAILED: throw ConnectionBindFailed("Failed to bind socket " "(port already in use?)"); @@ -2928,22 +2912,14 @@ u32 Connection::Receive(u16 &peer_id, SharedBuffer<u8> &data) throw NoIncomingDataException("No incoming data"); } -void Connection::SendToAll(u8 channelnum, SharedBuffer<u8> data, bool reliable) -{ - assert(channelnum < CHANNEL_COUNT); - - ConnectionCommand c; - c.sendToAll(channelnum, data, reliable); - putCommand(c); -} - void Connection::Send(u16 peer_id, u8 channelnum, - SharedBuffer<u8> data, bool reliable) + NetworkPacket* pkt, bool reliable) { - assert(channelnum < CHANNEL_COUNT); + assert(channelnum < CHANNEL_COUNT); // Pre-condition ConnectionCommand c; - c.send(peer_id, channelnum, data, reliable); + + c.send(peer_id, channelnum, pkt, reliable); putCommand(c); } @@ -2969,9 +2945,7 @@ float Connection::getLocalStat(rate_stat_type type) { PeerHelper peer = getPeerNoEx(PEER_ID_SERVER); - if (!peer) { - assert("Connection::getLocalStat we couldn't get our own peer? are you serious???" == 0); - } + FATAL_ERROR_IF(!peer, "Connection::getLocalStat we couldn't get our own peer? are you serious???"); float retval = 0.0; @@ -2996,7 +2970,7 @@ float Connection::getLocalStat(rate_stat_type type) retval += dynamic_cast<UDPPeer*>(&peer)->channels[j].getCurrentLossRateKB(); break; default: - assert("Connection::getLocalStat Invalid stat type" == 0); + FATAL_ERROR("Connection::getLocalStat Invalid stat type"); } } return retval; @@ -3013,37 +2987,37 @@ u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd) /* Find an unused peer id */ - { JMutexAutoLock lock(m_peers_mutex); - bool out_of_ids = false; - for(;;) - { - // Check if exists - if(m_peers.find(peer_id_new) == m_peers.end()) - break; - // Check for overflow - if(peer_id_new == overflow){ - out_of_ids = true; - break; - } - peer_id_new++; - } - if(out_of_ids){ - errorstream<<getDesc()<<" ran out of peer ids"<<std::endl; - return PEER_ID_INEXISTENT; - } + bool out_of_ids = false; + for(;;) { + // Check if exists + if (m_peers.find(peer_id_new) == m_peers.end()) - // Create a peer - Peer *peer = 0; - peer = new UDPPeer(peer_id_new, sender, this); + break; + // Check for overflow + if (peer_id_new == overflow) { + out_of_ids = true; + break; + } + peer_id_new++; + } - m_peers[peer->id] = peer; + if (out_of_ids) { + errorstream << getDesc() << " ran out of peer ids" << std::endl; + return PEER_ID_INEXISTENT; } - m_next_remote_peer_id = (peer_id_new +1) % MAX_UDP_PEERS; + // Create a peer + Peer *peer = 0; + peer = new UDPPeer(peer_id_new, sender, this); - LOG(dout_con<<getDesc() - <<"createPeer(): giving peer_id="<<peer_id_new<<std::endl); + m_peers[peer->id] = peer; + m_peer_ids.push_back(peer->id); + + m_next_remote_peer_id = (peer_id_new +1 ) % MAX_UDP_PEERS; + + LOG(dout_con << getDesc() + << "createPeer(): giving peer_id=" << peer_id_new << std::endl); ConnectionCommand cmd; SharedBuffer<u8> reply(4); @@ -3051,7 +3025,7 @@ u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd) writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID); writeU16(&reply[2], peer_id_new); cmd.createPeer(peer_id_new,reply); - this->putCommand(cmd); + putCommand(cmd); // Create peer addition event ConnectionEvent e; @@ -3089,7 +3063,7 @@ void Connection::DisconnectPeer(u16 peer_id) void Connection::sendAck(u16 peer_id, u8 channelnum, u16 seqnum) { - assert(channelnum < CHANNEL_COUNT); + assert(channelnum < CHANNEL_COUNT); // Pre-condition LOG(dout_con<<getDesc() <<" Queuing ACK command to peer_id: " << peer_id << @@ -3119,22 +3093,10 @@ UDPPeer* Connection::createServerPeer(Address& address) { JMutexAutoLock lock(m_peers_mutex); m_peers[peer->id] = peer; + m_peer_ids.push_back(peer->id); } return peer; } -std::list<u16> Connection::getPeerIDs() -{ - std::list<u16> retval; - - JMutexAutoLock lock(m_peers_mutex); - for(std::map<u16, Peer*>::iterator j = m_peers.begin(); - j != m_peers.end(); ++j) - { - retval.push_back(j->first); - } - return retval; -} - } // namespace diff --git a/src/connection.h b/src/network/connection.h index be1627dfa..15ea7e20f 100644 --- a/src/connection.h +++ b/src/network/connection.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "socket.h" #include "exceptions.h" #include "constants.h" +#include "network/networkpacket.h" #include "util/pointer.h" #include "util/container.h" #include "util/thread.h" @@ -33,6 +34,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <list> #include <map> +class NetworkPacket; + namespace con { @@ -130,14 +133,14 @@ inline bool seqnum_higher(u16 totest, u16 base) { if (totest > base) { - if((totest - base) > (SEQNUM_MAX/2)) + if ((totest - base) > (SEQNUM_MAX/2)) return false; else return true; } else { - if((base - totest) > (SEQNUM_MAX/2)) + if ((base - totest) > (SEQNUM_MAX/2)) return true; else return false; @@ -169,7 +172,7 @@ struct BufferedPacket data(a_size), time(0.0), totaltime(0.0), absolute_send_time(-1), resend_count(0) {} - SharedBuffer<u8> data; // Data of the packet, including headers + Buffer<u8> data; // Data of the packet, including headers float time; // Seconds from buffering the packet or re-sending float totaltime; // Seconds from buffering the packet unsigned int absolute_send_time; @@ -362,9 +365,9 @@ public: packet is constructed. If not, returns one of length 0. */ SharedBuffer<u8> insert(BufferedPacket &p, bool reliable); - + void removeUnreliableTimedOuts(float dtime, float timeout); - + private: // Key is seqnum std::map<u16, IncomingSplitPacket*> m_buf; @@ -436,19 +439,12 @@ struct ConnectionCommand peer_id = peer_id_; } void send(u16 peer_id_, u8 channelnum_, - SharedBuffer<u8> data_, bool reliable_) + NetworkPacket* pkt, bool reliable_) { type = CONNCMD_SEND; peer_id = peer_id_; channelnum = channelnum_; - data = data_; - reliable = reliable_; - } - void sendToAll(u8 channelnum_, SharedBuffer<u8> data_, bool reliable_) - { - type = CONNCMD_SEND_TO_ALL; - channelnum = channelnum_; - data = data_; + data = pkt->oldForgePacket(); reliable = reliable_; } @@ -495,7 +491,7 @@ public: u16 readNextSplitSeqNum(); void setNextSplitSeqNum(u16 seqnum); - + // This is for buffering the incoming packets that are coming in // the wrong order ReliablePacketBuffer incoming_reliables; @@ -504,10 +500,10 @@ public: ReliablePacketBuffer outgoing_reliables_sent; //queued reliable packets - Queue<BufferedPacket> queued_reliables; + std::queue<BufferedPacket> queued_reliables; //queue commands prior splitting to packets - Queue<ConnectionCommand> queued_commands; + std::deque<ConnectionCommand> queued_commands; IncomingSplitBuffer incoming_splits; @@ -680,7 +676,7 @@ class Peer { virtual ~Peer() { JMutexAutoLock usage_lock(m_exclusive_access_mutex); - assert(m_usage == 0); + FATAL_ERROR_IF(m_usage != 0, "Reference counting failure"); }; // Unique id of the peer @@ -869,11 +865,12 @@ struct ConnectionEvent bool timeout; Address address; - ConnectionEvent(): type(CONNEVENT_NONE) {} + ConnectionEvent(): type(CONNEVENT_NONE), peer_id(0), + timeout(false) {} std::string describe() { - switch(type){ + switch(type) { case CONNEVENT_NONE: return "CONNEVENT_NONE"; case CONNEVENT_DATA_RECEIVED: @@ -887,7 +884,7 @@ struct ConnectionEvent } return "Invalid ConnectionEvent"; } - + void dataReceived(u16 peer_id_, SharedBuffer<u8> data_) { type = CONNEVENT_DATA_RECEIVED; @@ -925,7 +922,7 @@ public: void Trigger(); void setParent(Connection* parent) { - assert(parent != NULL); + assert(parent != NULL); // Pre-condition m_connection = parent; } @@ -963,7 +960,7 @@ private: Connection* m_connection; unsigned int m_max_packet_size; float m_timeout; - Queue<OutgoingPacket> m_outgoing_queue; + std::queue<OutgoingPacket> m_outgoing_queue; JSemaphore m_send_sleep_semaphore; unsigned int m_iteration_packets_avaialble; @@ -979,7 +976,7 @@ public: void * Thread (); void setParent(Connection* parent) { - assert(parent != NULL); + assert(parent != NULL); // Pre-condition m_connection = parent; } @@ -1016,25 +1013,22 @@ public: friend class ConnectionSendThread; friend class ConnectionReceiveThread; - Connection(u32 protocol_id, u32 max_packet_size, float timeout, bool ipv6); Connection(u32 protocol_id, u32 max_packet_size, float timeout, bool ipv6, PeerHandler *peerhandler); ~Connection(); /* Interface */ - ConnectionEvent getEvent(); ConnectionEvent waitEvent(u32 timeout_ms); void putCommand(ConnectionCommand &c); - - void SetTimeoutMs(int timeout){ m_bc_receive_timeout = timeout; } + + void SetTimeoutMs(int timeout) { m_bc_receive_timeout = timeout; } void Serve(Address bind_addr); void Connect(Address address); bool Connected(); void Disconnect(); - u32 Receive(u16 &peer_id, SharedBuffer<u8> &data); - void SendToAll(u8 channelnum, SharedBuffer<u8> data, bool reliable); - void Send(u16 peer_id, u8 channelnum, SharedBuffer<u8> data, bool reliable); - u16 GetPeerID(){ return m_peer_id; } + void Receive(NetworkPacket* pkt); + void Send(u16 peer_id, u8 channelnum, NetworkPacket* pkt, bool reliable); + u16 GetPeerID() { return m_peer_id; } Address GetPeerAddress(u16 peer_id); float getPeerStat(u16 peer_id, rtt_stat_type type); float getLocalStat(rate_stat_type type); @@ -1051,14 +1045,18 @@ protected: UDPPeer* createServerPeer(Address& sender); bool deletePeer(u16 peer_id, bool timeout); - void SetPeerID(u16 id){ m_peer_id = id; } + void SetPeerID(u16 id) { m_peer_id = id; } void sendAck(u16 peer_id, u8 channelnum, u16 seqnum); void PrintInfo(std::ostream &out); void PrintInfo(); - std::list<u16> getPeerIDs(); + std::list<u16> getPeerIDs() + { + JMutexAutoLock peerlock(m_peers_mutex); + return m_peer_ids; + } UDPSocket m_udpSocket; MutexedQueue<ConnectionCommand> m_command_queue; @@ -1074,8 +1072,9 @@ private: u16 m_peer_id; u32 m_protocol_id; - + std::map<u16, Peer*> m_peers; + std::list<u16> m_peer_ids; JMutex m_peers_mutex; ConnectionSendThread m_sendThread; @@ -1095,4 +1094,3 @@ private: } // namespace #endif - diff --git a/src/network/networkpacket.cpp b/src/network/networkpacket.cpp new file mode 100644 index 000000000..b5e451cdb --- /dev/null +++ b/src/network/networkpacket.cpp @@ -0,0 +1,520 @@ +/* +Minetest +Copyright (C) 2015 nerzhul, Loic Blot <loic.blot@unix-experience.fr> + +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 "networkpacket.h" +#include "debug.h" +#include "exceptions.h" +#include "util/serialize.h" + +NetworkPacket::NetworkPacket(u16 command, u32 datasize, u16 peer_id): +m_datasize(datasize), m_read_offset(0), m_command(command), m_peer_id(peer_id) +{ + m_data.resize(m_datasize); +} + +NetworkPacket::NetworkPacket(u16 command, u32 datasize): +m_datasize(datasize), m_read_offset(0), m_command(command), m_peer_id(0) +{ + m_data.resize(m_datasize); +} + +NetworkPacket::~NetworkPacket() +{ + m_data.clear(); +} + +void NetworkPacket::checkReadOffset(u32 from_offset, u32 field_size) +{ + if (from_offset + field_size > m_datasize) { + std::stringstream ss; + ss << "Reading outside packet (offset: " << + from_offset << ", packet size: " << getSize() << ")"; + throw PacketError(ss.str()); + } +} + +void NetworkPacket::putRawPacket(u8 *data, u32 datasize, u16 peer_id) +{ + // If a m_command is already set, we are rewriting on same packet + // This is not permitted + assert(m_command == 0); + + m_datasize = datasize - 2; + m_peer_id = peer_id; + + // split command and datas + m_command = readU16(&data[0]); + m_data = std::vector<u8>(&data[2], &data[2 + m_datasize]); +} + +char* NetworkPacket::getString(u32 from_offset) +{ + checkReadOffset(from_offset, 0); + + return (char*)&m_data[from_offset]; +} + +void NetworkPacket::putRawString(const char* src, u32 len) +{ + if (m_read_offset + len > m_datasize) { + m_datasize = m_read_offset + len; + m_data.resize(m_datasize); + } + + memcpy(&m_data[m_read_offset], src, len); + m_read_offset += len; +} + +NetworkPacket& NetworkPacket::operator>>(std::string& dst) +{ + checkReadOffset(m_read_offset, 2); + u16 strLen = readU16(&m_data[m_read_offset]); + m_read_offset += 2; + + dst.clear(); + + if (strLen == 0) { + return *this; + } + + checkReadOffset(m_read_offset, strLen); + + dst.reserve(strLen); + dst.append((char*)&m_data[m_read_offset], strLen); + + m_read_offset += strLen; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(std::string src) +{ + u16 msgsize = src.size(); + if (msgsize > STRING_MAX_LEN) { + throw PacketError("String too long"); + } + + *this << msgsize; + + putRawString(src.c_str(), (u32)msgsize); + + return *this; +} + +void NetworkPacket::putLongString(std::string src) +{ + u32 msgsize = src.size(); + if (msgsize > LONG_STRING_MAX_LEN) { + throw PacketError("String too long"); + } + + *this << msgsize; + + putRawString(src.c_str(), msgsize); +} + +NetworkPacket& NetworkPacket::operator>>(std::wstring& dst) +{ + checkReadOffset(m_read_offset, 2); + u16 strLen = readU16(&m_data[m_read_offset]); + m_read_offset += 2; + + dst.clear(); + + if (strLen == 0) { + return *this; + } + + checkReadOffset(m_read_offset, strLen * 2); + + dst.reserve(strLen); + for(u16 i=0; i<strLen; i++) { + wchar_t c16 = readU16(&m_data[m_read_offset]); + dst.append(&c16, 1); + m_read_offset += sizeof(u16); + } + + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(std::wstring src) +{ + u16 msgsize = src.size(); + if (msgsize > WIDE_STRING_MAX_LEN) { + throw PacketError("String too long"); + } + + *this << msgsize; + + // Write string + for (u16 i=0; i<msgsize; i++) { + *this << (u16) src[i]; + } + + return *this; +} + +std::string NetworkPacket::readLongString() +{ + checkReadOffset(m_read_offset, 4); + u32 strLen = readU32(&m_data[m_read_offset]); + m_read_offset += 4; + + if (strLen == 0) { + return ""; + } + + if (strLen > LONG_STRING_MAX_LEN) { + throw PacketError("String too long"); + } + + checkReadOffset(m_read_offset, strLen); + + std::string dst; + + dst.reserve(strLen); + dst.append((char*)&m_data[m_read_offset], strLen); + + m_read_offset += strLen; + + return dst; +} + +NetworkPacket& NetworkPacket::operator>>(char& dst) +{ + checkReadOffset(m_read_offset, 1); + + dst = readU8(&m_data[m_read_offset]); + + m_read_offset += 1; + return *this; +} + +char NetworkPacket::getChar(u32 offset) +{ + checkReadOffset(offset, 1); + + return readU8(&m_data[offset]); +} + +NetworkPacket& NetworkPacket::operator<<(char src) +{ + checkDataSize(1); + + writeU8(&m_data[m_read_offset], src); + + m_read_offset += 1; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(u8 src) +{ + checkDataSize(1); + + writeU8(&m_data[m_read_offset], src); + + m_read_offset += 1; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(bool src) +{ + checkDataSize(1); + + writeU8(&m_data[m_read_offset], src); + + m_read_offset += 1; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(u16 src) +{ + checkDataSize(2); + + writeU16(&m_data[m_read_offset], src); + + m_read_offset += 2; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(u32 src) +{ + checkDataSize(4); + + writeU32(&m_data[m_read_offset], src); + + m_read_offset += 4; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(u64 src) +{ + checkDataSize(8); + + writeU64(&m_data[m_read_offset], src); + + m_read_offset += 8; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(float src) +{ + checkDataSize(4); + + writeF1000(&m_data[m_read_offset], src); + + m_read_offset += 4; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(bool& dst) +{ + checkReadOffset(m_read_offset, 1); + + dst = readU8(&m_data[m_read_offset]); + + m_read_offset += 1; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(u8& dst) +{ + checkReadOffset(m_read_offset, 1); + + dst = readU8(&m_data[m_read_offset]); + + m_read_offset += 1; + return *this; +} + +u8 NetworkPacket::getU8(u32 offset) +{ + checkReadOffset(offset, 1); + + return readU8(&m_data[offset]); +} + +u8* NetworkPacket::getU8Ptr(u32 from_offset) +{ + if (m_datasize == 0) { + return NULL; + } + + checkReadOffset(from_offset, 1); + + return (u8*)&m_data[from_offset]; +} + +NetworkPacket& NetworkPacket::operator>>(u16& dst) +{ + checkReadOffset(m_read_offset, 2); + + dst = readU16(&m_data[m_read_offset]); + + m_read_offset += 2; + return *this; +} + +u16 NetworkPacket::getU16(u32 from_offset) +{ + checkReadOffset(from_offset, 2); + + return readU16(&m_data[from_offset]); +} + +NetworkPacket& NetworkPacket::operator>>(u32& dst) +{ + checkReadOffset(m_read_offset, 4); + + dst = readU32(&m_data[m_read_offset]); + + m_read_offset += 4; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(u64& dst) +{ + checkReadOffset(m_read_offset, 8); + + dst = readU64(&m_data[m_read_offset]); + + m_read_offset += 8; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(float& dst) +{ + checkReadOffset(m_read_offset, 4); + + dst = readF1000(&m_data[m_read_offset]); + + m_read_offset += 4; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(v2f& dst) +{ + checkReadOffset(m_read_offset, 8); + + dst = readV2F1000(&m_data[m_read_offset]); + + m_read_offset += 8; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(v3f& dst) +{ + checkReadOffset(m_read_offset, 12); + + dst = readV3F1000(&m_data[m_read_offset]); + + m_read_offset += 12; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(s16& dst) +{ + checkReadOffset(m_read_offset, 2); + + dst = readS16(&m_data[m_read_offset]); + + m_read_offset += 2; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(s16 src) +{ + *this << (u16) src; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(s32& dst) +{ + checkReadOffset(m_read_offset, 4); + + dst = readS32(&m_data[m_read_offset]); + + m_read_offset += 4; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(s32 src) +{ + *this << (u32) src; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(v3s16& dst) +{ + checkReadOffset(m_read_offset, 6); + + dst = readV3S16(&m_data[m_read_offset]); + + m_read_offset += 6; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(v2s32& dst) +{ + checkReadOffset(m_read_offset, 8); + + dst = readV2S32(&m_data[m_read_offset]); + + m_read_offset += 8; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(v3s32& dst) +{ + checkReadOffset(m_read_offset, 12); + + dst = readV3S32(&m_data[m_read_offset]); + + m_read_offset += 12; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(v2f src) +{ + *this << (float) src.X; + *this << (float) src.Y; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(v3f src) +{ + *this << (float) src.X; + *this << (float) src.Y; + *this << (float) src.Z; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(v3s16 src) +{ + *this << (s16) src.X; + *this << (s16) src.Y; + *this << (s16) src.Z; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(v2s32 src) +{ + *this << (s32) src.X; + *this << (s32) src.Y; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(v3s32 src) +{ + *this << (s32) src.X; + *this << (s32) src.Y; + *this << (s32) src.Z; + return *this; +} + +NetworkPacket& NetworkPacket::operator>>(video::SColor& dst) +{ + checkReadOffset(m_read_offset, 4); + + dst = readARGB8(&m_data[m_read_offset]); + + m_read_offset += 4; + return *this; +} + +NetworkPacket& NetworkPacket::operator<<(video::SColor src) +{ + checkDataSize(4); + + writeU32(&m_data[m_read_offset], src.color); + + m_read_offset += 4; + return *this; +} + +Buffer<u8> NetworkPacket::oldForgePacket() +{ + Buffer<u8> sb(m_datasize + 2); + writeU16(&sb[0], m_command); + + u8* datas = getU8Ptr(0); + + if (datas != NULL) + memcpy(&sb[2], datas, m_datasize); + return sb; +} diff --git a/src/network/networkpacket.h b/src/network/networkpacket.h new file mode 100644 index 000000000..72f8cabe2 --- /dev/null +++ b/src/network/networkpacket.h @@ -0,0 +1,131 @@ +/* +Minetest +Copyright (C) 2015 nerzhul, Loic Blot <loic.blot@unix-experience.fr> + +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. +*/ + +#ifndef NETWORKPACKET_HEADER +#define NETWORKPACKET_HEADER + +#include "util/pointer.h" +#include "util/numeric.h" +#include "networkprotocol.h" + +class NetworkPacket +{ + +public: + NetworkPacket(u16 command, u32 datasize, u16 peer_id); + NetworkPacket(u16 command, u32 datasize); + NetworkPacket(): m_datasize(0), m_read_offset(0), m_command(0), + m_peer_id(0) {} + ~NetworkPacket(); + + void putRawPacket(u8 *data, u32 datasize, u16 peer_id); + + // Getters + u32 getSize() { return m_datasize; } + u16 getPeerId() { return m_peer_id; } + u16 getCommand() { return m_command; } + + // Returns a c-string without copying. + // A better name for this would be getRawString() + char* getString(u32 from_offset); + // major difference to putCString(): doesn't write len into the buffer + void putRawString(const char* src, u32 len); + + NetworkPacket& operator>>(std::string& dst); + NetworkPacket& operator<<(std::string src); + + void putLongString(std::string src); + + NetworkPacket& operator>>(std::wstring& dst); + NetworkPacket& operator<<(std::wstring src); + + std::string readLongString(); + + char getChar(u32 offset); + NetworkPacket& operator>>(char& dst); + NetworkPacket& operator<<(char src); + + NetworkPacket& operator>>(bool& dst); + NetworkPacket& operator<<(bool src); + + u8 getU8(u32 offset); + + NetworkPacket& operator>>(u8& dst); + NetworkPacket& operator<<(u8 src); + + u8* getU8Ptr(u32 offset); + + u16 getU16(u32 from_offset); + NetworkPacket& operator>>(u16& dst); + NetworkPacket& operator<<(u16 src); + + NetworkPacket& operator>>(u32& dst); + NetworkPacket& operator<<(u32 src); + + NetworkPacket& operator>>(u64& dst); + NetworkPacket& operator<<(u64 src); + + NetworkPacket& operator>>(float& dst); + NetworkPacket& operator<<(float src); + + NetworkPacket& operator>>(v2f& dst); + NetworkPacket& operator<<(v2f src); + + NetworkPacket& operator>>(v3f& dst); + NetworkPacket& operator<<(v3f src); + + NetworkPacket& operator>>(s16& dst); + NetworkPacket& operator<<(s16 src); + + NetworkPacket& operator>>(s32& dst); + NetworkPacket& operator<<(s32 src); + + NetworkPacket& operator>>(v2s32& dst); + NetworkPacket& operator<<(v2s32 src); + + NetworkPacket& operator>>(v3s16& dst); + NetworkPacket& operator<<(v3s16 src); + + NetworkPacket& operator>>(v3s32& dst); + NetworkPacket& operator<<(v3s32 src); + + NetworkPacket& operator>>(video::SColor& dst); + NetworkPacket& operator<<(video::SColor src); + + // Temp, we remove SharedBuffer when migration finished + Buffer<u8> oldForgePacket(); +private: + void checkReadOffset(u32 from_offset, u32 field_size); + + inline void checkDataSize(u32 field_size) + { + if (m_read_offset + field_size > m_datasize) { + m_datasize = m_read_offset + field_size; + m_data.resize(m_datasize); + } + } + + std::vector<u8> m_data; + u32 m_datasize; + u32 m_read_offset; + u16 m_command; + u16 m_peer_id; +}; + +#endif diff --git a/src/clientserver.h b/src/network/networkprotocol.h index 5f7864768..82c82f79e 100644 --- a/src/clientserver.h +++ b/src/network/networkprotocol.h @@ -17,8 +17,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef CLIENTSERVER_HEADER -#define CLIENTSERVER_HEADER +#ifndef NETWORKPROTOCOL_HEADER +#define NETWORKPROTOCOL_HEADER #include "util/string.h" /* @@ -108,9 +108,33 @@ with this program; if not, write to the Free Software Foundation, Inc., PROTOCOL_VERSION 24: ContentFeatures version 7 ContentFeatures: change number of special tiles to 6 (CF_SPECIAL_COUNT) + PROTOCOL_VERSION 25: + Rename TOCLIENT_ACCESS_DENIED to TOCLIENT_ACCESS_DENIED_LEGAGY + Rename TOCLIENT_DELETE_PARTICLESPAWNER to + TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY + Rename TOSERVER_PASSWORD to TOSERVER_PASSWORD_LEGACY + Rename TOSERVER_INIT to TOSERVER_INIT_LEGACY + Rename TOCLIENT_INIT to TOCLIENT_INIT_LEGACY + Add TOCLIENT_ACCESS_DENIED new opcode (0x0A), using error codes + for standard error, keeping customisation possible. This + permit translation + Add TOCLIENT_DELETE_PARTICLESPAWNER (0x53), fixing the u16 read and + reading u32 + Add new opcode TOSERVER_INIT for client presentation to server + Add new opcodes TOSERVER_FIRST_SRP, TOSERVER_SRP_BYTES_A, + TOSERVER_SRP_BYTES_M, TOCLIENT_SRP_BYTES_S_B + for the three supported auth mechanisms around srp + Add new opcodes TOCLIENT_ACCEPT_SUDO_MODE and TOCLIENT_DENY_SUDO_MODE + for sudo mode handling (auth mech generic way of changing password). + Add TOCLIENT_HELLO for presenting server to client after client + presentation + Add TOCLIENT_AUTH_ACCEPT to accept connection from client + Rename GENERIC_CMD_SET_ATTACHMENT to GENERIC_CMD_ATTACH_TO + PROTOCOL_VERSION 26: + Add TileDef tileable_horizontal, tileable_vertical flags */ -#define LATEST_PROTOCOL_VERSION 24 +#define LATEST_PROTOCOL_VERSION 26 // Server's supported network protocol range #define SERVER_PROTOCOL_VERSION_MIN 13 @@ -133,7 +157,35 @@ with this program; if not, write to the Free Software Foundation, Inc., enum ToClientCommand { - TOCLIENT_INIT = 0x10, + TOCLIENT_HELLO = 0x02, + /* + Sent after TOSERVER_INIT. + + u8 deployed serialisation version + u16 deployed network compression mode + u16 deployed protocol version + u32 supported auth methods + std::string username that should be used for legacy hash (for proper casing) + */ + TOCLIENT_AUTH_ACCEPT = 0x03, + /* + Message from server to accept auth. + + v3s16 player's position + v3f(0,BS/2,0) floatToInt'd + u64 map seed + f1000 recommended send interval + u32 : supported auth methods for sudo mode + (where the user can change their password) + */ + TOCLIENT_ACCEPT_SUDO_MODE = 0x04, + /* + Sent to client to show it is in sudo mode now. + */ + TOCLIENT_DENY_SUDO_MODE = 0x05, + /* + Signals client that sudo mode auth failed. + */ + TOCLIENT_INIT_LEGACY = 0x10, /* Server's reply to TOSERVER_INIT. Sent second after connected. @@ -147,7 +199,12 @@ enum ToClientCommand NOTE: The position in here is deprecated; position is explicitly sent afterwards */ - + TOCLIENT_ACCESS_DENIED = 0x0A, + /* + u8 reason + std::string custom reason (if needed, otherwise "") + u8 (bool) reconnect + */ TOCLIENT_BLOCKDATA = 0x20, //TODO: Multiple blocks TOCLIENT_ADDNODE = 0x21, /* @@ -270,7 +327,7 @@ enum ToClientCommand f1000 player yaw */ - TOCLIENT_ACCESS_DENIED = 0x35, + TOCLIENT_ACCESS_DENIED_LEGACY = 0x35, /* u16 command u16 reason_length @@ -457,10 +514,10 @@ enum ToClientCommand u32 id */ - TOCLIENT_DELETE_PARTICLESPAWNER = 0x48, + TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY = 0x48, /* u16 command - u32 id + u16 id */ TOCLIENT_HUDADD = 0x49, @@ -556,15 +613,43 @@ enum ToClientCommand v3f1000 first v3f1000 third */ + + TOCLIENT_DELETE_PARTICLESPAWNER = 0x53, + /* + u16 command + u32 id + */ + + TOCLIENT_SRP_BYTES_S_B = 0x60, + /* + Belonging to AUTH_MECHANISM_LEGACY_PASSWORD and AUTH_MECHANISM_SRP. + + u16 command + std::string bytes_s + std::string bytes_B + */ + + TOCLIENT_NUM_MSG_TYPES = 0x61, }; enum ToServerCommand { - TOSERVER_INIT=0x10, + TOSERVER_INIT = 0x02, /* Sent first after connected. - [0] u16 TOSERVER_INIT + u8 serialisation version (=SER_FMT_VER_HIGHEST_READ) + u16 supported network compression modes + u16 minimum supported network protocol version + u16 maximum supported network protocol version + std::string player name + */ + + TOSERVER_INIT_LEGACY = 0x10, + /* + Sent first after connected. + + [0] u16 TOSERVER_INIT_LEGACY [2] u8 SER_FMT_VER_HIGHEST_READ [3] u8[20] player_name [23] u8[28] password (new in some version) @@ -659,7 +744,7 @@ enum ToServerCommand TOSERVER_INVENTORY_ACTION = 0x31, /* - See InventoryAction in inventory.h + See InventoryAction in inventorymanager.h */ TOSERVER_CHAT_MESSAGE = 0x32, @@ -692,7 +777,7 @@ enum ToServerCommand u8 amount */ - TOSERVER_PASSWORD=0x36, + TOSERVER_PASSWORD_LEGACY = 0x36, /* Sent to change password. @@ -701,7 +786,7 @@ enum ToServerCommand [30] u8[28] new password */ - TOSERVER_PLAYERITEM=0x37, + TOSERVER_PLAYERITEM = 0x37, /* Sent to change selected item. @@ -709,7 +794,7 @@ enum ToServerCommand [2] u16 item */ - TOSERVER_RESPAWN=0x38, + TOSERVER_RESPAWN = 0x38, /* u16 TOSERVER_RESPAWN */ @@ -795,7 +880,87 @@ enum ToServerCommand u16 len u8[len] full_version_string */ + + TOSERVER_FIRST_SRP = 0x50, + /* + Belonging to AUTH_MECHANISM_FIRST_SRP. + + std::string srp salt + std::string srp verification key + u8 is_empty (=1 if password is empty, 0 otherwise) + */ + + TOSERVER_SRP_BYTES_A = 0x51, + /* + Belonging to AUTH_MECHANISM_LEGACY_PASSWORD and AUTH_MECHANISM_SRP, + depending on current_login_based_on. + + std::string bytes_A + u8 current_login_based_on : on which version of the password's + hash this login is based on (0 legacy hash, + or 1 directly the password) + */ + + TOSERVER_SRP_BYTES_M = 0x52, + /* + Belonging to AUTH_MECHANISM_LEGACY_PASSWORD and AUTH_MECHANISM_SRP. + + std::string bytes_M + */ + + TOSERVER_NUM_MSG_TYPES = 0x53, }; -#endif +enum AuthMechanism +{ + // reserved + AUTH_MECHANISM_NONE = 0, + + // SRP based on the legacy hash + AUTH_MECHANISM_LEGACY_PASSWORD = 1 << 0, + + // SRP based on the srp verification key + AUTH_MECHANISM_SRP = 1 << 1, + + // Establishes a srp verification key, for first login and password changing + AUTH_MECHANISM_FIRST_SRP = 1 << 2, +}; +enum AccessDeniedCode { + SERVER_ACCESSDENIED_WRONG_PASSWORD, + SERVER_ACCESSDENIED_UNEXPECTED_DATA, + SERVER_ACCESSDENIED_SINGLEPLAYER, + SERVER_ACCESSDENIED_WRONG_VERSION, + SERVER_ACCESSDENIED_WRONG_CHARS_IN_NAME, + SERVER_ACCESSDENIED_WRONG_NAME, + SERVER_ACCESSDENIED_TOO_MANY_USERS, + SERVER_ACCESSDENIED_EMPTY_PASSWORD, + SERVER_ACCESSDENIED_ALREADY_CONNECTED, + SERVER_ACCESSDENIED_SERVER_FAIL, + SERVER_ACCESSDENIED_CUSTOM_STRING, + SERVER_ACCESSDENIED_SHUTDOWN, + SERVER_ACCESSDENIED_CRASH, + SERVER_ACCESSDENIED_MAX, +}; + +enum NetProtoCompressionMode { + NETPROTO_COMPRESSION_NONE = 0, +}; + +const static std::string accessDeniedStrings[SERVER_ACCESSDENIED_MAX] = { + "Invalid password", + "Your client sent something the server didn't expect. Try reconnecting or updating your client", + "The server is running in simple singleplayer mode. You cannot connect.", + "Your client's version is not supported.\nPlease contact server administrator.", + "Player name contains disallowed characters.", + "Player name not allowed.", + "Too many users.", + "Empty passwords are disallowed. Set a password and try again.", + "Another client is connected with this name. If your client closed unexpectedly, try again in a minute.", + "Server authentication failed. This is likely a server error.", + "", + "Server shutting down.", + "This server has experienced an internal error. You will now be disconnected." +}; + +#endif diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp new file mode 100644 index 000000000..9b14a1be3 --- /dev/null +++ b/src/network/serveropcodes.cpp @@ -0,0 +1,213 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2015 nerzhul, Loic Blot <loic.blot@unix-experience.fr> + +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 "serveropcodes.h" + +const static ToServerCommandHandler null_command_handler = { "TOSERVER_NULL", TOSERVER_STATE_ALL, &Server::handleCommand_Null }; + +const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] = +{ + null_command_handler, // 0x00 (never use this) + null_command_handler, // 0x01 + { "TOSERVER_INIT", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_Init }, // 0x02 + null_command_handler, // 0x03 + null_command_handler, // 0x04 + null_command_handler, // 0x05 + null_command_handler, // 0x06 + null_command_handler, // 0x07 + null_command_handler, // 0x08 + null_command_handler, // 0x09 + null_command_handler, // 0x0a + null_command_handler, // 0x0b + null_command_handler, // 0x0c + null_command_handler, // 0x0d + null_command_handler, // 0x0e + null_command_handler, // 0x0f + { "TOSERVER_INIT_LEGACY", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_Init_Legacy }, // 0x10 + { "TOSERVER_INIT2", TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_Init2 }, // 0x11 + null_command_handler, // 0x12 + null_command_handler, // 0x13 + null_command_handler, // 0x14 + null_command_handler, // 0x15 + null_command_handler, // 0x16 + null_command_handler, // 0x17 + null_command_handler, // 0x18 + null_command_handler, // 0x19 + null_command_handler, // 0x1a + null_command_handler, // 0x1b + null_command_handler, // 0x1c + null_command_handler, // 0x1d + null_command_handler, // 0x1e + null_command_handler, // 0x1f + null_command_handler, // 0x20 + null_command_handler, // 0x21 + null_command_handler, // 0x22 + { "TOSERVER_PLAYERPOS", TOSERVER_STATE_INGAME, &Server::handleCommand_PlayerPos }, // 0x23 + { "TOSERVER_GOTBLOCKS", TOSERVER_STATE_STARTUP, &Server::handleCommand_GotBlocks }, // 0x24 + { "TOSERVER_DELETEDBLOCKS", TOSERVER_STATE_INGAME, &Server::handleCommand_DeletedBlocks }, // 0x25 + null_command_handler, // 0x26 + { "TOSERVER_CLICK_OBJECT", TOSERVER_STATE_INGAME, &Server::handleCommand_Deprecated }, // 0x27 + { "TOSERVER_GROUND_ACTION", TOSERVER_STATE_INGAME, &Server::handleCommand_Deprecated }, // 0x28 + { "TOSERVER_RELEASE", TOSERVER_STATE_INGAME, &Server::handleCommand_Deprecated }, // 0x29 + null_command_handler, // 0x2a + null_command_handler, // 0x2b + null_command_handler, // 0x2c + null_command_handler, // 0x2d + null_command_handler, // 0x2e + null_command_handler, // 0x2f + { "TOSERVER_SIGNTEXT", TOSERVER_STATE_INGAME, &Server::handleCommand_Deprecated }, // 0x30 + { "TOSERVER_INVENTORY_ACTION", TOSERVER_STATE_INGAME, &Server::handleCommand_InventoryAction }, // 0x31 + { "TOSERVER_CHAT_MESSAGE", TOSERVER_STATE_INGAME, &Server::handleCommand_ChatMessage }, // 0x32 + { "TOSERVER_SIGNNODETEXT", TOSERVER_STATE_INGAME, &Server::handleCommand_Deprecated }, // 0x33 + { "TOSERVER_CLICK_ACTIVEOBJECT", TOSERVER_STATE_INGAME, &Server::handleCommand_Deprecated }, // 0x34 + { "TOSERVER_DAMAGE", TOSERVER_STATE_INGAME, &Server::handleCommand_Damage }, // 0x35 + { "TOSERVER_PASSWORD_LEGACY", TOSERVER_STATE_INGAME, &Server::handleCommand_Password }, // 0x36 + { "TOSERVER_PLAYERITEM", TOSERVER_STATE_INGAME, &Server::handleCommand_PlayerItem }, // 0x37 + { "TOSERVER_RESPAWN", TOSERVER_STATE_INGAME, &Server::handleCommand_Respawn }, // 0x38 + { "TOSERVER_INTERACT", TOSERVER_STATE_INGAME, &Server::handleCommand_Interact }, // 0x39 + { "TOSERVER_REMOVED_SOUNDS", TOSERVER_STATE_INGAME, &Server::handleCommand_RemovedSounds }, // 0x3a + { "TOSERVER_NODEMETA_FIELDS", TOSERVER_STATE_INGAME, &Server::handleCommand_NodeMetaFields }, // 0x3b + { "TOSERVER_INVENTORY_FIELDS", TOSERVER_STATE_INGAME, &Server::handleCommand_InventoryFields }, // 0x3c + null_command_handler, // 0x3d + null_command_handler, // 0x3e + null_command_handler, // 0x3f + { "TOSERVER_REQUEST_MEDIA", TOSERVER_STATE_STARTUP, &Server::handleCommand_RequestMedia }, // 0x40 + { "TOSERVER_RECEIVED_MEDIA", TOSERVER_STATE_STARTUP, &Server::handleCommand_ReceivedMedia }, // 0x41 + { "TOSERVER_BREATH", TOSERVER_STATE_INGAME, &Server::handleCommand_Breath }, // 0x42 + { "TOSERVER_CLIENT_READY", TOSERVER_STATE_STARTUP, &Server::handleCommand_ClientReady }, // 0x43 + null_command_handler, // 0x44 + null_command_handler, // 0x45 + null_command_handler, // 0x46 + null_command_handler, // 0x47 + null_command_handler, // 0x48 + null_command_handler, // 0x49 + null_command_handler, // 0x4a + null_command_handler, // 0x4b + null_command_handler, // 0x4c + 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 +}; + +const static ClientCommandFactory null_command_factory = { "TOCLIENT_NULL", 0, false }; + +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 + null_command_factory, // 0x06 + null_command_factory, // 0x07 + null_command_factory, // 0x08 + null_command_factory, // 0x09 + { "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_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, + { "TOCLIENT_INVENTORY", 0, true }, // 0x27 + null_command_factory, + { "TOCLIENT_TIME_OF_DAY", 0, true }, // 0x29 + null_command_factory, + null_command_factory, + null_command_factory, + null_command_factory, + null_command_factory, + null_command_factory, + { "TOCLIENT_CHAT_MESSAGE", 0, true }, // 0x30 + { "TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD", 0, true }, // 0x31 + { "TOCLIENT_ACTIVE_OBJECT_MESSAGES", 0, true }, // 0x32 Special packet, sent by 0 (rel) and 1 (unrel) channel + { "TOCLIENT_HP", 0, true }, // 0x33 + { "TOCLIENT_MOVE_PLAYER", 0, true }, // 0x34 + { "TOCLIENT_ACCESS_DENIED_LEGACY", 0, true }, // 0x35 + { "TOCLIENT_PLAYERITEM", 0, false }, // 0x36 obsolete + { "TOCLIENT_DEATHSCREEN", 0, true }, // 0x37 + { "TOCLIENT_MEDIA", 2, true }, // 0x38 + { "TOCLIENT_TOOLDEF", 0, false }, // 0x39 obsolete + { "TOCLIENT_NODEDEF", 0, true }, // 0x3a + { "TOCLIENT_CRAFTITEMDEF", 0, false }, // 0x3b obsolete + { "TOCLIENT_ANNOUNCE_MEDIA", 0, true }, // 0x3c + { "TOCLIENT_ITEMDEF", 0, true }, // 0x3d + null_command_factory, + { "TOCLIENT_PLAY_SOUND", 0, true }, // 0x3f + { "TOCLIENT_STOP_SOUND", 0, true }, // 0x40 + { "TOCLIENT_PRIVILEGES", 0, true }, // 0x41 + { "TOCLIENT_INVENTORY_FORMSPEC", 0, true }, // 0x42 + { "TOCLIENT_DETACHED_INVENTORY", 0, true }, // 0x43 + { "TOCLIENT_SHOW_FORMSPEC", 0, true }, // 0x44 + { "TOCLIENT_MOVEMENT", 0, true }, // 0x45 + { "TOCLIENT_SPAWN_PARTICLE", 0, true }, // 0x46 + { "TOCLIENT_ADD_PARTICLESPAWNER", 0, true }, // 0x47 + { "TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY", 0, true }, // 0x48 + { "TOCLIENT_HUDADD", 1, true }, // 0x49 + { "TOCLIENT_HUDRM", 1, true }, // 0x4a + { "TOCLIENT_HUDCHANGE", 0, true }, // 0x4b + { "TOCLIENT_HUD_SET_FLAGS", 0, true }, // 0x4c + { "TOCLIENT_HUD_SET_PARAM", 0, true }, // 0x4d + { "TOCLIENT_BREATH", 0, true }, // 0x4e + { "TOCLIENT_SET_SKY", 0, true }, // 0x4f + { "TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO", 0, true }, // 0x50 + { "TOCLIENT_LOCAL_PLAYER_ANIMATIONS", 0, true }, // 0x51 + { "TOCLIENT_EYE_OFFSET", 0, true }, // 0x52 + { "TOCLIENT_DELETE_PARTICLESPAWNER", 0, true }, // 0x53 + 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, + { "TOSERVER_SRP_BYTES_S_B", 0, true }, // 0x60 +}; diff --git a/src/network/serveropcodes.h b/src/network/serveropcodes.h new file mode 100644 index 000000000..aa3301069 --- /dev/null +++ b/src/network/serveropcodes.h @@ -0,0 +1,52 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2015 nerzhul, Loic Blot <loic.blot@unix-experience.fr> + +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. +*/ + +#ifndef SERVEROPCODES_HEADER +#define SERVEROPCODES_HEADER + +#include "server.h" +#include "networkprotocol.h" +#include "networkpacket.h" + +enum ToServerConnectionState { + TOSERVER_STATE_NOT_CONNECTED, + TOSERVER_STATE_STARTUP, + TOSERVER_STATE_INGAME, + TOSERVER_STATE_ALL, +}; +struct ToServerCommandHandler +{ + const std::string name; + ToServerConnectionState state; + void (Server::*handler)(NetworkPacket* pkt); +}; + +struct ClientCommandFactory +{ + const char* name; + u16 channel; + bool reliable; +}; + +extern const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES]; + +extern const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES]; + +#endif diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp new file mode 100644 index 000000000..f756d80ef --- /dev/null +++ b/src/network/serverpackethandler.cpp @@ -0,0 +1,2086 @@ +/* +Minetest +Copyright (C) 2015 nerzhul, Loic Blot <loic.blot@unix-experience.fr> + +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 "server.h" +#include "log.h" + +#include "content_abm.h" +#include "content_sao.h" +#include "emerge.h" +#include "nodedef.h" +#include "player.h" +#include "rollback_interface.h" +#include "scripting_game.h" +#include "settings.h" +#include "tool.h" +#include "version.h" +#include "network/networkprotocol.h" +#include "network/serveropcodes.h" +#include "util/auth.h" +#include "util/base64.h" +#include "util/pointedthing.h" +#include "util/serialize.h" +#include "util/srp.h" + +void Server::handleCommand_Deprecated(NetworkPacket* pkt) +{ + infostream << "Server: " << toServerCommandTable[pkt->getCommand()].name + << " not supported anymore" << std::endl; +} + +void Server::handleCommand_Init(NetworkPacket* pkt) +{ + + if(pkt->getSize() < 1) + return; + + RemoteClient* client = getClient(pkt->getPeerId(), CS_Created); + + std::string addr_s; + try { + Address address = getPeerAddress(pkt->getPeerId()); + addr_s = address.serializeString(); + } + catch (con::PeerNotFoundException &e) { + /* + * no peer for this packet found + * most common reason is peer timeout, e.g. peer didn't + * respond for some time, your server was overloaded or + * things like that. + */ + infostream << "Server::ProcessData(): Canceling: peer " + << pkt->getPeerId() << " not found" << std::endl; + return; + } + + // If net_proto_version is set, this client has already been handled + if (client->getState() > CS_Created) { + verbosestream << "Server: Ignoring multiple TOSERVER_INITs from " + << addr_s << " (peer_id=" << pkt->getPeerId() << ")" << std::endl; + return; + } + + verbosestream << "Server: Got TOSERVER_INIT from " << addr_s << " (peer_id=" + << pkt->getPeerId() << ")" << std::endl; + + // Do not allow multiple players in simple singleplayer mode. + // This isn't a perfect way to do it, but will suffice for now + if (m_simple_singleplayer_mode && m_clients.getClientIDs().size() > 1) { + infostream << "Server: Not allowing another client (" << addr_s + << ") to connect in simple singleplayer mode" << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_SINGLEPLAYER); + return; + } + + // First byte after command is maximum supported + // serialization version + u8 client_max; + u16 supp_compr_modes; + u16 min_net_proto_version = 0; + u16 max_net_proto_version; + std::string playerName; + + *pkt >> client_max >> supp_compr_modes >> min_net_proto_version + >> max_net_proto_version >> playerName; + + u8 our_max = SER_FMT_VER_HIGHEST_READ; + // Use the highest version supported by both + u8 depl_serial_v = std::min(client_max, our_max); + // If it's lower than the lowest supported, give up. + if (depl_serial_v < SER_FMT_VER_LOWEST) + depl_serial_v = SER_FMT_VER_INVALID; + + if (depl_serial_v == SER_FMT_VER_INVALID) { + actionstream << "Server: A mismatched client tried to connect from " + << addr_s << std::endl; + infostream<<"Server: Cannot negotiate serialization version with " + << addr_s << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_VERSION); + return; + } + + client->setPendingSerializationVersion(depl_serial_v); + + /* + Read and check network protocol version + */ + + u16 net_proto_version = 0; + + // Figure out a working version if it is possible at all + if (max_net_proto_version >= SERVER_PROTOCOL_VERSION_MIN || + min_net_proto_version <= SERVER_PROTOCOL_VERSION_MAX) { + // If maximum is larger than our maximum, go with our maximum + if (max_net_proto_version > SERVER_PROTOCOL_VERSION_MAX) + net_proto_version = SERVER_PROTOCOL_VERSION_MAX; + // Else go with client's maximum + else + net_proto_version = max_net_proto_version; + } + + verbosestream << "Server: " << addr_s << ": Protocol version: min: " + << min_net_proto_version << ", max: " << max_net_proto_version + << ", chosen: " << net_proto_version << std::endl; + + client->net_proto_version = net_proto_version; + + // On this handler at least protocol version 25 is required + if (net_proto_version < 25 || + net_proto_version < SERVER_PROTOCOL_VERSION_MIN || + net_proto_version > SERVER_PROTOCOL_VERSION_MAX) { + actionstream << "Server: A mismatched client tried to connect from " + << addr_s << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_VERSION); + return; + } + + if (g_settings->getBool("strict_protocol_version_checking")) { + if (net_proto_version != LATEST_PROTOCOL_VERSION) { + actionstream << "Server: A mismatched (strict) client tried to " + << "connect from " << addr_s << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_VERSION); + return; + } + } + + /* + Validate player name + */ + const char* playername = playerName.c_str(); + + size_t pns = playerName.size(); + if (pns == 0 || pns > PLAYERNAME_SIZE) { + actionstream << "Server: Player with " + << ((pns > PLAYERNAME_SIZE) ? "a too long" : "an empty") + << " name tried to connect from " << addr_s << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_NAME); + return; + } + + if (string_allowed(playerName, PLAYERNAME_ALLOWED_CHARS) == false) { + actionstream << "Server: Player with an invalid name " + << "tried to connect from " << addr_s << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_CHARS_IN_NAME); + return; + } + + m_clients.setPlayerName(pkt->getPeerId(), playername); + //TODO (later) case insensitivity + + std::string legacyPlayerNameCasing = playerName; + + if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) { + actionstream << "Server: Player with the name \"singleplayer\" " + << "tried to connect from " << addr_s << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_NAME); + return; + } + + { + std::string reason; + if (m_script->on_prejoinplayer(playername, addr_s, &reason)) { + actionstream << "Server: Player with the name \"" << playerName << "\" " + << "tried to connect from " << addr_s << " " + << "but it was disallowed for the following reason: " + << reason << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_CUSTOM_STRING, + reason.c_str()); + return; + } + } + + infostream << "Server: New connection: \"" << playerName << "\" from " + << addr_s << " (peer_id=" << pkt->getPeerId() << ")" << std::endl; + + // Enforce user limit. + // Don't enforce for users that have some admin right + if (m_clients.getClientIDs(CS_Created).size() >= g_settings->getU16("max_users") && + !checkPriv(playername, "server") && + !checkPriv(playername, "ban") && + !checkPriv(playername, "privs") && + !checkPriv(playername, "password") && + playername != g_settings->get("name")) { + actionstream << "Server: " << playername << " tried to join from " + << addr_s << ", but there" << " are already max_users=" + << g_settings->getU16("max_users") << " players." << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_TOO_MANY_USERS); + return; + } + + /* + Compose auth methods for answer + */ + std::string encpwd; // encrypted Password field for the user + bool has_auth = m_script->getAuth(playername, &encpwd, NULL); + u32 auth_mechs = 0; + + client->chosen_mech = AUTH_MECHANISM_NONE; + + if (has_auth) { + std::vector<std::string> pwd_components = str_split(encpwd, '#'); + if (pwd_components.size() == 4) { + if (pwd_components[1] == "1") { // 1 means srp + auth_mechs |= AUTH_MECHANISM_SRP; + client->enc_pwd = encpwd; + } else { + actionstream << "User " << playername + << " tried to log in, but password field" + << " was invalid (unknown mechcode)." << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_SERVER_FAIL); + return; + } + } else if (base64_is_valid(encpwd)) { + auth_mechs |= AUTH_MECHANISM_LEGACY_PASSWORD; + client->enc_pwd = encpwd; + } else { + actionstream << "User " << playername + << " tried to log in, but password field" + << " was invalid (invalid base64)." << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_SERVER_FAIL); + return; + } + } else { + std::string default_password = g_settings->get("default_password"); + if (default_password.length() == 0) { + auth_mechs |= AUTH_MECHANISM_FIRST_SRP; + } else { + // Take care of default passwords. + client->enc_pwd = getSRPVerifier(playerName, default_password); + auth_mechs |= AUTH_MECHANISM_SRP; + // Create auth, but only on successful login + client->create_player_on_auth_success = true; + } + } + + /* + Answer with a TOCLIENT_HELLO + */ + + verbosestream << "Sending TOCLIENT_HELLO with auth method field: " + << auth_mechs << std::endl; + + NetworkPacket resp_pkt(TOCLIENT_HELLO, 1 + 4 + + legacyPlayerNameCasing.size(), pkt->getPeerId()); + + u16 depl_compress_mode = NETPROTO_COMPRESSION_NONE; + resp_pkt << depl_serial_v << depl_compress_mode << net_proto_version + << auth_mechs << legacyPlayerNameCasing; + + Send(&resp_pkt); + + client->allowed_auth_mechs = auth_mechs; + client->setDeployedCompressionMode(depl_compress_mode); + + m_clients.event(pkt->getPeerId(), CSE_Hello); +} + +void Server::handleCommand_Init_Legacy(NetworkPacket* pkt) +{ + // [0] u8 SER_FMT_VER_HIGHEST_READ + // [1] u8[20] player_name + // [21] u8[28] password <--- can be sent without this, from old versions + + if (pkt->getSize() < 1+PLAYERNAME_SIZE) + return; + + RemoteClient* client = getClient(pkt->getPeerId(), CS_Created); + + std::string addr_s; + try { + Address address = getPeerAddress(pkt->getPeerId()); + addr_s = address.serializeString(); + } + catch (con::PeerNotFoundException &e) { + /* + * no peer for this packet found + * most common reason is peer timeout, e.g. peer didn't + * respond for some time, your server was overloaded or + * things like that. + */ + infostream << "Server::ProcessData(): Canceling: peer " + << pkt->getPeerId() << " not found" << std::endl; + return; + } + + // If net_proto_version is set, this client has already been handled + if (client->getState() > CS_Created) { + verbosestream << "Server: Ignoring multiple TOSERVER_INITs from " + << addr_s << " (peer_id=" << pkt->getPeerId() << ")" << std::endl; + return; + } + + verbosestream << "Server: Got TOSERVER_INIT_LEGACY from " << addr_s << " (peer_id=" + << pkt->getPeerId() << ")" << std::endl; + + // Do not allow multiple players in simple singleplayer mode. + // This isn't a perfect way to do it, but will suffice for now + if (m_simple_singleplayer_mode && m_clients.getClientIDs().size() > 1) { + infostream << "Server: Not allowing another client (" << addr_s + << ") to connect in simple singleplayer mode" << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Running in simple singleplayer mode."); + return; + } + + // First byte after command is maximum supported + // serialization version + u8 client_max; + + *pkt >> client_max; + + u8 our_max = SER_FMT_VER_HIGHEST_READ; + // Use the highest version supported by both + int deployed = std::min(client_max, our_max); + // If it's lower than the lowest supported, give up. + if (deployed < SER_FMT_VER_LOWEST) + deployed = SER_FMT_VER_INVALID; + + if (deployed == SER_FMT_VER_INVALID) { + actionstream << "Server: A mismatched client tried to connect from " + << addr_s << std::endl; + infostream<<"Server: Cannot negotiate serialization version with " + << addr_s << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), std::wstring( + L"Your client's version is not supported.\n" + L"Server version is ") + + utf8_to_wide(g_version_string) + L"." + ); + return; + } + + client->setPendingSerializationVersion(deployed); + + /* + Read and check network protocol version + */ + + u16 min_net_proto_version = 0; + if (pkt->getSize() >= 1 + PLAYERNAME_SIZE + PASSWORD_SIZE + 2) + min_net_proto_version = pkt->getU16(1 + PLAYERNAME_SIZE + PASSWORD_SIZE); + + // Use same version as minimum and maximum if maximum version field + // doesn't exist (backwards compatibility) + u16 max_net_proto_version = min_net_proto_version; + if (pkt->getSize() >= 1 + PLAYERNAME_SIZE + PASSWORD_SIZE + 2 + 2) + max_net_proto_version = pkt->getU16(1 + PLAYERNAME_SIZE + PASSWORD_SIZE + 2); + + // Start with client's maximum version + u16 net_proto_version = max_net_proto_version; + + // Figure out a working version if it is possible at all + if (max_net_proto_version >= SERVER_PROTOCOL_VERSION_MIN || + min_net_proto_version <= SERVER_PROTOCOL_VERSION_MAX) { + // If maximum is larger than our maximum, go with our maximum + if (max_net_proto_version > SERVER_PROTOCOL_VERSION_MAX) + net_proto_version = SERVER_PROTOCOL_VERSION_MAX; + // Else go with client's maximum + else + net_proto_version = max_net_proto_version; + } + + // The client will send up to date init packet, ignore this one + if (net_proto_version >= 25) + return; + + verbosestream << "Server: " << addr_s << ": Protocol version: min: " + << min_net_proto_version << ", max: " << max_net_proto_version + << ", chosen: " << net_proto_version << std::endl; + + client->net_proto_version = net_proto_version; + + if (net_proto_version < SERVER_PROTOCOL_VERSION_MIN || + net_proto_version > SERVER_PROTOCOL_VERSION_MAX) { + actionstream << "Server: A mismatched client tried to connect from " + << addr_s << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), std::wstring( + L"Your client's version is not supported.\n" + L"Server version is ") + + utf8_to_wide(g_version_string) + L",\n" + + L"server's PROTOCOL_VERSION is " + + utf8_to_wide(itos(SERVER_PROTOCOL_VERSION_MIN)) + + L"..." + + utf8_to_wide(itos(SERVER_PROTOCOL_VERSION_MAX)) + + L", client's PROTOCOL_VERSION is " + + utf8_to_wide(itos(min_net_proto_version)) + + L"..." + + utf8_to_wide(itos(max_net_proto_version)) + ); + return; + } + + if (g_settings->getBool("strict_protocol_version_checking")) { + if (net_proto_version != LATEST_PROTOCOL_VERSION) { + actionstream << "Server: A mismatched (strict) client tried to " + << "connect from " << addr_s << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), std::wstring( + L"Your client's version is not supported.\n" + L"Server version is ") + + utf8_to_wide(g_version_string) + L",\n" + + L"server's PROTOCOL_VERSION (strict) is " + + utf8_to_wide(itos(LATEST_PROTOCOL_VERSION)) + + L", client's PROTOCOL_VERSION is " + + utf8_to_wide(itos(min_net_proto_version)) + + L"..." + + utf8_to_wide(itos(max_net_proto_version)) + ); + return; + } + } + + /* + Set up player + */ + char playername[PLAYERNAME_SIZE]; + unsigned int playername_length = 0; + for (; playername_length < PLAYERNAME_SIZE; playername_length++ ) { + playername[playername_length] = pkt->getChar(1+playername_length); + if (pkt->getChar(1+playername_length) == 0) + break; + } + + if (playername_length == PLAYERNAME_SIZE) { + actionstream << "Server: Player with name exceeding max length " + << "tried to connect from " << addr_s << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Name too long"); + return; + } + + + if (playername[0]=='\0') { + actionstream << "Server: Player with an empty name " + << "tried to connect from " << addr_s << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Empty name"); + return; + } + + if (string_allowed(playername, PLAYERNAME_ALLOWED_CHARS) == false) { + actionstream << "Server: Player with an invalid name " + << "tried to connect from " << addr_s << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Name contains unallowed characters"); + return; + } + + if (!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) { + actionstream << "Server: Player with the name \"singleplayer\" " + << "tried to connect from " << addr_s << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Name is not allowed"); + return; + } + + { + std::string reason; + if (m_script->on_prejoinplayer(playername, addr_s, &reason)) { + actionstream << "Server: Player with the name \"" << playername << "\" " + << "tried to connect from " << addr_s << " " + << "but it was disallowed for the following reason: " + << reason << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), utf8_to_wide(reason.c_str())); + return; + } + } + + infostream<<"Server: New connection: \""<<playername<<"\" from " + <<addr_s<<" (peer_id="<<pkt->getPeerId()<<")"<<std::endl; + + // Get password + char given_password[PASSWORD_SIZE]; + if (pkt->getSize() < 1 + PLAYERNAME_SIZE + PASSWORD_SIZE) { + // old version - assume blank password + given_password[0] = 0; + } + else { + for (u16 i = 0; i < PASSWORD_SIZE - 1; i++) { + given_password[i] = pkt->getChar(21 + i); + } + given_password[PASSWORD_SIZE - 1] = 0; + } + + if (!base64_is_valid(given_password)) { + actionstream << "Server: " << playername + << " supplied invalid password hash" << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Invalid password hash"); + return; + } + + // Enforce user limit. + // Don't enforce for users that have some admin right + if (m_clients.getClientIDs(CS_Created).size() >= g_settings->getU16("max_users") && + !checkPriv(playername, "server") && + !checkPriv(playername, "ban") && + !checkPriv(playername, "privs") && + !checkPriv(playername, "password") && + playername != g_settings->get("name")) { + actionstream << "Server: " << playername << " tried to join, but there" + << " are already max_users=" + << g_settings->getU16("max_users") << " players." << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Too many users."); + return; + } + + std::string checkpwd; // Password hash to check against + bool has_auth = m_script->getAuth(playername, &checkpwd, NULL); + + // If no authentication info exists for user, create it + if (!has_auth) { + if (!isSingleplayer() && + g_settings->getBool("disallow_empty_password") && + std::string(given_password) == "") { + actionstream << "Server: " << playername + << " supplied empty password" << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Empty passwords are " + L"disallowed. Set a password and try again."); + return; + } + std::string raw_default_password = + g_settings->get("default_password"); + std::string initial_password = + translatePassword(playername, raw_default_password); + + // If default_password is empty, allow any initial password + if (raw_default_password.length() == 0) + initial_password = given_password; + + m_script->createAuth(playername, initial_password); + } + + has_auth = m_script->getAuth(playername, &checkpwd, NULL); + + if (!has_auth) { + actionstream << "Server: " << playername << " cannot be authenticated" + << " (auth handler does not work?)" << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Not allowed to login"); + return; + } + + if (given_password != checkpwd) { + actionstream << "Server: " << playername << " supplied wrong password" + << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Wrong password"); + return; + } + + RemotePlayer *player = + static_cast<RemotePlayer*>(m_env->getPlayer(playername)); + + if (player && player->peer_id != 0) { + actionstream << "Server: " << playername << ": Failed to emerge player" + << " (player allocated to an another client)" << std::endl; + DenyAccess_Legacy(pkt->getPeerId(), L"Another client is connected with this " + L"name. If your client closed unexpectedly, try again in " + L"a minute."); + } + + m_clients.setPlayerName(pkt->getPeerId(), playername); + + /* + Answer with a TOCLIENT_INIT + */ + + NetworkPacket resp_pkt(TOCLIENT_INIT_LEGACY, 1 + 6 + 8 + 4, + pkt->getPeerId()); + + resp_pkt << (u8) deployed << (v3s16) floatToInt(v3f(0,0,0), BS) + << (u64) m_env->getServerMap().getSeed() + << g_settings->getFloat("dedicated_server_step"); + + Send(&resp_pkt); + m_clients.event(pkt->getPeerId(), CSE_InitLegacy); +} + +void Server::handleCommand_Init2(NetworkPacket* pkt) +{ + verbosestream << "Server: Got TOSERVER_INIT2 from " + << pkt->getPeerId() << std::endl; + + m_clients.event(pkt->getPeerId(), CSE_GotInit2); + u16 protocol_version = m_clients.getProtocolVersion(pkt->getPeerId()); + + + ///// begin compatibility code + PlayerSAO* playersao = NULL; + if (protocol_version <= 22) { + playersao = StageTwoClientInit(pkt->getPeerId()); + + if (playersao == NULL) { + actionstream + << "TOSERVER_INIT2 stage 2 client init failed for peer " + << pkt->getPeerId() << std::endl; + return; + } + } + ///// end compatibility code + + /* + Send some initialization data + */ + + 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); + + // Send node definitions + SendNodeDef(pkt->getPeerId(), m_nodedef, protocol_version); + + m_clients.event(pkt->getPeerId(), CSE_SetDefinitionsSent); + + // Send media announcement + sendMediaAnnouncement(pkt->getPeerId()); + + // Send detached inventories + sendDetachedInventories(pkt->getPeerId()); + + // Send time of day + u16 time = m_env->getTimeOfDay(); + float time_speed = g_settings->getFloat("time_speed"); + SendTimeOfDay(pkt->getPeerId(), time, time_speed); + + ///// begin compatibility code + if (protocol_version <= 22) { + m_clients.event(pkt->getPeerId(), CSE_SetClientReady); + m_script->on_joinplayer(playersao); + } + ///// end compatibility code + + // Warnings about protocol version can be issued here + if (getClient(pkt->getPeerId())->net_proto_version < LATEST_PROTOCOL_VERSION) { + SendChatMessage(pkt->getPeerId(), L"# Server: WARNING: YOUR CLIENT'S " + L"VERSION MAY NOT BE FULLY COMPATIBLE WITH THIS SERVER!"); + } +} + +void Server::handleCommand_RequestMedia(NetworkPacket* pkt) +{ + std::vector<std::string> tosend; + u16 numfiles; + + *pkt >> numfiles; + + infostream << "Sending " << numfiles << " files to " + << getPlayerName(pkt->getPeerId()) << std::endl; + verbosestream << "TOSERVER_REQUEST_MEDIA: " << std::endl; + + for (u16 i = 0; i < numfiles; i++) { + std::string name; + + *pkt >> name; + + tosend.push_back(name); + verbosestream << "TOSERVER_REQUEST_MEDIA: requested file " + << name << std::endl; + } + + sendRequestedMedia(pkt->getPeerId(), tosend); +} + +void Server::handleCommand_ReceivedMedia(NetworkPacket* pkt) +{ +} + +void Server::handleCommand_ClientReady(NetworkPacket* pkt) +{ + u16 peer_id = pkt->getPeerId(); + u16 peer_proto_ver = getClient(peer_id, CS_InitDone)->net_proto_version; + + // clients <= protocol version 22 did not send ready message, + // they're already initialized + if (peer_proto_ver <= 22) { + infostream << "Client sent message not expected by a " + << "client using protocol version <= 22," + << "disconnecting peer_id: " << peer_id << std::endl; + m_con.DisconnectPeer(peer_id); + return; + } + + PlayerSAO* playersao = StageTwoClientInit(peer_id); + + if (playersao == NULL) { + actionstream + << "TOSERVER_CLIENT_READY stage 2 client init failed for peer_id: " + << peer_id << std::endl; + m_con.DisconnectPeer(peer_id); + return; + } + + + if (pkt->getSize() < 8) { + errorstream + << "TOSERVER_CLIENT_READY client sent inconsistent data, disconnecting peer_id: " + << peer_id << std::endl; + m_con.DisconnectPeer(peer_id); + return; + } + + u8 major_ver, minor_ver, patch_ver, reserved; + std::string full_ver; + *pkt >> major_ver >> minor_ver >> patch_ver >> reserved >> full_ver; + + m_clients.setClientVersion( + peer_id, major_ver, minor_ver, patch_ver, + full_ver); + + m_clients.event(peer_id, CSE_SetClientReady); + m_script->on_joinplayer(playersao); +} + +void Server::handleCommand_GotBlocks(NetworkPacket* pkt) +{ + if (pkt->getSize() < 1) + return; + + /* + [0] u16 command + [2] u8 count + [3] v3s16 pos_0 + [3+6] v3s16 pos_1 + ... + */ + + u8 count; + *pkt >> count; + + RemoteClient *client = getClient(pkt->getPeerId()); + + for (u16 i = 0; i < count; i++) { + if ((s16)pkt->getSize() < 1 + (i + 1) * 6) + throw con::InvalidIncomingDataException + ("GOTBLOCKS length is too short"); + v3s16 p; + + *pkt >> p; + + client->GotBlock(p); + } +} + +void Server::handleCommand_PlayerPos(NetworkPacket* pkt) +{ + if (pkt->getSize() < 12 + 12 + 4 + 4) + return; + + v3s32 ps, ss; + s32 f32pitch, f32yaw; + + *pkt >> ps; + *pkt >> ss; + *pkt >> f32pitch; + *pkt >> f32yaw; + + f32 pitch = (f32)f32pitch / 100.0; + f32 yaw = (f32)f32yaw / 100.0; + u32 keyPressed = 0; + + if (pkt->getSize() >= 12 + 12 + 4 + 4 + 4) + *pkt >> keyPressed; + + v3f position((f32)ps.X / 100.0, (f32)ps.Y / 100.0, (f32)ps.Z / 100.0); + v3f speed((f32)ss.X / 100.0, (f32)ss.Y / 100.0, (f32)ss.Z / 100.0); + + pitch = modulo360f(pitch); + yaw = modulo360f(yaw); + + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + // If player is dead we don't care of this packet + if (player->isDead()) { + verbosestream << "TOSERVER_PLAYERPOS: " << player->getName() + << " is dead. Ignoring packet"; + return; + } + + PlayerSAO *playersao = player->getPlayerSAO(); + if (playersao == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player object for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + player->setPosition(position); + player->setSpeed(speed); + player->setPitch(pitch); + player->setYaw(yaw); + player->keyPressed = keyPressed; + player->control.up = (keyPressed & 1); + player->control.down = (keyPressed & 2); + player->control.left = (keyPressed & 4); + player->control.right = (keyPressed & 8); + player->control.jump = (keyPressed & 16); + player->control.aux1 = (keyPressed & 32); + player->control.sneak = (keyPressed & 64); + player->control.LMB = (keyPressed & 128); + player->control.RMB = (keyPressed & 256); + + if (playersao->checkMovementCheat()) { + // Call callbacks + m_script->on_cheat(playersao, "moved_too_fast"); + SendMovePlayer(pkt->getPeerId()); + } +} + +void Server::handleCommand_DeletedBlocks(NetworkPacket* pkt) +{ + if (pkt->getSize() < 1) + return; + + /* + [0] u16 command + [2] u8 count + [3] v3s16 pos_0 + [3+6] v3s16 pos_1 + ... + */ + + u8 count; + *pkt >> count; + + RemoteClient *client = getClient(pkt->getPeerId()); + + for (u16 i = 0; i < count; i++) { + if ((s16)pkt->getSize() < 1 + (i + 1) * 6) + throw con::InvalidIncomingDataException + ("DELETEDBLOCKS length is too short"); + v3s16 p; + *pkt >> p; + + client->SetBlockNotSent(p); + } +} + +void Server::handleCommand_InventoryAction(NetworkPacket* pkt) +{ + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + PlayerSAO *playersao = player->getPlayerSAO(); + if (playersao == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player object for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + // Strip command and create a stream + std::string datastring(pkt->getString(0), pkt->getSize()); + verbosestream << "TOSERVER_INVENTORY_ACTION: data=" << datastring + << std::endl; + std::istringstream is(datastring, std::ios_base::binary); + // Create an action + InventoryAction *a = InventoryAction::deSerialize(is); + if (a == NULL) { + infostream << "TOSERVER_INVENTORY_ACTION: " + << "InventoryAction::deSerialize() returned NULL" + << std::endl; + return; + } + + // If something goes wrong, this player is to blame + RollbackScopeActor rollback_scope(m_rollback, + std::string("player:")+player->getName()); + + /* + Note: Always set inventory not sent, to repair cases + where the client made a bad prediction. + */ + + /* + Handle restrictions and special cases of the move action + */ + if (a->getType() == IACTION_MOVE) { + IMoveAction *ma = (IMoveAction*)a; + + ma->from_inv.applyCurrentPlayer(player->getName()); + ma->to_inv.applyCurrentPlayer(player->getName()); + + setInventoryModified(ma->from_inv, false); + setInventoryModified(ma->to_inv, false); + + bool from_inv_is_current_player = + (ma->from_inv.type == InventoryLocation::PLAYER) && + (ma->from_inv.name == player->getName()); + + bool to_inv_is_current_player = + (ma->to_inv.type == InventoryLocation::PLAYER) && + (ma->to_inv.name == player->getName()); + + /* + Disable moving items out of craftpreview + */ + if (ma->from_list == "craftpreview") { + infostream << "Ignoring IMoveAction from " + << (ma->from_inv.dump()) << ":" << ma->from_list + << " to " << (ma->to_inv.dump()) << ":" << ma->to_list + << " because src is " << ma->from_list << std::endl; + delete a; + return; + } + + /* + Disable moving items into craftresult and craftpreview + */ + if (ma->to_list == "craftpreview" || ma->to_list == "craftresult") { + infostream << "Ignoring IMoveAction from " + << (ma->from_inv.dump()) << ":" << ma->from_list + << " to " << (ma->to_inv.dump()) << ":" << ma->to_list + << " because dst is " << ma->to_list << std::endl; + delete a; + return; + } + + // Disallow moving items in elsewhere than player's inventory + // if not allowed to interact + if (!checkPriv(player->getName(), "interact") && + (!from_inv_is_current_player || + !to_inv_is_current_player)) { + infostream << "Cannot move outside of player's inventory: " + << "No interact privilege" << std::endl; + delete a; + return; + } + } + /* + Handle restrictions and special cases of the drop action + */ + else if (a->getType() == IACTION_DROP) { + IDropAction *da = (IDropAction*)a; + + da->from_inv.applyCurrentPlayer(player->getName()); + + setInventoryModified(da->from_inv, false); + + /* + Disable dropping items out of craftpreview + */ + if (da->from_list == "craftpreview") { + infostream << "Ignoring IDropAction from " + << (da->from_inv.dump()) << ":" << da->from_list + << " because src is " << da->from_list << std::endl; + delete a; + return; + } + + // Disallow dropping items if not allowed to interact + if (!checkPriv(player->getName(), "interact")) { + delete a; + return; + } + } + /* + Handle restrictions and special cases of the craft action + */ + else if (a->getType() == IACTION_CRAFT) { + ICraftAction *ca = (ICraftAction*)a; + + ca->craft_inv.applyCurrentPlayer(player->getName()); + + setInventoryModified(ca->craft_inv, false); + + //bool craft_inv_is_current_player = + // (ca->craft_inv.type == InventoryLocation::PLAYER) && + // (ca->craft_inv.name == player->getName()); + + // Disallow crafting if not allowed to interact + if (!checkPriv(player->getName(), "interact")) { + infostream << "Cannot craft: " + << "No interact privilege" << std::endl; + delete a; + return; + } + } + + // Do the action + a->apply(this, playersao, this); + // Eat the action + delete a; + + SendInventory(playersao); +} + +void Server::handleCommand_ChatMessage(NetworkPacket* pkt) +{ + /* + u16 command + u16 length + wstring message + */ + u16 len; + *pkt >> len; + + std::wstring message; + for (u16 i = 0; i < len; i++) { + u16 tmp_wchar; + *pkt >> tmp_wchar; + + message += (wchar_t)tmp_wchar; + } + + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + // If something goes wrong, this player is to blame + RollbackScopeActor rollback_scope(m_rollback, + std::string("player:")+player->getName()); + + // Get player name of this client + std::wstring name = narrow_to_wide(player->getName()); + + // Run script hook + bool ate = m_script->on_chat_message(player->getName(), + wide_to_narrow(message)); + // If script ate the message, don't proceed + if (ate) + return; + + // Line to send to players + std::wstring line; + // Whether to send to the player that sent the line + bool send_to_sender_only = false; + + // Commands are implemented in Lua, so only catch invalid + // commands that were not "eaten" and send an error back + if (message[0] == L'/') { + message = message.substr(1); + send_to_sender_only = true; + if (message.length() == 0) + line += L"-!- Empty command"; + else + line += L"-!- Invalid command: " + str_split(message, L' ')[0]; + } + else { + if (checkPriv(player->getName(), "shout")) { + line += L"<"; + line += name; + line += L"> "; + line += message; + } else { + line += L"-!- You don't have permission to shout."; + send_to_sender_only = true; + } + } + + if (line != L"") + { + /* + Send the message to sender + */ + if (send_to_sender_only) { + SendChatMessage(pkt->getPeerId(), line); + } + /* + Send the message to others + */ + else { + actionstream << "CHAT: " << wide_to_narrow(line)<<std::endl; + + std::vector<u16> clients = m_clients.getClientIDs(); + + for (std::vector<u16>::iterator i = clients.begin(); + i != clients.end(); ++i) { + if (*i != pkt->getPeerId()) + SendChatMessage(*i, line); + } + } + } +} + +void Server::handleCommand_Damage(NetworkPacket* pkt) +{ + u8 damage; + + *pkt >> damage; + + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + PlayerSAO *playersao = player->getPlayerSAO(); + if (playersao == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player object for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + if (g_settings->getBool("enable_damage")) { + actionstream << player->getName() << " damaged by " + << (int)damage << " hp at " << PP(player->getPosition() / BS) + << std::endl; + + playersao->setHP(playersao->getHP() - damage); + SendPlayerHPOrDie(playersao); + } +} + +void Server::handleCommand_Breath(NetworkPacket* pkt) +{ + u16 breath; + + *pkt >> breath; + + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + /* + * If player is dead, we don't need to update the breath + * He is dead ! + */ + if (player->isDead()) { + verbosestream << "TOSERVER_BREATH: " << player->getName() + << " is dead. Ignoring packet"; + return; + } + + + PlayerSAO *playersao = player->getPlayerSAO(); + if (playersao == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player object for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + playersao->setBreath(breath); + SendPlayerBreath(pkt->getPeerId()); +} + +void Server::handleCommand_Password(NetworkPacket* pkt) +{ + if (pkt->getSize() != PASSWORD_SIZE * 2) + return; + + std::string oldpwd; + std::string newpwd; + + // Deny for clients using the new protocol + RemoteClient* client = getClient(pkt->getPeerId(), CS_Created); + if (client->net_proto_version >= 25) { + infostream << "Server::handleCommand_Password(): Denying change: " + << " Client protocol version for peer_id=" << pkt->getPeerId() + << " too new!" << std::endl; + return; + } + + for (u16 i = 0; i < PASSWORD_SIZE - 1; i++) { + char c = pkt->getChar(i); + if (c == 0) + break; + oldpwd += c; + } + + for (u16 i = 0; i < PASSWORD_SIZE - 1; i++) { + char c = pkt->getChar(PASSWORD_SIZE + i); + if (c == 0) + break; + newpwd += c; + } + + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + if (!base64_is_valid(newpwd)) { + infostream<<"Server: " << player->getName() << + " supplied invalid password hash" << std::endl; + // Wrong old password supplied!! + SendChatMessage(pkt->getPeerId(), L"Invalid new password hash supplied. Password NOT changed."); + return; + } + + infostream << "Server: Client requests a password change from " + << "'" << oldpwd << "' to '" << newpwd << "'" << std::endl; + + std::string playername = player->getName(); + + std::string checkpwd; + m_script->getAuth(playername, &checkpwd, NULL); + + if (oldpwd != checkpwd) { + infostream << "Server: invalid old password" << std::endl; + // Wrong old password supplied!! + SendChatMessage(pkt->getPeerId(), L"Invalid old password supplied. Password NOT changed."); + return; + } + + bool success = m_script->setPassword(playername, newpwd); + if (success) { + actionstream << player->getName() << " changes password" << std::endl; + SendChatMessage(pkt->getPeerId(), L"Password change successful."); + } else { + actionstream << player->getName() << " tries to change password but " + << "it fails" << std::endl; + SendChatMessage(pkt->getPeerId(), L"Password change failed or unavailable."); + } +} + +void Server::handleCommand_PlayerItem(NetworkPacket* pkt) +{ + if (pkt->getSize() < 2) + return; + + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + PlayerSAO *playersao = player->getPlayerSAO(); + if (playersao == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player object for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + u16 item; + + *pkt >> item; + + playersao->setWieldIndex(item); +} + +void Server::handleCommand_Respawn(NetworkPacket* pkt) +{ + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + if (!player->isDead()) + return; + + RespawnPlayer(pkt->getPeerId()); + + actionstream << player->getName() << " respawns at " + << PP(player->getPosition()/BS) << std::endl; + + // ActiveObject is added to environment in AsyncRunStep after + // the previous addition has been successfully removed +} + +void Server::handleCommand_Interact(NetworkPacket* pkt) +{ + std::string datastring(pkt->getString(0), pkt->getSize()); + std::istringstream is(datastring, std::ios_base::binary); + + /* + [0] u16 command + [2] u8 action + [3] u16 item + [5] u32 length of the next item + [9] serialized PointedThing + 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 + */ + u8 action = readU8(is); + u16 item_i = readU16(is); + std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary); + PointedThing pointed; + pointed.deSerialize(tmp_is); + + verbosestream << "TOSERVER_INTERACT: action=" << (int)action << ", item=" + << item_i << ", pointed=" << pointed.dump() << std::endl; + + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + PlayerSAO *playersao = player->getPlayerSAO(); + if (playersao == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player object for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + if (player->isDead()) { + verbosestream << "TOSERVER_INTERACT: " << player->getName() + << " is dead. Ignoring packet"; + return; + } + + v3f player_pos = playersao->getLastGoodPosition(); + + // Update wielded item + playersao->setWieldIndex(item_i); + + // Get pointed to node (undefined if not POINTEDTYPE_NODE) + v3s16 p_under = pointed.node_undersurface; + v3s16 p_above = pointed.node_abovesurface; + + // Get pointed to object (NULL if not POINTEDTYPE_OBJECT) + ServerActiveObject *pointed_object = NULL; + if (pointed.type == POINTEDTHING_OBJECT) { + pointed_object = m_env->getActiveObject(pointed.object_id); + if (pointed_object == NULL) { + verbosestream << "TOSERVER_INTERACT: " + "pointed object is NULL" << std::endl; + return; + } + + } + + v3f pointed_pos_under = player_pos; + v3f pointed_pos_above = player_pos; + if (pointed.type == POINTEDTHING_NODE) { + pointed_pos_under = intToFloat(p_under, BS); + pointed_pos_above = intToFloat(p_above, BS); + } + else if (pointed.type == POINTEDTHING_OBJECT) { + pointed_pos_under = pointed_object->getBasePosition(); + pointed_pos_above = pointed_pos_under; + } + + /* + Check that target is reasonably close + (only when digging or placing things) + */ + if (action == 0 || action == 2 || action == 3) { + float d = player_pos.getDistanceFrom(pointed_pos_under); + float max_d = BS * 14; // Just some large enough value + if (d > max_d) { + actionstream << "Player " << player->getName() + << " tried to access " << pointed.dump() + << " from too far: " + << "d=" << d <<", max_d=" << max_d + << ". ignoring." << std::endl; + // Re-send block to revert change on client-side + RemoteClient *client = getClient(pkt->getPeerId()); + v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); + client->SetBlockNotSent(blockpos); + // Call callbacks + m_script->on_cheat(playersao, "interacted_too_far"); + // Do nothing else + return; + } + } + + /* + 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; + // Re-send block to revert change on client-side + RemoteClient *client = getClient(pkt->getPeerId()); + // Digging completed -> under + if (action == 2) { + v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); + client->SetBlockNotSent(blockpos); + } + // Placement -> above + if (action == 3) { + v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS)); + client->SetBlockNotSent(blockpos); + } + return; + } + + /* + If something goes wrong, this player is to blame + */ + RollbackScopeActor rollback_scope(m_rollback, + std::string("player:")+player->getName()); + + /* + 0: start digging or punch object + */ + if (action == 0) { + if (pointed.type == POINTEDTHING_NODE) { + /* + NOTE: This can be used in the future to check if + somebody is cheating, by checking the timing. + */ + MapNode n(CONTENT_IGNORE); + bool pos_ok; + n = m_env->getMap().getNodeNoEx(p_under, &pos_ok); + if (pos_ok) + n = m_env->getMap().getNodeNoEx(p_under, &pos_ok); + + if (!pos_ok) { + infostream << "Server: Not punching: Node not found." + << " Adding block to emerge queue." + << std::endl; + m_emerge->enqueueBlockEmerge(pkt->getPeerId(), getNodeBlockPos(p_above), false); + } + + if (n.getContent() != CONTENT_IGNORE) + m_script->node_on_punch(p_under, n, playersao, pointed); + // Cheat prevention + playersao->noCheatDigStart(p_under); + } + else if (pointed.type == POINTEDTHING_OBJECT) { + // Skip if object has been removed + if (pointed_object->m_removed) + return; + + actionstream<<player->getName()<<" punches object " + <<pointed.object_id<<": " + <<pointed_object->getDescription()<<std::endl; + + ItemStack punchitem = playersao->getWieldedItem(); + ToolCapabilities toolcap = + punchitem.getToolCapabilities(m_itemdef); + v3f dir = (pointed_object->getBasePosition() - + (player->getPosition() + player->getEyeOffset()) + ).normalize(); + float time_from_last_punch = + playersao->resetTimeFromLastPunch(); + + s16 src_original_hp = pointed_object->getHP(); + s16 dst_origin_hp = playersao->getHP(); + + pointed_object->punch(dir, &toolcap, playersao, + time_from_last_punch); + + // If the object is a player and its HP changed + if (src_original_hp != pointed_object->getHP() && + pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + SendPlayerHPOrDie((PlayerSAO *)pointed_object); + } + + // If the puncher is a player and its HP changed + if (dst_origin_hp != playersao->getHP()) + SendPlayerHPOrDie(playersao); + } + + } // action == 0 + + /* + 1: stop digging + */ + else if (action == 1) { + } // action == 1 + + /* + 2: Digging completed + */ + else if (action == 2) { + // Only digging of nodes + if (pointed.type == POINTEDTHING_NODE) { + bool pos_ok; + MapNode n = m_env->getMap().getNodeNoEx(p_under, &pos_ok); + if (!pos_ok) { + infostream << "Server: Not finishing digging: Node not found." + << " Adding block to emerge queue." + << std::endl; + m_emerge->enqueueBlockEmerge(pkt->getPeerId(), getNodeBlockPos(p_above), false); + } + + /* Cheat prevention */ + bool is_valid_dig = true; + if (!isSingleplayer() && !g_settings->getBool("disable_anticheat")) { + v3s16 nocheat_p = playersao->getNoCheatDigPos(); + float nocheat_t = playersao->getNoCheatDigTime(); + playersao->noCheatDigEnd(); + // If player didn't start digging this, ignore dig + if (nocheat_p != p_under) { + infostream << "Server: NoCheat: " << player->getName() + << " started digging " + << PP(nocheat_p) << " and completed digging " + << PP(p_under) << "; not digging." << std::endl; + is_valid_dig = false; + // Call callbacks + m_script->on_cheat(playersao, "finished_unknown_dig"); + } + // Get player's wielded item + ItemStack playeritem; + InventoryList *mlist = playersao->getInventory()->getList("main"); + if (mlist != NULL) + playeritem = mlist->getItem(playersao->getWieldIndex()); + ToolCapabilities playeritem_toolcap = + playeritem.getToolCapabilities(m_itemdef); + // Get diggability and expected digging time + DigParams params = getDigParams(m_nodedef->get(n).groups, + &playeritem_toolcap); + // If can't dig, try hand + if (!params.diggable) { + const ItemDefinition &hand = m_itemdef->get(""); + const ToolCapabilities *tp = hand.tool_capabilities; + if (tp) + params = getDigParams(m_nodedef->get(n).groups, tp); + } + // If can't dig, ignore dig + if (!params.diggable) { + infostream << "Server: NoCheat: " << player->getName() + << " completed digging " << PP(p_under) + << ", which is not diggable with tool. not digging." + << std::endl; + is_valid_dig = false; + // Call callbacks + m_script->on_cheat(playersao, "dug_unbreakable"); + } + // Check digging time + // If already invalidated, we don't have to + if (!is_valid_dig) { + // Well not our problem then + } + // Clean and long dig + else if (params.time > 2.0 && nocheat_t * 1.2 > params.time) { + // All is good, but grab time from pool; don't care if + // it's actually available + playersao->getDigPool().grab(params.time); + } + // Short or laggy dig + // Try getting the time from pool + else if (playersao->getDigPool().grab(params.time)) { + // All is good + } + // Dig not possible + else { + infostream << "Server: NoCheat: " << player->getName() + << " completed digging " << PP(p_under) + << "too fast; not digging." << std::endl; + is_valid_dig = false; + // Call callbacks + m_script->on_cheat(playersao, "dug_too_fast"); + } + } + + /* Actually dig node */ + + if (is_valid_dig && n.getContent() != CONTENT_IGNORE) + m_script->node_on_dig(p_under, n, playersao); + + 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) { + // Re-send block to revert change on client-side + client->SetBlockNotSent(blockpos); + } + else { + client->ResendBlockIfOnWire(blockpos); + } + } + } // action == 2 + + /* + 3: place block or right-click object + */ + else if (action == 3) { + ItemStack item = playersao->getWieldedItem(); + + // Reset build time counter + if (pointed.type == POINTEDTHING_NODE && + item.getDefinition(m_itemdef).type == ITEM_NODE) + getClient(pkt->getPeerId())->m_time_from_building = 0.0; + + if (pointed.type == POINTEDTHING_OBJECT) { + // Right click object + + // Skip if object has been removed + if (pointed_object->m_removed) + return; + + actionstream << player->getName() << " right-clicks object " + << pointed.object_id << ": " + << pointed_object->getDescription() << std::endl; + + // Do stuff + pointed_object->rightClick(playersao); + } + else if (m_script->item_OnPlace( + item, playersao, pointed)) { + // Placement was handled in lua + + // Apply returned ItemStack + if (playersao->setWieldedItem(item)) { + SendInventory(playersao); + } + } + + // If item has node placement prediction, always send the + // blocks to make sure the client knows what exactly happened + 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 != "") { + client->SetBlockNotSent(blockpos); + if (blockpos2 != blockpos) { + client->SetBlockNotSent(blockpos2); + } + } + else { + client->ResendBlockIfOnWire(blockpos); + if (blockpos2 != blockpos) { + client->ResendBlockIfOnWire(blockpos2); + } + } + } // action == 3 + + /* + 4: use + */ + else if (action == 4) { + ItemStack item = playersao->getWieldedItem(); + + actionstream << player->getName() << " uses " << item.name + << ", pointing at " << pointed.dump() << std::endl; + + if (m_script->item_OnUse( + item, playersao, pointed)) { + // Apply returned ItemStack + if (playersao->setWieldedItem(item)) { + SendInventory(playersao); + } + } + + } // action == 4 + + + /* + Catch invalid actions + */ + else { + infostream << "WARNING: Server: Invalid action " + << action << std::endl; + } +} + +void Server::handleCommand_RemovedSounds(NetworkPacket* pkt) +{ + u16 num; + *pkt >> num; + for (u16 k = 0; k < num; k++) { + s32 id; + + *pkt >> id; + + std::map<s32, ServerPlayingSound>::iterator i = + m_playing_sounds.find(id); + + if (i == m_playing_sounds.end()) + continue; + + ServerPlayingSound &psound = i->second; + psound.clients.erase(pkt->getPeerId()); + if (psound.clients.empty()) + m_playing_sounds.erase(i++); + } +} + +void Server::handleCommand_NodeMetaFields(NetworkPacket* pkt) +{ + v3s16 p; + std::string formname; + u16 num; + + *pkt >> p >> formname >> num; + + StringMap fields; + for (u16 k = 0; k < num; k++) { + std::string fieldname; + *pkt >> fieldname; + fields[fieldname] = pkt->readLongString(); + } + + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + PlayerSAO *playersao = player->getPlayerSAO(); + if (playersao == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player object for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + // If something goes wrong, this player is to blame + RollbackScopeActor rollback_scope(m_rollback, + std::string("player:")+player->getName()); + + // Check the target node for rollback data; leave others unnoticed + RollbackNode rn_old(&m_env->getMap(), p, this); + + m_script->node_on_receive_fields(p, formname, fields, playersao); + + // Report rollback data + RollbackNode rn_new(&m_env->getMap(), p, this); + if (rollback() && rn_new != rn_old) { + RollbackAction action; + action.setSetNode(p, rn_old, rn_new); + rollback()->reportAction(action); + } +} + +void Server::handleCommand_InventoryFields(NetworkPacket* pkt) +{ + std::string formname; + u16 num; + + *pkt >> formname >> num; + + StringMap fields; + for (u16 k = 0; k < num; k++) { + std::string fieldname; + *pkt >> fieldname; + fields[fieldname] = pkt->readLongString(); + } + + Player *player = m_env->getPlayer(pkt->getPeerId()); + if (player == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + PlayerSAO *playersao = player->getPlayerSAO(); + if (playersao == NULL) { + errorstream << "Server::ProcessData(): Canceling: " + "No player object for peer_id=" << pkt->getPeerId() + << " disconnecting peer!" << std::endl; + m_con.DisconnectPeer(pkt->getPeerId()); + return; + } + + m_script->on_playerReceiveFields(playersao, formname, fields); +} + +void Server::handleCommand_FirstSrp(NetworkPacket* pkt) +{ + RemoteClient* client = getClient(pkt->getPeerId(), CS_Invalid); + ClientState cstate = client->getState(); + + std::string playername = client->getName(); + + std::string salt; + std::string verification_key; + + std::string addr_s = getPeerAddress(pkt->getPeerId()).serializeString(); + u8 is_empty; + + *pkt >> salt >> verification_key >> is_empty; + + verbosestream << "Server: Got TOSERVER_FIRST_SRP from " << addr_s + << ", with is_empty= " << is_empty << std::endl; + + // Either this packet is sent because the user is new or to change the password + if (cstate == CS_HelloSent) { + if (!client->isMechAllowed(AUTH_MECHANISM_FIRST_SRP)) { + actionstream << "Server: Client from " << addr_s + << " tried to set password without being " + << "authenticated, or the username being new." << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA); + return; + } + + if (!isSingleplayer() && + g_settings->getBool("disallow_empty_password") && + is_empty == 1) { + actionstream << "Server: " << playername + << " supplied empty password from " << addr_s << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_EMPTY_PASSWORD); + return; + } + + std::string initial_ver_key; + + initial_ver_key = encodeSRPVerifier(verification_key, salt); + m_script->createAuth(playername, initial_ver_key); + + acceptAuth(pkt->getPeerId(), false); + } else { + if (cstate < CS_SudoMode) { + infostream << "Server::ProcessData(): Ignoring TOSERVER_FIRST_SRP from " + << addr_s << ": " << "Client has wrong state " << cstate << "." + << std::endl; + return; + } + m_clients.event(pkt->getPeerId(), CSE_SudoLeave); + std::string pw_db_field = encodeSRPVerifier(verification_key, salt); + bool success = m_script->setPassword(playername, pw_db_field); + if (success) { + actionstream << playername << " changes password" << std::endl; + SendChatMessage(pkt->getPeerId(), L"Password change successful."); + } else { + actionstream << playername << " tries to change password but " + << "it fails" << std::endl; + SendChatMessage(pkt->getPeerId(), L"Password change failed or unavailable."); + } + } +} + +void Server::handleCommand_SrpBytesA(NetworkPacket* pkt) +{ + RemoteClient* client = getClient(pkt->getPeerId(), CS_Invalid); + ClientState cstate = client->getState(); + + bool wantSudo = (cstate == CS_Active); + + if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { + actionstream << "Server: got SRP _A packet in wrong state " + << cstate << " from " + << getPeerAddress(pkt->getPeerId()).serializeString() + << ". Ignoring." << std::endl; + return; + } + + if (client->chosen_mech != AUTH_MECHANISM_NONE) { + actionstream << "Server: got SRP _A packet, while auth" + << "is already going on with mech " << client->chosen_mech + << " from " << getPeerAddress(pkt->getPeerId()).serializeString() + << " (wantSudo=" << wantSudo << "). Ignoring." << std::endl; + if (wantSudo) { + DenySudoAccess(pkt->getPeerId()); + return; + } else { + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA); + return; + } + } + + std::string bytes_A; + u8 based_on; + *pkt >> bytes_A >> based_on; + + infostream << "Server: TOSERVER_SRP_BYTES_A received with " + << "based_on=" << int(based_on) << " and len_A=" + << bytes_A.length() << "." << std::endl; + + AuthMechanism chosen = (based_on == 0) ? + AUTH_MECHANISM_LEGACY_PASSWORD : AUTH_MECHANISM_SRP; + + if (wantSudo) { + if (!client->isSudoMechAllowed(chosen)) { + actionstream << "Server: Player \"" << client->getName() + << "\" at " << getPeerAddress(pkt->getPeerId()).serializeString() + << " tried to change password using unallowed mech " + << chosen << "." << std::endl; + DenySudoAccess(pkt->getPeerId()); + return; + } + } else { + if (!client->isMechAllowed(chosen)) { + actionstream << "Server: Client tried to authenticate from " + << getPeerAddress(pkt->getPeerId()).serializeString() + << " using unallowed mech " << chosen << "." << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA); + return; + } + } + + client->chosen_mech = chosen; + + std::string bytes_s; + std::string bytes_v; + + if (based_on == 0) { + char *p_bytes_s = 0; + size_t len_s = 0; + char *p_bytes_v = 0; + size_t len_v = 0; + getSRPVerifier(client->getName(), client->enc_pwd, + &p_bytes_s, &len_s, + &p_bytes_v, &len_v); + bytes_s = std::string(p_bytes_s, len_s); + bytes_v = std::string(p_bytes_v, len_v); + free(p_bytes_s); + free(p_bytes_v); + } else if (!decodeSRPVerifier(client->enc_pwd, &bytes_s, &bytes_v)) { + // Non-base64 errors should have been catched in the init handler + actionstream << "Server: User " << client->getName() + << " tried to log in, but srp verifier field" + << " was invalid (most likely invalid base64)." << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_SERVER_FAIL); + return; + } + + char *bytes_B = 0; + size_t len_B = 0; + + client->auth_data = srp_verifier_new(SRP_SHA256, SRP_NG_2048, + client->getName().c_str(), + (const unsigned char *) bytes_s.c_str(), bytes_s.size(), + (const unsigned char *) bytes_v.c_str(), bytes_v.size(), + (const unsigned char *) bytes_A.c_str(), bytes_A.size(), + NULL, 0, + (unsigned char **) &bytes_B, &len_B, NULL, NULL); + + if (!bytes_B) { + actionstream << "Server: User " << client->getName() + << " tried to log in, SRP-6a safety check violated in _A handler." + << std::endl; + if (wantSudo) { + DenySudoAccess(pkt->getPeerId()); + return; + } else { + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA); + return; + } + } + + NetworkPacket resp_pkt(TOCLIENT_SRP_BYTES_S_B, 0, pkt->getPeerId()); + resp_pkt << bytes_s << std::string(bytes_B, len_B); + Send(&resp_pkt); +} + +void Server::handleCommand_SrpBytesM(NetworkPacket* pkt) +{ + RemoteClient* client = getClient(pkt->getPeerId(), CS_Invalid); + ClientState cstate = client->getState(); + + bool wantSudo = (cstate == CS_Active); + + verbosestream << "Server: Recieved TOCLIENT_SRP_BYTES_M." << std::endl; + + if (!((cstate == CS_HelloSent) || (cstate == CS_Active))) { + actionstream << "Server: got SRP _M packet in wrong state " + << cstate << " from " + << getPeerAddress(pkt->getPeerId()).serializeString() + << ". Ignoring." << std::endl; + return; + } + + if ((client->chosen_mech != AUTH_MECHANISM_SRP) + && (client->chosen_mech != AUTH_MECHANISM_LEGACY_PASSWORD)) { + actionstream << "Server: got SRP _M packet, while auth" + << "is going on with mech " << client->chosen_mech + << " from " << getPeerAddress(pkt->getPeerId()).serializeString() + << " (wantSudo=" << wantSudo << "). Denying." << std::endl; + if (wantSudo) { + DenySudoAccess(pkt->getPeerId()); + return; + } else { + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA); + return; + } + } + + std::string bytes_M; + *pkt >> bytes_M; + + if (srp_verifier_get_session_key_length((SRPVerifier *) client->auth_data) + != bytes_M.size()) { + actionstream << "Server: User " << client->getName() + << " at " << getPeerAddress(pkt->getPeerId()).serializeString() + << " sent bytes_M with invalid length " << bytes_M.size() << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_UNEXPECTED_DATA); + return; + } + + unsigned char *bytes_HAMK = 0; + + srp_verifier_verify_session((SRPVerifier *) client->auth_data, + (unsigned char *)bytes_M.c_str(), &bytes_HAMK); + + if (!bytes_HAMK) { + if (wantSudo) { + actionstream << "Server: User " << client->getName() + << " at " << getPeerAddress(pkt->getPeerId()).serializeString() + << " tried to change their password, but supplied wrong" + << " (SRP) password for authentication." << std::endl; + DenySudoAccess(pkt->getPeerId()); + return; + } else { + actionstream << "Server: User " << client->getName() + << " at " << getPeerAddress(pkt->getPeerId()).serializeString() + << " supplied wrong (SRP) password from address " + << getPeerAddress(pkt->getPeerId()).serializeString() + << "." << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_WRONG_PASSWORD); + return; + } + } + + if (client->create_player_on_auth_success) { + std::string playername = client->getName(); + m_script->createAuth(playername, client->enc_pwd); + + std::string checkpwd; // not used, but needed for passing something + if (!m_script->getAuth(playername, &checkpwd, NULL)) { + actionstream << "Server: " << playername << " cannot be authenticated" + << " (auth handler does not work?)" << std::endl; + DenyAccess(pkt->getPeerId(), SERVER_ACCESSDENIED_SERVER_FAIL); + return; + } + client->create_player_on_auth_success = false; + } + + acceptAuth(pkt->getPeerId(), wantSudo); +} diff --git a/src/nodedef.cpp b/src/nodedef.cpp index bcf51a072..269c2b9d6 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -19,10 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" -#include "main.h" // For g_settings #include "itemdef.h" #ifndef SERVER -#include "tile.h" +#include "client/tile.h" #include "mesh.h" #include <IMeshManipulator.h> #endif @@ -34,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "exceptions.h" #include "debug.h" #include "gamedef.h" +#include <fstream> // Used in applyTextureOverrides() /* NodeBox @@ -120,7 +120,9 @@ void NodeBox::deSerialize(std::istream &is) void TileDef::serialize(std::ostream &os, u16 protocol_version) const { - if(protocol_version >= 17) + if (protocol_version >= 26) + writeU8(os, 2); + else if (protocol_version >= 17) writeU8(os, 1); else writeU8(os, 0); @@ -129,8 +131,12 @@ void TileDef::serialize(std::ostream &os, u16 protocol_version) const writeU16(os, animation.aspect_w); writeU16(os, animation.aspect_h); writeF1000(os, animation.length); - if(protocol_version >= 17) + if (protocol_version >= 17) writeU8(os, backface_culling); + if (protocol_version >= 26) { + writeU8(os, tileable_horizontal); + writeU8(os, tileable_vertical); + } } void TileDef::deSerialize(std::istream &is) @@ -141,10 +147,15 @@ void TileDef::deSerialize(std::istream &is) animation.aspect_w = readU16(is); animation.aspect_h = readU16(is); animation.length = readF1000(is); - if(version >= 1) + if (version >= 1) backface_culling = readU8(is); + if (version >= 2) { + tileable_horizontal = readU8(is); + tileable_vertical = readU8(is); + } } + /* SimpleSoundSpec serialization */ @@ -183,6 +194,7 @@ void ContentFeatures::reset() solidness = 2; visual_solidness = 0; backface_culling = true; + #endif has_on_construct = false; has_on_destruct = false; @@ -202,6 +214,7 @@ void ContentFeatures::reset() #ifndef SERVER for(u32 i = 0; i < 24; i++) mesh_ptr[i] = NULL; + minimap_color = video::SColor(0, 0, 0, 0); #endif visual_scale = 1.0; for(u32 i = 0; i < 6; i++) @@ -242,7 +255,7 @@ void ContentFeatures::reset() sound_dug = SimpleSoundSpec(); } -void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) +void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const { if(protocol_version < 24){ serializeOld(os, protocol_version); @@ -398,21 +411,20 @@ public: virtual content_t set(const std::string &name, const ContentFeatures &def); virtual content_t allocateDummy(const std::string &name); virtual void updateAliases(IItemDefManager *idef); - virtual void updateTextures(IGameDef *gamedef); - void serialize(std::ostream &os, u16 protocol_version); + virtual void applyTextureOverrides(const std::string &override_filepath); + virtual void updateTextures(IGameDef *gamedef, + void (*progress_cbk)(void *progress_args, u32 progress, u32 max_progress), + void *progress_cbk_args); + void serialize(std::ostream &os, u16 protocol_version) const; void deSerialize(std::istream &is); inline virtual bool getNodeRegistrationStatus() const; inline virtual void setNodeRegistrationStatus(bool completed); - virtual void pendNodeResolve(NodeResolveInfo *nri); - virtual void cancelNodeResolve(NodeResolver *resolver); - virtual void runNodeResolverCallbacks(); - - virtual bool getIdFromResolveInfo(NodeResolveInfo *nri, - const std::string &node_alt, content_t c_fallback, content_t &result); - virtual bool getIdsFromResolveInfo(NodeResolveInfo *nri, - std::vector<content_t> &result); + virtual void pendNodeResolve(NodeResolver *nr); + virtual bool cancelNodeResolveCallback(NodeResolver *nr); + virtual void runNodeResolveCallbacks(); + virtual void resetNodeResolveState(); private: void addNameIdMapping(content_t i, std::string name); @@ -442,8 +454,8 @@ private: // Next possibly free id content_t m_next_id; - // List of node strings and node resolver callbacks to perform - std::list<NodeResolveInfo *> m_pending_node_lookups; + // NodeResolvers to callback once node registration has ended + std::vector<NodeResolver *> m_pending_resolve_callbacks; // True when all nodes have been registered bool m_node_registration_complete; @@ -478,13 +490,7 @@ void CNodeDefManager::clear() m_group_to_items.clear(); m_next_id = 0; - m_node_registration_complete = false; - for (std::list<NodeResolveInfo *>::iterator - it = m_pending_node_lookups.begin(); - it != m_pending_node_lookups.end(); - ++it) - delete *it; - m_pending_node_lookups.clear(); + resetNodeResolveState(); u32 initial_length = 0; initial_length = MYMAX(initial_length, CONTENT_UNKNOWN + 1); @@ -641,6 +647,7 @@ content_t CNodeDefManager::allocateId() // IWritableNodeDefManager content_t CNodeDefManager::set(const std::string &name, const ContentFeatures &def) { + // Pre-conditions assert(name != ""); assert(name == def.name); @@ -678,7 +685,7 @@ content_t CNodeDefManager::set(const std::string &name, const ContentFeatures &d j = m_group_to_items.find(group_name); if (j == m_group_to_items.end()) { m_group_to_items[group_name].push_back( - std::make_pair(id, i->second)); + std::make_pair(id, i->second)); } else { GroupItems &items = j->second; items.push_back(std::make_pair(id, i->second)); @@ -690,7 +697,7 @@ content_t CNodeDefManager::set(const std::string &name, const ContentFeatures &d content_t CNodeDefManager::allocateDummy(const std::string &name) { - assert(name != ""); + assert(name != ""); // Pre-condition ContentFeatures f; f.name = name; return set(name, f); @@ -708,38 +715,104 @@ void CNodeDefManager::updateAliases(IItemDefManager *idef) content_t id; if (m_name_id_mapping.getId(convert_to, id)) { m_name_id_mapping_with_aliases.insert( - std::make_pair(name, id)); + std::make_pair(name, id)); } } } +void CNodeDefManager::applyTextureOverrides(const std::string &override_filepath) +{ + infostream << "CNodeDefManager::applyTextureOverrides(): Applying " + "overrides to textures from " << override_filepath << std::endl; + + std::ifstream infile(override_filepath.c_str()); + std::string line; + int line_c = 0; + while (std::getline(infile, line)) { + line_c++; + if (trim(line) == "") + continue; + std::vector<std::string> splitted = str_split(line, ' '); + if (splitted.size() != 3) { + errorstream << override_filepath + << ":" << line_c << " Could not apply texture override \"" + << line << "\": Syntax error" << std::endl; + continue; + } + + content_t id; + if (!getId(splitted[0], id)) { + errorstream << override_filepath + << ":" << line_c << " Could not apply texture override \"" + << line << "\": Unknown node \"" + << splitted[0] << "\"" << std::endl; + continue; + } + + ContentFeatures &nodedef = m_content_features[id]; + + if (splitted[1] == "top") + nodedef.tiledef[0].name = splitted[2]; + else if (splitted[1] == "bottom") + nodedef.tiledef[1].name = splitted[2]; + else if (splitted[1] == "right") + nodedef.tiledef[2].name = splitted[2]; + else if (splitted[1] == "left") + nodedef.tiledef[3].name = splitted[2]; + else if (splitted[1] == "back") + nodedef.tiledef[4].name = splitted[2]; + else if (splitted[1] == "front") + nodedef.tiledef[5].name = splitted[2]; + else if (splitted[1] == "all" || splitted[1] == "*") + for (int i = 0; i < 6; i++) + nodedef.tiledef[i].name = splitted[2]; + else if (splitted[1] == "sides") + for (int i = 2; i < 6; i++) + nodedef.tiledef[i].name = splitted[2]; + else { + errorstream << override_filepath + << ":" << line_c << " Could not apply texture override \"" + << line << "\": Unknown node side \"" + << splitted[1] << "\"" << std::endl; + continue; + } + } +} -void CNodeDefManager::updateTextures(IGameDef *gamedef) +void CNodeDefManager::updateTextures(IGameDef *gamedef, + void (*progress_callback)(void *progress_args, u32 progress, u32 max_progress), + void *progress_callback_args) { #ifndef SERVER infostream << "CNodeDefManager::updateTextures(): Updating " "textures in node definitions" << std::endl; - ITextureSource *tsrc = gamedef->tsrc(); IShaderSource *shdsrc = gamedef->getShaderSource(); scene::ISceneManager* smgr = gamedef->getSceneManager(); scene::IMeshManipulator* meshmanip = smgr->getMeshManipulator(); bool new_style_water = g_settings->getBool("new_style_water"); - bool new_style_leaves = g_settings->getBool("new_style_leaves"); bool connected_glass = g_settings->getBool("connected_glass"); bool opaque_water = g_settings->getBool("opaque_water"); bool enable_shaders = g_settings->getBool("enable_shaders"); bool enable_bumpmapping = g_settings->getBool("enable_bumpmapping"); bool enable_parallax_occlusion = g_settings->getBool("enable_parallax_occlusion"); bool enable_mesh_cache = g_settings->getBool("enable_mesh_cache"); + bool enable_minimap = g_settings->getBool("enable_minimap"); + std::string leaves_style = g_settings->get("leaves_style"); bool use_normal_texture = enable_shaders && (enable_bumpmapping || enable_parallax_occlusion); - for (u32 i = 0; i < m_content_features.size(); i++) { + u32 size = m_content_features.size(); + + for (u32 i = 0; i < size; i++) { ContentFeatures *f = &m_content_features[i]; + // minimap pixel color - the average color of a texture + if (enable_minimap && f->tiledef[0].name != "") + f->minimap_color = tsrc->getTextureAverageColor(f->tiledef[0].name); + // Figure out the actual tiles to use TileDef tiledef[6]; for (u32 j = 0; j < 6; j++) { @@ -799,10 +872,18 @@ void CNodeDefManager::updateTextures(IGameDef *gamedef) f->visual_solidness = 1; break; case NDT_ALLFACES_OPTIONAL: - if (new_style_leaves) { + if (leaves_style == "fancy") { f->drawtype = NDT_ALLFACES; f->solidness = 0; f->visual_solidness = 1; + } else if (leaves_style == "simple") { + for (u32 j = 0; j < 6; j++) { + if (f->tiledef_special[j].name != "") + tiledef[j].name = f->tiledef_special[j].name; + } + f->drawtype = NDT_GLASSLIKE; + f->solidness = 0; + f->visual_solidness = 1; } else { f->drawtype = NDT_NORMAL; f->solidness = 2; @@ -910,6 +991,8 @@ void CNodeDefManager::updateTextures(IGameDef *gamedef) recalculateBoundingBox(f->mesh_ptr[0]); meshmanip->recalculateNormals(f->mesh_ptr[0], true, false); } + + progress_callback(progress_callback_args, i, size); } #endif } @@ -921,13 +1004,15 @@ void CNodeDefManager::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, bool backface_culling, u8 alpha, u8 material_type) { tile->shader_id = shader_id; - tile->texture = tsrc->getTexture(tiledef->name, &tile->texture_id); + tile->texture = tsrc->getTextureForMesh(tiledef->name, &tile->texture_id); tile->alpha = alpha; tile->material_type = material_type; - // Normal texture - if (use_normal_texture) + // Normal texture and shader flags texture + if (use_normal_texture) { tile->normal_texture = tsrc->getNormalTexture(tiledef->name); + } + tile->flags_texture = tsrc->getShaderFlagsTexture(tile->normal_texture ? true : false); // Material flags tile->material_flags = 0; @@ -935,6 +1020,10 @@ void CNodeDefManager::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, tile->material_flags |= MATERIAL_FLAG_BACKFACE_CULLING; if (tiledef->animation.type == TAT_VERTICAL_FRAMES) tile->material_flags |= MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES; + if (tiledef->tileable_horizontal) + tile->material_flags |= MATERIAL_FLAG_TILEABLE_HORIZONTAL; + if (tiledef->tileable_vertical) + tile->material_flags |= MATERIAL_FLAG_TILEABLE_VERTICAL; // Animation parameters int frame_count = 1; @@ -964,9 +1053,10 @@ void CNodeDefManager::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, os << tiledef->name << "^[verticalframe:" << frame_count << ":" << i; - frame.texture = tsrc->getTexture(os.str(), &frame.texture_id); + frame.texture = tsrc->getTextureForMesh(os.str(), &frame.texture_id); if (tile->normal_texture) frame.normal_texture = tsrc->getNormalTexture(os.str()); + frame.flags_texture = tile->flags_texture; tile->frames[i] = frame; } } @@ -974,7 +1064,7 @@ void CNodeDefManager::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, #endif -void CNodeDefManager::serialize(std::ostream &os, u16 protocol_version) +void CNodeDefManager::serialize(std::ostream &os, u16 protocol_version) const { writeU8(os, 1); // version u16 count = 0; @@ -983,7 +1073,7 @@ void CNodeDefManager::serialize(std::ostream &os, u16 protocol_version) if (i == CONTENT_IGNORE || i == CONTENT_AIR || i == CONTENT_UNKNOWN) continue; - ContentFeatures *f = &m_content_features[i]; + const ContentFeatures *f = &m_content_features[i]; if (f->name == "") continue; writeU16(os2, i); @@ -993,7 +1083,9 @@ void CNodeDefManager::serialize(std::ostream &os, u16 protocol_version) f->serialize(wrapper_os, protocol_version); os2<<serializeString(wrapper_os.str()); - assert(count + 1 > count); // must not overflow + // must not overflow + u16 next = count + 1; + FATAL_ERROR_IF(next < count, "Overflow"); count++; } writeU16(os, count); @@ -1062,7 +1154,7 @@ IWritableNodeDefManager *createNodeDefManager() //// Serialization of old ContentFeatures formats -void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) +void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const { if (protocol_version == 13) { @@ -1294,114 +1386,152 @@ inline void CNodeDefManager::setNodeRegistrationStatus(bool completed) } -void CNodeDefManager::pendNodeResolve(NodeResolveInfo *nri) +void CNodeDefManager::pendNodeResolve(NodeResolver *nr) { - nri->resolver->m_ndef = this; - if (m_node_registration_complete) { - nri->resolver->resolveNodeNames(nri); - nri->resolver->m_lookup_done = true; - delete nri; - } else { - m_pending_node_lookups.push_back(nri); - } + nr->m_ndef = this; + if (m_node_registration_complete) + nr->nodeResolveInternal(); + else + m_pending_resolve_callbacks.push_back(nr); } -void CNodeDefManager::cancelNodeResolve(NodeResolver *resolver) +bool CNodeDefManager::cancelNodeResolveCallback(NodeResolver *nr) { - for (std::list<NodeResolveInfo *>::iterator - it = m_pending_node_lookups.begin(); - it != m_pending_node_lookups.end(); - ++it) { - NodeResolveInfo *nri = *it; - if (resolver == nri->resolver) { - it = m_pending_node_lookups.erase(it); - delete nri; - } + size_t len = m_pending_resolve_callbacks.size(); + for (size_t i = 0; i != len; i++) { + if (nr != m_pending_resolve_callbacks[i]) + continue; + + len--; + m_pending_resolve_callbacks[i] = m_pending_resolve_callbacks[len]; + m_pending_resolve_callbacks.resize(len); + return true; } + + return false; } -void CNodeDefManager::runNodeResolverCallbacks() +void CNodeDefManager::runNodeResolveCallbacks() { - while (!m_pending_node_lookups.empty()) { - NodeResolveInfo *nri = m_pending_node_lookups.front(); - m_pending_node_lookups.pop_front(); - nri->resolver->resolveNodeNames(nri); - nri->resolver->m_lookup_done = true; - delete nri; + for (size_t i = 0; i != m_pending_resolve_callbacks.size(); i++) { + NodeResolver *nr = m_pending_resolve_callbacks[i]; + nr->nodeResolveInternal(); } + + m_pending_resolve_callbacks.clear(); +} + + +void CNodeDefManager::resetNodeResolveState() +{ + m_node_registration_complete = false; + m_pending_resolve_callbacks.clear(); +} + + +//// +//// NodeResolver +//// + +NodeResolver::NodeResolver() +{ + m_ndef = NULL; + m_nodenames_idx = 0; + m_nnlistsizes_idx = 0; + m_resolve_done = false; + + m_nodenames.reserve(16); + m_nnlistsizes.reserve(4); +} + + +NodeResolver::~NodeResolver() +{ + if (!m_resolve_done && m_ndef) + m_ndef->cancelNodeResolveCallback(this); +} + + +void NodeResolver::nodeResolveInternal() +{ + m_nodenames_idx = 0; + m_nnlistsizes_idx = 0; + + resolveNodeNames(); + m_resolve_done = true; + + m_nodenames.clear(); + m_nnlistsizes.clear(); } -bool CNodeDefManager::getIdFromResolveInfo(NodeResolveInfo *nri, - const std::string &node_alt, content_t c_fallback, content_t &result) +bool NodeResolver::getIdFromNrBacklog(content_t *result_out, + const std::string &node_alt, content_t c_fallback) { - if (nri->nodenames.empty()) { - result = c_fallback; - errorstream << "Resolver empty nodename list" << std::endl; + if (m_nodenames_idx == m_nodenames.size()) { + *result_out = c_fallback; + errorstream << "NodeResolver: no more nodes in list" << std::endl; return false; } content_t c; - std::string name = nri->nodenames.front(); - nri->nodenames.pop_front(); + std::string name = m_nodenames[m_nodenames_idx++]; - bool success = getId(name, c); + bool success = m_ndef->getId(name, c); if (!success && node_alt != "") { name = node_alt; - success = getId(name, c); + success = m_ndef->getId(name, c); } if (!success) { - errorstream << "Resolver: Failed to resolve node name '" << name + errorstream << "NodeResolver: failed to resolve node name '" << name << "'." << std::endl; c = c_fallback; } - result = c; + *result_out = c; return success; } -bool CNodeDefManager::getIdsFromResolveInfo(NodeResolveInfo *nri, - std::vector<content_t> &result) +bool NodeResolver::getIdsFromNrBacklog(std::vector<content_t> *result_out, + bool all_required, content_t c_fallback) { bool success = true; - if (nri->nodelistinfo.empty()) { - errorstream << "Resolver: Empty nodelistinfo list" << std::endl; + if (m_nnlistsizes_idx == m_nnlistsizes.size()) { + errorstream << "NodeResolver: no more node lists" << std::endl; return false; } - NodeListInfo listinfo = nri->nodelistinfo.front(); - nri->nodelistinfo.pop_front(); + size_t length = m_nnlistsizes[m_nnlistsizes_idx++]; - while (listinfo.length--) { - if (nri->nodenames.empty()) { - errorstream << "Resolver: Empty nodename list" << std::endl; + while (length--) { + if (m_nodenames_idx == m_nodenames.size()) { + errorstream << "NodeResolver: no more nodes in list" << std::endl; return false; } content_t c; - std::string name = nri->nodenames.front(); - nri->nodenames.pop_front(); + std::string &name = m_nodenames[m_nodenames_idx++]; if (name.substr(0,6) != "group:") { - if (getId(name, c)) { - result.push_back(c); - } else if (listinfo.all_required) { - errorstream << "Resolver: Failed to resolve node name '" << name - << "'." << std::endl; - result.push_back(listinfo.c_fallback); + if (m_ndef->getId(name, c)) { + result_out->push_back(c); + } else if (all_required) { + errorstream << "NodeResolver: failed to resolve node name '" + << name << "'." << std::endl; + result_out->push_back(c_fallback); success = false; } } else { std::set<content_t> cids; std::set<content_t>::iterator it; - getIds(name, cids); + m_ndef->getIds(name, cids); for (it = cids.begin(); it != cids.end(); ++it) - result.push_back(*it); + result_out->push_back(*it); } } diff --git a/src/nodedef.h b/src/nodedef.h index 7280bb8ea..a4a7b6e41 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -25,19 +25,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iostream> #include <map> #include <list> +#include "util/numeric.h" #include "mapnode.h" #ifndef SERVER -#include "tile.h" +#include "client/tile.h" #include "shader.h" #endif #include "itemgroup.h" #include "sound.h" // SimpleSoundSpec #include "constants.h" // BS +class INodeDefManager; class IItemDefManager; class ITextureSource; class IShaderSource; class IGameDef; +class NodeResolver; typedef std::list<std::pair<content_t, int> > GroupItems; @@ -110,6 +113,8 @@ struct TileDef { std::string name; bool backface_culling; // Takes effect only in special cases + bool tileable_horizontal; + bool tileable_vertical; struct{ enum TileAnimationType type; int aspect_w; // width for aspect ratio @@ -121,6 +126,8 @@ struct TileDef { name = ""; backface_culling = true; + tileable_horizontal = true; + tileable_vertical = true; animation.type = TAT_NONE; animation.aspect_w = 1; animation.aspect_h = 1; @@ -191,6 +198,7 @@ struct ContentFeatures std::string mesh; #ifndef SERVER scene::IMesh *mesh_ptr[24]; + video::SColor minimap_color; #endif float visual_scale; // Misc. scale parameter TileDef tiledef[6]; @@ -199,6 +207,7 @@ struct ContentFeatures // Post effect color, drawn when the camera is inside the node. video::SColor post_effect_color; + // Type of MapNode::param1 ContentParamType param_type; // Type of MapNode::param2 @@ -263,9 +272,9 @@ struct ContentFeatures ContentFeatures(); ~ContentFeatures(); void reset(); - void serialize(std::ostream &os, u16 protocol_version); + void serialize(std::ostream &os, u16 protocol_version) const; void deSerialize(std::istream &is); - void serializeOld(std::ostream &os, u16 protocol_version); + void serializeOld(std::ostream &os, u16 protocol_version) const; void deSerializeOld(std::istream &is, int version); /* @@ -280,87 +289,44 @@ struct ContentFeatures } }; -class NodeResolver; -class INodeDefManager; - -struct NodeListInfo { - NodeListInfo(u32 len) - { - length = len; - all_required = false; - c_fallback = CONTENT_IGNORE; - } - - NodeListInfo(u32 len, content_t fallback) - { - length = len; - all_required = true; - c_fallback = fallback; - } - - u32 length; - bool all_required; - content_t c_fallback; -}; - -struct NodeResolveInfo { - NodeResolveInfo(NodeResolver *nr) - { - resolver = nr; - } - - std::list<std::string> nodenames; - std::list<NodeListInfo> nodelistinfo; - NodeResolver *resolver; -}; - -class INodeDefManager -{ +class INodeDefManager { public: INodeDefManager(){} virtual ~INodeDefManager(){} // Get node definition - virtual const ContentFeatures& get(content_t c) const=0; - virtual const ContentFeatures& get(const MapNode &n) const=0; + virtual const ContentFeatures &get(content_t c) const=0; + virtual const ContentFeatures &get(const MapNode &n) const=0; virtual bool getId(const std::string &name, content_t &result) const=0; virtual content_t getId(const std::string &name) const=0; // Allows "group:name" in addition to regular node names virtual void getIds(const std::string &name, std::set<content_t> &result) const=0; - virtual const ContentFeatures& get(const std::string &name) const=0; + virtual const ContentFeatures &get(const std::string &name) const=0; - virtual void serialize(std::ostream &os, u16 protocol_version)=0; + virtual void serialize(std::ostream &os, u16 protocol_version) const=0; virtual bool getNodeRegistrationStatus() const=0; - virtual void setNodeRegistrationStatus(bool completed)=0; - - virtual void pendNodeResolve(NodeResolveInfo *nri)=0; - virtual void cancelNodeResolve(NodeResolver *resolver)=0; - virtual void runNodeResolverCallbacks()=0; - virtual bool getIdFromResolveInfo(NodeResolveInfo *nri, - const std::string &node_alt, content_t c_fallback, content_t &result)=0; - virtual bool getIdsFromResolveInfo(NodeResolveInfo *nri, - std::vector<content_t> &result)=0; + virtual void pendNodeResolve(NodeResolver *nr)=0; + virtual bool cancelNodeResolveCallback(NodeResolver *nr)=0; }; -class IWritableNodeDefManager : public INodeDefManager -{ +class IWritableNodeDefManager : public INodeDefManager { public: IWritableNodeDefManager(){} virtual ~IWritableNodeDefManager(){} virtual IWritableNodeDefManager* clone()=0; // Get node definition - virtual const ContentFeatures& get(content_t c) const=0; - virtual const ContentFeatures& get(const MapNode &n) const=0; + virtual const ContentFeatures &get(content_t c) const=0; + virtual const ContentFeatures &get(const MapNode &n) const=0; virtual bool getId(const std::string &name, content_t &result) const=0; // If not found, returns CONTENT_IGNORE virtual content_t getId(const std::string &name) const=0; // Allows "group:name" in addition to regular node names virtual void getIds(const std::string &name, std::set<content_t> &result) - const=0; + const=0; // If not found, returns the features of CONTENT_UNKNOWN - virtual const ContentFeatures& get(const std::string &name) const=0; + virtual const ContentFeatures &get(const std::string &name) const=0; // Register node definition by name (allocate an id) // If returns CONTENT_IGNORE, could not allocate id @@ -376,48 +342,50 @@ public: virtual void updateAliases(IItemDefManager *idef)=0; /* + Override textures from servers with ones specified in texturepack/override.txt + */ + virtual void applyTextureOverrides(const std::string &override_filepath)=0; + + /* Update tile textures to latest return values of TextueSource. */ - virtual void updateTextures(IGameDef *gamedef)=0; + virtual void updateTextures(IGameDef *gamedef, + void (*progress_cbk)(void *progress_args, u32 progress, u32 max_progress), + void *progress_cbk_args)=0; - virtual void serialize(std::ostream &os, u16 protocol_version)=0; + virtual void serialize(std::ostream &os, u16 protocol_version) const=0; virtual void deSerialize(std::istream &is)=0; virtual bool getNodeRegistrationStatus() const=0; virtual void setNodeRegistrationStatus(bool completed)=0; - virtual void pendNodeResolve(NodeResolveInfo *nri)=0; - virtual void cancelNodeResolve(NodeResolver *resolver)=0; - virtual void runNodeResolverCallbacks()=0; - - virtual bool getIdFromResolveInfo(NodeResolveInfo *nri, - const std::string &node_alt, content_t c_fallback, content_t &result)=0; - virtual bool getIdsFromResolveInfo(NodeResolveInfo *nri, - std::vector<content_t> &result)=0; + virtual void pendNodeResolve(NodeResolver *nr)=0; + virtual bool cancelNodeResolveCallback(NodeResolver *nr)=0; + virtual void runNodeResolveCallbacks()=0; + virtual void resetNodeResolveState()=0; }; IWritableNodeDefManager *createNodeDefManager(); class NodeResolver { public: - NodeResolver() - { - m_lookup_done = false; - m_ndef = NULL; - } + NodeResolver(); + virtual ~NodeResolver(); + virtual void resolveNodeNames() = 0; - virtual ~NodeResolver() - { - if (!m_lookup_done && m_ndef) - m_ndef->cancelNodeResolve(this); - } + bool getIdFromNrBacklog(content_t *result_out, + const std::string &node_alt, content_t c_fallback); + bool getIdsFromNrBacklog(std::vector<content_t> *result_out, + bool all_required=false, content_t c_fallback=CONTENT_IGNORE); - virtual void resolveNodeNames(NodeResolveInfo *nri) = 0; + void nodeResolveInternal(); - bool m_lookup_done; + u32 m_nodenames_idx; + u32 m_nnlistsizes_idx; + std::vector<std::string> m_nodenames; + std::vector<size_t> m_nnlistsizes; INodeDefManager *m_ndef; + bool m_resolve_done; }; - #endif - diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index 1e40a1630..d4da7a5ed 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -45,10 +45,11 @@ void NodeMetadata::serialize(std::ostream &os) const { int num_vars = m_stringvars.size(); writeU32(os, num_vars); - for(std::map<std::string, std::string>::const_iterator - i = m_stringvars.begin(); i != m_stringvars.end(); i++){ - os<<serializeString(i->first); - os<<serializeLongString(i->second); + for (StringMap::const_iterator + it = m_stringvars.begin(); + it != m_stringvars.end(); ++it) { + os << serializeString(it->first); + os << serializeLongString(it->second); } m_inventory->serialize(os); @@ -157,10 +158,21 @@ NodeMetadataList::~NodeMetadataList() clear(); } -NodeMetadata* NodeMetadataList::get(v3s16 p) +std::vector<v3s16> NodeMetadataList::getAllKeys() { - std::map<v3s16, NodeMetadata*>::const_iterator n = m_data.find(p); - if(n == m_data.end()) + std::vector<v3s16> keys; + + std::map<v3s16, NodeMetadata *>::const_iterator it; + for (it = m_data.begin(); it != m_data.end(); ++it) + keys.push_back(it->first); + + return keys; +} + +NodeMetadata *NodeMetadataList::get(v3s16 p) +{ + std::map<v3s16, NodeMetadata *>::const_iterator n = m_data.find(p); + if (n == m_data.end()) return NULL; return n->second; } @@ -168,8 +180,7 @@ NodeMetadata* NodeMetadataList::get(v3s16 p) void NodeMetadataList::remove(v3s16 p) { NodeMetadata *olddata = get(p); - if(olddata) - { + if (olddata) { delete olddata; m_data.erase(p); } @@ -183,22 +194,20 @@ void NodeMetadataList::set(v3s16 p, NodeMetadata *d) void NodeMetadataList::clear() { - for(std::map<v3s16, NodeMetadata*>::iterator - i = m_data.begin(); - i != m_data.end(); i++) - { - delete i->second; + std::map<v3s16, NodeMetadata*>::iterator it; + for (it = m_data.begin(); it != m_data.end(); ++it) { + delete it->second; } m_data.clear(); } -std::string NodeMetadata::getString(const std::string &name, unsigned short recursion) const +std::string NodeMetadata::getString(const std::string &name, + unsigned short recursion) const { - std::map<std::string, std::string>::const_iterator it; - it = m_stringvars.find(name); - if (it == m_stringvars.end()) { + StringMap::const_iterator it = m_stringvars.find(name); + if (it == m_stringvars.end()) return ""; - } + return resolveString(it->second, recursion); } @@ -211,7 +220,8 @@ void NodeMetadata::setString(const std::string &name, const std::string &var) } } -std::string NodeMetadata::resolveString(const std::string &str, unsigned short recursion) const +std::string NodeMetadata::resolveString(const std::string &str, + unsigned short recursion) const { if (recursion > 1) { return str; diff --git a/src/nodemetadata.h b/src/nodemetadata.h index 6baf3b3d3..8e84e5af3 100644 --- a/src/nodemetadata.h +++ b/src/nodemetadata.h @@ -21,9 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #define NODEMETADATA_HEADER #include "irr_v3d.h" -#include <string> #include <iostream> -#include <map> +#include <vector> +#include "util/string.h" /* NodeMetadata stores arbitary amounts of data for special blocks. @@ -42,10 +42,10 @@ class NodeMetadata public: NodeMetadata(IGameDef *gamedef); ~NodeMetadata(); - + void serialize(std::ostream &os) const; void deSerialize(std::istream &is); - + void clear(); // Generic key/value store @@ -53,19 +53,19 @@ public: void setString(const std::string &name, const std::string &var); // Support variable names in values std::string resolveString(const std::string &str, unsigned short recursion = 0) const; - std::map<std::string, std::string> getStrings() const + StringMap getStrings() const { return m_stringvars; } // The inventory - Inventory* getInventory() + Inventory *getInventory() { return m_inventory; } private: - std::map<std::string, std::string> m_stringvars; + StringMap m_stringvars; Inventory *m_inventory; }; @@ -81,18 +81,20 @@ public: void serialize(std::ostream &os) const; void deSerialize(std::istream &is, IGameDef *gamedef); - + + // Add all keys in this list to the vector keys + std::vector<v3s16> getAllKeys(); // Get pointer to data - NodeMetadata* get(v3s16 p); + NodeMetadata *get(v3s16 p); // Deletes data void remove(v3s16 p); // Deletes old data and sets a new one void set(v3s16 p, NodeMetadata *d); // Deletes all void clear(); - + private: - std::map<v3s16, NodeMetadata*> m_data; + std::map<v3s16, NodeMetadata *> m_data; }; #endif diff --git a/src/noise.cpp b/src/noise.cpp index 5223450dc..2948fb765 100644 --- a/src/noise.cpp +++ b/src/noise.cpp @@ -62,6 +62,99 @@ FlagDesc flagdesc_noiseparams[] = { /////////////////////////////////////////////////////////////////////////////// +PcgRandom::PcgRandom(u64 state, u64 seq) +{ + seed(state, seq); +} + +void PcgRandom::seed(u64 state, u64 seq) +{ + m_state = 0U; + m_inc = (seq << 1u) | 1u; + next(); + m_state += state; + next(); +} + + +u32 PcgRandom::next() +{ + u64 oldstate = m_state; + m_state = oldstate * 6364136223846793005ULL + m_inc; + + u32 xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + u32 rot = oldstate >> 59u; + return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); +} + + +u32 PcgRandom::range(u32 bound) +{ + // If the bound is 0, we cover the whole RNG's range + if (bound == 0) + return next(); + /* + If the bound is not a multiple of the RNG's range, it may cause bias, + e.g. a RNG has a range from 0 to 3 and we take want a number 0 to 2. + Using rand() % 3, the number 0 would be twice as likely to appear. + With a very large RNG range, the effect becomes less prevalent but + still present. This can be solved by modifying the range of the RNG + to become a multiple of bound by dropping values above the a threshhold. + In our example, threshhold == 4 - 3 = 1 % 3 == 1, so reject 0, thus + making the range 3 with no bias. + + This loop looks dangerous, but will always terminate due to the + RNG's property of uniformity. + */ + u32 threshhold = -bound % bound; + u32 r; + + while ((r = next()) < threshhold) + ; + + return r % bound; +} + + +s32 PcgRandom::range(s32 min, s32 max) +{ + if (max < min) + throw PrngException("Invalid range (max < min)"); + + u32 bound = max - min + 1; + return range(bound) + min; +} + + +void PcgRandom::bytes(void *out, size_t len) +{ + u8 *outb = (u8 *)out; + int bytes_left = 0; + u32 r; + + while (len--) { + if (bytes_left == 0) { + bytes_left = sizeof(u32); + r = next(); + } + + *outb = r & 0xFF; + outb++; + bytes_left--; + r >>= CHAR_BIT; + } +} + + +s32 PcgRandom::randNormalDist(s32 min, s32 max, int num_trials) +{ + s32 accum = 0; + for (int i = 0; i != num_trials; i++) + accum += range(min, max); + return myround((float)accum / num_trials); +} + +/////////////////////////////////////////////////////////////////////////////// float noise2d(int x, int y, int seed) { @@ -102,14 +195,6 @@ inline float biLinearInterpolation( { float tx = easeCurve(x); float ty = easeCurve(y); -#if 0 - return ( - v00 * (1 - tx) * (1 - ty) + - v10 * tx * (1 - ty) + - v01 * (1 - tx) * ty + - v11 * tx * ty - ); -#endif float u = linearInterpolation(v00, v10, tx); float v = linearInterpolation(v01, v11, tx); return linearInterpolation(u, v, ty); @@ -135,18 +220,6 @@ float triLinearInterpolation( float tx = easeCurve(x); float ty = easeCurve(y); float tz = easeCurve(z); -#if 0 - return ( - v000 * (1 - tx) * (1 - ty) * (1 - tz) + - v100 * tx * (1 - ty) * (1 - tz) + - v010 * (1 - tx) * ty * (1 - tz) + - v110 * tx * ty * (1 - tz) + - v001 * (1 - tx) * (1 - ty) * tz + - v101 * tx * (1 - ty) * tz + - v011 * (1 - tx) * ty * tz + - v111 * tx * ty * tz - ); -#endif float u = biLinearInterpolationNoEase(v000, v100, v010, v110, tx, ty); float v = biLinearInterpolationNoEase(v001, v101, v011, v111, tx, ty); return linearInterpolation(u, v, tz); @@ -162,33 +235,6 @@ float triLinearInterpolationNoEase( return linearInterpolation(u, v, z); } - -#if 0 -float noise2d_gradient(float x, float y, int seed) -{ - // Calculate the integer coordinates - int x0 = (x > 0.0 ? (int)x : (int)x - 1); - int y0 = (y > 0.0 ? (int)y : (int)y - 1); - // Calculate the remaining part of the coordinates - float xl = x - (float)x0; - float yl = y - (float)y0; - // Calculate random cosine lookup table indices for the integer corners. - // They are looked up as unit vector gradients from the lookup table. - int n00 = (int)((noise2d(x0, y0, seed)+1)*8); - int n10 = (int)((noise2d(x0+1, y0, seed)+1)*8); - int n01 = (int)((noise2d(x0, y0+1, seed)+1)*8); - int n11 = (int)((noise2d(x0+1, y0+1, seed)+1)*8); - // Make a dot product for the gradients and the positions, to get the values - float s = dotProduct(cos_lookup[n00], cos_lookup[(n00+12)%16], xl, yl); - float u = dotProduct(-cos_lookup[n10], cos_lookup[(n10+12)%16], 1.-xl, yl); - float v = dotProduct(cos_lookup[n01], -cos_lookup[(n01+12)%16], xl, 1.-yl); - float w = dotProduct(-cos_lookup[n11], -cos_lookup[(n11+12)%16], 1.-xl, 1.-yl); - // Interpolate between the values - return biLinearInterpolation(s,u,v,w,xl,yl); -} -#endif - - float noise2d_gradient(float x, float y, int seed, bool eased) { // Calculate the integer coordinates @@ -370,7 +416,7 @@ float NoisePerlin3D(NoiseParams *np, float x, float y, float z, int seed) } -Noise::Noise(NoiseParams *np_, int seed, int sx, int sy, int sz) +Noise::Noise(NoiseParams *np_, int seed, u32 sx, u32 sy, u32 sz) { memcpy(&np, np_, sizeof(np)); this->seed = seed; @@ -397,6 +443,13 @@ Noise::~Noise() void Noise::allocBuffers() { + if (sx < 1) + sx = 1; + if (sy < 1) + sy = 1; + if (sz < 1) + sz = 1; + this->noise_buf = NULL; resizeNoiseBuf(sz > 1); @@ -415,7 +468,7 @@ void Noise::allocBuffers() } -void Noise::setSize(int sx, int sy, int sz) +void Noise::setSize(u32 sx, u32 sy, u32 sz) { this->sx = sx; this->sy = sy; @@ -443,19 +496,28 @@ void Noise::setOctaves(int octaves) void Noise::resizeNoiseBuf(bool is3d) { - int nlx, nly, nlz; - float ofactor; - //maximum possible spread value factor - ofactor = pow(np.lacunarity, np.octaves - 1); + float ofactor = (np.lacunarity > 1.0) ? + pow(np.lacunarity, np.octaves - 1) : + np.lacunarity; + + // noise lattice point count + // (int)(sz * spread * ofactor) is # of lattice points crossed due to length + float num_noise_points_x = sx * ofactor / np.spread.X; + float num_noise_points_y = sy * ofactor / np.spread.Y; + float num_noise_points_z = sz * ofactor / np.spread.Z; + + // protect against obviously invalid parameters + if (num_noise_points_x > 1000000000.f || + num_noise_points_y > 1000000000.f || + num_noise_points_z > 1000000000.f) + throw InvalidNoiseParamsException(); - //noise lattice point count - //(int)(sz * spread * ofactor) is # of lattice points crossed due to length // + 2 for the two initial endpoints // + 1 for potentially crossing a boundary due to offset - nlx = (int)ceil(sx * ofactor / np.spread.X) + 3; - nly = (int)ceil(sy * ofactor / np.spread.Y) + 3; - nlz = is3d ? (int)ceil(sz * ofactor / np.spread.Z) + 3 : 1; + size_t nlx = (size_t)ceil(num_noise_points_x) + 3; + size_t nly = (size_t)ceil(num_noise_points_y) + 3; + size_t nlz = is3d ? (size_t)ceil(num_noise_points_z) + 3 : 1; delete[] noise_buf; try { @@ -484,8 +546,9 @@ void Noise::gradientMap2D( int seed) { float v00, v01, v10, v11, u, v, orig_u; - int index, i, j, x0, y0, noisex, noisey; - int nlx, nly; + u32 index, i, j, noisex, noisey; + u32 nlx, nly; + s32 x0, y0; bool eased = np.flags & (NOISE_FLAG_DEFAULTS | NOISE_FLAG_EASED); Interp2dFxn interpolate = eased ? @@ -498,8 +561,8 @@ void Noise::gradientMap2D( orig_u = u; //calculate noise point lattice - nlx = (int)(u + sx * step_x) + 2; - nly = (int)(v + sy * step_y) + 2; + nlx = (u32)(u + sx * step_x) + 2; + nly = (u32)(v + sy * step_y) + 2; index = 0; for (j = 0; j != nly; j++) for (i = 0; i != nlx; i++) @@ -549,8 +612,9 @@ void Noise::gradientMap3D( float v000, v010, v100, v110; float v001, v011, v101, v111; float u, v, w, orig_u, orig_v; - int index, i, j, k, x0, y0, z0, noisex, noisey, noisez; - int nlx, nly, nlz; + u32 index, i, j, k, noisex, noisey, noisez; + u32 nlx, nly, nlz; + s32 x0, y0, z0; Interp3dFxn interpolate = (np.flags & NOISE_FLAG_EASED) ? triLinearInterpolation : triLinearInterpolationNoEase; @@ -565,9 +629,9 @@ void Noise::gradientMap3D( orig_v = v; //calculate noise point lattice - nlx = (int)(u + sx * step_x) + 2; - nly = (int)(v + sy * step_y) + 2; - nlz = (int)(w + sz * step_z) + 2; + nlx = (u32)(u + sx * step_x) + 2; + nly = (u32)(v + sy * step_y) + 2; + nlz = (u32)(w + sz * step_z) + 2; index = 0; for (k = 0; k != nlz; k++) for (j = 0; j != nly; j++) diff --git a/src/noise.h b/src/noise.h index 05c877b32..0e4252dd4 100644 --- a/src/noise.h +++ b/src/noise.h @@ -26,51 +26,73 @@ #ifndef NOISE_HEADER #define NOISE_HEADER -#include "debug.h" #include "irr_v3d.h" +#include "exceptions.h" #include "util/string.h" -#define PSEUDORANDOM_MAX 32767 - extern FlagDesc flagdesc_noiseparams[]; -class PseudoRandom -{ +// Note: this class is not polymorphic so that its high level of +// optimizability may be preserved in the common use case +class PseudoRandom { public: - PseudoRandom(): m_next(0) - { - } - PseudoRandom(int seed): m_next(seed) + const static u32 RANDOM_RANGE = 32767; + + inline PseudoRandom(int seed=0): + m_next(seed) { } - void seed(int seed) + + inline void seed(int seed) { m_next = seed; } - // Returns 0...PSEUDORANDOM_MAX - int next() + + inline int next() { m_next = m_next * 1103515245 + 12345; - return((unsigned)(m_next/65536) % (PSEUDORANDOM_MAX + 1)); + return (unsigned)(m_next / 65536) % (RANDOM_RANGE + 1); } - int range(int min, int max) + + inline int range(int min, int max) { - if (max-min > (PSEUDORANDOM_MAX + 1) / 10) - { - //dstream<<"WARNING: PseudoRandom::range: max > 32767"<<std::endl; - assert(0); - } - if(min > max) - { - assert(0); - return max; - } - return (next()%(max-min+1))+min; + if (max < min) + throw PrngException("Invalid range (max < min)"); + /* + Here, we ensure the range is not too large relative to RANDOM_MAX, + as otherwise the effects of bias would become noticable. Unlike + PcgRandom, we cannot modify this RNG's range as it would change the + output of this RNG for reverse compatibility. + */ + if ((u32)(max - min) > (RANDOM_RANGE + 1) / 10) + throw PrngException("Range too large"); + + return (next() % (max - min + 1)) + min; } + private: int m_next; }; +class PcgRandom { +public: + const static s32 RANDOM_MIN = -0x7fffffff - 1; + const static s32 RANDOM_MAX = 0x7fffffff; + const static u32 RANDOM_RANGE = 0xffffffff; + + PcgRandom(u64 state=0x853c49e6748fea9bULL, u64 seq=0xda3e39cb94b95bdbULL); + void seed(u64 state, u64 seq=0xda3e39cb94b95bdbULL); + u32 next(); + u32 range(u32 bound); + s32 range(s32 min, s32 max); + void bytes(void *out, size_t len); + s32 randNormalDist(s32 min, s32 max, int num_trials=6); + +private: + u64 m_state; + u64 m_inc; +}; + #define NOISE_FLAG_DEFAULTS 0x01 #define NOISE_FLAG_EASED 0x02 #define NOISE_FLAG_ABSVALUE 0x04 @@ -89,7 +111,8 @@ struct NoiseParams { float lacunarity; u32 flags; - NoiseParams() { + NoiseParams() + { offset = 0.0f; scale = 1.0f; spread = v3f(250, 250, 250); @@ -126,18 +149,18 @@ class Noise { public: NoiseParams np; int seed; - int sx; - int sy; - int sz; + u32 sx; + u32 sy; + u32 sz; float *noise_buf; float *gradient_buf; float *persist_buf; float *result; - Noise(NoiseParams *np, int seed, int sx, int sy, int sz=1); + Noise(NoiseParams *np, int seed, u32 sx, u32 sy, u32 sz=1); ~Noise(); - void setSize(int sx, int sy, int sz=1); + void setSize(u32 sx, u32 sy, u32 sz=1); void setSpreadFactor(v3f spread); void setOctaves(int octaves); diff --git a/src/objdef.cpp b/src/objdef.cpp new file mode 100644 index 000000000..08d6844fc --- /dev/null +++ b/src/objdef.cpp @@ -0,0 +1,184 @@ +/* +Minetest +Copyright (C) 2010-2015 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +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 "objdef.h" +#include "util/numeric.h" +#include "log.h" +#include "gamedef.h" + +ObjDefManager::ObjDefManager(IGameDef *gamedef, ObjDefType type) +{ + m_objtype = type; + m_ndef = gamedef ? gamedef->getNodeDefManager() : NULL; +} + + +ObjDefManager::~ObjDefManager() +{ + for (size_t i = 0; i != m_objects.size(); i++) + delete m_objects[i]; +} + + +ObjDefHandle ObjDefManager::add(ObjDef *obj) +{ + assert(obj); + + if (obj->name.length() && getByName(obj->name)) + return OBJDEF_INVALID_HANDLE; + + u32 index = addRaw(obj); + if (index == OBJDEF_INVALID_INDEX) + return OBJDEF_INVALID_HANDLE; + + obj->handle = createHandle(index, m_objtype, obj->uid); + return obj->handle; +} + + +ObjDef *ObjDefManager::get(ObjDefHandle handle) const +{ + u32 index = validateHandle(handle); + return (index != OBJDEF_INVALID_INDEX) ? getRaw(index) : NULL; +} + + +ObjDef *ObjDefManager::set(ObjDefHandle handle, ObjDef *obj) +{ + u32 index = validateHandle(handle); + if (index == OBJDEF_INVALID_INDEX) + return NULL; + + ObjDef *oldobj = setRaw(index, obj); + + obj->uid = oldobj->uid; + obj->index = oldobj->index; + obj->handle = oldobj->handle; + + return oldobj; +} + + +u32 ObjDefManager::addRaw(ObjDef *obj) +{ + size_t nobjects = m_objects.size(); + if (nobjects >= OBJDEF_MAX_ITEMS) + return -1; + + obj->index = nobjects; + + // Ensure UID is nonzero so that a valid handle == OBJDEF_INVALID_HANDLE + // is not possible. The slight randomness bias isn't very significant. + obj->uid = myrand() & OBJDEF_UID_MASK; + if (obj->uid == 0) + obj->uid = 1; + + m_objects.push_back(obj); + + infostream << "ObjDefManager: added " << getObjectTitle() + << ": name=\"" << obj->name + << "\" index=" << obj->index + << " uid=" << obj->uid + << std::endl; + + return nobjects; +} + + +ObjDef *ObjDefManager::getRaw(u32 index) const +{ + return m_objects[index]; +} + + +ObjDef *ObjDefManager::setRaw(u32 index, ObjDef *obj) +{ + ObjDef *old_obj = m_objects[index]; + m_objects[index] = obj; + return old_obj; +} + + +ObjDef *ObjDefManager::getByName(const std::string &name) const +{ + for (size_t i = 0; i != m_objects.size(); i++) { + ObjDef *obj = m_objects[i]; + if (obj && !strcasecmp(name.c_str(), obj->name.c_str())) + return obj; + } + + return NULL; +} + + +void ObjDefManager::clear() +{ + for (size_t i = 0; i != m_objects.size(); i++) + delete m_objects[i]; + + m_objects.clear(); +} + + +u32 ObjDefManager::validateHandle(ObjDefHandle handle) const +{ + ObjDefType type; + u32 index; + u32 uid; + + bool is_valid = + (handle != OBJDEF_INVALID_HANDLE) && + decodeHandle(handle, &index, &type, &uid) && + (type == m_objtype) && + (index < m_objects.size()) && + (m_objects[index]->uid == uid); + + return is_valid ? index : -1; +} + + +ObjDefHandle ObjDefManager::createHandle(u32 index, ObjDefType type, u32 uid) +{ + ObjDefHandle handle = 0; + set_bits(&handle, 0, 18, index); + set_bits(&handle, 18, 6, type); + set_bits(&handle, 24, 7, uid); + + u32 parity = calc_parity(handle); + set_bits(&handle, 31, 1, parity); + + return handle ^ OBJDEF_HANDLE_SALT; +} + + +bool ObjDefManager::decodeHandle(ObjDefHandle handle, u32 *index, + ObjDefType *type, u32 *uid) +{ + handle ^= OBJDEF_HANDLE_SALT; + + u32 parity = get_bits(handle, 31, 1); + set_bits(&handle, 31, 1, 0); + if (parity != calc_parity(handle)) + return false; + + *index = get_bits(handle, 0, 18); + *type = (ObjDefType)get_bits(handle, 18, 6); + *uid = get_bits(handle, 24, 7); + return true; +} diff --git a/src/objdef.h b/src/objdef.h new file mode 100644 index 000000000..65e5c0176 --- /dev/null +++ b/src/objdef.h @@ -0,0 +1,95 @@ +/* +Minetest +Copyright (C) 2010-2015 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +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. +*/ + +#ifndef OBJDEF_HEADER +#define OBJDEF_HEADER + +#include "porting.h" + +class IGameDef; +class INodeDefManager; + +#define OBJDEF_INVALID_INDEX ((u32)(-1)) +#define OBJDEF_INVALID_HANDLE 0 +#define OBJDEF_HANDLE_SALT 0x00585e6fu +#define OBJDEF_MAX_ITEMS (1 << 18) +#define OBJDEF_UID_MASK ((1 << 7) - 1) + +typedef u32 ObjDefHandle; + +enum ObjDefType { + OBJDEF_GENERIC, + OBJDEF_BIOME, + OBJDEF_ORE, + OBJDEF_DECORATION, + OBJDEF_SCHEMATIC, +}; + +class ObjDef { +public: + virtual ~ObjDef() {} + + u32 index; + u32 uid; + ObjDefHandle handle; + std::string name; +}; + +// WARNING: Ownership of ObjDefs is transferred to the ObjDefManager it is +// added/set to. Note that ObjDefs managed by ObjDefManager are NOT refcounted, +// so the same ObjDef instance must not be referenced multiple +class ObjDefManager { +public: + ObjDefManager(IGameDef *gamedef, ObjDefType type); + virtual ~ObjDefManager(); + + virtual const char *getObjectTitle() const { return "ObjDef"; } + + virtual void clear(); + virtual ObjDef *getByName(const std::string &name) const; + + //// Add new/get/set object definitions by handle + virtual ObjDefHandle add(ObjDef *obj); + virtual ObjDef *get(ObjDefHandle handle) const; + virtual ObjDef *set(ObjDefHandle handle, ObjDef *obj); + + //// Raw variants that work on indexes + virtual u32 addRaw(ObjDef *obj); + + // It is generally assumed that getRaw() will always return a valid object + // This won't be true if people do odd things such as call setRaw() with NULL + virtual ObjDef *getRaw(u32 index) const; + virtual ObjDef *setRaw(u32 index, ObjDef *obj); + + size_t getNumObjects() const { return m_objects.size(); } + ObjDefType getType() const { return m_objtype; } + INodeDefManager *getNodeDef() const { return m_ndef; } + + u32 validateHandle(ObjDefHandle handle) const; + static ObjDefHandle createHandle(u32 index, ObjDefType type, u32 uid); + static bool decodeHandle(ObjDefHandle handle, u32 *index, + ObjDefType *type, u32 *uid); + +protected: + INodeDefManager *m_ndef; + std::vector<ObjDef *> m_objects; + ObjDefType m_objtype; +}; + +#endif diff --git a/src/particles.cpp b/src/particles.cpp index 603e38cdd..15e2a6597 100644 --- a/src/particles.cpp +++ b/src/particles.cpp @@ -20,9 +20,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "particles.h" #include "constants.h" #include "debug.h" -#include "main.h" // For g_profiler and g_settings #include "settings.h" -#include "tile.h" +#include "client/tile.h" #include "gamedef.h" #include "collision.h" #include <stdlib.h> @@ -107,20 +106,13 @@ Particle::~Particle() void Particle::OnRegisterSceneNode() { if (IsVisible) - { - SceneManager->registerNodeForRendering - (this, scene::ESNRP_TRANSPARENT); - SceneManager->registerNodeForRendering - (this, scene::ESNRP_SOLID); - } + SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT); ISceneNode::OnRegisterSceneNode(); } void Particle::render() { - // TODO: Render particles in front of water and the selectionbox - video::IVideoDriver* driver = SceneManager->getVideoDriver(); driver->setMaterial(m_material); driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); @@ -316,7 +308,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env) *(m_maxsize-m_minsize) +m_minsize; - new Particle( + Particle* toadd = new Particle( m_gamedef, m_smgr, m_player, @@ -331,6 +323,7 @@ void ParticleSpawner::step(float dtime, ClientEnvironment* env) m_texture, v2f(0.0, 0.0), v2f(1.0, 1.0)); + m_particlemanager->addParticle(toadd); } } } @@ -441,7 +434,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, IGameDef *gamedef, } } video::ITexture *texture = - gamedef->tsrc()->getTexture(*(event->add_particlespawner.texture)); + gamedef->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture)); ParticleSpawner* toadd = new ParticleSpawner(gamedef, smgr, player, event->add_particlespawner.amount, @@ -484,7 +477,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, IGameDef *gamedef, if (event->type == CE_SPAWN_PARTICLE) { video::ITexture *texture = - gamedef->tsrc()->getTexture(*(event->spawn_particle.texture)); + gamedef->tsrc()->getTextureForMesh(*(event->spawn_particle.texture)); Particle* toadd = new Particle(gamedef, smgr, player, m_env, *event->spawn_particle.pos, diff --git a/src/particles.h b/src/particles.h index d7f1147f1..2bc2e7bfa 100644 --- a/src/particles.h +++ b/src/particles.h @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iostream> #include "irrlichttypes_extrabloated.h" -#include "tile.h" +#include "client/tile.h" #include "localplayer.h" #include "environment.h" diff --git a/src/pathfinder.cpp b/src/pathfinder.cpp index d39bdab3a..8eb52bfd1 100644 --- a/src/pathfinder.cpp +++ b/src/pathfinder.cpp @@ -310,27 +310,16 @@ std::vector<v3s16> pathfinder::get_Path(ServerEnvironment* env, print_path(path); #endif - //optimize path - std::vector<v3s16> optimized_path; - - std::vector<v3s16>::iterator startpos = path.begin(); - optimized_path.push_back(source); - + //finalize path + std::vector<v3s16> full_path; for (std::vector<v3s16>::iterator i = path.begin(); i != path.end(); i++) { - if (!m_env->line_of_sight( - tov3f(getIndexElement(*startpos).pos), - tov3f(getIndexElement(*i).pos))) { - optimized_path.push_back(getIndexElement(*(i-1)).pos); - startpos = (i-1); - } + full_path.push_back(getIndexElement(*i).pos); } - optimized_path.push_back(destination); - #ifdef PATHFINDER_DEBUG - std::cout << "Optimized path:" << std::endl; - print_path(optimized_path); + std::cout << "full path:" << std::endl; + print_path(full_path); #endif #ifdef PATHFINDER_CALC_TIME timespec ts2; @@ -344,7 +333,7 @@ std::vector<v3s16> pathfinder::get_Path(ServerEnvironment* env, std::cout << "Calculating path took: " << (ts2.tv_sec - ts.tv_sec) << "s " << ms << "ms " << us << "us " << ns << "ns " << std::endl; #endif - return optimized_path; + return full_path; } else { #ifdef PATHFINDER_DEBUG @@ -532,7 +521,7 @@ path_cost pathfinder::calc_cost(v3s16 pos,v3s16 dir) { if ((testpos.Y >= m_limits.Y.min) && (node_at_pos.param0 != CONTENT_IGNORE) && (node_at_pos.param0 != CONTENT_AIR)) { - if (((pos2.Y - testpos.Y)*-1) <= m_maxdrop) { + if ((pos2.Y - testpos.Y - 1) <= m_maxdrop) { retval.valid = true; retval.value = 2; //difference of y-pos +1 (target node is ABOVE solid node) diff --git a/src/player.cpp b/src/player.cpp index 0da761eed..cb2286ef6 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "player.h" #include <fstream> +#include "jthread/jmutexautolock.h" #include "util/numeric.h" #include "hud.h" #include "constants.h" @@ -43,6 +44,7 @@ Player::Player(IGameDef *gamedef, const char *name): hp(PLAYER_MAX_HP), hurt_tilt_timer(0), hurt_tilt_strength(0), + protocol_version(0), peer_id(PEER_ID_INEXISTENT), keyPressed(0), // protected @@ -70,9 +72,11 @@ Player::Player(IGameDef *gamedef, const char *name): //"image[1,0.6;1,2;player.png]" "list[current_player;main;0,3.5;8,4;]" "list[current_player;craft;3,0;3,3;]" + "listring[]" "list[current_player;craftpreview;7,1;1,1;]"; - // Initialize movement settings at default values, so movement can work if the server fails to send them + // Initialize movement settings at default values, so movement can work + // if the server fails to send them movement_acceleration_default = 3 * BS; movement_acceleration_air = 2 * BS; movement_acceleration_fast = 10 * BS; @@ -85,6 +89,7 @@ Player::Player(IGameDef *gamedef, const char *name): movement_liquid_fluidity_smooth = 0.5 * BS; movement_liquid_sink = 10 * BS; movement_gravity = 9.81 * BS; + local_animation_speed = 0.0; // Movement overrides are multipliers and must be 1 by default physics_override_speed = 1; @@ -93,9 +98,10 @@ Player::Player(IGameDef *gamedef, const char *name): physics_override_sneak = true; physics_override_sneak_glitch = true; - hud_flags = HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE | - HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE | - HUD_FLAG_BREATHBAR_VISIBLE; + hud_flags = + HUD_FLAG_HOTBAR_VISIBLE | HUD_FLAG_HEALTHBAR_VISIBLE | + HUD_FLAG_CROSSHAIR_VISIBLE | HUD_FLAG_WIELDITEM_VISIBLE | + HUD_FLAG_BREATHBAR_VISIBLE | HUD_FLAG_MINIMAP_VISIBLE; hud_hotbar_itemcount = HUD_HOTBAR_ITEMCOUNT_DEFAULT; } @@ -116,31 +122,12 @@ void Player::accelerateHorizontal(v3f target_speed, f32 max_increase) f32 dl = d_wanted.getLength(); if(dl > max_increase) dl = max_increase; - + v3f d = d_wanted.normalize() * dl; m_speed.X += d.X; m_speed.Z += d.Z; -#if 0 // old code - if(m_speed.X < target_speed.X - max_increase) - m_speed.X += max_increase; - else if(m_speed.X > target_speed.X + max_increase) - m_speed.X -= max_increase; - else if(m_speed.X < target_speed.X) - m_speed.X = target_speed.X; - else if(m_speed.X > target_speed.X) - m_speed.X = target_speed.X; - - if(m_speed.Z < target_speed.Z - max_increase) - m_speed.Z += max_increase; - else if(m_speed.Z > target_speed.Z + max_increase) - m_speed.Z -= max_increase; - else if(m_speed.Z < target_speed.Z) - m_speed.Z = target_speed.Z; - else if(m_speed.Z > target_speed.Z) - m_speed.Z = target_speed.Z; -#endif } // Vertical acceleration (Y), X and Z directions are ignored @@ -157,16 +144,6 @@ void Player::accelerateVertical(v3f target_speed, f32 max_increase) m_speed.Y += d_wanted; -#if 0 // old code - if(m_speed.Y < target_speed.Y - max_increase) - m_speed.Y += max_increase; - else if(m_speed.Y > target_speed.Y + max_increase) - m_speed.Y -= max_increase; - else if(m_speed.Y < target_speed.Y) - m_speed.Y = target_speed.Y; - else if(m_speed.Y > target_speed.Y) - m_speed.Y = target_speed.Y; -#endif } v3s16 Player::getLightPosition() const @@ -240,6 +217,8 @@ void Player::deSerialize(std::istream &is, std::string playername) u32 Player::addHud(HudElement *toadd) { + JMutexAutoLock lock(m_mutex); + u32 id = getFreeHudID(); if (id < hud.size()) @@ -252,6 +231,8 @@ u32 Player::addHud(HudElement *toadd) HudElement* Player::getHud(u32 id) { + JMutexAutoLock lock(m_mutex); + if (id < hud.size()) return hud[id]; @@ -260,6 +241,8 @@ HudElement* Player::getHud(u32 id) HudElement* Player::removeHud(u32 id) { + JMutexAutoLock lock(m_mutex); + HudElement* retval = NULL; if (id < hud.size()) { retval = hud[id]; @@ -270,6 +253,8 @@ HudElement* Player::removeHud(u32 id) void Player::clearHud() { + JMutexAutoLock lock(m_mutex); + while(!hud.empty()) { delete hud.back(); hud.pop_back(); diff --git a/src/player.h b/src/player.h index 435875233..3a336afc4 100644 --- a/src/player.h +++ b/src/player.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include "inventory.h" #include "constants.h" // BS +#include "jthread/jmutex.h" #include <list> #define PLAYERNAME_SIZE 20 @@ -91,6 +92,9 @@ class PlayerSAO; struct HudElement; class Environment; +// IMPORTANT: +// Do *not* perform an assignment or copy operation on a Player or +// RemotePlayer object! This will copy the lock held for HUD synchronization class Player { public: @@ -101,7 +105,7 @@ public: virtual void move(f32 dtime, Environment *env, f32 pos_max_d) {} virtual void move(f32 dtime, Environment *env, f32 pos_max_d, - std::list<CollisionInfo> *collision_info) + std::vector<CollisionInfo> *collision_info) {} v3f getSpeed() @@ -113,7 +117,7 @@ public: { m_speed = speed; } - + void accelerateHorizontal(v3f target_speed, f32 max_increase); void accelerateVertical(v3f target_speed, f32 max_increase); @@ -126,13 +130,8 @@ public: v3f getEyeOffset() { - // This is at the height of the eyes of the current figure - // return v3f(0, BS*1.5, 0); - // This is more like in minecraft - if(camera_barely_in_ceiling) - return v3f(0,BS*1.5,0); - else - return v3f(0,BS*1.625,0); + float eye_height = camera_barely_in_ceiling ? 1.5f : 1.625f; + return v3f(0, BS * eye_height, 0); } v3f getEyePosition() @@ -193,16 +192,17 @@ public: return (m_yaw + 90.) * core::DEGTORAD; } - const char * getName() const + const char *getName() const { return m_name; } - core::aabbox3d<f32> getCollisionbox() { + core::aabbox3d<f32> getCollisionbox() + { return m_collisionbox; } - u32 getFreeHudID() const { + u32 getFreeHudID() { size_t size = hud.size(); for (size_t i = 0; i != size; i++) { if (!hud[i]) @@ -211,12 +211,91 @@ public: return size; } + void setHotbarItemcount(s32 hotbar_itemcount) + { + hud_hotbar_itemcount = hotbar_itemcount; + } + + s32 getHotbarItemcount() + { + return hud_hotbar_itemcount; + } + + void setHotbarImage(const std::string &name) + { + hud_hotbar_image = name; + } + + std::string getHotbarImage() + { + return hud_hotbar_image; + } + + void setHotbarSelectedImage(const std::string &name) + { + hud_hotbar_selected_image = name; + } + + std::string getHotbarSelectedImage() { + return hud_hotbar_selected_image; + } + + void setSky(const video::SColor &bgcolor, const std::string &type, + const std::vector<std::string> ¶ms) + { + m_sky_bgcolor = bgcolor; + m_sky_type = type; + m_sky_params = params; + } + + void getSky(video::SColor *bgcolor, std::string *type, + std::vector<std::string> *params) + { + *bgcolor = m_sky_bgcolor; + *type = m_sky_type; + *params = m_sky_params; + } + + void overrideDayNightRatio(bool do_override, float ratio) + { + m_day_night_ratio_do_override = do_override; + m_day_night_ratio = ratio; + } + + void getDayNightRatio(bool *do_override, float *ratio) + { + *do_override = m_day_night_ratio_do_override; + *ratio = m_day_night_ratio; + } + + void setLocalAnimations(v2s32 frames[4], float frame_speed) + { + for (int i = 0; i < 4; i++) + local_animations[i] = frames[i]; + local_animation_speed = frame_speed; + } + + void getLocalAnimations(v2s32 *frames, float *frame_speed) + { + for (int i = 0; i < 4; i++) + frames[i] = local_animations[i]; + *frame_speed = local_animation_speed; + } + virtual bool isLocal() const - { return false; } + { + return false; + } + virtual PlayerSAO *getPlayerSAO() - { return NULL; } + { + return NULL; + } + virtual void setPlayerSAO(PlayerSAO *sao) - { assert(0); } + { + FATAL_ERROR("FIXME"); + } /* serialize() writes a bunch of text that can contain @@ -238,6 +317,9 @@ public: inventory.setModified(x); } + // Use a function, if isDead can be defined by other conditions + bool isDead() { return hp == 0; } + bool touching_ground; // This oscillates so that the player jumps a bit above the surface bool in_liquid; @@ -248,7 +330,9 @@ public: bool is_climbing; bool swimming_vertical; bool camera_barely_in_ceiling; - + v3f eye_offset_first; + v3f eye_offset_third; + Inventory inventory; f32 movement_acceleration_default; @@ -278,18 +362,19 @@ public: float hurt_tilt_timer; float hurt_tilt_strength; + u16 protocol_version; u16 peer_id; std::string inventory_formspec; - + PlayerControl control; PlayerControl getPlayerControl() { return control; } - + u32 keyPressed; - + HudElement* getHud(u32 id); u32 addHud(HudElement* hud); @@ -301,6 +386,8 @@ public: u32 hud_flags; s32 hud_hotbar_itemcount; + std::string hud_hotbar_image; + std::string hud_hotbar_selected_image; protected: IGameDef *m_gamedef; @@ -315,6 +402,18 @@ protected: bool m_dirty; std::vector<HudElement *> hud; + + std::string m_sky_type; + video::SColor m_sky_bgcolor; + std::vector<std::string> m_sky_params; + + bool m_day_night_ratio_do_override; + float m_day_night_ratio; +private: + // Protect some critical areas + // hud for example can be modified by EmergeThread + // and ServerThread + JMutex m_mutex; }; @@ -337,7 +436,7 @@ public: void setPlayerSAO(PlayerSAO *sao) { m_sao = sao; } void setPosition(const v3f &position); - + private: PlayerSAO *m_sao; }; diff --git a/src/porting.cpp b/src/porting.cpp index 8a685539b..44f1fcff1 100644 --- a/src/porting.cpp +++ b/src/porting.cpp @@ -35,12 +35,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <unistd.h> #include <sys/utsname.h> #endif - +#if defined(__hpux) + #define _PSTAT64 + #include <sys/pstat.h> +#endif #if !defined(_WIN32) && !defined(__APPLE__) && \ !defined(__ANDROID__) && !defined(SERVER) #define XORG_USED #endif - #ifdef XORG_USED #include <X11/Xlib.h> #include <X11/Xutil.h> @@ -51,7 +53,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "log.h" #include "util/string.h" -#include "main.h" #include "settings.h" #include <list> @@ -74,8 +75,7 @@ bool * signal_handler_killstatus(void) void sigint_handler(int sig) { - if(g_killed == false) - { + if(!g_killed) { dstream<<DTIME<<"INFO: sigint_handler(): " <<"Ctrl-C pressed, shutting down."<<std::endl; @@ -85,9 +85,7 @@ void sigint_handler(int sig) debug_stacks_print();*/ g_killed = true; - } - else - { + } else { (void)signal(SIGINT, SIG_DFL); } } @@ -100,42 +98,32 @@ void signal_handler_init(void) #else // _WIN32 #include <signal.h> - BOOL WINAPI event_handler(DWORD sig) - { - switch(sig) - { - case CTRL_C_EVENT: - case CTRL_CLOSE_EVENT: - case CTRL_LOGOFF_EVENT: - case CTRL_SHUTDOWN_EVENT: - - if(g_killed == false) - { - dstream<<DTIME<<"INFO: event_handler(): " - <<"Ctrl+C, Close Event, Logoff Event or Shutdown Event, shutting down."<<std::endl; - // Comment out for less clutter when testing scripts - /*dstream<<DTIME<<"INFO: event_handler(): " - <<"Printing debug stacks"<<std::endl; - debug_stacks_print();*/ - - g_killed = true; - } - else - { - (void)signal(SIGINT, SIG_DFL); - } - - break; - case CTRL_BREAK_EVENT: - break; +BOOL WINAPI event_handler(DWORD sig) +{ + switch (sig) { + case CTRL_C_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + if (g_killed == false) { + dstream << DTIME << "INFO: event_handler(): " + << "Ctrl+C, Close Event, Logoff Event or Shutdown Event," + " shutting down." << std::endl; + g_killed = true; + } else { + (void)signal(SIGINT, SIG_DFL); } - - return TRUE; + break; + case CTRL_BREAK_EVENT: + break; } + return TRUE; +} + void signal_handler_init(void) { - SetConsoleCtrlHandler( (PHANDLER_ROUTINE)event_handler,TRUE); + SetConsoleCtrlHandler((PHANDLER_ROUTINE)event_handler, TRUE); } #endif @@ -144,7 +132,8 @@ void signal_handler_init(void) /* Multithreading support */ -int getNumberOfProcessors() { +int getNumberOfProcessors() +{ #if defined(_SC_NPROCESSORS_ONLN) return sysconf(_SC_NPROCESSORS_ONLN); @@ -178,7 +167,8 @@ int getNumberOfProcessors() { #ifndef __ANDROID__ -bool threadBindToProcessor(threadid_t tid, int pnumber) { +bool threadBindToProcessor(threadid_t tid, int pnumber) +{ #if defined(_WIN32) HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, 0, tid); @@ -202,7 +192,7 @@ bool threadBindToProcessor(threadid_t tid, int pnumber) { #elif defined(__sun) || defined(sun) return processor_bind(P_LWPID, MAKE_LWPID_PTHREAD(tid), - pnumber, NULL) == 0; + pnumber, NULL) == 0; #elif defined(_AIX) @@ -213,7 +203,7 @@ bool threadBindToProcessor(threadid_t tid, int pnumber) { pthread_spu_t answer; return pthread_processor_bind_np(PTHREAD_BIND_ADVISORY_NP, - &answer, pnumber, tid) == 0; + &answer, pnumber, tid) == 0; #elif defined(__APPLE__) @@ -222,7 +212,7 @@ bool threadBindToProcessor(threadid_t tid, int pnumber) { thread_port_t threadport = pthread_mach_thread_np(tid); tapol.affinity_tag = pnumber + 1; return thread_policy_set(threadport, THREAD_AFFINITY_POLICY, - (thread_policy_t)&tapol, THREAD_AFFINITY_POLICY_COUNT) == KERN_SUCCESS; + (thread_policy_t)&tapol, THREAD_AFFINITY_POLICY_COUNT) == KERN_SUCCESS; #else @@ -232,7 +222,8 @@ bool threadBindToProcessor(threadid_t tid, int pnumber) { } #endif -bool threadSetPriority(threadid_t tid, int prio) { +bool threadSetPriority(threadid_t tid, int prio) +{ #if defined(_WIN32) HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, 0, tid); @@ -287,14 +278,14 @@ void pathRemoveFile(char *path, char delim) path[i] = 0; } -bool detectMSVCBuildDir(char *c_path) +bool detectMSVCBuildDir(const std::string &path) { - std::string path(c_path); const char *ends[] = { "bin\\Release", "bin\\Debug", "bin\\Build", - NULL}; + NULL + }; return (removeStringEnd(path, ends) != ""); } @@ -312,14 +303,14 @@ std::string get_sysinfo() oss << "Windows/" << osvi.dwMajorVersion << "." << osvi.dwMinorVersion; - if(osvi.szCSDVersion[0]) + if (osvi.szCSDVersion[0]) oss << "-" << tmp; oss << " "; #ifdef _WIN64 oss << "x86_64"; #else BOOL is64 = FALSE; - if(IsWow64Process(GetCurrentProcess(), &is64) && is64) + if (IsWow64Process(GetCurrentProcess(), &is64) && is64) oss << "x86_64"; // 32-bit app on 64-bit OS else oss << "x86"; @@ -334,232 +325,323 @@ std::string get_sysinfo() #endif } -void initializePaths() + +bool getCurrentWorkingDir(char *buf, size_t len) { -#if RUN_IN_PLACE - /* - Use relative paths if RUN_IN_PLACE - */ +#ifdef _WIN32 + DWORD ret = GetCurrentDirectory(len, buf); + return (ret != 0) && (ret <= len); +#else + return getcwd(buf, len); +#endif +} - infostream<<"Using relative paths (RUN_IN_PLACE)"<<std::endl; - /* - Windows - */ - #if defined(_WIN32) +bool getExecPathFromProcfs(char *buf, size_t buflen) +{ +#ifndef _WIN32 + buflen--; - const DWORD buflen = 1000; - char buf[buflen]; - DWORD len; + ssize_t len; + if ((len = readlink("/proc/self/exe", buf, buflen)) == -1 && + (len = readlink("/proc/curproc/file", buf, buflen)) == -1 && + (len = readlink("/proc/curproc/exe", buf, buflen)) == -1) + return false; - // Find path of executable and set path_share relative to it - len = GetModuleFileName(GetModuleHandle(NULL), buf, buflen); - assert(len < buflen); - pathRemoveFile(buf, '\\'); + buf[len] = '\0'; + return true; +#else + return false; +#endif +} - if(detectMSVCBuildDir(buf)){ - infostream<<"MSVC build directory detected"<<std::endl; - path_share = std::string(buf) + "\\..\\.."; - path_user = std::string(buf) + "\\..\\.."; - } - else{ - path_share = std::string(buf) + "\\.."; - path_user = std::string(buf) + "\\.."; - } +//// Windows +#if defined(_WIN32) - /* - Linux - */ - #elif defined(linux) +bool getCurrentExecPath(char *buf, size_t len) +{ + DWORD written = GetModuleFileNameA(NULL, buf, len); + if (written == 0 || written == len) + return false; - char buf[BUFSIZ]; - memset(buf, 0, BUFSIZ); - // Get path to executable - assert(readlink("/proc/self/exe", buf, BUFSIZ-1) != -1); + return true; +} - pathRemoveFile(buf, '/'); - path_share = std::string(buf) + "/.."; - path_user = std::string(buf) + "/.."; +//// Linux +#elif defined(linux) || defined(__linux) || defined(__linux__) - /* - OS X - */ - #elif defined(__APPLE__) +bool getCurrentExecPath(char *buf, size_t len) +{ + if (!getExecPathFromProcfs(buf, len)) + return false; - //https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/dyld.3.html - //TODO: Test this code - char buf[BUFSIZ]; - uint32_t len = sizeof(buf); - assert(_NSGetExecutablePath(buf, &len) != -1); + return true; +} + + +//// Mac OS X, Darwin +#elif defined(__APPLE__) + +bool getCurrentExecPath(char *buf, size_t len) +{ + uint32_t lenb = (uint32_t)len; + if (_NSGetExecutablePath(buf, &lenb) == -1) + return false; + + return true; +} - pathRemoveFile(buf, '/'); - path_share = std::string(buf) + "/.."; - path_user = std::string(buf) + "/.."; +//// FreeBSD, NetBSD, DragonFlyBSD +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__) - /* - FreeBSD - */ - #elif defined(__FreeBSD__) +bool getCurrentExecPath(char *buf, size_t len) +{ + // Try getting path from procfs first, since valgrind + // doesn't work with the latter + if (getExecPathFromProcfs(buf, len)) + return true; int mib[4]; - char buf[BUFSIZ]; - size_t len = sizeof(buf); mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PATHNAME; mib[3] = -1; - assert(sysctl(mib, 4, buf, &len, NULL, 0) != -1); - pathRemoveFile(buf, '/'); + if (sysctl(mib, 4, buf, &len, NULL, 0) == -1) + return false; - path_share = std::string(buf) + "/.."; - path_user = std::string(buf) + "/.."; + return true; +} - #else - //TODO: Get path of executable. This assumes working directory is bin/ - dstream<<"WARNING: Relative path not properly supported on this platform" - <<std::endl; +//// Solaris +#elif defined(__sun) || defined(sun) + +bool getCurrentExecPath(char *buf, size_t len) +{ + const char *exec = getexecname(); + if (exec == NULL) + return false; + + if (strlcpy(buf, exec, len) >= len) + return false; + + return true; +} - /* scriptapi no longer allows paths that start with "..", so assuming that - the current working directory is bin/, strip off the last component. */ - char *cwd = getcwd(NULL, 0); - pathRemoveFile(cwd, '/'); - path_share = std::string(cwd); - path_user = std::string(cwd); - #endif +// HP-UX +#elif defined(__hpux) + +bool getCurrentExecPath(char *buf, size_t len) +{ + struct pst_status psts; + + if (pstat_getproc(&psts, sizeof(psts), 0, getpid()) == -1) + return false; + + if (pstat_getpathname(buf, len, &psts.pst_fid_text) == -1) + return false; -#else // RUN_IN_PLACE + return true; +} - /* - Use platform-specific paths otherwise - */ - infostream<<"Using system-wide paths (NOT RUN_IN_PLACE)"<<std::endl; +#else - /* - Windows - */ - #if defined(_WIN32) +bool getCurrentExecPath(char *buf, size_t len) +{ + return false; +} + +#endif - const DWORD buflen = 1000; - char buf[buflen]; - DWORD len; + +//// Windows +#if defined(_WIN32) + +bool setSystemPaths() +{ + char buf[BUFSIZ]; // Find path of executable and set path_share relative to it - len = GetModuleFileName(GetModuleHandle(NULL), buf, buflen); - assert(len < buflen); + FATAL_ERROR_IF(!getCurrentExecPath(buf, sizeof(buf)), + "Failed to get current executable path"); pathRemoveFile(buf, '\\'); // Use ".\bin\.." path_share = std::string(buf) + "\\.."; // Use "C:\Documents and Settings\user\Application Data\<PROJECT_NAME>" - len = GetEnvironmentVariable("APPDATA", buf, buflen); - assert(len < buflen); + DWORD len = GetEnvironmentVariable("APPDATA", buf, sizeof(buf)); + FATAL_ERROR_IF(len == 0 || len > sizeof(buf), "Failed to get APPDATA"); + path_user = std::string(buf) + DIR_DELIM + PROJECT_NAME; + return true; +} - /* - Linux - */ - #elif defined(linux) - // Get path to executable - std::string bindir = ""; - { - char buf[BUFSIZ]; - memset(buf, 0, BUFSIZ); - if (readlink("/proc/self/exe", buf, BUFSIZ-1) == -1) { - errorstream << "Unable to read bindir "<< std::endl; -#ifndef __ANDROID__ - assert("Unable to read bindir" == 0); +//// Linux +#elif defined(linux) || defined(__linux) + +bool setSystemPaths() +{ + char buf[BUFSIZ]; + + if (!getCurrentExecPath(buf, sizeof(buf))) { +#ifdef __ANDROID__ + errorstream << "Unable to read bindir "<< std::endl; +#else + FATAL_ERROR("Unable to read bindir"); #endif - } else { - pathRemoveFile(buf, '/'); - bindir = buf; - } + return false; } + pathRemoveFile(buf, '/'); + std::string bindir(buf); + // Find share directory from these. // It is identified by containing the subdirectory "builtin". std::list<std::string> trylist; std::string static_sharedir = STATIC_SHAREDIR; - if(static_sharedir != "" && static_sharedir != ".") + if (static_sharedir != "" && static_sharedir != ".") trylist.push_back(static_sharedir); - trylist.push_back( - bindir + DIR_DELIM + ".." + DIR_DELIM + "share" + DIR_DELIM + PROJECT_NAME); - trylist.push_back(bindir + DIR_DELIM + ".."); + + trylist.push_back(bindir + DIR_DELIM ".." DIR_DELIM "share" + DIR_DELIM + PROJECT_NAME); + trylist.push_back(bindir + DIR_DELIM ".."); + #ifdef __ANDROID__ trylist.push_back(path_user); #endif - for(std::list<std::string>::const_iterator i = trylist.begin(); - i != trylist.end(); i++) - { + for (std::list<std::string>::const_iterator + i = trylist.begin(); i != trylist.end(); i++) { const std::string &trypath = *i; - if(!fs::PathExists(trypath) || !fs::PathExists(trypath + DIR_DELIM + "builtin")){ - dstream<<"WARNING: system-wide share not found at \"" - <<trypath<<"\""<<std::endl; + if (!fs::PathExists(trypath) || + !fs::PathExists(trypath + DIR_DELIM + "builtin")) { + dstream << "WARNING: system-wide share not found at \"" + << trypath << "\""<< std::endl; continue; } + // Warn if was not the first alternative - if(i != trylist.begin()){ - dstream<<"WARNING: system-wide share found at \"" - <<trypath<<"\""<<std::endl; + if (i != trylist.begin()) { + dstream << "WARNING: system-wide share found at \"" + << trypath << "\"" << std::endl; } + path_share = trypath; break; } + #ifndef __ANDROID__ - path_user = std::string(getenv("HOME")) + DIR_DELIM + "." + PROJECT_NAME; + path_user = std::string(getenv("HOME")) + DIR_DELIM "." + + PROJECT_NAME; #endif - /* - OS X - */ - #elif defined(__APPLE__) + return true; +} + - // Code based on - // http://stackoverflow.com/questions/516200/relative-paths-not-working-in-xcode-c +//// Mac OS X +#elif defined(__APPLE__) + +bool setSystemPaths() +{ CFBundleRef main_bundle = CFBundleGetMainBundle(); CFURLRef resources_url = CFBundleCopyResourcesDirectoryURL(main_bundle); char path[PATH_MAX]; - if(CFURLGetFileSystemRepresentation(resources_url, TRUE, (UInt8 *)path, PATH_MAX)) - { - dstream<<"Bundle resource path: "<<path<<std::endl; - //chdir(path); - path_share = std::string(path) + DIR_DELIM + STATIC_SHAREDIR; - } - else - { - // error! - dstream<<"WARNING: Could not determine bundle resource path"<<std::endl; + if (CFURLGetFileSystemRepresentation(resources_url, + TRUE, (UInt8 *)path, PATH_MAX)) { + path_share = std::string(path); + } else { + dstream << "WARNING: Could not determine bundle resource path" << std::endl; } CFRelease(resources_url); - path_user = std::string(getenv("HOME")) + "/Library/Application Support/" + PROJECT_NAME; + path_user = std::string(getenv("HOME")) + + "/Library/Application Support/" + + PROJECT_NAME; + return true; +} + - #else // FreeBSD, and probably many other POSIX-like systems. +#else +bool setSystemPaths() +{ path_share = STATIC_SHAREDIR; - path_user = std::string(getenv("HOME")) + DIR_DELIM + "." + PROJECT_NAME; + path_user = std::string(getenv("HOME")) + DIR_DELIM "." + + lowercase(PROJECT_NAME); + return true; +} - #endif -#endif // RUN_IN_PLACE -} +#endif -static irr::IrrlichtDevice *device; -void initIrrlicht(irr::IrrlichtDevice *device_) +void initializePaths() { - device = device_; +#if RUN_IN_PLACE + char buf[BUFSIZ]; + + infostream << "Using relative paths (RUN_IN_PLACE)" << std::endl; + + bool success = + getCurrentExecPath(buf, sizeof(buf)) || + getExecPathFromProcfs(buf, sizeof(buf)); + + if (success) { + pathRemoveFile(buf, DIR_DELIM_CHAR); + std::string execpath(buf); + + path_share = execpath + DIR_DELIM ".."; + path_user = execpath + DIR_DELIM ".."; + + if (detectMSVCBuildDir(execpath)) { + path_share += DIR_DELIM ".."; + path_user += DIR_DELIM ".."; + } + } else { + errorstream << "Failed to get paths by executable location, " + "trying cwd" << std::endl; + + if (!getCurrentWorkingDir(buf, sizeof(buf))) + FATAL_ERROR("Ran out of methods to get paths"); + + size_t cwdlen = strlen(buf); + if (cwdlen >= 1 && buf[cwdlen - 1] == DIR_DELIM_CHAR) { + cwdlen--; + buf[cwdlen] = '\0'; + } + + if (cwdlen >= 4 && !strcmp(buf + cwdlen - 4, DIR_DELIM "bin")) + pathRemoveFile(buf, DIR_DELIM_CHAR); + + std::string execpath(buf); + + path_share = execpath; + path_user = execpath; + } + +#else + infostream << "Using system-wide paths (NOT RUN_IN_PLACE)" << std::endl; + + if (!setSystemPaths()) + errorstream << "Failed to get one or more system-wide path" << std::endl; + +#endif + + infostream << "Detected share path: " << path_share << std::endl; + infostream << "Detected user path: " << path_user << std::endl; } + + void setXorgClassHint(const video::SExposedVideoData &video_data, const std::string &name) { @@ -577,17 +659,33 @@ void setXorgClassHint(const video::SExposedVideoData &video_data, #endif } + +//// +//// Video/Display Information (Client-only) +//// + #ifndef SERVER + +static irr::IrrlichtDevice *device; + +void initIrrlicht(irr::IrrlichtDevice *device_) +{ + device = device_; +} + v2u32 getWindowSize() { return device->getVideoDriver()->getScreenSize(); } -std::vector<core::vector3d<u32> > getVideoModes() +std::vector<core::vector3d<u32> > getSupportedVideoModes() { + IrrlichtDevice *nulldevice = createDevice(video::EDT_NULL); + sanity_check(nulldevice != NULL); + std::vector<core::vector3d<u32> > mlist; - video::IVideoModeList *modelist = device->getVideoModeList(); + video::IVideoModeList *modelist = nulldevice->getVideoModeList(); u32 num_modes = modelist->getVideoModeCount(); for (u32 i = 0; i != num_modes; i++) { @@ -596,6 +694,8 @@ std::vector<core::vector3d<u32> > getVideoModes() mlist.push_back(core::vector3d<u32>(mode_res.Width, mode_res.Height, mode_depth)); } + nulldevice->drop(); + return mlist; } @@ -644,31 +744,28 @@ const char *getVideoDriverFriendlyName(irr::video::E_DRIVER_TYPE type) return driver_names[type]; } - -#ifndef __ANDROID__ -#ifdef XORG_USED +# ifndef __ANDROID__ +# ifdef XORG_USED static float calcDisplayDensity() { - const char* current_display = getenv("DISPLAY"); + const char *current_display = getenv("DISPLAY"); if (current_display != NULL) { - Display * x11display = XOpenDisplay(current_display); + Display *x11display = XOpenDisplay(current_display); - if (x11display != NULL) { - /* try x direct */ - float dpi_height = - floor(DisplayHeight(x11display, 0) / - (DisplayHeightMM(x11display, 0) * 0.039370) + 0.5); - float dpi_width = - floor(DisplayWidth(x11display, 0) / - (DisplayWidthMM(x11display, 0) * 0.039370) +0.5); + if (x11display != NULL) { + /* try x direct */ + float dpi_height = floor(DisplayHeight(x11display, 0) / + (DisplayHeightMM(x11display, 0) * 0.039370) + 0.5); + float dpi_width = floor(DisplayWidth(x11display, 0) / + (DisplayWidthMM(x11display, 0) * 0.039370) + 0.5); - XCloseDisplay(x11display); + XCloseDisplay(x11display); - return std::max(dpi_height,dpi_width) / 96.0; - } + return std::max(dpi_height,dpi_width) / 96.0; } + } /* return manually specified dpi */ return g_settings->getFloat("screen_dpi")/96.0; @@ -682,12 +779,12 @@ float getDisplayDensity() } -#else +# else // XORG_USED float getDisplayDensity() { return g_settings->getFloat("screen_dpi")/96.0; } -#endif +# endif // XORG_USED v2u32 getDisplaySize() { @@ -698,8 +795,8 @@ v2u32 getDisplaySize() return deskres; } -#endif -#endif +# endif // __ANDROID__ +#endif // SERVER } //namespace porting diff --git a/src/porting.h b/src/porting.h index 3d2677564..2a91fdd06 100644 --- a/src/porting.h +++ b/src/porting.h @@ -60,7 +60,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <unistd.h> #include <stdint.h> //for uintptr_t -#if (defined(linux) || defined(__linux) || defined(__GNU__)) && !defined(_GNU_SOURCE) + #if (defined(linux) || defined(__linux) || defined(__GNU__)) && !defined(_GNU_SOURCE) #define _GNU_SOURCE #endif @@ -371,12 +371,13 @@ float getDisplayDensity(); v2u32 getDisplaySize(); v2u32 getWindowSize(); +std::vector<core::vector3d<u32> > getSupportedVideoModes(); std::vector<irr::video::E_DRIVER_TYPE> getSupportedVideoDrivers(); const char *getVideoDriverName(irr::video::E_DRIVER_TYPE type); const char *getVideoDriverFriendlyName(irr::video::E_DRIVER_TYPE type); #endif -inline const char * getPlatformName() +inline const char *getPlatformName() { return #if defined(ANDROID) @@ -400,8 +401,12 @@ inline const char * getPlatformName() "AIX" #elif defined(__hpux) "HP-UX" -#elif defined(__sun) && defined(__SVR4) - "Solaris" +#elif defined(__sun) || defined(sun) + #if defined(__SVR4) + "Solaris" + #else + "SunOS" + #endif #elif defined(__CYGWIN__) "Cygwin" #elif defined(__unix__) || defined(__unix) diff --git a/src/test.h b/src/profiler.cpp index 547e9a986..197e094f6 100644 --- a/src/test.h +++ b/src/profiler.cpp @@ -1,6 +1,6 @@ /* Minetest -Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2015 celeron55, Perttu Ahola <celeron55@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -17,10 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef TEST_HEADER -#define TEST_HEADER - -void run_tests(); - -#endif +#include "profiler.h" +static Profiler main_profiler; +Profiler *g_profiler = &main_profiler; diff --git a/src/profiler.h b/src/profiler.h index 5816f05ca..78d3b08e0 100644 --- a/src/profiler.h +++ b/src/profiler.h @@ -27,11 +27,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "jthread/jmutex.h" #include "jthread/jmutexautolock.h" #include "util/timetaker.h" -#include "util/numeric.h" // paging() -#include "debug.h" // assert() +#include "util/numeric.h" // paging() +#include "debug.h" // assert() #define MAX_PROFILER_TEXT_ROWS 20 +// Global profiler +class Profiler; +extern Profiler *g_profiler; + /* Time profiler */ @@ -69,23 +73,11 @@ public: void avg(const std::string &name, float value) { JMutexAutoLock lock(m_mutex); - { - std::map<std::string, int>::iterator n = m_avgcounts.find(name); - if(n == m_avgcounts.end()) - m_avgcounts[name] = 1; - else{ - /* No add shall have been used */ - assert(n->second != -2); - n->second = MYMAX(n->second, 0) + 1; - } - } - { - std::map<std::string, float>::iterator n = m_data.find(name); - if(n == m_data.end()) - m_data[name] = value; - else - n->second += value; - } + int &count = m_avgcounts[name]; + + assert(count != -2); + count = MYMAX(count, 0) + 1; + m_data[name] += value; } void clear() @@ -105,6 +97,21 @@ public: 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) { JMutexAutoLock lock(m_mutex); diff --git a/src/rollback_interface.cpp b/src/rollback_interface.cpp index c35ad5781..b3f457029 100644 --- a/src/rollback_interface.cpp +++ b/src/rollback_interface.cpp @@ -178,7 +178,7 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam MapBlock *block = map->getBlockNoCreateNoEx(blockpos); if (block) { block->raiseModified(MOD_STATE_WRITE_NEEDED, - "NodeMetaRef::reportMetadataChange"); + MOD_REASON_REPORT_META_CHANGE); } } catch (InvalidPositionException &e) { infostream << "RollbackAction::applyRevert(): " @@ -210,6 +210,7 @@ bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gam << inventory_index << " too large in " << "inventory list \"" << inventory_list << "\" in " << inventory_location << std::endl; + return false; } // If item was added, take away item, otherwise add removed item if (inventory_add) { diff --git a/src/script/CMakeLists.txt b/src/script/CMakeLists.txt index 491c05a1e..5ef672ca9 100644 --- a/src/script/CMakeLists.txt +++ b/src/script/CMakeLists.txt @@ -11,9 +11,10 @@ set(common_SCRIPT_SRCS PARENT_SCOPE) # Used by client only -set(minetest_SCRIPT_SRCS +set(client_SCRIPT_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/scripting_mainmenu.cpp - ${minetest_SCRIPT_COMMON_SRCS} - ${minetest_SCRIPT_CPP_API_SRCS} - ${minetest_SCRIPT_LUA_API_SRCS} + ${client_SCRIPT_COMMON_SRCS} + ${client_SCRIPT_CPP_API_SRCS} + ${client_SCRIPT_LUA_API_SRCS} PARENT_SCOPE) + diff --git a/src/script/common/CMakeLists.txt b/src/script/common/CMakeLists.txt index 27e2fb4d5..4a8e6bab5 100644 --- a/src/script/common/CMakeLists.txt +++ b/src/script/common/CMakeLists.txt @@ -1,4 +1,3 @@ -# Used by server and client set(common_SCRIPT_COMMON_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/c_content.cpp ${CMAKE_CURRENT_SOURCE_DIR}/c_converter.cpp @@ -6,6 +5,6 @@ set(common_SCRIPT_COMMON_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/c_internal.cpp PARENT_SCOPE) -# Used by client only -set(minetest_SCRIPT_COMMON_SRCS +set(client_SCRIPT_COMMON_SRCS PARENT_SCOPE) + diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index ff9aee8ed..3754fc2ff 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -162,18 +162,13 @@ void read_object_properties(lua_State *L, int index, lua_pop(L, 1); lua_getfield(L, -1, "colors"); - if(lua_istable(L, -1)){ - prop->colors.clear(); + if (lua_istable(L, -1)) { int table = lua_gettop(L); - lua_pushnil(L); - while(lua_next(L, table) != 0){ - // key at index -2 and value at index -1 - if(lua_isstring(L, -1)) - prop->colors.push_back(readARGB8(L, -1)); - else - prop->colors.push_back(video::SColor(255, 255, 255, 255)); - // removes value, keeps key for next iteration - lua_pop(L, 1); + prop->colors.clear(); + for (lua_pushnil(L); lua_next(L, table); lua_pop(L, 1)) { + video::SColor color(255, 255, 255, 255); + read_color(L, -1, &color); + prop->colors.push_back(color); } } lua_pop(L, 1); @@ -205,17 +200,78 @@ void read_object_properties(lua_State *L, int index, } /******************************************************************************/ -TileDef read_tiledef(lua_State *L, int index) +void push_object_properties(lua_State *L, ObjectProperties *prop) +{ + lua_newtable(L); + lua_pushnumber(L, prop->hp_max); + lua_setfield(L, -2, "hp_max"); + lua_pushboolean(L, prop->physical); + lua_setfield(L, -2, "physical"); + lua_pushboolean(L, prop->collideWithObjects); + lua_setfield(L, -2, "collide_with_objects"); + lua_pushnumber(L, prop->weight); + lua_setfield(L, -2, "weight"); + push_aabb3f(L, prop->collisionbox); + lua_setfield(L, -2, "collisionbox"); + lua_pushlstring(L, prop->visual.c_str(), prop->visual.size()); + lua_setfield(L, -2, "visual"); + lua_pushlstring(L, prop->mesh.c_str(), prop->mesh.size()); + lua_setfield(L, -2, "mesh"); + push_v2f(L, prop->visual_size); + lua_setfield(L, -2, "visual_size"); + + lua_newtable(L); + u16 i = 1; + for (std::vector<std::string>::iterator it = prop->textures.begin(); + it != prop->textures.end(); ++it) { + lua_pushlstring(L, it->c_str(), it->size()); + lua_rawseti(L, -2, i); + } + lua_setfield(L, -2, "textures"); + + lua_newtable(L); + i = 1; + for (std::vector<video::SColor>::iterator it = prop->colors.begin(); + it != prop->colors.end(); ++it) { + push_ARGB8(L, *it); + lua_rawseti(L, -2, i); + } + lua_setfield(L, -2, "colors"); + + push_v2s16(L, prop->spritediv); + lua_setfield(L, -2, "spritediv"); + push_v2s16(L, prop->initial_sprite_basepos); + lua_setfield(L, -2, "initial_sprite_basepos"); + lua_pushboolean(L, prop->is_visible); + lua_setfield(L, -2, "is_visible"); + lua_pushboolean(L, prop->makes_footstep_sound); + lua_setfield(L, -2, "makes_footstep_sound"); + lua_pushnumber(L, prop->automatic_rotate); + lua_setfield(L, -2, "automatic_rotate"); + lua_pushnumber(L, prop->stepheight / BS); + lua_setfield(L, -2, "stepheight"); + if (prop->automatic_face_movement_dir) + lua_pushnumber(L, prop->automatic_face_movement_dir_offset); + else + lua_pushboolean(L, false); + lua_setfield(L, -2, "automatic_face_movement_dir"); +} + +/******************************************************************************/ +TileDef read_tiledef(lua_State *L, int index, u8 drawtype) { if(index < 0) index = lua_gettop(L) + 1 + index; TileDef tiledef; - + bool default_tiling = (drawtype == NDT_PLANTLIKE || drawtype == NDT_FIRELIKE) + ? false : true; // key at index -2 and value at index if(lua_isstring(L, index)){ // "default_lava.png" tiledef.name = lua_tostring(L, index); + tiledef.tileable_vertical = default_tiling; + tiledef.tileable_horizontal = default_tiling; } else if(lua_istable(L, index)) { @@ -224,20 +280,24 @@ TileDef read_tiledef(lua_State *L, int index) getstringfield(L, index, "name", tiledef.name); getstringfield(L, index, "image", tiledef.name); // MaterialSpec compat. tiledef.backface_culling = getboolfield_default( - L, index, "backface_culling", true); + L, index, "backface_culling", true); + tiledef.tileable_horizontal = getboolfield_default( + L, index, "tileable_horizontal", default_tiling); + tiledef.tileable_vertical = getboolfield_default( + L, index, "tileable_vertical", default_tiling); // animation = {} lua_getfield(L, index, "animation"); if(lua_istable(L, -1)){ // {type="vertical_frames", aspect_w=16, aspect_h=16, length=2.0} tiledef.animation.type = (TileAnimationType) - getenumfield(L, -1, "type", es_TileAnimationType, - TAT_NONE); + getenumfield(L, -1, "type", es_TileAnimationType, + TAT_NONE); tiledef.animation.aspect_w = - getintfield_default(L, -1, "aspect_w", 16); + getintfield_default(L, -1, "aspect_w", 16); tiledef.animation.aspect_h = - getintfield_default(L, -1, "aspect_h", 16); + getintfield_default(L, -1, "aspect_h", 16); tiledef.animation.length = - getfloatfield_default(L, -1, "length", 1.0); + getfloatfield_default(L, -1, "length", 1.0); } lua_pop(L, 1); } @@ -300,7 +360,7 @@ ContentFeatures read_content_features(lua_State *L, int index) int i = 0; while(lua_next(L, table) != 0){ // Read tiledef from value - f.tiledef[i] = read_tiledef(L, -1); + f.tiledef[i] = read_tiledef(L, -1, f.drawtype); // removes value, keeps key for next iteration lua_pop(L, 1); i++; @@ -335,7 +395,7 @@ ContentFeatures read_content_features(lua_State *L, int index) int i = 0; while(lua_next(L, table) != 0){ // Read tiledef from value - f.tiledef_special[i] = read_tiledef(L, -1); + f.tiledef_special[i] = read_tiledef(L, -1, f.drawtype); // removes value, keeps key for next iteration lua_pop(L, 1); i++; @@ -357,8 +417,7 @@ ContentFeatures read_content_features(lua_State *L, int index) /* Other stuff */ lua_getfield(L, index, "post_effect_color"); - if(!lua_isnil(L, -1)) - f.post_effect_color = readARGB8(L, -1); + read_color(L, -1, &f.post_effect_color); lua_pop(L, 1); f.param_type = (ContentParamType)getenumfield(L, index, "paramtype", @@ -546,22 +605,23 @@ NodeBox read_nodebox(lua_State *L, int index) MapNode readnode(lua_State *L, int index, INodeDefManager *ndef) { lua_getfield(L, index, "name"); - const char *name = luaL_checkstring(L, -1); + if (!lua_isstring(L, -1)) + throw LuaError("Node name is not set or is not a string!"); + const char *name = lua_tostring(L, -1); lua_pop(L, 1); - u8 param1; + + u8 param1 = 0; lua_getfield(L, index, "param1"); - if(lua_isnil(L, -1)) - param1 = 0; - else + if (!lua_isnil(L, -1)) param1 = lua_tonumber(L, -1); lua_pop(L, 1); - u8 param2; + + u8 param2 = 0; lua_getfield(L, index, "param2"); - if(lua_isnil(L, -1)) - param2 = 0; - else + if (!lua_isnil(L, -1)) param2 = lua_tonumber(L, -1); lua_pop(L, 1); + return MapNode(ndef, name, param1, param2); } @@ -901,6 +961,12 @@ u32 read_flags_table(lua_State *L, int table, FlagDesc *flagdesc, u32 *flagmask) return flags; } +void push_flags_string(lua_State *L, FlagDesc *flagdesc, u32 flags, u32 flagmask) +{ + std::string flagstring = writeFlagString(flags, flagdesc, flagmask); + lua_pushlstring(L, flagstring.c_str(), flagstring.size()); +} + /******************************************************************************/ /* Lua Stored data! */ /******************************************************************************/ @@ -926,14 +992,23 @@ void read_groups(lua_State *L, int index, } /******************************************************************************/ +void push_groups(lua_State *L, const std::map<std::string, int> &groups) +{ + lua_newtable(L); + std::map<std::string, int>::const_iterator it; + for (it = groups.begin(); it != groups.end(); ++it) { + lua_pushnumber(L, it->second); + lua_setfield(L, -2, it->first.c_str()); + } +} + +/******************************************************************************/ void push_items(lua_State *L, const std::vector<ItemStack> &items) { - // Create and fill table lua_createtable(L, items.size(), 0); - std::vector<ItemStack>::const_iterator iter = items.begin(); - for (u32 i = 0; iter != items.end(); iter++) { - LuaItemStack::create(L, *iter); - lua_rawseti(L, -2, ++i); + for (u32 i = 0; i != items.size(); i++) { + LuaItemStack::create(L, items[i]); + lua_rawseti(L, -2, i + 1); } } @@ -982,14 +1057,16 @@ bool read_noiseparams(lua_State *L, int index, NoiseParams *np) if (!lua_istable(L, index)) return false; - np->offset = getfloatfield_default(L, index, "offset", 0.0); - np->scale = getfloatfield_default(L, index, "scale", 0.0); - np->persist = getfloatfield_default(L, index, "persist", 0.0); - np->lacunarity = getfloatfield_default(L, index, "lacunarity", 2.0); - np->seed = getintfield_default(L, index, "seed", 0); - np->octaves = getintfield_default(L, index, "octaves", 0); + getfloatfield(L, index, "offset", np->offset); + getfloatfield(L, index, "scale", np->scale); + getfloatfield(L, index, "persist", np->persist); + getfloatfield(L, index, "persistence", np->persist); + getfloatfield(L, index, "lacunarity", np->lacunarity); + getintfield(L, index, "seed", np->seed); + getintfield(L, index, "octaves", np->octaves); - u32 flags = 0, flagmask = 0; + u32 flags = 0; + u32 flagmask = 0; np->flags = getflagsfield(L, index, "flags", flagdesc_noiseparams, &flags, &flagmask) ? flags : NOISE_FLAG_DEFAULTS; @@ -1000,6 +1077,30 @@ bool read_noiseparams(lua_State *L, int index, NoiseParams *np) return true; } +void push_noiseparams(lua_State *L, NoiseParams *np) +{ + lua_newtable(L); + lua_pushnumber(L, np->offset); + lua_setfield(L, -2, "offset"); + lua_pushnumber(L, np->scale); + lua_setfield(L, -2, "scale"); + lua_pushnumber(L, np->persist); + lua_setfield(L, -2, "persistence"); + lua_pushnumber(L, np->lacunarity); + lua_setfield(L, -2, "lacunarity"); + lua_pushnumber(L, np->seed); + lua_setfield(L, -2, "seed"); + lua_pushnumber(L, np->octaves); + lua_setfield(L, -2, "octaves"); + + push_flags_string(L, flagdesc_noiseparams, np->flags, + np->flags); + lua_setfield(L, -2, "flags"); + + push_v3f(L, np->spread); + lua_setfield(L, -2, "spread"); +} + /******************************************************************************/ // Returns depth of json value tree static int push_json_value_getdepth(const Json::Value &value) diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 241b1ca76..46416ad8e 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -62,59 +62,57 @@ struct NoiseParams; class Schematic; -ContentFeatures read_content_features (lua_State *L, int index); -TileDef read_tiledef (lua_State *L, int index); -void read_soundspec (lua_State *L, int index, - SimpleSoundSpec &spec); -NodeBox read_nodebox (lua_State *L, int index); +ContentFeatures read_content_features (lua_State *L, int index); +TileDef read_tiledef (lua_State *L, int index, + u8 drawtype); +void read_soundspec (lua_State *L, int index, + SimpleSoundSpec &spec); +NodeBox read_nodebox (lua_State *L, int index); -void read_server_sound_params (lua_State *L, int index, - ServerSoundParams ¶ms); +void read_server_sound_params (lua_State *L, int index, + ServerSoundParams ¶ms); -void push_dig_params (lua_State *L,const DigParams ¶ms); -void push_hit_params (lua_State *L,const HitParams ¶ms); +void push_dig_params (lua_State *L, + const DigParams ¶ms); +void push_hit_params (lua_State *L, + const HitParams ¶ms); -ItemStack read_item (lua_State *L, int index, Server* srv); +ItemStack read_item (lua_State *L, int index, Server *srv); -ToolCapabilities read_tool_capabilities (lua_State *L, - int table); +ToolCapabilities read_tool_capabilities (lua_State *L, int table); void push_tool_capabilities (lua_State *L, const ToolCapabilities &prop); -ItemDefinition read_item_definition (lua_State *L, - int index, +ItemDefinition read_item_definition (lua_State *L, int index, ItemDefinition default_def); -void read_object_properties (lua_State *L, - int index, +void read_object_properties (lua_State *L, int index, + ObjectProperties *prop); +void push_object_properties (lua_State *L, ObjectProperties *prop); void push_inventory_list (lua_State *L, Inventory *inv, const char *name); -void read_inventory_list (lua_State *L, - int tableindex, - Inventory *inv, - const char *name, - Server* srv, - int forcesize=-1); +void read_inventory_list (lua_State *L, int tableindex, + Inventory *inv, const char *name, + Server *srv, int forcesize=-1); -MapNode readnode (lua_State *L, - int index, +MapNode readnode (lua_State *L, int index, INodeDefManager *ndef); -void pushnode (lua_State *L, - const MapNode &n, +void pushnode (lua_State *L, const MapNode &n, INodeDefManager *ndef); NodeBox read_nodebox (lua_State *L, int index); -void read_groups (lua_State *L, - int index, +void read_groups (lua_State *L, int index, std::map<std::string, int> &result); +void push_groups (lua_State *L, + const std::map<std::string, int> &groups); + //TODO rename to "read_enum_field" -int getenumfield (lua_State *L, - int table, +int getenumfield (lua_State *L, int table, const char *fieldname, const EnumString *spec, int default_); @@ -128,6 +126,9 @@ bool read_flags (lua_State *L, int index, FlagDesc *flagdesc, u32 *flags, u32 *flagmask); +void push_flags_string (lua_State *L, FlagDesc *flagdesc, + u32 flags, u32 flagmask); + u32 read_flags_table (lua_State *L, int table, FlagDesc *flagdesc, u32 *flagmask); @@ -142,23 +143,21 @@ void read_soundspec (lua_State *L, int index, SimpleSoundSpec &spec); - bool string_to_enum (const EnumString *spec, int &result, const std::string &str); bool read_noiseparams (lua_State *L, int index, NoiseParams *np); +void push_noiseparams (lua_State *L, NoiseParams *np); void luaentity_get (lua_State *L,u16 id); bool push_json_value (lua_State *L, const Json::Value &value, int nullindex); -void read_json_value (lua_State *L, - Json::Value &root, - int index, - u8 recursion = 0); +void read_json_value (lua_State *L, Json::Value &root, + int index, u8 recursion = 0); extern struct EnumString es_TileAnimationType[]; diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 66eeec68e..f1d3cc421 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -23,9 +23,23 @@ extern "C" { } #include "util/numeric.h" +#include "util/string.h" #include "common/c_converter.h" #include "constants.h" + +#define CHECK_TYPE(index, name, type) do { \ + int t = lua_type(L, (index)); \ + if (t != (type)) { \ + throw LuaError(std::string("Invalid ") + (name) + \ + " (expected " + lua_typename(L, (type)) + \ + " got " + lua_typename(L, t) + ")."); \ + } \ + } while(0) +#define CHECK_POS_COORD(name) CHECK_TYPE(-1, "position coordinate '" name "'", LUA_TNUMBER) +#define CHECK_POS_TAB(index) CHECK_TYPE(index, "position", LUA_TTABLE) + + void push_v3f(lua_State *L, v3f p) { lua_newtable(L); @@ -49,7 +63,7 @@ void push_v2f(lua_State *L, v2f p) v2s16 read_v2s16(lua_State *L, int index) { v2s16 p; - luaL_checktype(L, index, LUA_TTABLE); + CHECK_POS_TAB(index); lua_getfield(L, index, "x"); p.X = lua_tonumber(L, -1); lua_pop(L, 1); @@ -59,10 +73,43 @@ v2s16 read_v2s16(lua_State *L, int index) return p; } +v2s16 check_v2s16(lua_State *L, int index) +{ + v2s16 p; + CHECK_POS_TAB(index); + lua_getfield(L, index, "x"); + CHECK_POS_COORD("x"); + p.X = lua_tonumber(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "y"); + CHECK_POS_COORD("y"); + p.Y = lua_tonumber(L, -1); + lua_pop(L, 1); + return p; +} + +void push_v2s16(lua_State *L, v2s16 p) +{ + lua_newtable(L); + lua_pushnumber(L, p.X); + lua_setfield(L, -2, "x"); + lua_pushnumber(L, p.Y); + lua_setfield(L, -2, "y"); +} + +void push_v2s32(lua_State *L, v2s32 p) +{ + lua_newtable(L); + lua_pushnumber(L, p.X); + lua_setfield(L, -2, "x"); + lua_pushnumber(L, p.Y); + lua_setfield(L, -2, "y"); +} + v2s32 read_v2s32(lua_State *L, int index) { v2s32 p; - luaL_checktype(L, index, LUA_TTABLE); + CHECK_POS_TAB(index); lua_getfield(L, index, "x"); p.X = lua_tonumber(L, -1); lua_pop(L, 1); @@ -75,11 +122,26 @@ v2s32 read_v2s32(lua_State *L, int index) v2f read_v2f(lua_State *L, int index) { v2f p; - luaL_checktype(L, index, LUA_TTABLE); + CHECK_POS_TAB(index); + lua_getfield(L, index, "x"); + p.X = lua_tonumber(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "y"); + p.Y = lua_tonumber(L, -1); + lua_pop(L, 1); + return p; +} + +v2f check_v2f(lua_State *L, int index) +{ + v2f p; + CHECK_POS_TAB(index); lua_getfield(L, index, "x"); + CHECK_POS_COORD("x"); p.X = lua_tonumber(L, -1); lua_pop(L, 1); lua_getfield(L, index, "y"); + CHECK_POS_COORD("y"); p.Y = lua_tonumber(L, -1); lua_pop(L, 1); return p; @@ -88,7 +150,7 @@ v2f read_v2f(lua_State *L, int index) v3f read_v3f(lua_State *L, int index) { v3f pos; - luaL_checktype(L, index, LUA_TTABLE); + CHECK_POS_TAB(index); lua_getfield(L, index, "x"); pos.X = lua_tonumber(L, -1); lua_pop(L, 1); @@ -104,19 +166,35 @@ v3f read_v3f(lua_State *L, int index) v3f check_v3f(lua_State *L, int index) { v3f pos; - luaL_checktype(L, index, LUA_TTABLE); + CHECK_POS_TAB(index); lua_getfield(L, index, "x"); - pos.X = luaL_checknumber(L, -1); + CHECK_POS_COORD("x"); + pos.X = lua_tonumber(L, -1); lua_pop(L, 1); lua_getfield(L, index, "y"); - pos.Y = luaL_checknumber(L, -1); + CHECK_POS_COORD("y"); + pos.Y = lua_tonumber(L, -1); lua_pop(L, 1); lua_getfield(L, index, "z"); - pos.Z = luaL_checknumber(L, -1); + CHECK_POS_COORD("z"); + pos.Z = lua_tonumber(L, -1); lua_pop(L, 1); return pos; } +void push_ARGB8(lua_State *L, video::SColor color) +{ + lua_newtable(L); + lua_pushnumber(L, color.getAlpha()); + lua_setfield(L, -2, "a"); + lua_pushnumber(L, color.getRed()); + lua_setfield(L, -2, "r"); + lua_pushnumber(L, color.getGreen()); + lua_setfield(L, -2, "g"); + lua_pushnumber(L, color.getBlue()); + lua_setfield(L, -2, "b"); +} + void pushFloatPos(lua_State *L, v3f p) { p /= BS; @@ -153,13 +231,31 @@ v3s16 check_v3s16(lua_State *L, int index) return floatToInt(pf, 1.0); } -video::SColor readARGB8(lua_State *L, int index) +bool read_color(lua_State *L, int index, video::SColor *color) +{ + if (lua_istable(L, index)) { + *color = read_ARGB8(L, index); + } else if (lua_isnumber(L, index)) { + color->set(lua_tonumber(L, index)); + } else if (lua_isstring(L, index)) { + video::SColor parsed_color; + if (!parseColorString(lua_tostring(L, index), parsed_color, true)) + return false; + + *color = parsed_color; + } else { + return false; + } + + return true; +} + +video::SColor read_ARGB8(lua_State *L, int index) { video::SColor color(0); - luaL_checktype(L, index, LUA_TTABLE); + CHECK_TYPE(index, "ARGB color", LUA_TTABLE); lua_getfield(L, index, "a"); - if(lua_isnumber(L, -1)) - color.setAlpha(lua_tonumber(L, -1)); + color.setAlpha(lua_isnumber(L, -1) ? lua_tonumber(L, -1) : 0xFF); lua_pop(L, 1); lua_getfield(L, index, "r"); color.setRed(lua_tonumber(L, -1)); @@ -199,6 +295,23 @@ aabb3f read_aabb3f(lua_State *L, int index, f32 scale) return box; } +void push_aabb3f(lua_State *L, aabb3f box) +{ + lua_newtable(L); + lua_pushnumber(L, box.MinEdge.X); + lua_rawseti(L, -2, 1); + lua_pushnumber(L, box.MinEdge.Y); + lua_rawseti(L, -2, 2); + lua_pushnumber(L, box.MinEdge.Z); + lua_rawseti(L, -2, 3); + lua_pushnumber(L, box.MaxEdge.X); + lua_rawseti(L, -2, 4); + lua_pushnumber(L, box.MaxEdge.Y); + lua_rawseti(L, -2, 5); + lua_pushnumber(L, box.MaxEdge.Z); + lua_rawseti(L, -2, 6); +} + std::vector<aabb3f> read_aabb3f_vector(lua_State *L, int index, f32 scale) { std::vector<aabb3f> boxes; @@ -227,24 +340,28 @@ std::vector<aabb3f> read_aabb3f_vector(lua_State *L, int index, f32 scale) return boxes; } -bool read_stringlist(lua_State *L, int index, std::vector<const char *> &result) +size_t read_stringlist(lua_State *L, int index, std::vector<std::string> *result) { if (index < 0) index = lua_gettop(L) + 1 + index; + size_t num_strings = 0; + if (lua_istable(L, index)) { lua_pushnil(L); while (lua_next(L, index)) { - if (lua_isstring(L, -1)) - result.push_back(lua_tostring(L, -1)); + if (lua_isstring(L, -1)) { + result->push_back(lua_tostring(L, -1)); + num_strings++; + } lua_pop(L, 1); } } else if (lua_isstring(L, index)) { - result.push_back(lua_tostring(L, index)); - } else { - return false; + result->push_back(lua_tostring(L, index)); + num_strings++; } - return true; + + return num_strings; } /* @@ -281,6 +398,45 @@ bool getintfield(lua_State *L, int table, return got; } +bool getintfield(lua_State *L, int table, + const char *fieldname, u8 &result) +{ + lua_getfield(L, table, fieldname); + bool got = false; + if(lua_isnumber(L, -1)){ + result = lua_tonumber(L, -1); + got = true; + } + lua_pop(L, 1); + return got; +} + +bool getintfield(lua_State *L, int table, + const char *fieldname, u16 &result) +{ + lua_getfield(L, table, fieldname); + bool got = false; + if(lua_isnumber(L, -1)){ + result = lua_tonumber(L, -1); + got = true; + } + lua_pop(L, 1); + return got; +} + +bool getintfield(lua_State *L, int table, + const char *fieldname, u32 &result) +{ + lua_getfield(L, table, fieldname); + bool got = false; + if(lua_isnumber(L, -1)){ + result = lua_tonumber(L, -1); + got = true; + } + lua_pop(L, 1); + return got; +} + bool getfloatfield(lua_State *L, int table, const char *fieldname, float &result) { @@ -307,24 +463,26 @@ bool getboolfield(lua_State *L, int table, return got; } -bool getstringlistfield(lua_State *L, int table, const char *fieldname, - std::vector<const char *> &result) +size_t getstringlistfield(lua_State *L, int table, const char *fieldname, + std::vector<std::string> *result) { lua_getfield(L, table, fieldname); - bool got = read_stringlist(L, -1, result); + size_t num_strings_read = read_stringlist(L, -1, result); lua_pop(L, 1); - return got; + return num_strings_read; } std::string checkstringfield(lua_State *L, int table, const char *fieldname) { lua_getfield(L, table, fieldname); - std::string s = luaL_checkstring(L, -1); + CHECK_TYPE(-1, std::string("field \"") + fieldname + '"', LUA_TSTRING); + size_t len; + const char *s = lua_tolstring(L, -1, &len); lua_pop(L, 1); - return s; + return std::string(s, len); } std::string getstringfield_default(lua_State *L, int table, @@ -387,3 +545,95 @@ void setboolfield(lua_State *L, int table, } +//// +//// Array table slices +//// + +size_t write_array_slice_float( + lua_State *L, + int table_index, + float *data, + v3u16 data_size, + v3u16 slice_offset, + v3u16 slice_size) +{ + v3u16 pmin, pmax(data_size); + + if (slice_offset.X > 0) { + slice_offset.X--; + pmin.X = slice_offset.X; + pmax.X = MYMIN(slice_offset.X + slice_size.X, data_size.X); + } + + if (slice_offset.Y > 0) { + slice_offset.Y--; + pmin.Y = slice_offset.Y; + pmax.Y = MYMIN(slice_offset.Y + slice_size.Y, data_size.Y); + } + + if (slice_offset.Z > 0) { + slice_offset.Z--; + pmin.Z = slice_offset.Z; + pmax.Z = MYMIN(slice_offset.Z + slice_size.Z, data_size.Z); + } + + const u32 ystride = data_size.X; + const u32 zstride = data_size.X * data_size.Y; + + u32 elem_index = 1; + for (u32 z = pmin.Z; z != pmax.Z; z++) + for (u32 y = pmin.Y; y != pmax.Y; y++) + for (u32 x = pmin.X; x != pmax.X; x++) { + u32 i = z * zstride + y * ystride + x; + lua_pushnumber(L, data[i]); + lua_rawseti(L, table_index, elem_index); + elem_index++; + } + + return elem_index - 1; +} + + +size_t write_array_slice_u16( + lua_State *L, + int table_index, + u16 *data, + v3u16 data_size, + v3u16 slice_offset, + v3u16 slice_size) +{ + v3u16 pmin, pmax(data_size); + + if (slice_offset.X > 0) { + slice_offset.X--; + pmin.X = slice_offset.X; + pmax.X = MYMIN(slice_offset.X + slice_size.X, data_size.X); + } + + if (slice_offset.Y > 0) { + slice_offset.Y--; + pmin.Y = slice_offset.Y; + pmax.Y = MYMIN(slice_offset.Y + slice_size.Y, data_size.Y); + } + + if (slice_offset.Z > 0) { + slice_offset.Z--; + pmin.Z = slice_offset.Z; + pmax.Z = MYMIN(slice_offset.Z + slice_size.Z, data_size.Z); + } + + const u32 ystride = data_size.X; + const u32 zstride = data_size.X * data_size.Y; + + u32 elem_index = 1; + for (u32 z = pmin.Z; z != pmax.Z; z++) + for (u32 y = pmin.Y; y != pmax.Y; y++) + for (u32 x = pmin.X; x != pmax.X; x++) { + u32 i = z * zstride + y * ystride + x; + lua_pushinteger(L, data[i]); + lua_rawseti(L, table_index, elem_index); + elem_index++; + } + + return elem_index - 1; +} diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index 3b7eb6f7d..18a045d2a 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -48,11 +48,17 @@ int getintfield_default (lua_State *L, int table, bool getstringfield(lua_State *L, int table, const char *fieldname, std::string &result); -bool getstringlistfield(lua_State *L, int table, +size_t getstringlistfield(lua_State *L, int table, const char *fieldname, - std::vector<const char *> &result); + std::vector<std::string> *result); bool getintfield(lua_State *L, int table, const char *fieldname, int &result); +bool getintfield(lua_State *L, int table, + const char *fieldname, u8 &result); +bool getintfield(lua_State *L, int table, + const char *fieldname, u16 &result); +bool getintfield(lua_State *L, int table, + const char *fieldname, u32 &result); void read_groups(lua_State *L, int index, std::map<std::string, int> &result); bool getboolfield(lua_State *L, int table, @@ -70,8 +76,9 @@ void setfloatfield(lua_State *L, int table, void setboolfield(lua_State *L, int table, const char *fieldname, bool value); - v3f checkFloatPos (lua_State *L, int index); +v2f check_v2f (lua_State *L, int index); +v2s16 check_v2s16 (lua_State *L, int index); v3f check_v3f (lua_State *L, int index); v3s16 check_v3s16 (lua_State *L, int index); @@ -79,23 +86,32 @@ v3f read_v3f (lua_State *L, int index); v2f read_v2f (lua_State *L, int index); v2s16 read_v2s16 (lua_State *L, int index); v2s32 read_v2s32 (lua_State *L, int index); -video::SColor readARGB8 (lua_State *L, int index); +video::SColor read_ARGB8 (lua_State *L, int index); +bool read_color (lua_State *L, int index, + video::SColor *color); + aabb3f read_aabb3f (lua_State *L, int index, f32 scale); v3s16 read_v3s16 (lua_State *L, int index); std::vector<aabb3f> read_aabb3f_vector (lua_State *L, int index, f32 scale); -bool read_stringlist (lua_State *L, int index, - std::vector<const char *> &result); +size_t read_stringlist (lua_State *L, int index, + std::vector<std::string> *result); +void push_v2s16 (lua_State *L, v2s16 p); +void push_v2s32 (lua_State *L, v2s32 p); void push_v3s16 (lua_State *L, v3s16 p); +void push_aabb3f (lua_State *L, aabb3f box); +void push_ARGB8 (lua_State *L, video::SColor color); void pushFloatPos (lua_State *L, v3f p); void push_v3f (lua_State *L, v3f p); void push_v2f (lua_State *L, v2f p); +void warn_if_field_exists(lua_State *L, int table, + const char *fieldname, + const std::string &message); - -void warn_if_field_exists (lua_State *L, - int table, - const char *fieldname, - const std::string &message); +size_t write_array_slice_float(lua_State *L, int table_index, float *data, + v3u16 data_size, v3u16 slice_offset, v3u16 slice_size); +size_t write_array_slice_u16(lua_State *L, int table_index, u16 *data, + v3u16 data_size, v3u16 slice_offset, v3u16 slice_size); #endif /* C_CONVERTER_H_ */ diff --git a/src/script/common/c_internal.cpp b/src/script/common/c_internal.cpp index f811dd5d3..2a10ce0f2 100644 --- a/src/script/common/c_internal.cpp +++ b/src/script/common/c_internal.cpp @@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_internal.h" #include "debug.h" #include "log.h" -#include "main.h" #include "settings.h" std::string script_get_backtrace(lua_State *L) @@ -64,19 +63,65 @@ int script_exception_wrapper(lua_State *L, lua_CFunction f) return f(L); // Call wrapped function and return result. } catch (const char *s) { // Catch and convert exceptions. lua_pushstring(L, s); - } catch (std::exception& e) { + } catch (std::exception &e) { lua_pushstring(L, e.what()); - } catch (...) { - lua_pushliteral(L, "caught (...)"); } return lua_error(L); // Rethrow as a Lua error. } -void script_error(lua_State *L) +/* + * Note that we can't get tracebacks for LUA_ERRMEM or LUA_ERRERR (without + * hacking Lua internals). For LUA_ERRMEM, this is because memory errors will + * not execute the the error handler, and by the time lua_pcall returns the + * execution stack will have already been unwound. For LUA_ERRERR, there was + * another error while trying to generate a backtrace from a LUA_ERRRUN. It is + * presumed there is an error with the internal Lua state and thus not possible + * to gather a coherent backtrace. Realistically, the best we can do here is + * print which C function performed the failing pcall. + */ +void script_error(lua_State *L, int pcall_result, const char *mod, const char *fxn) { - const char *s = lua_tostring(L, -1); - std::string str(s ? s : ""); - throw LuaError(str); + if (pcall_result == 0) + return; + + const char *err_type; + switch (pcall_result) { + case LUA_ERRRUN: + err_type = "Runtime"; + break; + case LUA_ERRMEM: + err_type = "OOM"; + break; + case LUA_ERRERR: + err_type = "Double fault"; + break; + default: + err_type = "Unknown"; + } + + if (!mod) + mod = "??"; + + if (!fxn) + fxn = "??"; + + const char *err_descr = lua_tostring(L, -1); + if (!err_descr) + err_descr = "<no description>"; + + char buf[256]; + snprintf(buf, sizeof(buf), "%s error from mod '%s' in callback %s(): ", + err_type, mod, fxn); + + std::string err_msg(buf); + err_msg += err_descr; + + if (pcall_result == LUA_ERRMEM) { + err_msg += "\nCurrent Lua memory usage: " + + itos(lua_gc(L, LUA_GCCOUNT, 0) >> 10) + " MB"; + } + + throw LuaError(err_msg); } // Push the list of callbacks (a lua table). @@ -85,9 +130,10 @@ void script_error(lua_State *L) // - runs the callbacks // - replaces the table and arguments with the return value, // computed depending on mode -void script_run_callbacks(lua_State *L, int nargs, RunCallbacksMode mode) +void script_run_callbacks_f(lua_State *L, int nargs, + RunCallbacksMode mode, const char *fxn) { - assert(lua_gettop(L) >= nargs + 1); + FATAL_ERROR_IF(lua_gettop(L) < nargs + 1, "Not enough arguments"); // Insert error handler lua_pushcfunction(L, script_error_handler); @@ -107,14 +153,14 @@ void script_run_callbacks(lua_State *L, int nargs, RunCallbacksMode mode) // Stack now looks like this: // ... <error handler> <run_callbacks> <table> <mode> <arg#1> <arg#2> ... <arg#n> - if (lua_pcall(L, nargs + 2, 1, errorhandler)) { - script_error(L); - } + int result = lua_pcall(L, nargs + 2, 1, errorhandler); + if (result != 0) + script_error(L, result, NULL, fxn); lua_remove(L, -2); // Remove error handler } -void log_deprecated(lua_State *L, std::string message) +void log_deprecated(lua_State *L, const std::string &message) { static bool configured = false; static bool dolog = false; @@ -125,8 +171,7 @@ void log_deprecated(lua_State *L, std::string message) std::string value = g_settings->get("deprecated_lua_api_handling"); if (value == "log") { dolog = true; - } - if (value == "error") { + } else if (value == "error") { dolog = true; doerror = true; } @@ -134,11 +179,10 @@ void log_deprecated(lua_State *L, std::string message) if (doerror) { if (L != NULL) { - script_error(L); + script_error(L, LUA_ERRRUN, NULL, NULL); } else { - /* As of april 2014 assert is not optimized to nop in release builds - * therefore this is correct. */ - assert("Can't do a scripterror for this deprecated message, so exit completely!"); + FATAL_ERROR("Can't do a scripterror for this deprecated message, " + "so exit completely!"); } } diff --git a/src/script/common/c_internal.h b/src/script/common/c_internal.h index eb9181b09..ecb514c8f 100644 --- a/src/script/common/c_internal.h +++ b/src/script/common/c_internal.h @@ -34,6 +34,16 @@ extern "C" { #include "common/c_types.h" +#define PCALL_RESL(L, RES) do { \ + int result_ = (RES); \ + if (result_ != 0) { \ + script_error((L), result_, NULL, __FUNCTION__); \ + } \ +} while (0) + +#define script_run_callbacks(L, nargs, mode) \ + script_run_callbacks_f((L), (nargs), (mode), __FUNCTION__) + // What script_run_callbacks does with the return values of callbacks. // Regardless of the mode, if only one callback is defined, // its return value is the total return value. @@ -67,8 +77,9 @@ enum RunCallbacksMode std::string script_get_backtrace(lua_State *L); int script_error_handler(lua_State *L); int script_exception_wrapper(lua_State *L, lua_CFunction f); -void script_error(lua_State *L); -void script_run_callbacks(lua_State *L, int nargs, RunCallbacksMode mode); -void log_deprecated(lua_State *L, std::string message); +void script_error(lua_State *L, int pcall_result, const char *mod, const char *fxn); +void script_run_callbacks_f(lua_State *L, int nargs, + RunCallbacksMode mode, const char *fxn); +void log_deprecated(lua_State *L, const std::string &message); #endif /* C_INTERNAL_H_ */ diff --git a/src/script/cpp_api/CMakeLists.txt b/src/script/cpp_api/CMakeLists.txt index c45020055..be4d0131e 100644 --- a/src/script/cpp_api/CMakeLists.txt +++ b/src/script/cpp_api/CMakeLists.txt @@ -1,5 +1,5 @@ -# Used by server and client set(common_SCRIPT_CPP_API_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/s_async.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_base.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_entity.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_env.cpp @@ -8,11 +8,11 @@ set(common_SCRIPT_CPP_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/s_node.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_nodemeta.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_player.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/s_security.cpp ${CMAKE_CURRENT_SOURCE_DIR}/s_server.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/s_async.cpp PARENT_SCOPE) -# Used by client only -set(minetest_SCRIPT_CPP_API_SRCS +set(client_SCRIPT_CPP_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/s_mainmenu.cpp PARENT_SCOPE) + diff --git a/src/script/cpp_api/s_async.cpp b/src/script/cpp_api/s_async.cpp index de1ebc07b..c00b22f98 100644 --- a/src/script/cpp_api/s_async.cpp +++ b/src/script/cpp_api/s_async.cpp @@ -155,7 +155,7 @@ void AsyncEngine::step(lua_State *L, int errorhandler) lua_getfield(L, -1, "async_event_handler"); if (lua_isnil(L, -1)) { - assert("Async event handler does not exist!" == 0); + FATAL_ERROR("Async event handler does not exist!"); } luaL_checktype(L, -1, LUA_TFUNCTION); @@ -164,9 +164,7 @@ void AsyncEngine::step(lua_State *L, int errorhandler) lua_pushlstring(L, jobDone.serializedResult.data(), jobDone.serializedResult.size()); - if (lua_pcall(L, 2, 0, errorhandler)) { - script_error(L); - } + PCALL_RESL(L, lua_pcall(L, 2, 0, errorhandler)); } resultQueueMutex.Unlock(); lua_pop(L, 1); // Pop core @@ -237,7 +235,7 @@ AsyncWorkerThread::AsyncWorkerThread(AsyncEngine* jobDispatcher, /******************************************************************************/ AsyncWorkerThread::~AsyncWorkerThread() { - assert(IsRunning() == false); + sanity_check(IsRunning() == false); } /******************************************************************************/ @@ -257,7 +255,7 @@ void* AsyncWorkerThread::Thread() std::string script = getServer()->getBuiltinLuaPath() + DIR_DELIM + "init.lua"; if (!loadScript(script)) { errorstream - << "AsyncWorkderThread execution of async base environment failed!" + << "AsyncWorkerThread execution of async base environment failed!" << std::endl; abort(); } @@ -293,8 +291,9 @@ void* AsyncWorkerThread::Thread() toProcess.serializedParams.data(), toProcess.serializedParams.size()); - if (lua_pcall(L, 2, 1, m_errorhandler)) { - scriptError(); + int result = lua_pcall(L, 2, 1, m_errorhandler); + if (result) { + PCALL_RES(result); toProcess.serializedResult = ""; } else { // Fetch result diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 1f96373dc..dcfbac4bf 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -19,7 +19,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_base.h" #include "cpp_api/s_internal.h" +#include "cpp_api/s_security.h" #include "lua_api/l_object.h" +#include "common/c_converter.h" #include "serverobject.h" #include "debug.h" #include "filesys.h" @@ -45,18 +47,18 @@ class ModNameStorer private: lua_State *L; public: - ModNameStorer(lua_State *L_, const std::string &modname): + ModNameStorer(lua_State *L_, const std::string &mod_name): L(L_) { - // Store current modname in registry - lua_pushstring(L, modname.c_str()); - lua_setfield(L, LUA_REGISTRYINDEX, "current_modname"); + // Store current mod name in registry + lua_pushstring(L, mod_name.c_str()); + lua_setfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); } ~ModNameStorer() { - // Clear current modname in registry + // Clear current mod name from registry lua_pushnil(L); - lua_setfield(L, LUA_REGISTRYINDEX, "current_modname"); + lua_setfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); } }; @@ -67,12 +69,12 @@ public: ScriptApiBase::ScriptApiBase() { - #ifdef SCRIPTAPI_LOCK_DEBUG +#ifdef SCRIPTAPI_LOCK_DEBUG m_locked = false; - #endif +#endif m_luastack = luaL_newstate(); - assert(m_luastack); + FATAL_ERROR_IF(!m_luastack, "luaL_newstate() failed"); luaL_openlibs(m_luastack); @@ -102,6 +104,11 @@ ScriptApiBase::ScriptApiBase() lua_pushstring(m_luastack, porting::getPlatformName()); lua_setglobal(m_luastack, "PLATFORM"); + // m_secure gets set to true inside + // ScriptApiSecurity::initializeSecurity(), if neccessary. + // Default to false otherwise + m_secure = false; + m_server = NULL; m_environment = NULL; m_guiengine = NULL; @@ -112,88 +119,136 @@ ScriptApiBase::~ScriptApiBase() lua_close(m_luastack); } -bool ScriptApiBase::loadMod(const std::string &scriptpath, - const std::string &modname) +bool ScriptApiBase::loadMod(const std::string &script_path, + const std::string &mod_name, std::string *error) { - ModNameStorer modnamestorer(getStack(), modname); - - if (!string_allowed(modname, MODNAME_ALLOWED_CHARS)) { - errorstream<<"Error loading mod \""<<modname - <<"\": modname does not follow naming conventions: " - <<"Only chararacters [a-z0-9_] are allowed."<<std::endl; - return false; - } + ModNameStorer mod_name_storer(getStack(), mod_name); - return loadScript(scriptpath); + return loadScript(script_path, error); } -bool ScriptApiBase::loadScript(const std::string &scriptpath) +bool ScriptApiBase::loadScript(const std::string &script_path, std::string *error) { - verbosestream<<"Loading and running script from "<<scriptpath<<std::endl; + verbosestream << "Loading and running script from " << script_path << std::endl; lua_State *L = getStack(); - int ret = luaL_loadfile(L, scriptpath.c_str()) || lua_pcall(L, 0, 0, m_errorhandler); - if (ret) { - errorstream << "========== ERROR FROM LUA ===========" << std::endl; - errorstream << "Failed to load and run script from " << std::endl; - errorstream << scriptpath << ":" << std::endl; - errorstream << std::endl; - errorstream << lua_tostring(L, -1) << std::endl; - errorstream << std::endl; - errorstream << "======= END OF ERROR FROM LUA ========" << std::endl; + bool ok; + if (m_secure) { + ok = ScriptApiSecurity::safeLoadFile(L, script_path.c_str()); + } else { + ok = !luaL_loadfile(L, script_path.c_str()); + } + ok = ok && !lua_pcall(L, 0, 0, m_errorhandler); + if (!ok) { + std::string error_msg = lua_tostring(L, -1); + if (error) + *error = error_msg; + errorstream << "========== ERROR FROM LUA ===========" << std::endl + << "Failed to load and run script from " << std::endl + << script_path << ":" << std::endl << std::endl + << error_msg << std::endl << std::endl + << "======= END OF ERROR FROM LUA ========" << std::endl; lua_pop(L, 1); // Pop error message from stack return false; } return true; } +// Push the list of callbacks (a lua table). +// Then push nargs arguments. +// Then call this function, which +// - runs the callbacks +// - replaces the table and arguments with the return value, +// computed depending on mode +void ScriptApiBase::runCallbacksRaw(int nargs, + RunCallbacksMode mode, const char *fxn) +{ + lua_State *L = getStack(); + FATAL_ERROR_IF(lua_gettop(L) < nargs + 1, "Not enough arguments"); + + // Insert error handler + lua_pushcfunction(L, script_error_handler); + int errorhandler = lua_gettop(L) - nargs - 1; + lua_insert(L, errorhandler); + + // Insert run_callbacks between error handler and table + lua_getglobal(L, "core"); + lua_getfield(L, -1, "run_callbacks"); + lua_remove(L, -2); + lua_insert(L, errorhandler + 1); + + // Insert mode after table + lua_pushnumber(L, (int)mode); + lua_insert(L, errorhandler + 3); + + // Stack now looks like this: + // ... <error handler> <run_callbacks> <table> <mode> <arg#1> <arg#2> ... <arg#n> + + int result = lua_pcall(L, nargs + 2, 1, errorhandler); + if (result != 0) + scriptError(result, fxn); + + lua_remove(L, -2); // Remove error handler +} + void ScriptApiBase::realityCheck() { int top = lua_gettop(m_luastack); - if(top >= 30){ - dstream<<"Stack is over 30:"<<std::endl; + if (top >= 30) { + dstream << "Stack is over 30:" << std::endl; stackDump(dstream); std::string traceback = script_get_backtrace(m_luastack); throw LuaError("Stack is over 30 (reality check)\n" + traceback); } } -void ScriptApiBase::scriptError() +void ScriptApiBase::scriptError(int result, const char *fxn) { - throw LuaError(lua_tostring(m_luastack, -1)); + script_error(getStack(), result, m_last_run_mod.c_str(), fxn); } void ScriptApiBase::stackDump(std::ostream &o) { - int i; int top = lua_gettop(m_luastack); - for (i = 1; i <= top; i++) { /* repeat for each level */ + for (int i = 1; i <= top; i++) { /* repeat for each level */ int t = lua_type(m_luastack, i); switch (t) { - case LUA_TSTRING: /* strings */ - o<<"\""<<lua_tostring(m_luastack, i)<<"\""; + o << "\"" << lua_tostring(m_luastack, i) << "\""; break; - case LUA_TBOOLEAN: /* booleans */ - o<<(lua_toboolean(m_luastack, i) ? "true" : "false"); + o << (lua_toboolean(m_luastack, i) ? "true" : "false"); break; - case LUA_TNUMBER: /* numbers */ { char buf[10]; snprintf(buf, 10, "%g", lua_tonumber(m_luastack, i)); - o<<buf; - break; } - + o << buf; + break; + } default: /* other values */ - o<<lua_typename(m_luastack, t); + o << lua_typename(m_luastack, t); break; - } - o<<" "; + o << " "; } - o<<std::endl; + o << std::endl; +} + +void ScriptApiBase::setOriginDirect(const char *origin) +{ + m_last_run_mod = origin ? origin : "??"; +} + +void ScriptApiBase::setOriginFromTableRaw(int index, const char *fxn) +{ +#ifdef SCRIPTAPI_DEBUG + lua_State *L = getStack(); + + m_last_run_mod = lua_istable(L, index) ? + getstringfield_default(L, index, "mod_origin", "") : ""; + //printf(">>>> running %s for mod: %s\n", fxn, m_last_run_mod.c_str()); +#endif } void ScriptApiBase::addObjectReference(ServerActiveObject *cobj) @@ -245,7 +300,7 @@ void ScriptApiBase::removeObjectReference(ServerActiveObject *cobj) void ScriptApiBase::objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj) { - if(cobj == NULL || cobj->getId() == 0){ + if (cobj == NULL || cobj->getId() == 0) { ObjectRef::create(L, cobj); } else { objectrefGet(L, cobj->getId()); @@ -263,4 +318,3 @@ void ScriptApiBase::objectrefGet(lua_State *L, u16 id) lua_remove(L, -2); // object_refs lua_remove(L, -2); // core } - diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 4ea3677a9..d653b5bac 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -34,6 +34,25 @@ extern "C" { #include "common/c_internal.h" #define SCRIPTAPI_LOCK_DEBUG +#define SCRIPTAPI_DEBUG + +#define SCRIPT_MOD_NAME_FIELD "current_mod_name" +// MUST be an invalid mod name so that mods can't +// use that name to bypass security! +#define BUILTIN_MOD_NAME "*builtin*" + +#define PCALL_RES(RES) do { \ + int result_ = (RES); \ + if (result_ != 0) { \ + scriptError(result_, __FUNCTION__); \ + } \ +} while (0) + +#define runCallbacks(nargs, mode) \ + runCallbacksRaw((nargs), (mode), __FUNCTION__) + +#define setOriginFromTable(index) \ + setOriginFromTableRaw(index, __FUNCTION__) class Server; class Environment; @@ -42,17 +61,26 @@ class ServerActiveObject; class ScriptApiBase { public: - ScriptApiBase(); virtual ~ScriptApiBase(); - bool loadMod(const std::string &scriptpath, const std::string &modname); - bool loadScript(const std::string &scriptpath); + bool loadMod(const std::string &script_path, const std::string &mod_name, + std::string *error=NULL); + bool loadScript(const std::string &script_path, std::string *error=NULL); + + void runCallbacksRaw(int nargs, + RunCallbacksMode mode, const char *fxn); /* object */ void addObjectReference(ServerActiveObject *cobj); void removeObjectReference(ServerActiveObject *cobj); + Server* getServer() { return m_server; } + + std::string getOrigin() { return m_last_run_mod; } + void setOriginDirect(const char *origin); + void setOriginFromTableRaw(int index, const char *fxn); + protected: friend class LuaABM; friend class InvRef; @@ -66,10 +94,9 @@ protected: { return m_luastack; } void realityCheck(); - void scriptError(); + void scriptError(int result, const char *fxn); void stackDump(std::ostream &o); - Server* getServer() { return m_server; } void setServer(Server* server) { m_server = server; } Environment* getEnv() { return m_environment; } @@ -82,8 +109,10 @@ protected: void objectrefGet(lua_State *L, u16 id); JMutex m_luastackmutex; + std::string m_last_run_mod; // Stack index of Lua error handler int m_errorhandler; + bool m_secure; #ifdef SCRIPTAPI_LOCK_DEBUG bool m_locked; #endif diff --git a/src/script/cpp_api/s_entity.cpp b/src/script/cpp_api/s_entity.cpp index b52bde18a..0d159846a 100644 --- a/src/script/cpp_api/s_entity.cpp +++ b/src/script/cpp_api/s_entity.cpp @@ -91,9 +91,9 @@ void ScriptApiEntity::luaentity_Activate(u16 id, lua_pushvalue(L, object); // self lua_pushlstring(L, staticdata.c_str(), staticdata.size()); lua_pushinteger(L, dtime_s); - // Call with 3 arguments, 0 results - if (lua_pcall(L, 3, 0, m_errorhandler)) - scriptError(); + + setOriginFromTable(object); + PCALL_RES(lua_pcall(L, 3, 0, m_errorhandler)); } else { lua_pop(L, 1); } @@ -136,12 +136,12 @@ std::string ScriptApiEntity::luaentity_GetStaticdata(u16 id) lua_pop(L, 2); // Pop entity and get_staticdata return ""; } - luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushvalue(L, object); // self - // Call with 1 arguments, 1 results - if (lua_pcall(L, 1, 1, m_errorhandler)) - scriptError(); + + setOriginFromTable(object); + PCALL_RES(lua_pcall(L, 1, 1, m_errorhandler)); + lua_remove(L, object); // Remove object size_t len = 0; @@ -209,9 +209,10 @@ void ScriptApiEntity::luaentity_Step(u16 id, float dtime) luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushvalue(L, object); // self lua_pushnumber(L, dtime); // dtime - // Call with 2 arguments, 0 results - if (lua_pcall(L, 2, 0, m_errorhandler)) - scriptError(); + + setOriginFromTable(object); + PCALL_RES(lua_pcall(L, 2, 0, m_errorhandler)); + lua_pop(L, 1); // Pop object } @@ -241,9 +242,10 @@ void ScriptApiEntity::luaentity_Punch(u16 id, lua_pushnumber(L, time_from_last_punch); push_tool_capabilities(L, *toolcap); push_v3f(L, dir); - // Call with 5 arguments, 0 results - if (lua_pcall(L, 5, 0, m_errorhandler)) - scriptError(); + + setOriginFromTable(object); + PCALL_RES(lua_pcall(L, 5, 0, m_errorhandler)); + lua_pop(L, 1); // Pop object } @@ -268,9 +270,10 @@ void ScriptApiEntity::luaentity_Rightclick(u16 id, luaL_checktype(L, -1, LUA_TFUNCTION); lua_pushvalue(L, object); // self objectrefGetOrCreate(L, clicker); // Clicker reference - // Call with 2 arguments, 0 results - if (lua_pcall(L, 2, 0, m_errorhandler)) - scriptError(); + + setOriginFromTable(object); + PCALL_RES(lua_pcall(L, 2, 0, m_errorhandler)); + lua_pop(L, 1); // Pop object } diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index c171bbf02..9c733773a 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -38,7 +38,7 @@ void ScriptApiEnv::environment_OnGenerated(v3s16 minp, v3s16 maxp, push_v3s16(L, minp); push_v3s16(L, maxp); lua_pushnumber(L, blockseed); - script_run_callbacks(L, 3, RUN_CALLBACKS_MODE_FIRST); + runCallbacks(3, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiEnv::environment_Step(float dtime) @@ -52,7 +52,7 @@ void ScriptApiEnv::environment_Step(float dtime) // Call callbacks lua_pushnumber(L, dtime); try { - script_run_callbacks(L, 1, RUN_CALLBACKS_MODE_FIRST); + runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } catch (LuaError &e) { getServer()->setAsyncFatalError(e.what()); } @@ -73,7 +73,7 @@ void ScriptApiEnv::player_event(ServerActiveObject* player, std::string type) objectrefGetOrCreate(L, player); // player lua_pushstring(L,type.c_str()); // event type try { - script_run_callbacks(L, 2, RUN_CALLBACKS_MODE_FIRST); + runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } catch (LuaError &e) { getServer()->setAsyncFatalError(e.what()); } diff --git a/src/script/cpp_api/s_internal.h b/src/script/cpp_api/s_internal.h index 10ee1a7de..9999a584a 100644 --- a/src/script/cpp_api/s_internal.h +++ b/src/script/cpp_api/s_internal.h @@ -61,3 +61,4 @@ bool* m_variable; StackUnroller stack_unroller(L); #endif /* S_INTERNAL_H_ */ + diff --git a/src/script/cpp_api/s_inventory.cpp b/src/script/cpp_api/s_inventory.cpp index 422faf150..019d1ccc0 100644 --- a/src/script/cpp_api/s_inventory.cpp +++ b/src/script/cpp_api/s_inventory.cpp @@ -48,8 +48,7 @@ int ScriptApiDetached::detached_inventory_AllowMove( lua_pushinteger(L, to_index + 1); // to_index lua_pushinteger(L, count); // count objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 7, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 7, 1, m_errorhandler)); if(!lua_isnumber(L, -1)) throw LuaError("allow_move should return a number. name=" + name); int ret = luaL_checkinteger(L, -1); @@ -77,8 +76,7 @@ int ScriptApiDetached::detached_inventory_AllowPut( lua_pushinteger(L, index + 1); // index LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 5, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 5, 1, m_errorhandler)); if (!lua_isnumber(L, -1)) throw LuaError("allow_put should return a number. name=" + name); int ret = luaL_checkinteger(L, -1); @@ -106,8 +104,7 @@ int ScriptApiDetached::detached_inventory_AllowTake( lua_pushinteger(L, index + 1); // index LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 5, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 5, 1, m_errorhandler)); if (!lua_isnumber(L, -1)) throw LuaError("allow_take should return a number. name=" + name); int ret = luaL_checkinteger(L, -1); @@ -139,8 +136,7 @@ void ScriptApiDetached::detached_inventory_OnMove( lua_pushinteger(L, to_index + 1); // to_index lua_pushinteger(L, count); // count objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 7, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 7, 0, m_errorhandler)); } // Report put items @@ -164,8 +160,7 @@ void ScriptApiDetached::detached_inventory_OnPut( lua_pushinteger(L, index + 1); // index LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 5, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 5, 0, m_errorhandler)); } // Report taken items @@ -189,8 +184,7 @@ void ScriptApiDetached::detached_inventory_OnTake( lua_pushinteger(L, index + 1); // index LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 5, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 5, 0, m_errorhandler)); } // Retrieves core.detached_inventories[name][callbackname] @@ -215,6 +209,9 @@ bool ScriptApiDetached::getDetachedInventoryCallback( lua_pop(L, 1); return false; } + + setOriginFromTable(-1); + lua_getfield(L, -1, callbackname); lua_remove(L, -2); // Should be a function or nil diff --git a/src/script/cpp_api/s_item.cpp b/src/script/cpp_api/s_item.cpp index e3a9ac7aa..4d4d416ec 100644 --- a/src/script/cpp_api/s_item.cpp +++ b/src/script/cpp_api/s_item.cpp @@ -42,8 +42,7 @@ bool ScriptApiItem::item_OnDrop(ItemStack &item, LuaItemStack::create(L, item); objectrefGetOrCreate(L, dropper); pushFloatPos(L, pos); - if (lua_pcall(L, 3, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 3, 1, m_errorhandler)); if (!lua_isnil(L, -1)) { try { item = read_item(L,-1, getServer()); @@ -68,8 +67,7 @@ bool ScriptApiItem::item_OnPlace(ItemStack &item, LuaItemStack::create(L, item); objectrefGetOrCreate(L, placer); pushPointedThing(pointed); - if (lua_pcall(L, 3, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 3, 1, m_errorhandler)); if (!lua_isnil(L, -1)) { try { item = read_item(L,-1, getServer()); @@ -94,8 +92,7 @@ bool ScriptApiItem::item_OnUse(ItemStack &item, LuaItemStack::create(L, item); objectrefGetOrCreate(L, user); pushPointedThing(pointed); - if (lua_pcall(L, 3, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 3, 1, m_errorhandler)); if(!lua_isnil(L, -1)) { try { item = read_item(L,-1, getServer()); @@ -116,7 +113,7 @@ bool ScriptApiItem::item_OnCraft(ItemStack &item, ServerActiveObject *user, lua_getfield(L, -1, "on_craft"); LuaItemStack::create(L, item); objectrefGetOrCreate(L, user); - + // Push inventory list std::vector<ItemStack> items; for (u32 i = 0; i < old_craft_grid->getSize(); i++) { @@ -125,8 +122,7 @@ bool ScriptApiItem::item_OnCraft(ItemStack &item, ServerActiveObject *user, push_items(L, items); InvRef::create(L, craft_inv); - if (lua_pcall(L, 4, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 4, 1, m_errorhandler)); if (!lua_isnil(L, -1)) { try { item = read_item(L,-1, getServer()); @@ -156,8 +152,7 @@ bool ScriptApiItem::item_CraftPredict(ItemStack &item, ServerActiveObject *user, push_items(L, items); InvRef::create(L, craft_inv); - if (lua_pcall(L, 4, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 4, 1, m_errorhandler)); if (!lua_isnil(L, -1)) { try { item = read_item(L,-1, getServer()); @@ -198,6 +193,9 @@ bool ScriptApiItem::getItemCallback(const char *name, const char *callbackname) lua_remove(L, -2); luaL_checktype(L, -1, LUA_TTABLE); } + + setOriginFromTable(-1); + lua_getfield(L, -1, callbackname); lua_remove(L, -2); // Remove item def // Should be a function or nil diff --git a/src/script/cpp_api/s_mainmenu.cpp b/src/script/cpp_api/s_mainmenu.cpp index ef8cea6c9..17ceff082 100644 --- a/src/script/cpp_api/s_mainmenu.cpp +++ b/src/script/cpp_api/s_mainmenu.cpp @@ -21,15 +21,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_internal.h" #include "common/c_converter.h" -void ScriptApiMainMenu::setMainMenuErrorMessage(std::string errormessage) +void ScriptApiMainMenu::setMainMenuData(MainMenuDataForScript *data) { SCRIPTAPI_PRECHECKHEADER lua_getglobal(L, "gamedata"); int gamedata_idx = lua_gettop(L); lua_pushstring(L, "errormessage"); - lua_pushstring(L, errormessage.c_str()); + if (!data->errormessage.empty()) { + lua_pushstring(L, data->errormessage.c_str()); + } else { + lua_pushnil(L); + } lua_settable(L, gamedata_idx); + setboolfield(L, gamedata_idx, "reconnect_requested", + data->reconnect_requested); lua_pop(L, 1); } @@ -49,11 +55,10 @@ void ScriptApiMainMenu::handleMainMenuEvent(std::string text) // Call it lua_pushstring(L, text.c_str()); - if (lua_pcall(L, 1, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 1, 0, m_errorhandler)); } -void ScriptApiMainMenu::handleMainMenuButtons(std::map<std::string, std::string> fields) +void ScriptApiMainMenu::handleMainMenuButtons(const StringMap &fields) { SCRIPTAPI_PRECHECKHEADER @@ -69,8 +74,8 @@ void ScriptApiMainMenu::handleMainMenuButtons(std::map<std::string, std::string> // Convert fields to a Lua table lua_newtable(L); - std::map<std::string, std::string>::const_iterator it; - for (it = fields.begin(); it != fields.end(); it++){ + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) { const std::string &name = it->first; const std::string &value = it->second; lua_pushstring(L, name.c_str()); @@ -79,7 +84,6 @@ void ScriptApiMainMenu::handleMainMenuButtons(std::map<std::string, std::string> } // Call it - if (lua_pcall(L, 1, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 1, 0, m_errorhandler)); } diff --git a/src/script/cpp_api/s_mainmenu.h b/src/script/cpp_api/s_mainmenu.h index 53dcd37e9..8d5895817 100644 --- a/src/script/cpp_api/s_mainmenu.h +++ b/src/script/cpp_api/s_mainmenu.h @@ -21,17 +21,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #define S_MAINMENU_H_ #include "cpp_api/s_base.h" -#include <map> +#include "util/string.h" +#include "../guiMainMenu.h" -class ScriptApiMainMenu - : virtual public ScriptApiBase -{ +class ScriptApiMainMenu : virtual public ScriptApiBase { public: /** - * set gamedata.errormessage to inform lua of an error - * @param errormessage the error message + * Hand over MainMenuDataForScript to lua to inform lua of the content + * @param data the data */ - void setMainMenuErrorMessage(std::string errormessage); + void setMainMenuData(MainMenuDataForScript *data); /** * process events received from formspec @@ -43,7 +42,7 @@ public: * process field data recieved from formspec * @param fields data in field format */ - void handleMainMenuButtons(std::map<std::string, std::string> fields); + void handleMainMenuButtons(const StringMap &fields); }; #endif /* S_MAINMENU_H_ */ diff --git a/src/script/cpp_api/s_node.cpp b/src/script/cpp_api/s_node.cpp index e3d3fb58b..dac058b13 100644 --- a/src/script/cpp_api/s_node.cpp +++ b/src/script/cpp_api/s_node.cpp @@ -106,8 +106,7 @@ bool ScriptApiNode::node_on_punch(v3s16 p, MapNode node, pushnode(L, node, ndef); objectrefGetOrCreate(L, puncher); pushPointedThing(pointed); - if (lua_pcall(L, 4, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 4, 0, m_errorhandler)); return true; } @@ -126,8 +125,7 @@ bool ScriptApiNode::node_on_dig(v3s16 p, MapNode node, push_v3s16(L, p); pushnode(L, node, ndef); objectrefGetOrCreate(L, digger); - if (lua_pcall(L, 3, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 3, 0, m_errorhandler)); return true; } @@ -143,8 +141,7 @@ void ScriptApiNode::node_on_construct(v3s16 p, MapNode node) // Call function push_v3s16(L, p); - if (lua_pcall(L, 1, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 1, 0, m_errorhandler)); } void ScriptApiNode::node_on_destruct(v3s16 p, MapNode node) @@ -159,8 +156,7 @@ void ScriptApiNode::node_on_destruct(v3s16 p, MapNode node) // Call function push_v3s16(L, p); - if (lua_pcall(L, 1, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 1, 0, m_errorhandler)); } void ScriptApiNode::node_after_destruct(v3s16 p, MapNode node) @@ -176,8 +172,7 @@ void ScriptApiNode::node_after_destruct(v3s16 p, MapNode node) // Call function push_v3s16(L, p); pushnode(L, node, ndef); - if (lua_pcall(L, 2, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 2, 0, m_errorhandler)); } bool ScriptApiNode::node_on_timer(v3s16 p, MapNode node, f32 dtime) @@ -193,14 +188,13 @@ bool ScriptApiNode::node_on_timer(v3s16 p, MapNode node, f32 dtime) // Call function push_v3s16(L, p); lua_pushnumber(L,dtime); - if (lua_pcall(L, 2, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 2, 1, m_errorhandler)); return (bool) lua_isboolean(L, -1) && (bool) lua_toboolean(L, -1) == true; } void ScriptApiNode::node_on_receive_fields(v3s16 p, const std::string &formname, - const std::map<std::string, std::string> &fields, + const StringMap &fields, ServerActiveObject *sender) { SCRIPTAPI_PRECHECKHEADER @@ -220,8 +214,8 @@ void ScriptApiNode::node_on_receive_fields(v3s16 p, push_v3s16(L, p); // pos lua_pushstring(L, formname.c_str()); // formname lua_newtable(L); // fields - std::map<std::string, std::string>::const_iterator it; - for (it = fields.begin(); it != fields.end(); it++){ + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); it++) { const std::string &name = it->first; const std::string &value = it->second; lua_pushstring(L, name.c_str()); @@ -229,8 +223,7 @@ void ScriptApiNode::node_on_receive_fields(v3s16 p, lua_settable(L, -3); } objectrefGetOrCreate(L, sender); // player - if (lua_pcall(L, 4, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 4, 0, m_errorhandler)); } void ScriptApiNode::node_falling_update(v3s16 p) @@ -239,8 +232,7 @@ void ScriptApiNode::node_falling_update(v3s16 p) lua_getglobal(L, "nodeupdate"); push_v3s16(L, p); - if (lua_pcall(L, 1, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 1, 0, m_errorhandler)); } void ScriptApiNode::node_falling_update_single(v3s16 p) @@ -249,7 +241,5 @@ void ScriptApiNode::node_falling_update_single(v3s16 p) lua_getglobal(L, "nodeupdate_single"); push_v3s16(L, p); - if (lua_pcall(L, 1, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 1, 0, m_errorhandler)); } - diff --git a/src/script/cpp_api/s_node.h b/src/script/cpp_api/s_node.h index b3a6c83b5..fe1180cb3 100644 --- a/src/script/cpp_api/s_node.h +++ b/src/script/cpp_api/s_node.h @@ -20,11 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef S_NODE_H_ #define S_NODE_H_ -#include <map> - #include "irr_v3d.h" #include "cpp_api/s_base.h" #include "cpp_api/s_nodemeta.h" +#include "util/string.h" struct MapNode; class ServerActiveObject; @@ -47,7 +46,7 @@ public: bool node_on_timer(v3s16 p, MapNode node, f32 dtime); void node_on_receive_fields(v3s16 p, const std::string &formname, - const std::map<std::string, std::string> &fields, + const StringMap &fields, ServerActiveObject *sender); void node_falling_update(v3s16 p); void node_falling_update_single(v3s16 p); diff --git a/src/script/cpp_api/s_nodemeta.cpp b/src/script/cpp_api/s_nodemeta.cpp index 01eee337c..638750b0e 100644 --- a/src/script/cpp_api/s_nodemeta.cpp +++ b/src/script/cpp_api/s_nodemeta.cpp @@ -54,8 +54,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowMove(v3s16 p, lua_pushinteger(L, to_index + 1); // to_index lua_pushinteger(L, count); // count objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 7, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 7, 1, m_errorhandler)); if (!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_move should" " return a number, guilty node: " + nodename); @@ -89,8 +88,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowPut(v3s16 p, lua_pushinteger(L, index + 1); // index LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 5, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 5, 1, m_errorhandler)); if(!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_put should" " return a number, guilty node: " + nodename); @@ -124,8 +122,7 @@ int ScriptApiNodemeta::nodemeta_inventory_AllowTake(v3s16 p, lua_pushinteger(L, index + 1); // index LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 5, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 5, 1, m_errorhandler)); if (!lua_isnumber(L, -1)) throw LuaError("allow_metadata_inventory_take should" " return a number, guilty node: " + nodename); @@ -162,8 +159,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnMove(v3s16 p, lua_pushinteger(L, to_index + 1); // to_index lua_pushinteger(L, count); // count objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 7, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 7, 0, m_errorhandler)); } // Report put items @@ -191,8 +187,7 @@ void ScriptApiNodemeta::nodemeta_inventory_OnPut(v3s16 p, lua_pushinteger(L, index + 1); // index LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 5, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 5, 0, m_errorhandler)); } // Report taken items @@ -220,13 +215,14 @@ void ScriptApiNodemeta::nodemeta_inventory_OnTake(v3s16 p, lua_pushinteger(L, index + 1); // index LuaItemStack::create(L, stack); // stack objectrefGetOrCreate(L, player); // player - if (lua_pcall(L, 5, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 5, 0, m_errorhandler)); } -ScriptApiNodemeta::ScriptApiNodemeta() { +ScriptApiNodemeta::ScriptApiNodemeta() +{ } -ScriptApiNodemeta::~ScriptApiNodemeta() { +ScriptApiNodemeta::~ScriptApiNodemeta() +{ } diff --git a/src/script/cpp_api/s_player.cpp b/src/script/cpp_api/s_player.cpp index 81bfd4505..ef3c31cfd 100644 --- a/src/script/cpp_api/s_player.cpp +++ b/src/script/cpp_api/s_player.cpp @@ -19,6 +19,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_player.h" #include "cpp_api/s_internal.h" +#include "common/c_converter.h" +#include "common/c_content.h" #include "util/string.h" void ScriptApiPlayer::on_newplayer(ServerActiveObject *player) @@ -30,7 +32,7 @@ void ScriptApiPlayer::on_newplayer(ServerActiveObject *player) lua_getfield(L, -1, "registered_on_newplayers"); // Call callbacks objectrefGetOrCreate(L, player); - script_run_callbacks(L, 1, RUN_CALLBACKS_MODE_FIRST); + runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiPlayer::on_dieplayer(ServerActiveObject *player) @@ -42,7 +44,47 @@ void ScriptApiPlayer::on_dieplayer(ServerActiveObject *player) lua_getfield(L, -1, "registered_on_dieplayers"); // Call callbacks objectrefGetOrCreate(L, player); - script_run_callbacks(L, 1, RUN_CALLBACKS_MODE_FIRST); + runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); +} + +bool ScriptApiPlayer::on_punchplayer(ServerActiveObject *player, + ServerActiveObject *hitter, + float time_from_last_punch, + const ToolCapabilities *toolcap, + v3f dir, + s16 damage) +{ + SCRIPTAPI_PRECHECKHEADER + // Get core.registered_on_punchplayers + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_punchplayers"); + // Call callbacks + objectrefGetOrCreate(L, player); + objectrefGetOrCreate(L, hitter); + lua_pushnumber(L, time_from_last_punch); + push_tool_capabilities(L, *toolcap); + push_v3f(L, dir); + lua_pushnumber(L, damage); + runCallbacks(6, RUN_CALLBACKS_MODE_OR); + return lua_toboolean(L, -1); +} + +s16 ScriptApiPlayer::on_player_hpchange(ServerActiveObject *player, + s16 hp_change) +{ + SCRIPTAPI_PRECHECKHEADER + + // Get core.registered_on_player_hpchange + lua_getglobal(L, "core"); + lua_getfield(L, -1, "registered_on_player_hpchange"); + lua_remove(L, -2); + + objectrefGetOrCreate(L, player); + lua_pushnumber(L, hp_change); + PCALL_RES(lua_pcall(L, 2, 1, m_errorhandler)); + hp_change = lua_tointeger(L, -1); + lua_pop(L, -1); + return hp_change; } bool ScriptApiPlayer::on_respawnplayer(ServerActiveObject *player) @@ -54,12 +96,15 @@ bool ScriptApiPlayer::on_respawnplayer(ServerActiveObject *player) lua_getfield(L, -1, "registered_on_respawnplayers"); // Call callbacks objectrefGetOrCreate(L, player); - script_run_callbacks(L, 1, RUN_CALLBACKS_MODE_OR); + runCallbacks(1, RUN_CALLBACKS_MODE_OR); bool positioning_handled_by_some = lua_toboolean(L, -1); return positioning_handled_by_some; } -bool ScriptApiPlayer::on_prejoinplayer(std::string name, std::string ip, std::string &reason) +bool ScriptApiPlayer::on_prejoinplayer( + const std::string &name, + const std::string &ip, + std::string *reason) { SCRIPTAPI_PRECHECKHEADER @@ -68,9 +113,9 @@ bool ScriptApiPlayer::on_prejoinplayer(std::string name, std::string ip, std::st lua_getfield(L, -1, "registered_on_prejoinplayers"); lua_pushstring(L, name.c_str()); lua_pushstring(L, ip.c_str()); - script_run_callbacks(L, 2, RUN_CALLBACKS_MODE_OR); + runCallbacks(2, RUN_CALLBACKS_MODE_OR); if (lua_isstring(L, -1)) { - reason.assign(lua_tostring(L, -1)); + reason->assign(lua_tostring(L, -1)); return true; } return false; @@ -85,7 +130,7 @@ void ScriptApiPlayer::on_joinplayer(ServerActiveObject *player) lua_getfield(L, -1, "registered_on_joinplayers"); // Call callbacks objectrefGetOrCreate(L, player); - script_run_callbacks(L, 1, RUN_CALLBACKS_MODE_FIRST); + runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiPlayer::on_leaveplayer(ServerActiveObject *player) @@ -97,7 +142,7 @@ void ScriptApiPlayer::on_leaveplayer(ServerActiveObject *player) lua_getfield(L, -1, "registered_on_leaveplayers"); // Call callbacks objectrefGetOrCreate(L, player); - script_run_callbacks(L, 1, RUN_CALLBACKS_MODE_FIRST); + runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiPlayer::on_cheat(ServerActiveObject *player, @@ -113,12 +158,12 @@ void ScriptApiPlayer::on_cheat(ServerActiveObject *player, lua_newtable(L); lua_pushlstring(L, cheat_type.c_str(), cheat_type.size()); lua_setfield(L, -2, "type"); - script_run_callbacks(L, 2, RUN_CALLBACKS_MODE_FIRST); + runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } void ScriptApiPlayer::on_playerReceiveFields(ServerActiveObject *player, const std::string &formname, - const std::map<std::string, std::string> &fields) + const StringMap &fields) { SCRIPTAPI_PRECHECKHEADER @@ -132,17 +177,19 @@ void ScriptApiPlayer::on_playerReceiveFields(ServerActiveObject *player, lua_pushstring(L, formname.c_str()); // param 3 lua_newtable(L); - for(std::map<std::string, std::string>::const_iterator - i = fields.begin(); i != fields.end(); i++){ - const std::string &name = i->first; - const std::string &value = i->second; + StringMap::const_iterator it; + for (it = fields.begin(); it != fields.end(); ++it) { + const std::string &name = it->first; + const std::string &value = it->second; lua_pushstring(L, name.c_str()); lua_pushlstring(L, value.c_str(), value.size()); lua_settable(L, -3); } - script_run_callbacks(L, 3, RUN_CALLBACKS_MODE_OR_SC); + runCallbacks(3, RUN_CALLBACKS_MODE_OR_SC); } -ScriptApiPlayer::~ScriptApiPlayer() { + +ScriptApiPlayer::~ScriptApiPlayer() +{ } diff --git a/src/script/cpp_api/s_player.h b/src/script/cpp_api/s_player.h index c77d397c4..2e4dc2222 100644 --- a/src/script/cpp_api/s_player.h +++ b/src/script/cpp_api/s_player.h @@ -20,10 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef S_PLAYER_H_ #define S_PLAYER_H_ -#include <map> - #include "cpp_api/s_base.h" +#include "irr_v3d.h" +#include "util/string.h" +struct ToolCapabilities; class ScriptApiPlayer : virtual public ScriptApiBase @@ -34,14 +35,17 @@ public: void on_newplayer(ServerActiveObject *player); void on_dieplayer(ServerActiveObject *player); bool on_respawnplayer(ServerActiveObject *player); - bool on_prejoinplayer(std::string name, std::string ip, std::string &reason); + bool on_prejoinplayer(const std::string &name, const std::string &ip, + std::string *reason); void on_joinplayer(ServerActiveObject *player); void on_leaveplayer(ServerActiveObject *player); void on_cheat(ServerActiveObject *player, const std::string &cheat_type); - + bool on_punchplayer(ServerActiveObject *player, + ServerActiveObject *hitter, float time_from_last_punch, + const ToolCapabilities *toolcap, v3f dir, s16 damage); + s16 on_player_hpchange(ServerActiveObject *player, s16 hp_change); void on_playerReceiveFields(ServerActiveObject *player, - const std::string &formname, - const std::map<std::string, std::string> &fields); + const std::string &formname, const StringMap &fields); }; diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp new file mode 100644 index 000000000..6a6d40307 --- /dev/null +++ b/src/script/cpp_api/s_security.cpp @@ -0,0 +1,604 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "cpp_api/s_security.h" + +#include "filesys.h" +#include "porting.h" +#include "server.h" +#include "settings.h" + +#include <cerrno> +#include <string> +#include <iostream> + + +#define SECURE_API(lib, name) \ + lua_pushcfunction(L, sl_##lib##_##name); \ + lua_setfield(L, -2, #name); + + +static inline void copy_safe(lua_State *L, const char *list[], unsigned len, int from=-2, int to=-1) +{ + if (from < 0) from = lua_gettop(L) + from + 1; + if (to < 0) to = lua_gettop(L) + to + 1; + for (unsigned i = 0; i < (len / sizeof(list[0])); i++) { + lua_getfield(L, from, list[i]); + lua_setfield(L, to, list[i]); + } +} + +// Pushes the original version of a library function on the stack, from the old version +static inline void push_original(lua_State *L, const char *lib, const char *func) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "globals_backup"); + lua_getfield(L, -1, lib); + lua_remove(L, -2); // Remove globals_backup + lua_getfield(L, -1, func); + lua_remove(L, -2); // Remove lib +} + + +void ScriptApiSecurity::initializeSecurity() +{ + static const char *whitelist[] = { + "assert", + "core", + "collectgarbage", + "DIR_DELIM", + "error", + "getfenv", + "getmetatable", + "ipairs", + "next", + "pairs", + "pcall", + "print", + "rawequal", + "rawget", + "rawset", + "select", + "setfenv", + "setmetatable", + "tonumber", + "tostring", + "type", + "unpack", + "_VERSION", + "xpcall", + // Completely safe libraries + "coroutine", + "string", + "table", + "math", + }; + static const char *io_whitelist[] = { + "close", + "flush", + "read", + "type", + "write", + }; + static const char *os_whitelist[] = { + "clock", + "date", + "difftime", + "exit", + "getenv", + "setlocale", + "time", + "tmpname", + }; + static const char *debug_whitelist[] = { + "gethook", + "traceback", + "getinfo", + "getmetatable", + "setupvalue", + "setmetatable", + "upvalueid", + "upvaluejoin", + "sethook", + "debug", + "getupvalue", + "setlocal", + }; + static const char *package_whitelist[] = { + "config", + "cpath", + "path", + "searchpath", + }; + static const char *jit_whitelist[] = { + "arch", + "flush", + "off", + "on", + "opt", + "os", + "status", + "version", + "version_num", + }; + + m_secure = true; + + lua_State *L = getStack(); + + // Backup globals to the registry + lua_getglobal(L, "_G"); + lua_setfield(L, LUA_REGISTRYINDEX, "globals_backup"); + + // Replace the global environment with an empty one +#if LUA_VERSION_NUM <= 501 + int is_main = lua_pushthread(L); // Push the main thread + FATAL_ERROR_IF(!is_main, "Security: ScriptApi's Lua state " + "isn't the main Lua thread!"); +#endif + lua_newtable(L); // Create new environment + lua_pushvalue(L, -1); + lua_setfield(L, -2, "_G"); // Set _G of new environment +#if LUA_VERSION_NUM >= 502 // Lua >= 5.2 + // Set the global environment + lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); +#else // Lua <= 5.1 + // Set the environment of the main thread + FATAL_ERROR_IF(!lua_setfenv(L, -2), "Security: Unable to set " + "environment of the main Lua thread!"); + lua_pop(L, 1); // Pop thread +#endif + + // Get old globals + lua_getfield(L, LUA_REGISTRYINDEX, "globals_backup"); + int old_globals = lua_gettop(L); + + + // Copy safe base functions + lua_getglobal(L, "_G"); + copy_safe(L, whitelist, sizeof(whitelist)); + + // And replace unsafe ones + SECURE_API(g, dofile); + SECURE_API(g, load); + SECURE_API(g, loadfile); + SECURE_API(g, loadstring); + SECURE_API(g, require); + lua_pop(L, 1); + + + // Copy safe IO functions + lua_getfield(L, old_globals, "io"); + lua_newtable(L); + copy_safe(L, io_whitelist, sizeof(io_whitelist)); + + // And replace unsafe ones + SECURE_API(io, open); + SECURE_API(io, input); + SECURE_API(io, output); + SECURE_API(io, lines); + + lua_setglobal(L, "io"); + lua_pop(L, 1); // Pop old IO + + + // Copy safe OS functions + lua_getfield(L, old_globals, "os"); + lua_newtable(L); + copy_safe(L, os_whitelist, sizeof(os_whitelist)); + + // And replace unsafe ones + SECURE_API(os, remove); + SECURE_API(os, rename); + + lua_setglobal(L, "os"); + lua_pop(L, 1); // Pop old OS + + + // Copy safe debug functions + lua_getfield(L, old_globals, "debug"); + lua_newtable(L); + copy_safe(L, debug_whitelist, sizeof(debug_whitelist)); + lua_setglobal(L, "debug"); + lua_pop(L, 1); // Pop old debug + + + // Copy safe package fields + lua_getfield(L, old_globals, "package"); + lua_newtable(L); + copy_safe(L, package_whitelist, sizeof(package_whitelist)); + lua_setglobal(L, "package"); + lua_pop(L, 1); // Pop old package + + + // Copy safe jit functions, if they exist + lua_getfield(L, -1, "jit"); + if (!lua_isnil(L, -1)) { + lua_newtable(L); + copy_safe(L, jit_whitelist, sizeof(jit_whitelist)); + lua_setglobal(L, "jit"); + } + lua_pop(L, 1); // Pop old jit + + lua_pop(L, 1); // Pop globals_backup +} + + +bool ScriptApiSecurity::isSecure(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, "globals_backup"); + bool secure = !lua_isnil(L, -1); + lua_pop(L, 1); + return secure; +} + + +#define CHECK_FILE_ERR(ret, fp) \ + if (ret) { \ + if (fp) std::fclose(fp); \ + lua_pushfstring(L, "%s: %s", path, strerror(errno)); \ + return false; \ + } + + +bool ScriptApiSecurity::safeLoadFile(lua_State *L, const char *path) +{ + FILE *fp; + char *chunk_name; + if (path == NULL) { + fp = stdin; + chunk_name = const_cast<char *>("=stdin"); + } else { + fp = fopen(path, "rb"); + if (!fp) { + lua_pushfstring(L, "%s: %s", path, strerror(errno)); + return false; + } + chunk_name = new char[strlen(path) + 2]; + chunk_name[0] = '@'; + chunk_name[1] = '\0'; + strcat(chunk_name, path); + } + + size_t start = 0; + int c = std::getc(fp); + if (c == '#') { + // Skip the first line + while ((c = std::getc(fp)) != EOF && c != '\n'); + if (c == '\n') c = std::getc(fp); + start = std::ftell(fp); + } + + if (c == LUA_SIGNATURE[0]) { + lua_pushliteral(L, "Bytecode prohibited when mod security is enabled."); + return false; + } + + // Read the file + int ret = std::fseek(fp, 0, SEEK_END); + CHECK_FILE_ERR(ret, fp); + if (ret) { + std::fclose(fp); + lua_pushfstring(L, "%s: %s", path, strerror(errno)); + return false; + } + size_t size = std::ftell(fp) - start; + char *code = new char[size]; + ret = std::fseek(fp, start, SEEK_SET); + CHECK_FILE_ERR(ret, fp); + if (ret) { + std::fclose(fp); + lua_pushfstring(L, "%s: %s", path, strerror(errno)); + return false; + } + size_t num_read = std::fread(code, 1, size, fp); + if (path) { + std::fclose(fp); + } + if (num_read != size) { + lua_pushliteral(L, "Error reading file to load."); + return false; + } + + if (luaL_loadbuffer(L, code, size, chunk_name)) { + return false; + } + + if (path) { + delete [] chunk_name; + } + return true; +} + + +bool ScriptApiSecurity::checkPath(lua_State *L, const char *path) +{ + std::string str; // Transient + + std::string norel_path = fs::RemoveRelativePathComponents(path); + std::string abs_path = fs::AbsolutePath(norel_path); + + if (!abs_path.empty()) { + // Don't allow accessing the settings file + str = fs::AbsolutePath(g_settings_path); + if (str == abs_path) return false; + } + + // If we couldn't find the absolute path (path doesn't exist) then + // try removing the last components until it works (to allow + // non-existent files/folders for mkdir). + std::string cur_path = norel_path; + std::string removed; + while (abs_path.empty() && !cur_path.empty()) { + std::string tmp_rmed; + cur_path = fs::RemoveLastPathComponent(cur_path, &tmp_rmed); + removed = tmp_rmed + (removed.empty() ? "" : DIR_DELIM + removed); + abs_path = fs::AbsolutePath(cur_path); + } + if (abs_path.empty()) return false; + // Add the removed parts back so that you can't, eg, create a + // directory in worldmods if worldmods doesn't exist. + if (!removed.empty()) abs_path += DIR_DELIM + removed; + + // Get server from registry + lua_getfield(L, LUA_REGISTRYINDEX, "scriptapi"); + ScriptApiBase *script = (ScriptApiBase *) lua_touserdata(L, -1); + lua_pop(L, 1); + const Server *server = script->getServer(); + + if (!server) return false; + + // Get mod name + lua_getfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); + if (lua_isstring(L, -1)) { + std::string mod_name = lua_tostring(L, -1); + + // Builtin can access anything + if (mod_name == BUILTIN_MOD_NAME) { + return true; + } + + // Allow paths in mod path + const ModSpec *mod = server->getModSpec(mod_name); + if (mod) { + str = fs::AbsolutePath(mod->path); + if (!str.empty() && fs::PathStartsWith(abs_path, str)) { + return true; + } + } + } + lua_pop(L, 1); // Pop mod name + + str = fs::AbsolutePath(server->getWorldPath()); + if (str.empty()) return false; + // Don't allow access to world mods. We add to the absolute path + // of the world instead of getting the absolute paths directly + // because that won't work if they don't exist. + if (fs::PathStartsWith(abs_path, str + DIR_DELIM + "worldmods") || + fs::PathStartsWith(abs_path, str + DIR_DELIM + "game")) { + return false; + } + // Allow all other paths in world path + if (fs::PathStartsWith(abs_path, str)) { + return true; + } + + // Default to disallowing + return false; +} + + +int ScriptApiSecurity::sl_g_dofile(lua_State *L) +{ + int nret = sl_g_loadfile(L); + if (nret != 1) { + lua_error(L); + // code after this function isn't executed + } + int top_precall = lua_gettop(L); + lua_call(L, 0, LUA_MULTRET); + // Return number of arguments returned by the function, + // adjusting for the function being poped. + return lua_gettop(L) - (top_precall - 1); +} + + +int ScriptApiSecurity::sl_g_load(lua_State *L) +{ + size_t len; + const char *buf; + std::string code; + const char *chunk_name = "=(load)"; + + luaL_checktype(L, 1, LUA_TFUNCTION); + if (!lua_isnone(L, 2)) { + luaL_checktype(L, 2, LUA_TSTRING); + chunk_name = lua_tostring(L, 2); + } + + while (true) { + lua_pushvalue(L, 1); + lua_call(L, 0, 1); + int t = lua_type(L, -1); + if (t == LUA_TNIL) { + break; + } else if (t != LUA_TSTRING) { + lua_pushnil(L); + lua_pushliteral(L, "Loader didn't return a string"); + return 2; + } + buf = lua_tolstring(L, -1, &len); + code += std::string(buf, len); + lua_pop(L, 1); // Pop return value + } + if (code[0] == LUA_SIGNATURE[0]) { + lua_pushnil(L); + lua_pushliteral(L, "Bytecode prohibited when mod security is enabled."); + return 2; + } + if (luaL_loadbuffer(L, code.data(), code.size(), chunk_name)) { + lua_pushnil(L); + lua_insert(L, lua_gettop(L) - 1); + return 2; + } + return 1; +} + + +int ScriptApiSecurity::sl_g_loadfile(lua_State *L) +{ + const char *path = NULL; + + if (lua_isstring(L, 1)) { + path = lua_tostring(L, 1); + CHECK_SECURE_PATH(L, path); + } + + if (!safeLoadFile(L, path)) { + lua_pushnil(L); + lua_insert(L, -2); + return 2; + } + + return 1; +} + + +int ScriptApiSecurity::sl_g_loadstring(lua_State *L) +{ + const char *chunk_name = "=(load)"; + + luaL_checktype(L, 1, LUA_TSTRING); + if (!lua_isnone(L, 2)) { + luaL_checktype(L, 2, LUA_TSTRING); + chunk_name = lua_tostring(L, 2); + } + + size_t size; + const char *code = lua_tolstring(L, 1, &size); + + if (size > 0 && code[0] == LUA_SIGNATURE[0]) { + lua_pushnil(L); + lua_pushliteral(L, "Bytecode prohibited when mod security is enabled."); + return 2; + } + if (luaL_loadbuffer(L, code, size, chunk_name)) { + lua_pushnil(L); + lua_insert(L, lua_gettop(L) - 1); + return 2; + } + return 1; +} + + +int ScriptApiSecurity::sl_g_require(lua_State *L) +{ + lua_pushliteral(L, "require() is disabled when mod security is on."); + return lua_error(L); +} + + +int ScriptApiSecurity::sl_io_open(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TSTRING); + const char *path = lua_tostring(L, 1); + CHECK_SECURE_PATH(L, path); + + push_original(L, "io", "open"); + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_call(L, 2, 2); + return 2; +} + + +int ScriptApiSecurity::sl_io_input(lua_State *L) +{ + if (lua_isstring(L, 1)) { + const char *path = lua_tostring(L, 1); + CHECK_SECURE_PATH(L, path); + } + + push_original(L, "io", "input"); + lua_pushvalue(L, 1); + lua_call(L, 1, 1); + return 1; +} + + +int ScriptApiSecurity::sl_io_output(lua_State *L) +{ + if (lua_isstring(L, 1)) { + const char *path = lua_tostring(L, 1); + CHECK_SECURE_PATH(L, path); + } + + push_original(L, "io", "output"); + lua_pushvalue(L, 1); + lua_call(L, 1, 1); + return 1; +} + + +int ScriptApiSecurity::sl_io_lines(lua_State *L) +{ + if (lua_isstring(L, 1)) { + const char *path = lua_tostring(L, 1); + CHECK_SECURE_PATH(L, path); + } + + push_original(L, "io", "lines"); + lua_pushvalue(L, 1); + int top_precall = lua_gettop(L); + lua_call(L, 1, LUA_MULTRET); + // Return number of arguments returned by the function, + // adjusting for the function being poped. + return lua_gettop(L) - (top_precall - 1); +} + + +int ScriptApiSecurity::sl_os_rename(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TSTRING); + const char *path1 = lua_tostring(L, 1); + CHECK_SECURE_PATH(L, path1); + + luaL_checktype(L, 2, LUA_TSTRING); + const char *path2 = lua_tostring(L, 2); + CHECK_SECURE_PATH(L, path2); + + push_original(L, "os", "rename"); + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_call(L, 2, 2); + return 2; +} + + +int ScriptApiSecurity::sl_os_remove(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TSTRING); + const char *path = lua_tostring(L, 1); + CHECK_SECURE_PATH(L, path); + + push_original(L, "os", "remove"); + lua_pushvalue(L, 1); + lua_call(L, 1, 2); + return 2; +} + diff --git a/src/script/cpp_api/s_security.h b/src/script/cpp_api/s_security.h new file mode 100644 index 000000000..4a4389cf5 --- /dev/null +++ b/src/script/cpp_api/s_security.h @@ -0,0 +1,70 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef S_SECURITY_H +#define S_SECURITY_H + +#include "cpp_api/s_base.h" + + +#define CHECK_SECURE_PATH(L, path) \ + if (!ScriptApiSecurity::checkPath(L, path)) { \ + lua_pushstring(L, (std::string("Attempt to access external file ") + \ + path + " with mod security on.").c_str()); \ + lua_error(L); \ + } +#define CHECK_SECURE_PATH_OPTIONAL(L, path) \ + if (ScriptApiSecurity::isSecure(L)) { \ + CHECK_SECURE_PATH(L, path); \ + } + + +class ScriptApiSecurity : virtual public ScriptApiBase +{ +public: + // Sets up security on the ScriptApi's Lua state + void initializeSecurity(); + // Checks if the Lua state has been secured + static bool isSecure(lua_State *L); + // Loads a file as Lua code safely (doesn't allow bytecode). + static bool safeLoadFile(lua_State *L, const char *path); + // Checks if mods are allowed to read and write to the path + static bool checkPath(lua_State *L, const char *path); + +private: + // Syntax: "sl_" <Library name or 'g' (global)> '_' <Function name> + // (sl stands for Secure Lua) + + static int sl_g_dofile(lua_State *L); + static int sl_g_load(lua_State *L); + static int sl_g_loadfile(lua_State *L); + static int sl_g_loadstring(lua_State *L); + static int sl_g_require(lua_State *L); + + static int sl_io_open(lua_State *L); + static int sl_io_input(lua_State *L); + static int sl_io_output(lua_State *L); + static int sl_io_lines(lua_State *L); + + static int sl_os_rename(lua_State *L); + static int sl_os_remove(lua_State *L); +}; + +#endif + diff --git a/src/script/cpp_api/s_server.cpp b/src/script/cpp_api/s_server.cpp index 21fe164aa..ec2f9c0af 100644 --- a/src/script/cpp_api/s_server.cpp +++ b/src/script/cpp_api/s_server.cpp @@ -32,8 +32,7 @@ bool ScriptApiServer::getAuth(const std::string &playername, if (lua_type(L, -1) != LUA_TFUNCTION) throw LuaError("Authentication handler missing get_auth"); lua_pushstring(L, playername.c_str()); - if (lua_pcall(L, 1, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 1, 1, m_errorhandler)); lua_remove(L, -2); // Remove auth handler // nil = login not allowed @@ -68,6 +67,9 @@ void ScriptApiServer::getAuthHandler() lua_pop(L, 1); lua_getfield(L, -1, "builtin_auth_handler"); } + + setOriginFromTable(-1); + lua_remove(L, -2); // Remove core if (lua_type(L, -1) != LUA_TTABLE) throw LuaError("Authentication handler table not valid"); @@ -104,8 +106,7 @@ void ScriptApiServer::createAuth(const std::string &playername, throw LuaError("Authentication handler missing create_auth"); lua_pushstring(L, playername.c_str()); lua_pushstring(L, password.c_str()); - if (lua_pcall(L, 2, 0, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 2, 0, m_errorhandler)); } bool ScriptApiServer::setPassword(const std::string &playername, @@ -120,8 +121,7 @@ bool ScriptApiServer::setPassword(const std::string &playername, throw LuaError("Authentication handler missing set_password"); lua_pushstring(L, playername.c_str()); lua_pushstring(L, password.c_str()); - if (lua_pcall(L, 2, 1, m_errorhandler)) - scriptError(); + PCALL_RES(lua_pcall(L, 2, 1, m_errorhandler)); return lua_toboolean(L, -1); } @@ -136,7 +136,7 @@ bool ScriptApiServer::on_chat_message(const std::string &name, // Call callbacks lua_pushstring(L, name.c_str()); lua_pushstring(L, message.c_str()); - script_run_callbacks(L, 2, RUN_CALLBACKS_MODE_OR_SC); + runCallbacks(2, RUN_CALLBACKS_MODE_OR_SC); bool ate = lua_toboolean(L, -1); return ate; } @@ -149,6 +149,6 @@ void ScriptApiServer::on_shutdown() lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_shutdown"); // Call callbacks - script_run_callbacks(L, 0, RUN_CALLBACKS_MODE_FIRST); + runCallbacks(0, RUN_CALLBACKS_MODE_FIRST); } diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt index 08960d2ad..2501ce6d6 100644 --- a/src/script/lua_api/CMakeLists.txt +++ b/src/script/lua_api/CMakeLists.txt @@ -1,5 +1,5 @@ -# Used by server and client set(common_SCRIPT_LUA_API_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/l_areastore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_base.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_craft.cpp ${CMAKE_CURRENT_SOURCE_DIR}/l_env.cpp @@ -18,7 +18,7 @@ set(common_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_settings.cpp PARENT_SCOPE) -# Used by client only -set(minetest_SCRIPT_LUA_API_SRCS +set(client_SCRIPT_LUA_API_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/l_mainmenu.cpp PARENT_SCOPE) + diff --git a/src/script/lua_api/l_areastore.cpp b/src/script/lua_api/l_areastore.cpp new file mode 100644 index 000000000..1e9075119 --- /dev/null +++ b/src/script/lua_api/l_areastore.cpp @@ -0,0 +1,401 @@ +/* +Minetest +Copyright (C) 2015 est31 <mtest31@outlook.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + + +#include "lua_api/l_areastore.h" +#include "lua_api/l_internal.h" +#include "common/c_converter.h" +#include "cpp_api/s_security.h" +#include "areastore.h" +#include "filesys.h" +#ifndef ANDROID + #include "cmake_config.h" +#endif +#include <fstream> + +static inline void get_data_and_border_flags(lua_State *L, u8 start_i, + bool *borders, bool *data) +{ + if (!lua_isboolean(L, start_i)) + return; + *borders = lua_toboolean(L, start_i); + if (!lua_isboolean(L, start_i + 1)) + return; + *data = lua_toboolean(L, start_i + 1); +} + +static void push_area(lua_State *L, const Area *a, + bool include_borders, bool include_data) +{ + if (!include_borders && !include_data) { + lua_pushboolean(L, true); + } + lua_newtable(L); + if (include_borders) { + push_v3s16(L, a->minedge); + lua_setfield(L, -2, "min"); + push_v3s16(L, a->maxedge); + lua_setfield(L, -2, "max"); + } + if (include_data) { + lua_pushlstring(L, a->data.c_str(), a->data.size()); + lua_setfield(L, -2, "data"); + } +} + +static inline void push_areas(lua_State *L, const std::vector<Area *> &areas, + bool borders, bool data) +{ + lua_newtable(L); + size_t cnt = areas.size(); + for (size_t i = 0; i < cnt; i++) { + lua_pushnumber(L, areas[i]->id); + push_area(L, areas[i], borders, data); + lua_settable(L, -3); + } +} + +// garbage collector +int LuaAreaStore::gc_object(lua_State *L) +{ + LuaAreaStore *o = *(LuaAreaStore **)(lua_touserdata(L, 1)); + delete o; + return 0; +} + +// get_area(id, include_borders, include_data) +int LuaAreaStore::l_get_area(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + u32 id = luaL_checknumber(L, 2); + + bool include_borders = true; + bool include_data = false; + get_data_and_border_flags(L, 3, &include_borders, &include_data); + + const Area *res; + + res = ast->getArea(id); + push_area(L, res, include_borders, include_data); + + return 1; +} + +// get_areas_for_pos(pos, include_borders, include_data) +int LuaAreaStore::l_get_areas_for_pos(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + v3s16 pos = check_v3s16(L, 2); + + bool include_borders = true; + bool include_data = false; + get_data_and_border_flags(L, 3, &include_borders, &include_data); + + std::vector<Area *> res; + + ast->getAreasForPos(&res, pos); + push_areas(L, res, include_borders, include_data); + + return 1; +} + +// get_areas_in_area(edge1, edge2, accept_overlap, include_borders, include_data) +int LuaAreaStore::l_get_areas_in_area(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + v3s16 minedge = check_v3s16(L, 2); + v3s16 maxedge = check_v3s16(L, 3); + + bool include_borders = true; + bool include_data = false; + bool accept_overlap = false; + if (lua_isboolean(L, 4)) { + accept_overlap = lua_toboolean(L, 4); + get_data_and_border_flags(L, 5, &include_borders, &include_data); + } + std::vector<Area *> res; + + ast->getAreasInArea(&res, minedge, maxedge, accept_overlap); + push_areas(L, res, include_borders, include_data); + + return 1; +} + +// insert_area(edge1, edge2, data) +int LuaAreaStore::l_insert_area(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + Area a; + + a.minedge = check_v3s16(L, 2); + a.maxedge = check_v3s16(L, 3); + + a.extremifyEdges(); + a.id = ast->getFreeId(a.minedge, a.maxedge); + + if (a.id == AREA_ID_INVALID) { + // couldn't get free id + lua_pushnil(L); + return 1; + } + + size_t d_len; + const char *data = luaL_checklstring(L, 4, &d_len); + + a.data = std::string(data, d_len); + + ast->insertArea(a); + + lua_pushnumber(L, a.id); + return 1; +} + +// reserve(count) +int LuaAreaStore::l_reserve(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + size_t count = luaL_checknumber(L, 2); + ast->reserve(count); + return 0; +} + +// remove_area(id) +int LuaAreaStore::l_remove_area(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + u32 id = luaL_checknumber(L, 2); + bool success = ast->removeArea(id); + + lua_pushboolean(L, success); + return 1; +} + +// set_cache_params(params) +int LuaAreaStore::l_set_cache_params(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + luaL_checktype(L, 2, LUA_TTABLE); + + bool enabled = getboolfield_default(L, 2, "enabled", true); + u8 block_radius = getintfield_default(L, 2, "block_radius", 64); + size_t limit = getintfield_default(L, 2, "block_radius", 1000); + + ast->setCacheParams(enabled, block_radius, limit); + + return 0; +} + +#if 0 +// to_string() +int LuaAreaStore::l_to_string(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + std::ostringstream os(std::ios_base::binary); + ast->serialize(os); + std::string str = os.str(); + + lua_pushlstring(L, str.c_str(), str.length()); + return 1; +} + +// to_file(filename) +int LuaAreaStore::l_to_file(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + const char *filename = luaL_checkstring(L, 2); + CHECK_SECURE_PATH_OPTIONAL(L, filename); + + std::ostringstream os(std::ios_base::binary); + ast->serialize(os); + + lua_pushboolean(L, fs::safeWriteToFile(filename, os.str())); + return 1; +} + +// from_string(str) +int LuaAreaStore::l_from_string(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + size_t len; + const char *str = luaL_checklstring(L, 2, &len); + + std::istringstream is(std::string(str, len), std::ios::binary); + bool success = ast->deserialize(is); + + lua_pushboolean(L, success); + return 1; +} + +// from_file(filename) +int LuaAreaStore::l_from_file(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = checkobject(L, 1); + AreaStore *ast = o->as; + + const char *filename = luaL_checkstring(L, 2); + CHECK_SECURE_PATH_OPTIONAL(L, filename); + + std::ifstream is(filename, std::ios::binary); + bool success = ast->deserialize(is); + + lua_pushboolean(L, success); + return 1; +} +#endif + +LuaAreaStore::LuaAreaStore() +{ +#if USE_SPATIAL + this->as = new SpatialAreaStore(); +#else + this->as = new VectorAreaStore(); +#endif +} + +LuaAreaStore::LuaAreaStore(const std::string &type) +{ +#if USE_SPATIAL + if (type == "LibSpatial") { + this->as = new SpatialAreaStore(); + } else +#endif + { + this->as = new VectorAreaStore(); + } +} + +LuaAreaStore::~LuaAreaStore() +{ + delete as; +} + +// LuaAreaStore() +// Creates an LuaAreaStore and leaves it on top of stack +int LuaAreaStore::create_object(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaAreaStore *o = (lua_isstring(L, 1)) ? + new LuaAreaStore(lua_tostring(L, 1)) : + new LuaAreaStore(); + + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + return 1; +} + +LuaAreaStore *LuaAreaStore::checkobject(lua_State *L, int narg) +{ + NO_MAP_LOCK_REQUIRED; + + luaL_checktype(L, narg, LUA_TUSERDATA); + + void *ud = luaL_checkudata(L, narg, className); + if (!ud) + luaL_typerror(L, narg, className); + + return *(LuaAreaStore **)ud; // unbox pointer +} + +void LuaAreaStore::Register(lua_State *L) +{ + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); // hide metatable from Lua getmetatable() + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pop(L, 1); // drop metatable + + luaL_openlib(L, 0, methods, 0); // fill methodtable + lua_pop(L, 1); // drop methodtable + + // Can be created from Lua (AreaStore()) + lua_register(L, className, create_object); +} + +const char LuaAreaStore::className[] = "AreaStore"; +const luaL_reg LuaAreaStore::methods[] = { + luamethod(LuaAreaStore, get_area), + luamethod(LuaAreaStore, get_areas_for_pos), + luamethod(LuaAreaStore, get_areas_in_area), + luamethod(LuaAreaStore, insert_area), + luamethod(LuaAreaStore, reserve), + luamethod(LuaAreaStore, remove_area), + luamethod(LuaAreaStore, set_cache_params), + /* luamethod(LuaAreaStore, to_string), + luamethod(LuaAreaStore, to_file), + luamethod(LuaAreaStore, from_string), + luamethod(LuaAreaStore, from_file),*/ + {0,0} +}; diff --git a/src/script/lua_api/l_areastore.h b/src/script/lua_api/l_areastore.h new file mode 100644 index 000000000..a25529627 --- /dev/null +++ b/src/script/lua_api/l_areastore.h @@ -0,0 +1,70 @@ +/* +Minetest +Copyright (C) 2015 est31 <mtest31@outlook.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef L_AREASTORE_H_ +#define L_AREASTORE_H_ + +#include "lua_api/l_base.h" +#include "irr_v3d.h" +#include "areastore.h" + +/* + AreaStore + */ +class LuaAreaStore : public ModApiBase { +private: + + static const char className[]; + static const luaL_reg methods[]; + + static int gc_object(lua_State *L); + + static int l_get_area(lua_State *L); + + static int l_get_areas_for_pos(lua_State *L); + static int l_get_areas_in_area(lua_State *L); + static int l_insert_area(lua_State *L); + static int l_reserve(lua_State *L); + static int l_remove_area(lua_State *L); + + static int l_set_cache_params(lua_State *L); + + /* static int l_to_string(lua_State *L); + static int l_to_file(lua_State *L); + + static int l_from_string(lua_State *L); + static int l_from_file(lua_State *L); */ + +public: + AreaStore *as; + + LuaAreaStore(); + LuaAreaStore(const std::string &type); + ~LuaAreaStore(); + + // AreaStore() + // Creates a AreaStore and leaves it on top of stack + static int create_object(lua_State *L); + + static LuaAreaStore *checkobject(lua_State *L, int narg); + + static void Register(lua_State *L); +}; + +#endif /* L_AREASTORE_H_ */ diff --git a/src/script/lua_api/l_base.cpp b/src/script/lua_api/l_base.cpp index b8d673ee4..6ad3e4ba2 100644 --- a/src/script/lua_api/l_base.cpp +++ b/src/script/lua_api/l_base.cpp @@ -20,8 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_base.h" #include "lua_api/l_internal.h" #include "cpp_api/s_base.h" +#include <mods.h> +#include <server.h> -ScriptApiBase* ModApiBase::getScriptApiBase(lua_State *L) { +ScriptApiBase *ModApiBase::getScriptApiBase(lua_State *L) +{ // Get server from registry lua_getfield(L, LUA_REGISTRYINDEX, "scriptapi"); ScriptApiBase *sapi_ptr = (ScriptApiBase*) lua_touserdata(L, -1); @@ -29,23 +32,42 @@ ScriptApiBase* ModApiBase::getScriptApiBase(lua_State *L) { return sapi_ptr; } -Server* ModApiBase::getServer(lua_State *L) { +Server *ModApiBase::getServer(lua_State *L) +{ return getScriptApiBase(L)->getServer(); } -Environment* ModApiBase::getEnv(lua_State *L) { +Environment *ModApiBase::getEnv(lua_State *L) +{ return getScriptApiBase(L)->getEnv(); } -GUIEngine* ModApiBase::getGuiEngine(lua_State *L) { +GUIEngine *ModApiBase::getGuiEngine(lua_State *L) +{ return getScriptApiBase(L)->getGuiEngine(); } -bool ModApiBase::registerFunction(lua_State *L, - const char *name, - lua_CFunction fct, - int top - ) { +std::string ModApiBase::getCurrentModPath(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); + const char *current_mod_name = lua_tostring(L, -1); + if (!current_mod_name) + return "."; + + const ModSpec *mod = getServer(L)->getModSpec(current_mod_name); + if (!mod) + return "."; + + return mod->path; +} + + +bool ModApiBase::registerFunction( + lua_State *L, + const char *name, + lua_CFunction fct, + int top) +{ //TODO check presence first! lua_pushstring(L,name); diff --git a/src/script/lua_api/l_base.h b/src/script/lua_api/l_base.h index debbcd09b..641013dfd 100644 --- a/src/script/lua_api/l_base.h +++ b/src/script/lua_api/l_base.h @@ -35,11 +35,13 @@ class GUIEngine; class ModApiBase { -protected: +public: static ScriptApiBase* getScriptApiBase(lua_State *L); static Server* getServer(lua_State *L); static Environment* getEnv(lua_State *L); static GUIEngine* getGuiEngine(lua_State *L); + // When we are not loading the mod, this function returns "." + static std::string getCurrentModPath(lua_State *L); // Get an arbitrary subclass of ScriptApiBase // by using dynamic_cast<> on getScriptApiBase() diff --git a/src/script/lua_api/l_craft.cpp b/src/script/lua_api/l_craft.cpp index 8f8efbfbc..391a0133d 100644 --- a/src/script/lua_api/l_craft.cpp +++ b/src/script/lua_api/l_craft.cpp @@ -173,7 +173,7 @@ int ModApiCraft::l_register_craft(lua_State *L) CraftDefinition *def = new CraftDefinitionShaped( output, width, recipe, replacements); - craftdef->registerCraft(def); + craftdef->registerCraft(def, getServer(L)); } /* CraftDefinitionShapeless @@ -205,7 +205,7 @@ int ModApiCraft::l_register_craft(lua_State *L) CraftDefinition *def = new CraftDefinitionShapeless( output, recipe, replacements); - craftdef->registerCraft(def); + craftdef->registerCraft(def, getServer(L)); } /* CraftDefinitionToolRepair @@ -216,7 +216,7 @@ int ModApiCraft::l_register_craft(lua_State *L) CraftDefinition *def = new CraftDefinitionToolRepair( additional_wear); - craftdef->registerCraft(def); + craftdef->registerCraft(def, getServer(L)); } /* CraftDefinitionCooking @@ -246,7 +246,7 @@ int ModApiCraft::l_register_craft(lua_State *L) CraftDefinition *def = new CraftDefinitionCooking( output, recipe, cooktime, replacements); - craftdef->registerCraft(def); + craftdef->registerCraft(def, getServer(L)); } /* CraftDefinitionFuel @@ -270,7 +270,7 @@ int ModApiCraft::l_register_craft(lua_State *L) CraftDefinition *def = new CraftDefinitionFuel( recipe, burntime, replacements); - craftdef->registerCraft(def); + craftdef->registerCraft(def, getServer(L)); } else { @@ -303,18 +303,23 @@ int ModApiCraft::l_get_craft_result(lua_State *L) ICraftDefManager *cdef = gdef->cdef(); CraftInput input(method, width, items); CraftOutput output; - bool got = cdef->getCraftResult(input, output, true, gdef); + std::vector<ItemStack> output_replacements; + bool got = cdef->getCraftResult(input, output, output_replacements, true, gdef); lua_newtable(L); // output table - if(got){ + if (got) { ItemStack item; item.deSerialize(output.item, gdef->idef()); LuaItemStack::create(L, item); lua_setfield(L, -2, "item"); setintfield(L, -1, "time", output.time); + push_items(L, output_replacements); + lua_setfield(L, -2, "replacements"); } else { LuaItemStack::create(L, ItemStack()); lua_setfield(L, -2, "item"); setintfield(L, -1, "time", 0); + lua_newtable(L); + lua_setfield(L, -2, "replacements"); } lua_newtable(L); // decremented input table lua_pushstring(L, method_s.c_str()); @@ -326,56 +331,82 @@ int ModApiCraft::l_get_craft_result(lua_State *L) return 2; } + +static void push_craft_recipe(lua_State *L, IGameDef *gdef, + const CraftDefinition *recipe, + const CraftOutput &tmpout) +{ + CraftInput input = recipe->getInput(tmpout, gdef); + CraftOutput output = recipe->getOutput(input, gdef); + + lua_newtable(L); // items + std::vector<ItemStack>::const_iterator iter = input.items.begin(); + for (u16 j = 1; iter != input.items.end(); iter++, j++) { + if (iter->empty()) + continue; + lua_pushstring(L, iter->name.c_str()); + lua_rawseti(L, -2, j); + } + lua_setfield(L, -2, "items"); + setintfield(L, -1, "width", input.width); + switch (input.method) { + case CRAFT_METHOD_NORMAL: + lua_pushstring(L, "normal"); + break; + case CRAFT_METHOD_COOKING: + lua_pushstring(L, "cooking"); + break; + case CRAFT_METHOD_FUEL: + lua_pushstring(L, "fuel"); + break; + default: + lua_pushstring(L, "unknown"); + } + lua_setfield(L, -2, "type"); + lua_pushstring(L, output.item.c_str()); + lua_setfield(L, -2, "output"); +} + +static void push_craft_recipes(lua_State *L, IGameDef *gdef, + const std::vector<CraftDefinition*> &recipes, + const CraftOutput &output) +{ + lua_createtable(L, recipes.size(), 0); + + if (recipes.empty()) { + lua_pushnil(L); + return; + } + + std::vector<CraftDefinition*>::const_iterator it = recipes.begin(); + for (unsigned i = 0; it != recipes.end(); ++it) { + lua_newtable(L); + push_craft_recipe(L, gdef, *it, output); + lua_rawseti(L, -2, ++i); + } +} + + // get_craft_recipe(result item) int ModApiCraft::l_get_craft_recipe(lua_State *L) { NO_MAP_LOCK_REQUIRED; - int k = 1; - int input_i = 1; - std::string o_item = luaL_checkstring(L,input_i); + std::string item = luaL_checkstring(L, 1); + Server *server = getServer(L); + CraftOutput output(item, 0); + std::vector<CraftDefinition*> recipes = server->cdef() + ->getCraftRecipes(output, server, 1); - IGameDef *gdef = getServer(L); - ICraftDefManager *cdef = gdef->cdef(); - CraftInput input; - CraftOutput output(o_item,0); - bool got = cdef->getCraftRecipe(input, output, gdef); - lua_newtable(L); // output table - if(got){ - lua_newtable(L); - for(std::vector<ItemStack>::const_iterator - i = input.items.begin(); - i != input.items.end(); i++, k++) - { - if (i->empty()) - { - continue; - } - lua_pushinteger(L,k); - lua_pushstring(L,i->name.c_str()); - lua_settable(L, -3); - } - lua_setfield(L, -2, "items"); - setintfield(L, -1, "width", input.width); - switch (input.method) { - case CRAFT_METHOD_NORMAL: - lua_pushstring(L,"normal"); - break; - case CRAFT_METHOD_COOKING: - lua_pushstring(L,"cooking"); - break; - case CRAFT_METHOD_FUEL: - lua_pushstring(L,"fuel"); - break; - default: - lua_pushstring(L,"unknown"); - } - lua_setfield(L, -2, "type"); - } else { + lua_createtable(L, 1, 0); + + if (recipes.empty()) { lua_pushnil(L); lua_setfield(L, -2, "items"); setintfield(L, -1, "width", 0); + return 1; } + push_craft_recipe(L, server, recipes[0], output); return 1; } @@ -384,59 +415,13 @@ int ModApiCraft::l_get_all_craft_recipes(lua_State *L) { NO_MAP_LOCK_REQUIRED; - std::string o_item = luaL_checkstring(L,1); - IGameDef *gdef = getServer(L); - ICraftDefManager *cdef = gdef->cdef(); - CraftInput input; - CraftOutput output(o_item,0); - std::vector<CraftDefinition*> recipes_list; - recipes_list = cdef->getCraftRecipes(output, gdef); - if (recipes_list.empty()) { - lua_pushnil(L); - return 1; - } + std::string item = luaL_checkstring(L, 1); + Server *server = getServer(L); + CraftOutput output(item, 0); + std::vector<CraftDefinition*> recipes = server->cdef() + ->getCraftRecipes(output, server); - lua_createtable(L, recipes_list.size(), 0); - std::vector<CraftDefinition*>::const_iterator iter = recipes_list.begin(); - for (u16 i = 0; iter != recipes_list.end(); iter++) { - CraftOutput tmpout; - tmpout.item = ""; - tmpout.time = 0; - tmpout = (*iter)->getOutput(input, gdef); - std::string query = tmpout.item; - char *fmtpos, *fmt = &query[0]; - if (strtok_r(fmt, " ", &fmtpos) == output.item) { - input = (*iter)->getInput(output, gdef); - lua_newtable(L); - lua_newtable(L); // items - std::vector<ItemStack>::const_iterator iter = input.items.begin(); - for (u16 j = 1; iter != input.items.end(); iter++, j++) { - if (iter->empty()) - continue; - lua_pushstring(L, iter->name.c_str()); - lua_rawseti(L, -2, j); - } - lua_setfield(L, -2, "items"); - setintfield(L, -1, "width", input.width); - switch (input.method) { - case CRAFT_METHOD_NORMAL: - lua_pushstring(L, "normal"); - break; - case CRAFT_METHOD_COOKING: - lua_pushstring(L, "cooking"); - break; - case CRAFT_METHOD_FUEL: - lua_pushstring(L, "fuel"); - break; - default: - lua_pushstring(L, "unknown"); - } - lua_setfield(L, -2, "type"); - lua_pushstring(L, &tmpout.item[0]); - lua_setfield(L, -2, "output"); - lua_rawseti(L, -2, ++i); - } - } + push_craft_recipes(L, server, recipes, output); return 1; } diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index cd5d253ac..28afdd071 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -49,7 +49,7 @@ void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, scriptIface->realityCheck(); lua_State *L = scriptIface->getStack(); - assert(lua_checkstack(L, 20)); + sanity_check(lua_checkstack(L, 20)); StackUnroller stack_unroller(L); lua_pushcfunction(L, script_error_handler); @@ -65,9 +65,11 @@ void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, lua_pushnumber(L, m_id); lua_gettable(L, -2); if(lua_isnil(L, -1)) - assert(0); + FATAL_ERROR(""); lua_remove(L, -2); // Remove registered_abms + scriptIface->setOriginFromTable(-1); + // Call action luaL_checktype(L, -1, LUA_TTABLE); lua_getfield(L, -1, "action"); @@ -77,8 +79,11 @@ void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, pushnode(L, n, env->getGameDef()->ndef()); lua_pushnumber(L, active_object_count); lua_pushnumber(L, active_object_count_wider); - if(lua_pcall(L, 4, 0, errorhandler)) - script_error(L); + + int result = lua_pcall(L, 4, 0, errorhandler); + if (result) + scriptIface->scriptError(result, "LuaABM::trigger"); + lua_pop(L, 1); // Pop error handler } @@ -334,6 +339,22 @@ int ModApiEnvMod::l_add_node_level(lua_State *L) return 1; } +// find_nodes_with_meta(pos1, pos2) +int ModApiEnvMod::l_find_nodes_with_meta(lua_State *L) +{ + GET_ENV_PTR; + + std::vector<v3s16> positions = env->getMap().findNodesWithMetadata( + check_v3s16(L, 1), check_v3s16(L, 2)); + + lua_newtable(L); + for (size_t i = 0; i != positions.size(); i++) { + push_v3s16(L, positions[i]); + lua_rawseti(L, -2, i + 1); + } + + return 1; +} // get_meta(pos) int ModApiEnvMod::l_get_meta(lua_State *L) @@ -402,23 +423,11 @@ int ModApiEnvMod::l_add_item(lua_State *L) return 0; lua_pushvalue(L, 1); lua_pushstring(L, item.getItemString().c_str()); - if(lua_pcall(L, 2, 1, errorhandler)) - script_error(L); + + PCALL_RESL(L, lua_pcall(L, 2, 1, errorhandler)); + lua_remove(L, errorhandler); // Remove error handler return 1; - /*lua_pushvalue(L, 1); - lua_pushstring(L, "__builtin:item"); - lua_pushstring(L, item.getItemString().c_str()); - return l_add_entity(L);*/ - /*// Do it - ServerActiveObject *obj = createItemSAO(env, pos, item.getItemString()); - int objectid = env->addActiveObject(obj); - // If failed to add, return nothing (reads as nil) - if(objectid == 0) - return 0; - // Return ObjectRef - objectrefGetOrCreate(L, obj); - return 1;*/ } // get_player_by_name(name) @@ -451,10 +460,11 @@ int ModApiEnvMod::l_get_objects_inside_radius(lua_State *L) // Do it v3f pos = checkFloatPos(L, 1); float radius = luaL_checknumber(L, 2) * BS; - std::set<u16> ids = env->getObjectsInsideRadius(pos, radius); + std::vector<u16> ids; + env->getObjectsInsideRadius(ids, pos, radius); ScriptApiBase *script = getScriptApiBase(L); lua_createtable(L, ids.size(), 0); - std::set<u16>::const_iterator iter = ids.begin(); + std::vector<u16>::const_iterator iter = ids.begin(); for(u32 i = 0; iter != ids.end(); iter++) { ServerActiveObject *obj = env->getActiveObject(*iter); // Insert object reference into table @@ -472,7 +482,7 @@ int ModApiEnvMod::l_set_timeofday(lua_State *L) // Do it float timeofday_f = luaL_checknumber(L, 1); - assert(timeofday_f >= 0.0 && timeofday_f <= 1.0); + sanity_check(timeofday_f >= 0.0 && timeofday_f <= 1.0); int timeofday_mh = (int)(timeofday_f * 24000.0); // This should be set directly in the environment but currently // such changes aren't immediately sent to the clients, so call @@ -530,9 +540,8 @@ int ModApiEnvMod::l_find_node_near(lua_State *L) } for(int d=1; d<=radius; d++){ - std::list<v3s16> list; - getFacePositions(list, d); - for(std::list<v3s16>::iterator i = list.begin(); + std::vector<v3s16> list = FacePositionCache::getFacePositions(d); + for(std::vector<v3s16>::iterator i = list.begin(); i != list.end(); ++i){ v3s16 p = pos + (*i); content_t c = env->getMap().getNodeNoEx(p).getContent(); @@ -555,30 +564,92 @@ int ModApiEnvMod::l_find_nodes_in_area(lua_State *L) v3s16 minp = read_v3s16(L, 1); v3s16 maxp = read_v3s16(L, 2); std::set<content_t> filter; - if(lua_istable(L, 3)){ + if(lua_istable(L, 3)) { int table = 3; lua_pushnil(L); - while(lua_next(L, table) != 0){ + while(lua_next(L, table) != 0) { // key at index -2 and value at index -1 luaL_checktype(L, -1, LUA_TSTRING); ndef->getIds(lua_tostring(L, -1), filter); // removes value, keeps key for next iteration lua_pop(L, 1); } - } else if(lua_isstring(L, 3)){ + } else if(lua_isstring(L, 3)) { + ndef->getIds(lua_tostring(L, 3), filter); + } + + std::map<content_t, u16> individual_count; + + lua_newtable(L); + u64 i = 0; + for (s16 x = minp.X; x <= maxp.X; x++) + 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(); + if (filter.count(c) != 0) { + push_v3s16(L, p); + lua_rawseti(L, -2, ++i); + individual_count[c]++; + } + } + lua_newtable(L); + for (std::set<content_t>::iterator it = filter.begin(); + it != filter.end(); ++it) { + lua_pushnumber(L, individual_count[*it]); + lua_setfield(L, -2, ndef->get(*it).name.c_str()); + } + return 2; +} + +// find_nodes_in_area_under_air(minp, maxp, nodenames) -> list of positions +// nodenames: e.g. {"ignore", "group:tree"} or "default:dirt" +int ModApiEnvMod::l_find_nodes_in_area_under_air(lua_State *L) +{ + /* Note: A similar but generalized (and therefore slower) version of this + * function could be created -- e.g. find_nodes_in_area_under -- which + * would accept a node name (or ID?) or list of names that the "above node" + * should be. + * TODO + */ + + GET_ENV_PTR; + + INodeDefManager *ndef = getServer(L)->ndef(); + v3s16 minp = read_v3s16(L, 1); + v3s16 maxp = read_v3s16(L, 2); + std::set<content_t> filter; + + if (lua_istable(L, 3)) { + int table = 3; + lua_pushnil(L); + while(lua_next(L, table) != 0) { + // key at index -2 and value at index -1 + luaL_checktype(L, -1, LUA_TSTRING); + ndef->getIds(lua_tostring(L, -1), filter); + // removes value, keeps key for next iteration + lua_pop(L, 1); + } + } else if (lua_isstring(L, 3)) { ndef->getIds(lua_tostring(L, 3), filter); } lua_newtable(L); u64 i = 0; - for(s16 x = minp.X; x <= maxp.X; x++) - for(s16 y = minp.Y; y <= maxp.Y; y++) - for(s16 z = minp.Z; z <= maxp.Z; z++) { + for (s16 x = minp.X; x <= maxp.X; x++) + 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(); - if(filter.count(c) != 0) { - push_v3s16(L, p); - lua_rawseti(L, -2, ++i); + for (; y <= maxp.Y; y++) { + v3s16 psurf(x, y + 1, z); + content_t csurf = env->getMap().getNodeNoEx(psurf).getContent(); + if(c != CONTENT_AIR && csurf == CONTENT_AIR && + filter.count(c) != 0) { + push_v3s16(L, v3s16(x, y, z)); + lua_rawseti(L, -2, ++i); + } + c = csurf; } } return 1; @@ -702,10 +773,12 @@ int ModApiEnvMod::l_delete_area(lua_State *L) for (s16 y = bpmin.Y; y <= bpmax.Y; y++) for (s16 x = bpmin.X; x <= bpmax.X; x++) { v3s16 bp(x, y, z); - if (map.deleteBlock(bp)) + if (map.deleteBlock(bp)) { + env->setStaticForActiveObjectsInBlock(bp, false); event.modified_blocks.insert(bp); - else + } else { success = false; + } } map.dispatchEvent(&event); @@ -872,6 +945,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(set_node_level); API_FCT(add_node_level); API_FCT(add_entity); + API_FCT(find_nodes_with_meta); API_FCT(get_meta); API_FCT(get_node_timer); API_FCT(get_player_by_name); @@ -881,6 +955,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top) API_FCT(get_gametime); API_FCT(find_node_near); API_FCT(find_nodes_in_area); + API_FCT(find_nodes_in_area_under_air); API_FCT(delete_area); API_FCT(get_perlin); API_FCT(get_perlin_map); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index bfaea1c4d..0d4ca788e 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -64,7 +64,6 @@ private: // pos = {x=num, y=num, z=num} static int l_punch_node(lua_State *L); - // get_node_max_level(pos) // pos = {x=num, y=num, z=num} static int l_get_node_max_level(lua_State *L); @@ -81,6 +80,9 @@ private: // pos = {x=num, y=num, z=num} static int l_add_node_level(lua_State *L); + // find_nodes_with_meta(pos1, pos2) + static int l_find_nodes_with_meta(lua_State *L); + // get_meta(pos) static int l_get_meta(lua_State *L); @@ -119,6 +121,10 @@ private: // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" static int l_find_nodes_in_area(lua_State *L); + // find_surface_nodes_in_area(minp, maxp, nodenames) -> list of positions + // nodenames: eg. {"ignore", "group:tree"} or "default:dirt" + static int l_find_nodes_in_area_under_air(lua_State *L); + // delete_area(p1, p2) -> true/false static int l_delete_area(lua_State *L); diff --git a/src/script/lua_api/l_internal.h b/src/script/lua_api/l_internal.h index 5936ac046..1e40c5c4a 100644 --- a/src/script/lua_api/l_internal.h +++ b/src/script/lua_api/l_internal.h @@ -33,13 +33,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #define API_FCT(name) registerFunction(L, #name, l_##name,top) #define ASYNC_API_FCT(name) engine.registerFunction(#name, l_##name) -#if (defined(WIN32) || defined(_WIN32_WCE)) #define NO_MAP_LOCK_REQUIRED + +/* +#if (defined(WIN32) || defined(_WIN32_WCE)) + #define NO_MAP_LOCK_REQUIRED #else -#include "main.h" -#include "profiler.h" -#define NO_MAP_LOCK_REQUIRED \ - ScopeProfiler nolocktime(g_profiler,"Scriptapi: unlockable time",SPT_ADD) + #include "profiler.h" + #define NO_MAP_LOCK_REQUIRED \ + ScopeProfiler nolocktime(g_profiler,"Scriptapi: unlockable time",SPT_ADD) #endif +*/ #endif /* L_INTERNAL_H_ */ diff --git a/src/script/lua_api/l_mainmenu.cpp b/src/script/lua_api/l_mainmenu.cpp index 2bed2a255..92311d6fc 100644 --- a/src/script/lua_api/l_mainmenu.cpp +++ b/src/script/lua_api/l_mainmenu.cpp @@ -34,7 +34,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "emerge.h" #include "sound.h" #include "settings.h" -#include "main.h" // for g_settings #include "log.h" #include "EDriverTypes.h" @@ -90,7 +89,7 @@ int ModApiMainMenu::getBoolData(lua_State *L, std::string name,bool& valid) int ModApiMainMenu::l_update_formspec(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine != NULL); if (engine->m_startgame) return 0; @@ -109,21 +108,25 @@ int ModApiMainMenu::l_update_formspec(lua_State *L) int ModApiMainMenu::l_start(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine != NULL); //update c++ gamedata from lua table bool valid = false; + MainMenuData *data = engine->m_data; - engine->m_data->selected_world = getIntegerData(L, "selected_world",valid) -1; - engine->m_data->simple_singleplayer_mode = getBoolData(L,"singleplayer",valid); - engine->m_data->name = getTextData(L,"playername"); - engine->m_data->password = getTextData(L,"password"); - engine->m_data->address = getTextData(L,"address"); - engine->m_data->port = getTextData(L,"port"); - engine->m_data->serverdescription = getTextData(L,"serverdescription"); - engine->m_data->servername = getTextData(L,"servername"); + data->selected_world = getIntegerData(L, "selected_world",valid) -1; + data->simple_singleplayer_mode = getBoolData(L,"singleplayer",valid); + data->do_reconnect = getBoolData(L, "do_reconnect", valid); + if (!data->do_reconnect) { + data->name = getTextData(L,"playername"); + data->password = getTextData(L,"password"); + data->address = getTextData(L,"address"); + data->port = getTextData(L,"port"); + } + data->serverdescription = getTextData(L,"serverdescription"); + data->servername = getTextData(L,"servername"); //close menu next time engine->m_startgame = true; @@ -134,7 +137,7 @@ int ModApiMainMenu::l_start(lua_State *L) int ModApiMainMenu::l_close(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine != NULL); engine->m_kill = true; return 0; @@ -144,7 +147,7 @@ int ModApiMainMenu::l_close(lua_State *L) int ModApiMainMenu::l_set_background(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine != NULL); std::string backgroundlevel(luaL_checkstring(L, 1)); std::string texturename(luaL_checkstring(L, 2)); @@ -189,7 +192,7 @@ int ModApiMainMenu::l_set_background(lua_State *L) int ModApiMainMenu::l_set_clouds(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine != NULL); bool value = lua_toboolean(L,1); @@ -209,9 +212,9 @@ int ModApiMainMenu::l_get_textlist_index(lua_State *L) int ModApiMainMenu::l_get_table_index(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine != NULL); - std::wstring tablename(narrow_to_wide(luaL_checkstring(L, 1))); + std::string tablename(luaL_checkstring(L, 1)); GUITable *table = engine->m_menu->getTable(tablename); s32 selection = table ? table->getSelected() : 0; @@ -617,7 +620,7 @@ int ModApiMainMenu::l_delete_favorite(lua_State *L) int ModApiMainMenu::l_show_keys_menu(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine != NULL); GUIKeyChangeMenu *kmenu = new GUIKeyChangeMenu( engine->m_device->getGUIEnvironment(), @@ -644,15 +647,12 @@ int ModApiMainMenu::l_create_world(lua_State *L) (gameidx < (int) games.size())) { // Create world if it doesn't exist - if(!initializeWorld(path, games[gameidx].id)){ + if (!loadGameConfAndInitWorld(path, games[gameidx])) { lua_pushstring(L, "Failed to initialize world"); - - } - else { - lua_pushnil(L); + } else { + lua_pushnil(L); } - } - else { + } else { lua_pushstring(L, "Invalid game index"); } return 1; @@ -692,7 +692,7 @@ int ModApiMainMenu::l_delete_world(lua_State *L) int ModApiMainMenu::l_set_topleft_text(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine != NULL); std::string text = ""; @@ -758,30 +758,6 @@ int ModApiMainMenu::l_get_texturepath_share(lua_State *L) } /******************************************************************************/ -int ModApiMainMenu::l_get_dirlist(lua_State *L) -{ - const char *path = luaL_checkstring(L, 1); - bool dironly = lua_toboolean(L, 2); - - std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path); - - unsigned int index = 1; - lua_newtable(L); - int table = lua_gettop(L); - - for (unsigned int i=0;i< dirlist.size(); i++) { - if ((dirlist[i].dir) || (dironly == false)) { - lua_pushnumber(L,index); - lua_pushstring(L,dirlist[i].name.c_str()); - lua_settable(L, table); - index++; - } - } - - return 1; -} - -/******************************************************************************/ int ModApiMainMenu::l_create_dir(lua_State *L) { const char *path = luaL_checkstring(L, 1); @@ -843,7 +819,7 @@ int ModApiMainMenu::l_copy_dir(lua_State *L) int ModApiMainMenu::l_extract_zip(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine); const char *zipfile = luaL_checkstring(L, 1); const char *destination = luaL_checkstring(L, 2); @@ -860,7 +836,7 @@ int ModApiMainMenu::l_extract_zip(lua_State *L) return 1; } - assert(fs->getFileArchiveCount() > 0); + sanity_check(fs->getFileArchiveCount() > 0); /**********************************************************************/ /* WARNING this is not threadsafe!! */ @@ -931,7 +907,7 @@ int ModApiMainMenu::l_extract_zip(lua_State *L) int ModApiMainMenu::l_get_mainmenu_path(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine != NULL); lua_pushstring(L,engine->getScriptDir().c_str()); return 1; @@ -963,7 +939,7 @@ bool ModApiMainMenu::isMinetestPath(std::string path) int ModApiMainMenu::l_show_file_open_dialog(lua_State *L) { GUIEngine* engine = getGuiEngine(L); - assert(engine != 0); + sanity_check(engine != NULL); const char *formname= luaL_checkstring(L, 1); const char *title = luaL_checkstring(L, 2); @@ -983,7 +959,7 @@ int ModApiMainMenu::l_show_file_open_dialog(lua_State *L) /******************************************************************************/ int ModApiMainMenu::l_get_version(lua_State *L) { - lua_pushstring(L, minetest_version_simple); + lua_pushstring(L, g_version_string); return 1; } @@ -1060,10 +1036,32 @@ int ModApiMainMenu::l_get_video_drivers(lua_State *L) } /******************************************************************************/ +int ModApiMainMenu::l_get_video_modes(lua_State *L) +{ + std::vector<core::vector3d<u32> > videomodes + = porting::getSupportedVideoModes(); + + lua_newtable(L); + for (u32 i = 0; i != videomodes.size(); i++) { + lua_newtable(L); + lua_pushnumber(L, videomodes[i].X); + lua_setfield(L, -2, "w"); + lua_pushnumber(L, videomodes[i].Y); + lua_setfield(L, -2, "h"); + lua_pushnumber(L, videomodes[i].Z); + lua_setfield(L, -2, "depth"); + + lua_rawseti(L, -2, i + 1); + } + + return 1; +} + +/******************************************************************************/ int ModApiMainMenu::l_gettext(lua_State *L) { std::wstring wtext = wstrgettext((std::string) luaL_checkstring(L, 1)); - lua_pushstring(L, wide_to_narrow(wtext).c_str()); + lua_pushstring(L, wide_to_utf8(wtext).c_str()); return 1; } @@ -1118,8 +1116,8 @@ int ModApiMainMenu::l_do_async_callback(lua_State *L) const char* serialized_param_raw = luaL_checklstring(L, 2, ¶m_length); - assert(serialized_func_raw != NULL); - assert(serialized_param_raw != NULL); + sanity_check(serialized_func_raw != NULL); + sanity_check(serialized_param_raw != NULL); std::string serialized_func = std::string(serialized_func_raw, func_length); std::string serialized_param = std::string(serialized_param_raw, param_length); @@ -1152,7 +1150,6 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(get_gamepath); API_FCT(get_texturepath); API_FCT(get_texturepath_share); - API_FCT(get_dirlist); API_FCT(create_dir); API_FCT(delete_dir); API_FCT(copy_dir); @@ -1167,6 +1164,7 @@ void ModApiMainMenu::Initialize(lua_State *L, int top) API_FCT(sound_stop); API_FCT(gettext); API_FCT(get_video_drivers); + API_FCT(get_video_modes); API_FCT(get_screen_info); API_FCT(get_min_supp_proto); API_FCT(get_max_supp_proto); @@ -1185,7 +1183,6 @@ void ModApiMainMenu::InitializeAsync(AsyncEngine& engine) ASYNC_API_FCT(get_gamepath); ASYNC_API_FCT(get_texturepath); ASYNC_API_FCT(get_texturepath_share); - ASYNC_API_FCT(get_dirlist); ASYNC_API_FCT(create_dir); ASYNC_API_FCT(delete_dir); ASYNC_API_FCT(copy_dir); diff --git a/src/script/lua_api/l_mainmenu.h b/src/script/lua_api/l_mainmenu.h index 8b21a93aa..9c1fed272 100644 --- a/src/script/lua_api/l_mainmenu.h +++ b/src/script/lua_api/l_mainmenu.h @@ -137,6 +137,8 @@ private: static int l_get_video_drivers(lua_State *L); + static int l_get_video_modes(lua_State *L); + //version compatibility static int l_get_min_supp_proto(lua_State *L); diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index d470cef88..d30b68054 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_vmanip.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "cpp_api/s_security.h" #include "util/serialize.h" #include "server.h" #include "environment.h" @@ -32,18 +33,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mg_schematic.h" #include "mapgen_v5.h" #include "mapgen_v7.h" +#include "filesys.h" #include "settings.h" -#include "main.h" #include "log.h" - struct EnumString ModApiMapgen::es_BiomeTerrainType[] = { - {BIOME_TYPE_NORMAL, "normal"}, - {BIOME_TYPE_LIQUID, "liquid"}, - {BIOME_TYPE_NETHER, "nether"}, - {BIOME_TYPE_AETHER, "aether"}, - {BIOME_TYPE_FLAT, "flat"}, + {BIOME_NORMAL, "normal"}, + {BIOME_LIQUID, "liquid"}, + {BIOME_NETHER, "nether"}, + {BIOME_AETHER, "aether"}, + {BIOME_FLAT, "flat"}, {0, NULL}, }; @@ -68,10 +68,10 @@ struct EnumString ModApiMapgen::es_MapgenObject[] = struct EnumString ModApiMapgen::es_OreType[] = { - {ORE_TYPE_SCATTER, "scatter"}, - {ORE_TYPE_SHEET, "sheet"}, - {ORE_TYPE_BLOB, "blob"}, - {ORE_TYPE_VEIN, "vein"}, + {ORE_SCATTER, "scatter"}, + {ORE_SHEET, "sheet"}, + {ORE_BLOB, "blob"}, + {ORE_VEIN, "vein"}, {0, NULL}, }; @@ -85,116 +85,238 @@ struct EnumString ModApiMapgen::es_Rotation[] = {0, NULL}, }; +struct EnumString ModApiMapgen::es_SchematicFormatType[] = +{ + {SCHEM_FMT_HANDLE, "handle"}, + {SCHEM_FMT_MTS, "mts"}, + {SCHEM_FMT_LUA, "lua"}, + {0, NULL}, +}; + +ObjDef *get_objdef(lua_State *L, int index, ObjDefManager *objmgr); + +Biome *get_or_load_biome(lua_State *L, int index, + BiomeManager *biomemgr); +Biome *read_biome_def(lua_State *L, int index, INodeDefManager *ndef); +size_t get_biome_list(lua_State *L, int index, + BiomeManager *biomemgr, std::set<u8> *biome_id_list); + +Schematic *get_or_load_schematic(lua_State *L, int index, + SchematicManager *schemmgr, StringMap *replace_names); +Schematic *load_schematic(lua_State *L, int index, INodeDefManager *ndef, + StringMap *replace_names); +Schematic *load_schematic_from_def(lua_State *L, int index, + INodeDefManager *ndef, StringMap *replace_names); +bool read_schematic_def(lua_State *L, int index, + Schematic *schem, std::vector<std::string> *names); + +bool read_deco_simple(lua_State *L, DecoSimple *deco); +bool read_deco_schematic(lua_State *L, SchematicManager *schemmgr, DecoSchematic *deco); + /////////////////////////////////////////////////////////////////////////////// +ObjDef *get_objdef(lua_State *L, int index, ObjDefManager *objmgr) +{ + if (index < 0) + index = lua_gettop(L) + 1 + index; -bool read_schematic(lua_State *L, int index, Schematic *schem, - INodeDefManager *ndef, std::map<std::string, std::string> &replace_names) + // If a number, assume this is a handle to an object def + if (lua_isnumber(L, index)) + return objmgr->get(lua_tointeger(L, index)); + + // If a string, assume a name is given instead + if (lua_isstring(L, index)) + return objmgr->getByName(lua_tostring(L, index)); + + return NULL; +} + +/////////////////////////////////////////////////////////////////////////////// + +Schematic *get_or_load_schematic(lua_State *L, int index, + SchematicManager *schemmgr, StringMap *replace_names) { + if (index < 0) + index = lua_gettop(L) + 1 + index; + + Schematic *schem = (Schematic *)get_objdef(L, index, schemmgr); + if (schem) + return schem; + + schem = load_schematic(L, index, schemmgr->getNodeDef(), + replace_names); + if (!schem) + return NULL; + + if (schemmgr->add(schem) == OBJDEF_INVALID_HANDLE) { + delete schem; + return NULL; + } + + return schem; +} + + +Schematic *load_schematic(lua_State *L, int index, INodeDefManager *ndef, + StringMap *replace_names) +{ + if (index < 0) + index = lua_gettop(L) + 1 + index; + + Schematic *schem = NULL; + + if (lua_istable(L, index)) { + schem = load_schematic_from_def(L, index, ndef, + replace_names); + if (!schem) { + delete schem; + return NULL; + } + } else if (lua_isnumber(L, index)) { + return NULL; + } else if (lua_isstring(L, index)) { + schem = SchematicManager::create(SCHEMATIC_NORMAL); + + std::string filepath = lua_tostring(L, index); + if (!fs::IsPathAbsolute(filepath)) + filepath = ModApiBase::getCurrentModPath(L) + DIR_DELIM + filepath; + + if (!schem->loadSchematicFromFile(filepath, ndef, + replace_names)) { + delete schem; + return NULL; + } + } + + return schem; +} + + +Schematic *load_schematic_from_def(lua_State *L, int index, + INodeDefManager *ndef, StringMap *replace_names) +{ + Schematic *schem = SchematicManager::create(SCHEMATIC_NORMAL); + + if (!read_schematic_def(L, index, schem, &schem->m_nodenames)) { + delete schem; + return NULL; + } + + size_t num_nodes = schem->m_nodenames.size(); + + schem->m_nnlistsizes.push_back(num_nodes); + + if (replace_names) { + for (size_t i = 0; i != num_nodes; i++) { + StringMap::iterator it = replace_names->find(schem->m_nodenames[i]); + if (it != replace_names->end()) + schem->m_nodenames[i] = it->second; + } + } + + if (ndef) + ndef->pendNodeResolve(schem); + + return schem; +} + + +bool read_schematic_def(lua_State *L, int index, + Schematic *schem, std::vector<std::string> *names) +{ + if (!lua_istable(L, index)) + return false; + //// Get schematic size lua_getfield(L, index, "size"); - v3s16 size = read_v3s16(L, -1); + v3s16 size = check_v3s16(L, -1); lua_pop(L, 1); + schem->size = size; + //// Get schematic data lua_getfield(L, index, "data"); luaL_checktype(L, -1, LUA_TTABLE); - int numnodes = size.X * size.Y * size.Z; - MapNode *schemdata = new MapNode[numnodes]; - int i = 0; + u32 numnodes = size.X * size.Y * size.Z; + schem->schemdata = new MapNode[numnodes]; - lua_pushnil(L); - while (lua_next(L, -2)) { - if (i >= numnodes) { - i++; - lua_pop(L, 1); + size_t names_base = names->size(); + std::map<std::string, content_t> name_id_map; + + u32 i = 0; + for (lua_pushnil(L); lua_next(L, -2); i++, lua_pop(L, 1)) { + if (i >= numnodes) continue; - } - // same as readnode, except param1 default is MTSCHEM_PROB_CONST - lua_getfield(L, -1, "name"); - std::string name = luaL_checkstring(L, -1); - lua_pop(L, 1); + //// Read name + std::string name; + if (!getstringfield(L, -1, "name", name)) + throw LuaError("Schematic data definition with missing name field"); + //// Read param1/prob u8 param1; - lua_getfield(L, -1, "param1"); - param1 = !lua_isnil(L, -1) ? lua_tonumber(L, -1) : MTSCHEM_PROB_ALWAYS; - lua_pop(L, 1); - - u8 param2; - lua_getfield(L, -1, "param2"); - param2 = !lua_isnil(L, -1) ? lua_tonumber(L, -1) : 0; - lua_pop(L, 1); - - std::map<std::string, std::string>::iterator it; - it = replace_names.find(name); - if (it != replace_names.end()) - name = it->second; + if (!getintfield(L, -1, "param1", param1) && + !getintfield(L, -1, "prob", param1)) + param1 = MTSCHEM_PROB_ALWAYS_OLD; + + //// Read param2 + u8 param2 = getintfield_default(L, -1, "param2", 0); + + //// Find or add new nodename-to-ID mapping + std::map<std::string, content_t>::iterator it = name_id_map.find(name); + content_t name_index; + if (it != name_id_map.end()) { + name_index = it->second; + } else { + name_index = names->size() - names_base; + name_id_map[name] = name_index; + names->push_back(name); + } - schemdata[i] = MapNode(ndef, name, param1, param2); + //// Perform probability/force_place fixup on param1 + param1 >>= 1; + if (getboolfield_default(L, -1, "force_place", false)) + param1 |= MTSCHEM_FORCE_PLACE; - i++; - lua_pop(L, 1); + //// Actually set the node in the schematic + schem->schemdata[i] = MapNode(name_index, param1, param2); } if (i != numnodes) { - errorstream << "read_schematic: incorrect number of " + errorstream << "read_schematic_def: incorrect number of " "nodes provided in raw schematic data (got " << i << ", expected " << numnodes << ")." << std::endl; - delete schemdata; return false; } //// Get Y-slice probability values (if present) - u8 *slice_probs = new u8[size.Y]; - for (i = 0; i != size.Y; i++) - slice_probs[i] = MTSCHEM_PROB_ALWAYS; + schem->slice_probs = new u8[size.Y]; + for (i = 0; i != (u32) size.Y; i++) + schem->slice_probs[i] = MTSCHEM_PROB_ALWAYS; lua_getfield(L, index, "yslice_prob"); if (lua_istable(L, -1)) { - lua_pushnil(L); - while (lua_next(L, -2)) { - if (getintfield(L, -1, "ypos", i) && i >= 0 && i < size.Y) { - slice_probs[i] = getintfield_default(L, -1, - "prob", MTSCHEM_PROB_ALWAYS); - } - lua_pop(L, 1); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + u16 ypos; + if (!getintfield(L, -1, "ypos", ypos) || (ypos >= size.Y) || + !getintfield(L, -1, "prob", schem->slice_probs[ypos])) + continue; + + schem->slice_probs[ypos] >>= 1; } } - // Here, we read the nodes directly from the INodeDefManager - there is no - // need for pending node resolutions so we'll mark this schematic as updated - schem->flags = SCHEM_CIDS_UPDATED; - - schem->size = size; - schem->schemdata = schemdata; - schem->slice_probs = slice_probs; return true; } -bool get_schematic(lua_State *L, int index, Schematic *schem, - INodeDefManager *ndef, std::map<std::string, std::string> &replace_names) +void read_schematic_replacements(lua_State *L, int index, StringMap *replace_names) { if (index < 0) index = lua_gettop(L) + 1 + index; - if (lua_istable(L, index)) { - return read_schematic(L, index, schem, ndef, replace_names); - } else if (lua_isstring(L, index)) { - const char *filename = lua_tostring(L, index); - return schem->loadSchematicFromFile(filename, ndef, replace_names); - } else { - return false; - } -} - - -void read_schematic_replacements(lua_State *L, - std::map<std::string, std::string> &replace_names, int index) -{ lua_pushnil(L); while (lua_next(L, index)) { std::string replace_from; @@ -213,11 +335,119 @@ void read_schematic_replacements(lua_State *L, replace_to = lua_tostring(L, -1); } - replace_names[replace_from] = replace_to; + replace_names->insert(std::make_pair(replace_from, replace_to)); lua_pop(L, 1); } } +/////////////////////////////////////////////////////////////////////////////// + +Biome *get_or_load_biome(lua_State *L, int index, BiomeManager *biomemgr) +{ + if (index < 0) + index = lua_gettop(L) + 1 + index; + + Biome *biome = (Biome *)get_objdef(L, index, biomemgr); + if (biome) + return biome; + + biome = read_biome_def(L, index, biomemgr->getNodeDef()); + if (!biome) + return NULL; + + if (biomemgr->add(biome) == OBJDEF_INVALID_HANDLE) { + delete biome; + return NULL; + } + + return biome; +} + + +Biome *read_biome_def(lua_State *L, int index, INodeDefManager *ndef) +{ + if (!lua_istable(L, index)) + return NULL; + + BiomeType biometype = (BiomeType)getenumfield(L, index, "type", + ModApiMapgen::es_BiomeTerrainType, BIOME_NORMAL); + Biome *b = BiomeManager::create(biometype); + + b->name = getstringfield_default(L, index, "name", ""); + b->depth_top = getintfield_default(L, index, "depth_top", 0); + b->depth_filler = getintfield_default(L, index, "depth_filler", -31000); + b->depth_water_top = getintfield_default(L, index, "depth_water_top", 0); + b->y_min = getintfield_default(L, index, "y_min", -31000); + b->y_max = getintfield_default(L, index, "y_max", 31000); + b->heat_point = getfloatfield_default(L, index, "heat_point", 0.f); + b->humidity_point = getfloatfield_default(L, index, "humidity_point", 0.f); + b->flags = 0; //reserved + + std::vector<std::string> &nn = b->m_nodenames; + nn.push_back(getstringfield_default(L, index, "node_top", "")); + nn.push_back(getstringfield_default(L, index, "node_filler", "")); + nn.push_back(getstringfield_default(L, index, "node_stone", "")); + nn.push_back(getstringfield_default(L, index, "node_water_top", "")); + nn.push_back(getstringfield_default(L, index, "node_water", "")); + nn.push_back(getstringfield_default(L, index, "node_river_water", "")); + nn.push_back(getstringfield_default(L, index, "node_dust", "")); + ndef->pendNodeResolve(b); + + return b; +} + + +size_t get_biome_list(lua_State *L, int index, + BiomeManager *biomemgr, std::set<u8> *biome_id_list) +{ + if (index < 0) + index = lua_gettop(L) + 1 + index; + + if (lua_isnil(L, index)) + return 0; + + bool is_single = true; + if (lua_istable(L, index)) { + lua_getfield(L, index, "name"); + is_single = !lua_isnil(L, -1); + lua_pop(L, 1); + } + + if (is_single) { + Biome *biome = get_or_load_biome(L, index, biomemgr); + if (!biome) { + errorstream << "get_biome_list: failed to get biome '" + << (lua_isstring(L, index) ? lua_tostring(L, index) : "") + << "'." << std::endl; + return 1; + } + + biome_id_list->insert(biome->index); + return 0; + } + + // returns number of failed resolutions + size_t fail_count = 0; + size_t count = 0; + + for (lua_pushnil(L); lua_next(L, index); lua_pop(L, 1)) { + count++; + Biome *biome = get_or_load_biome(L, -1, biomemgr); + if (!biome) { + fail_count++; + errorstream << "get_biome_list: failed to get biome '" + << (lua_isstring(L, -1) ? lua_tostring(L, -1) : "") + << "'" << std::endl; + continue; + } + + biome_id_list->insert(biome->index); + } + + return fail_count; +} + +/////////////////////////////////////////////////////////////////////////////// // get_mapgen_object(objectname) // returns the requested object used during map generation @@ -239,92 +469,98 @@ int ModApiMapgen::l_get_mapgen_object(lua_State *L) size_t maplen = mg->csize.X * mg->csize.Z; switch (mgobj) { - case MGOBJ_VMANIP: { - MMVManip *vm = mg->vm; + case MGOBJ_VMANIP: { + MMVManip *vm = mg->vm; - // VoxelManip object - LuaVoxelManip *o = new LuaVoxelManip(vm, true); - *(void **)(lua_newuserdata(L, sizeof(void *))) = o; - luaL_getmetatable(L, "VoxelManip"); - lua_setmetatable(L, -2); + // VoxelManip object + LuaVoxelManip *o = new LuaVoxelManip(vm, true); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, "VoxelManip"); + lua_setmetatable(L, -2); - // emerged min pos - push_v3s16(L, vm->m_area.MinEdge); + // emerged min pos + push_v3s16(L, vm->m_area.MinEdge); - // emerged max pos - push_v3s16(L, vm->m_area.MaxEdge); + // emerged max pos + push_v3s16(L, vm->m_area.MaxEdge); - return 3; + return 3; + } + case MGOBJ_HEIGHTMAP: { + if (!mg->heightmap) + return 0; + + lua_newtable(L); + for (size_t i = 0; i != maplen; i++) { + lua_pushinteger(L, mg->heightmap[i]); + lua_rawseti(L, -2, i + 1); } - case MGOBJ_HEIGHTMAP: { - if (!mg->heightmap) - return 0; - - lua_newtable(L); - for (size_t i = 0; i != maplen; i++) { - lua_pushinteger(L, mg->heightmap[i]); - lua_rawseti(L, -2, i + 1); - } - return 1; + return 1; + } + case MGOBJ_BIOMEMAP: { + if (!mg->biomemap) + return 0; + + lua_newtable(L); + for (size_t i = 0; i != maplen; i++) { + lua_pushinteger(L, mg->biomemap[i]); + lua_rawseti(L, -2, i + 1); } - case MGOBJ_BIOMEMAP: { - if (!mg->biomemap) - return 0; - lua_newtable(L); - for (size_t i = 0; i != maplen; i++) { - lua_pushinteger(L, mg->biomemap[i]); - lua_rawseti(L, -2, i + 1); - } - - return 1; + return 1; + } + case MGOBJ_HEATMAP: { + if (!mg->heatmap) + return 0; + + lua_newtable(L); + for (size_t i = 0; i != maplen; i++) { + lua_pushnumber(L, mg->heatmap[i]); + lua_rawseti(L, -2, i + 1); } - case MGOBJ_HEATMAP: { // Mapgen V7 specific objects - case MGOBJ_HUMIDMAP: - if (strcmp(emerge->params.mg_name.c_str(), "v7")) - return 0; - - MapgenV7 *mgv7 = (MapgenV7 *)mg; - float *arr = (mgobj == MGOBJ_HEATMAP) ? - mgv7->noise_heat->result : mgv7->noise_humidity->result; - if (!arr) - return 0; + return 1; + } - lua_newtable(L); - for (size_t i = 0; i != maplen; i++) { - lua_pushnumber(L, arr[i]); - lua_rawseti(L, -2, i + 1); - } + case MGOBJ_HUMIDMAP: { + if (!mg->humidmap) + return 0; - return 1; + lua_newtable(L); + for (size_t i = 0; i != maplen; i++) { + lua_pushnumber(L, mg->humidmap[i]); + lua_rawseti(L, -2, i + 1); } - case MGOBJ_GENNOTIFY: { - std::map<std::string, std::vector<v3s16> >event_map; - std::map<std::string, std::vector<v3s16> >::iterator it; - mg->gennotify.getEvents(event_map); + return 1; + } + case MGOBJ_GENNOTIFY: { + std::map<std::string, std::vector<v3s16> >event_map; + std::map<std::string, std::vector<v3s16> >::iterator it; - lua_newtable(L); - for (it = event_map.begin(); it != event_map.end(); ++it) { - lua_newtable(L); + mg->gennotify.getEvents(event_map); - for (size_t j = 0; j != it->second.size(); j++) { - push_v3s16(L, it->second[j]); - lua_rawseti(L, -2, j + 1); - } + lua_newtable(L); + for (it = event_map.begin(); it != event_map.end(); ++it) { + lua_newtable(L); - lua_setfield(L, -2, it->first.c_str()); + for (size_t j = 0; j != it->second.size(); j++) { + push_v3s16(L, it->second[j]); + lua_rawseti(L, -2, j + 1); } - return 1; + lua_setfield(L, -2, it->first.c_str()); } + + return 1; + } } return 0; } + int ModApiMapgen::l_get_mapgen_params(lua_State *L) { MapgenParams *params = &getServer(L)->getEmergeManager()->params; @@ -350,6 +586,7 @@ int ModApiMapgen::l_get_mapgen_params(lua_State *L) return 1; } + // set_mapgen_params(params) // set mapgen parameters int ModApiMapgen::l_set_mapgen_params(lua_State *L) @@ -389,6 +626,7 @@ int ModApiMapgen::l_set_mapgen_params(lua_State *L) return 0; } + // set_noiseparams(name, noiseparams, set_default) // set global config values for noise parameters int ModApiMapgen::l_set_noiseparams(lua_State *L) @@ -406,6 +644,21 @@ int ModApiMapgen::l_set_noiseparams(lua_State *L) return 0; } + +// get_noiseparams(name) +int ModApiMapgen::l_get_noiseparams(lua_State *L) +{ + std::string name = luaL_checkstring(L, 1); + + NoiseParams np; + if (!g_settings->getNoiseParams(name, np)) + return 0; + + push_noiseparams(L, &np); + return 1; +} + + // set_gen_notify(flags, {deco_id_table}) int ModApiMapgen::l_set_gen_notify(lua_State *L) { @@ -421,7 +674,7 @@ int ModApiMapgen::l_set_gen_notify(lua_State *L) lua_pushnil(L); while (lua_next(L, 2)) { if (lua_isnumber(L, -1)) - emerge->gen_notify_on_deco_ids.insert(lua_tonumber(L, -1)); + emerge->gen_notify_on_deco_ids.insert((u32)lua_tonumber(L, -1)); lua_pop(L, 1); } } @@ -429,6 +682,26 @@ int ModApiMapgen::l_set_gen_notify(lua_State *L) return 0; } + +// get_gen_notify() +int ModApiMapgen::l_get_gen_notify(lua_State *L) +{ + EmergeManager *emerge = getServer(L)->getEmergeManager(); + push_flags_string(L, flagdesc_gennotify, emerge->gen_notify_on, + emerge->gen_notify_on); + + lua_newtable(L); + int i = 1; + for (std::set<u32>::iterator it = emerge->gen_notify_on_deco_ids.begin(); + it != emerge->gen_notify_on_deco_ids.end(); ++it) { + lua_pushnumber(L, *it); + lua_rawseti(L, -2, i); + i++; + } + return 2; +} + + // register_biome({lots of stuff}) int ModApiMapgen::l_register_biome(lua_State *L) { @@ -438,66 +711,20 @@ int ModApiMapgen::l_register_biome(lua_State *L) INodeDefManager *ndef = getServer(L)->getNodeDefManager(); BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr; - enum BiomeType biometype = (BiomeType)getenumfield(L, index, "type", - es_BiomeTerrainType, BIOME_TYPE_NORMAL); - Biome *b = bmgr->create(biometype); - - b->name = getstringfield_default(L, index, "name", ""); - b->depth_top = getintfield_default(L, index, "depth_top", 1); - b->depth_filler = getintfield_default(L, index, "depth_filler", 3); - b->height_shore = getintfield_default(L, index, "height_shore", 3); - b->depth_water_top = getintfield_default(L, index, "depth_water_top", 0); - b->y_min = getintfield_default(L, index, "y_min", -31000); - b->y_max = getintfield_default(L, index, "y_max", 31000); - b->heat_point = getfloatfield_default(L, index, "heat_point", 0.f); - b->humidity_point = getfloatfield_default(L, index, "humidity_point", 0.f); - b->flags = 0; //reserved + Biome *biome = read_biome_def(L, index, ndef); + if (!biome) + return 0; - u32 id = bmgr->add(b); - if (id == (u32)-1) { - delete b; + ObjDefHandle handle = bmgr->add(biome); + if (handle == OBJDEF_INVALID_HANDLE) { + delete biome; return 0; } - NodeResolveInfo *nri = new NodeResolveInfo(b); - std::list<std::string> &nnames = nri->nodenames; - nnames.push_back(getstringfield_default(L, index, "node_top", "")); - nnames.push_back(getstringfield_default(L, index, "node_filler", "")); - nnames.push_back(getstringfield_default(L, index, "node_shore_top", "")); - nnames.push_back(getstringfield_default(L, index, "node_shore_filler", "")); - nnames.push_back(getstringfield_default(L, index, "node_underwater", "")); - nnames.push_back(getstringfield_default(L, index, "node_stone", "")); - nnames.push_back(getstringfield_default(L, index, "node_water_top", "")); - nnames.push_back(getstringfield_default(L, index, "node_water", "")); - nnames.push_back(getstringfield_default(L, index, "node_dust", "")); - ndef->pendNodeResolve(nri); - - verbosestream << "register_biome: " << b->name << std::endl; - - lua_pushinteger(L, id); + lua_pushinteger(L, handle); return 1; } -int ModApiMapgen::l_clear_registered_biomes(lua_State *L) -{ - BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr; - bmgr->clear(); - return 0; -} - -int ModApiMapgen::l_clear_registered_decorations(lua_State *L) -{ - DecorationManager *dmgr = getServer(L)->getEmergeManager()->decomgr; - dmgr->clear(); - return 0; -} - -int ModApiMapgen::l_clear_registered_ores(lua_State *L) -{ - OreManager *omgr = getServer(L)->getEmergeManager()->oremgr; - omgr->clear(); - return 0; -} // register_decoration({lots of stuff}) int ModApiMapgen::l_register_decoration(lua_State *L) @@ -508,6 +735,7 @@ int ModApiMapgen::l_register_decoration(lua_State *L) INodeDefManager *ndef = getServer(L)->getNodeDefManager(); DecorationManager *decomgr = getServer(L)->getEmergeManager()->decomgr; BiomeManager *biomemgr = getServer(L)->getEmergeManager()->biomemgr; + SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr; enum DecorationType decotype = (DecorationType)getenumfield(L, index, "deco_type", es_DecorationType, -1); @@ -515,7 +743,7 @@ int ModApiMapgen::l_register_decoration(lua_State *L) Decoration *deco = decomgr->create(decotype); if (!deco) { errorstream << "register_decoration: decoration placement type " - << decotype << " not implemented"; + << decotype << " not implemented" << std::endl; return 0; } @@ -531,15 +759,11 @@ int ModApiMapgen::l_register_decoration(lua_State *L) return 0; } - NodeResolveInfo *nri = new NodeResolveInfo(deco); - //// Get node name(s) to place decoration on - std::vector<const char *> place_on_names; - getstringlistfield(L, index, "place_on", place_on_names); - nri->nodelistinfo.push_back(NodeListInfo(place_on_names.size())); - for (size_t i = 0; i != place_on_names.size(); i++) - nri->nodenames.push_back(place_on_names[i]); + size_t nread = getstringlistfield(L, index, "place_on", &deco->m_nodenames); + deco->m_nnlistsizes.push_back(nread); + //// Get decoration flags getflagsfield(L, index, "flags", flagdesc_deco, &deco->flags, NULL); //// Get NoiseParams to define how decoration is placed @@ -549,51 +773,45 @@ int ModApiMapgen::l_register_decoration(lua_State *L) lua_pop(L, 1); //// Get biomes associated with this decoration (if any) - std::vector<const char *> biome_list; - getstringlistfield(L, index, "biomes", biome_list); - for (size_t i = 0; i != biome_list.size(); i++) { - Biome *b = (Biome *)biomemgr->getByName(biome_list[i]); - if (!b) - continue; - - deco->biomes.insert(b->id); - } + lua_getfield(L, index, "biomes"); + if (get_biome_list(L, -1, biomemgr, &deco->biomes)) + errorstream << "register_decoration: couldn't get all biomes " << std::endl; + lua_pop(L, 1); //// Handle decoration type-specific parameters bool success = false; switch (decotype) { - case DECO_SIMPLE: - success = regDecoSimple(L, nri, (DecoSimple *)deco); - break; - case DECO_SCHEMATIC: - success = regDecoSchematic(L, ndef, (DecoSchematic *)deco); - break; - case DECO_LSYSTEM: - break; + case DECO_SIMPLE: + success = read_deco_simple(L, (DecoSimple *)deco); + break; + case DECO_SCHEMATIC: + success = read_deco_schematic(L, schemmgr, (DecoSchematic *)deco); + break; + case DECO_LSYSTEM: + break; } - ndef->pendNodeResolve(nri); - if (!success) { delete deco; return 0; } - u32 id = decomgr->add(deco); - if (id == (u32)-1) { + ndef->pendNodeResolve(deco); + + ObjDefHandle handle = decomgr->add(deco); + if (handle == OBJDEF_INVALID_HANDLE) { delete deco; return 0; } - verbosestream << "register_decoration: " << deco->name << std::endl; - - lua_pushinteger(L, id); + lua_pushinteger(L, handle); return 1; } -bool ModApiMapgen::regDecoSimple(lua_State *L, - NodeResolveInfo *nri, DecoSimple *deco) + +bool read_deco_simple(lua_State *L, DecoSimple *deco) { + size_t nnames; int index = 1; deco->deco_height = getintfield_default(L, index, "height", 1); @@ -606,60 +824,48 @@ bool ModApiMapgen::regDecoSimple(lua_State *L, return false; } - std::vector<const char *> deco_names; - getstringlistfield(L, index, "decoration", deco_names); - if (deco_names.size() == 0) { + nnames = getstringlistfield(L, index, "decoration", &deco->m_nodenames); + deco->m_nnlistsizes.push_back(nnames); + if (nnames == 0) { errorstream << "register_decoration: no decoration nodes " "defined" << std::endl; return false; } - nri->nodelistinfo.push_back(NodeListInfo(deco_names.size())); - for (size_t i = 0; i != deco_names.size(); i++) - nri->nodenames.push_back(deco_names[i]); - std::vector<const char *> spawnby_names; - getstringlistfield(L, index, "spawn_by", spawnby_names); - if (deco->nspawnby != -1 && spawnby_names.size() == 0) { + nnames = getstringlistfield(L, index, "spawn_by", &deco->m_nodenames); + deco->m_nnlistsizes.push_back(nnames); + if (nnames == 0 && deco->nspawnby != -1) { errorstream << "register_decoration: no spawn_by nodes defined," " but num_spawn_by specified" << std::endl; return false; } - nri->nodelistinfo.push_back(NodeListInfo(spawnby_names.size())); - for (size_t i = 0; i != spawnby_names.size(); i++) - nri->nodenames.push_back(spawnby_names[i]); return true; } -bool ModApiMapgen::regDecoSchematic(lua_State *L, INodeDefManager *ndef, - DecoSchematic *deco) + +bool read_deco_schematic(lua_State *L, SchematicManager *schemmgr, DecoSchematic *deco) { int index = 1; deco->rotation = (Rotation)getenumfield(L, index, "rotation", - es_Rotation, ROTATE_0); + ModApiMapgen::es_Rotation, ROTATE_0); - std::map<std::string, std::string> replace_names; + StringMap replace_names; lua_getfield(L, index, "replacements"); if (lua_istable(L, -1)) - read_schematic_replacements(L, replace_names, lua_gettop(L)); + read_schematic_replacements(L, -1, &replace_names); lua_pop(L, 1); - // TODO(hmmmm): get a ref from registered schematics - Schematic *schem = new Schematic; lua_getfield(L, index, "schematic"); - if (!get_schematic(L, -1, schem, ndef, replace_names)) { - lua_pop(L, 1); - delete schem; - return false; - } + Schematic *schem = get_or_load_schematic(L, -1, schemmgr, &replace_names); lua_pop(L, 1); deco->schematic = schem; - - return true; + return schem != NULL; } + // register_ore({lots of stuff}) int ModApiMapgen::l_register_ore(lua_State *L) { @@ -667,10 +873,11 @@ int ModApiMapgen::l_register_ore(lua_State *L) luaL_checktype(L, index, LUA_TTABLE); INodeDefManager *ndef = getServer(L)->getNodeDefManager(); + BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr; OreManager *oremgr = getServer(L)->getEmergeManager()->oremgr; enum OreType oretype = (OreType)getenumfield(L, index, - "ore_type", es_OreType, ORE_TYPE_SCATTER); + "ore_type", es_OreType, ORE_SCATTER); Ore *ore = oremgr->create(oretype); if (!ore) { errorstream << "register_ore: ore_type " << oretype << " not implemented"; @@ -686,6 +893,7 @@ int ModApiMapgen::l_register_ore(lua_State *L) ore->noise = NULL; ore->flags = 0; + //// Get y_min/y_max warn_if_field_exists(L, index, "height_min", "Deprecated: new name is \"y_min\"."); warn_if_field_exists(L, index, "height_max", @@ -708,8 +916,16 @@ int ModApiMapgen::l_register_ore(lua_State *L) return 0; } + //// Get flags getflagsfield(L, index, "flags", flagdesc_ore, &ore->flags, NULL); + //// Get biomes associated with this decoration (if any) + lua_getfield(L, index, "biomes"); + if (get_biome_list(L, -1, bmgr, &ore->biomes)) + errorstream << "register_ore: couldn't get all biomes " << std::endl; + lua_pop(L, 1); + + //// Get noise parameters if needed lua_getfield(L, index, "noise_params"); if (read_noiseparams(L, -1, &ore->np)) { ore->flags |= OREFLAG_USE_NOISE; @@ -721,45 +937,152 @@ int ModApiMapgen::l_register_ore(lua_State *L) } lua_pop(L, 1); - if (oretype == ORE_TYPE_VEIN) { + if (oretype == ORE_VEIN) { OreVein *orevein = (OreVein *)ore; orevein->random_factor = getfloatfield_default(L, index, "random_factor", 1.f); } - u32 id = oremgr->add(ore); - if (id == (u32)-1) { + ObjDefHandle handle = oremgr->add(ore); + if (handle == OBJDEF_INVALID_HANDLE) { delete ore; return 0; } - NodeResolveInfo *nri = new NodeResolveInfo(ore); - nri->nodenames.push_back(getstringfield_default(L, index, "ore", "")); + ore->m_nodenames.push_back(getstringfield_default(L, index, "ore", "")); + + size_t nnames = getstringlistfield(L, index, "wherein", &ore->m_nodenames); + ore->m_nnlistsizes.push_back(nnames); + + ndef->pendNodeResolve(ore); + + lua_pushinteger(L, handle); + return 1; +} + - std::vector<const char *> wherein_names; - getstringlistfield(L, index, "wherein", wherein_names); - nri->nodelistinfo.push_back(NodeListInfo(wherein_names.size())); - for (size_t i = 0; i != wherein_names.size(); i++) - nri->nodenames.push_back(wherein_names[i]); +// register_schematic({schematic}, replacements={}) +int ModApiMapgen::l_register_schematic(lua_State *L) +{ + SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr; + + StringMap replace_names; + if (lua_istable(L, 2)) + read_schematic_replacements(L, 2, &replace_names); - ndef->pendNodeResolve(nri); + Schematic *schem = load_schematic(L, 1, schemmgr->getNodeDef(), + &replace_names); + if (!schem) + return 0; - verbosestream << "register_ore: " << ore->name << std::endl; + ObjDefHandle handle = schemmgr->add(schem); + if (handle == OBJDEF_INVALID_HANDLE) { + delete schem; + return 0; + } - lua_pushinteger(L, id); + lua_pushinteger(L, handle); return 1; } -// create_schematic(p1, p2, probability_list, filename) + +// clear_registered_biomes() +int ModApiMapgen::l_clear_registered_biomes(lua_State *L) +{ + BiomeManager *bmgr = getServer(L)->getEmergeManager()->biomemgr; + bmgr->clear(); + return 0; +} + + +// clear_registered_decorations() +int ModApiMapgen::l_clear_registered_decorations(lua_State *L) +{ + DecorationManager *dmgr = getServer(L)->getEmergeManager()->decomgr; + dmgr->clear(); + return 0; +} + + +// clear_registered_ores() +int ModApiMapgen::l_clear_registered_ores(lua_State *L) +{ + OreManager *omgr = getServer(L)->getEmergeManager()->oremgr; + omgr->clear(); + return 0; +} + + +// clear_registered_schematics() +int ModApiMapgen::l_clear_registered_schematics(lua_State *L) +{ + SchematicManager *smgr = getServer(L)->getEmergeManager()->schemmgr; + smgr->clear(); + return 0; +} + + +// generate_ores(vm, p1, p2, [ore_id]) +int ModApiMapgen::l_generate_ores(lua_State *L) +{ + EmergeManager *emerge = getServer(L)->getEmergeManager(); + + Mapgen mg; + mg.seed = emerge->params.seed; + mg.vm = LuaVoxelManip::checkobject(L, 1)->vm; + mg.ndef = getServer(L)->getNodeDefManager(); + + v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : + mg.vm->m_area.MinEdge + v3s16(1,1,1) * MAP_BLOCKSIZE; + v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) : + mg.vm->m_area.MaxEdge - v3s16(1,1,1) * MAP_BLOCKSIZE; + sortBoxVerticies(pmin, pmax); + + u32 blockseed = Mapgen::getBlockSeed(pmin, mg.seed); + + emerge->oremgr->placeAllOres(&mg, blockseed, pmin, pmax); + + return 0; +} + + +// generate_decorations(vm, p1, p2, [deco_id]) +int ModApiMapgen::l_generate_decorations(lua_State *L) +{ + EmergeManager *emerge = getServer(L)->getEmergeManager(); + + Mapgen mg; + mg.seed = emerge->params.seed; + mg.vm = LuaVoxelManip::checkobject(L, 1)->vm; + mg.ndef = getServer(L)->getNodeDefManager(); + + v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : + mg.vm->m_area.MinEdge + v3s16(1,1,1) * MAP_BLOCKSIZE; + v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) : + mg.vm->m_area.MaxEdge - v3s16(1,1,1) * MAP_BLOCKSIZE; + sortBoxVerticies(pmin, pmax); + + u32 blockseed = Mapgen::getBlockSeed(pmin, mg.seed); + + emerge->decomgr->placeAllDecos(&mg, blockseed, pmin, pmax); + + return 0; +} + + +// create_schematic(p1, p2, probability_list, filename, y_slice_prob_list) int ModApiMapgen::l_create_schematic(lua_State *L) { - Schematic schem; + INodeDefManager *ndef = getServer(L)->getNodeDefManager(); + + const char *filename = luaL_checkstring(L, 4); + CHECK_SECURE_PATH_OPTIONAL(L, filename); Map *map = &(getEnv(L)->getMap()); - INodeDefManager *ndef = getServer(L)->getNodeDefManager(); + Schematic schem; - v3s16 p1 = read_v3s16(L, 1); - v3s16 p2 = read_v3s16(L, 2); + v3s16 p1 = check_v3s16(L, 1); + v3s16 p2 = check_v3s16(L, 2); sortBoxVerticies(p1, p2); std::vector<std::pair<v3s16, u8> > prob_list; @@ -768,7 +1091,7 @@ int ModApiMapgen::l_create_schematic(lua_State *L) while (lua_next(L, 3)) { if (lua_istable(L, -1)) { lua_getfield(L, -1, "pos"); - v3s16 pos = read_v3s16(L, -1); + v3s16 pos = check_v3s16(L, -1); lua_pop(L, 1); u8 prob = getintfield_default(L, -1, "prob", MTSCHEM_PROB_ALWAYS); @@ -793,8 +1116,6 @@ int ModApiMapgen::l_create_schematic(lua_State *L) } } - const char *filename = luaL_checkstring(L, 4); - if (!schem.getSchematicFromMap(map, p1, p2)) { errorstream << "create_schematic: failed to get schematic " "from map" << std::endl; @@ -807,60 +1128,25 @@ int ModApiMapgen::l_create_schematic(lua_State *L) actionstream << "create_schematic: saved schematic file '" << filename << "'." << std::endl; + lua_pushboolean(L, true); return 1; } -// generate_ores(vm, [ore_id]) -int ModApiMapgen::l_generate_ores(lua_State *L) -{ - EmergeManager *emerge = getServer(L)->getEmergeManager(); - - Mapgen mg; - mg.seed = emerge->params.seed; - mg.vm = LuaVoxelManip::checkobject(L, 1)->vm; - mg.ndef = getServer(L)->getNodeDefManager(); - - u32 blockseed = Mapgen::getBlockSeed(mg.vm->m_area.MinEdge, mg.seed); - - emerge->oremgr->placeAllOres(&mg, blockseed, - mg.vm->m_area.MinEdge, mg.vm->m_area.MaxEdge); - - return 0; -} - -// generate_decorations(vm, [deco_id]) -int ModApiMapgen::l_generate_decorations(lua_State *L) -{ - EmergeManager *emerge = getServer(L)->getEmergeManager(); - - Mapgen mg; - mg.seed = emerge->params.seed; - mg.vm = LuaVoxelManip::checkobject(L, 1)->vm; - mg.ndef = getServer(L)->getNodeDefManager(); - - u32 blockseed = Mapgen::getBlockSeed(mg.vm->m_area.MinEdge, mg.seed); - - emerge->decomgr->placeAllDecos(&mg, blockseed, - mg.vm->m_area.MinEdge, mg.vm->m_area.MaxEdge); - - return 0; -} // place_schematic(p, schematic, rotation, replacement) int ModApiMapgen::l_place_schematic(lua_State *L) { - Schematic schem; - Map *map = &(getEnv(L)->getMap()); - INodeDefManager *ndef = getServer(L)->getNodeDefManager(); + SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr; //// Read position - v3s16 p = read_v3s16(L, 1); + v3s16 p = check_v3s16(L, 1); //// Read rotation int rot = ROTATE_0; - if (lua_isstring(L, 3)) - string_to_enum(es_Rotation, rot, std::string(lua_tostring(L, 3))); + const char *enumstr = lua_tostring(L, 3); + if (enumstr) + string_to_enum(es_Rotation, rot, std::string(enumstr)); //// Read force placement bool force_placement = true; @@ -868,21 +1154,73 @@ int ModApiMapgen::l_place_schematic(lua_State *L) force_placement = lua_toboolean(L, 5); //// Read node replacements - std::map<std::string, std::string> replace_names; + StringMap replace_names; if (lua_istable(L, 4)) - read_schematic_replacements(L, replace_names, 4); + read_schematic_replacements(L, 4, &replace_names); //// Read schematic - if (!get_schematic(L, 2, &schem, ndef, replace_names)) { + Schematic *schem = get_or_load_schematic(L, 2, schemmgr, &replace_names); + if (!schem) { errorstream << "place_schematic: failed to get schematic" << std::endl; return 0; } - schem.placeStructure(map, p, 0, (Rotation)rot, force_placement, ndef); + schem->placeStructure(map, p, 0, (Rotation)rot, force_placement); + + lua_pushboolean(L, true); + return 1; +} + +// serialize_schematic(schematic, format, options={...}) +int ModApiMapgen::l_serialize_schematic(lua_State *L) +{ + SchematicManager *schemmgr = getServer(L)->getEmergeManager()->schemmgr; + + //// Read options + bool use_comments = getboolfield_default(L, 3, "lua_use_comments", false); + u32 indent_spaces = getintfield_default(L, 3, "lua_num_indent_spaces", 0); + + //// 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 << "serialize_schematic: failed to get schematic" << std::endl; + return 0; + } + + //// Read format of definition to save as + int schem_format = SCHEM_FMT_MTS; + const char *enumstr = lua_tostring(L, 2); + if (enumstr) + string_to_enum(es_SchematicFormatType, schem_format, std::string(enumstr)); + + //// Serialize to binary string + std::ostringstream os(std::ios_base::binary); + switch (schem_format) { + case SCHEM_FMT_MTS: + schem->serializeToMts(&os, schem->m_nodenames); + break; + case SCHEM_FMT_LUA: + schem->serializeToLua(&os, schem->m_nodenames, + use_comments, indent_spaces); + break; + default: + return 0; + } + + if (was_loaded) + delete schem; + std::string ser = os.str(); + lua_pushlstring(L, ser.c_str(), ser.length()); return 1; } + void ModApiMapgen::Initialize(lua_State *L, int top) { API_FCT(get_mapgen_object); @@ -890,19 +1228,23 @@ void ModApiMapgen::Initialize(lua_State *L, int top) API_FCT(get_mapgen_params); API_FCT(set_mapgen_params); API_FCT(set_noiseparams); + API_FCT(get_noiseparams); API_FCT(set_gen_notify); + API_FCT(get_gen_notify); API_FCT(register_biome); API_FCT(register_decoration); API_FCT(register_ore); + API_FCT(register_schematic); API_FCT(clear_registered_biomes); API_FCT(clear_registered_decorations); API_FCT(clear_registered_ores); + API_FCT(clear_registered_schematics); API_FCT(generate_ores); API_FCT(generate_decorations); - API_FCT(create_schematic); API_FCT(place_schematic); + API_FCT(serialize_schematic); } diff --git a/src/script/lua_api/l_mapgen.h b/src/script/lua_api/l_mapgen.h index e17d1b85a..7440d1285 100644 --- a/src/script/lua_api/l_mapgen.h +++ b/src/script/lua_api/l_mapgen.h @@ -22,11 +22,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_base.h" -class INodeDefManager; -struct NodeResolveInfo; -class DecoSimple; -class DecoSchematic; - class ModApiMapgen : public ModApiBase { private: // get_mapgen_object(objectname) @@ -44,9 +39,15 @@ private: // set_noiseparam_defaults(name, noiseparams, set_default) static int l_set_noiseparams(lua_State *L); + // get_noiseparam_defaults(name) + static int l_get_noiseparams(lua_State *L); + // set_gen_notify(flagstring) static int l_set_gen_notify(lua_State *L); + // set_gen_notify(flagstring) + static int l_get_gen_notify(lua_State *L); + // register_biome({lots of stuff}) static int l_register_biome(lua_State *L); @@ -56,16 +57,22 @@ private: // register_ore({lots of stuff}) static int l_register_ore(lua_State *L); + // register_schematic({schematic}, replacements={}) + static int l_register_schematic(lua_State *L); + // clear_registered_biomes() static int l_clear_registered_biomes(lua_State *L); // clear_registered_decorations() static int l_clear_registered_decorations(lua_State *L); - // generate_ores(vm) + // clear_registered_schematics() + static int l_clear_registered_schematics(lua_State *L); + + // generate_ores(vm, p1, p2) static int l_generate_ores(lua_State *L); - // generate_decorations(vm) + // generate_decorations(vm, p1, p2) static int l_generate_decorations(lua_State *L); // clear_registered_ores @@ -77,19 +84,19 @@ private: // place_schematic(p, schematic, rotation, replacement) static int l_place_schematic(lua_State *L); - static bool regDecoSimple(lua_State *L, - NodeResolveInfo *nri, DecoSimple *deco); - static bool regDecoSchematic(lua_State *L, - INodeDefManager *ndef, DecoSchematic *deco); + // serialize_schematic(schematic, format, options={...}) + static int l_serialize_schematic(lua_State *L); + +public: + static void Initialize(lua_State *L, int top); static struct EnumString es_BiomeTerrainType[]; static struct EnumString es_DecorationType[]; static struct EnumString es_MapgenObject[]; static struct EnumString es_OreType[]; static struct EnumString es_Rotation[]; - -public: - static void Initialize(lua_State *L, int top); + static struct EnumString es_SchematicFormatType[]; + static struct EnumString es_NodeResolveMethod[]; }; #endif /* L_MAPGEN_H_ */ diff --git a/src/script/lua_api/l_nodemeta.cpp b/src/script/lua_api/l_nodemeta.cpp index 4f20e56f9..6cdbe5c68 100644 --- a/src/script/lua_api/l_nodemeta.cpp +++ b/src/script/lua_api/l_nodemeta.cpp @@ -63,9 +63,10 @@ void NodeMetaRef::reportMetadataChange(NodeMetaRef *ref) ref->m_env->getMap().dispatchEvent(&event); // Set the block to be saved MapBlock *block = ref->m_env->getMap().getBlockNoCreateNoEx(blockpos); - if(block) + if (block) { block->raiseModified(MOD_STATE_WRITE_NEEDED, - "NodeMetaRef::reportMetadataChange"); + MOD_REASON_REPORT_META_CHANGE); + } } // Exported functions @@ -189,32 +190,34 @@ int NodeMetaRef::l_to_table(lua_State *L) NodeMetaRef *ref = checkobject(L, 1); NodeMetadata *meta = getmeta(ref, true); - if(meta == NULL){ + if (meta == NULL) { lua_pushnil(L); return 1; } lua_newtable(L); + // fields lua_newtable(L); { - std::map<std::string, std::string> fields = meta->getStrings(); - for(std::map<std::string, std::string>::const_iterator - i = fields.begin(); i != fields.end(); i++){ - const std::string &name = i->first; - const std::string &value = i->second; + StringMap fields = meta->getStrings(); + for (StringMap::const_iterator + it = fields.begin(); it != fields.end(); ++it) { + const std::string &name = it->first; + const std::string &value = it->second; lua_pushlstring(L, name.c_str(), name.size()); lua_pushlstring(L, value.c_str(), value.size()); lua_settable(L, -3); } } lua_setfield(L, -2, "fields"); + // inventory lua_newtable(L); Inventory *inv = meta->getInventory(); - if(inv){ - std::vector<const InventoryList*> lists = inv->getLists(); - for(std::vector<const InventoryList*>::const_iterator - i = lists.begin(); i != lists.end(); i++){ + if (inv) { + std::vector<const InventoryList *> lists = inv->getLists(); + for(std::vector<const InventoryList *>::const_iterator + i = lists.begin(); i != lists.end(); i++) { push_inventory_list(L, inv, (*i)->getName().c_str()); lua_setfield(L, -2, (*i)->getName().c_str()); } diff --git a/src/script/lua_api/l_noise.cpp b/src/script/lua_api/l_noise.cpp index 5a82b6485..c8dc2d2dc 100644 --- a/src/script/lua_api/l_noise.cpp +++ b/src/script/lua_api/l_noise.cpp @@ -23,12 +23,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_content.h" #include "log.h" -// garbage collector -int LuaPerlinNoise::gc_object(lua_State *L) +/////////////////////////////////////// +/* + LuaPerlinNoise +*/ + +LuaPerlinNoise::LuaPerlinNoise(NoiseParams *params) : + np(*params) +{ +} + + +LuaPerlinNoise::~LuaPerlinNoise() { - LuaPerlinNoise *o = *(LuaPerlinNoise **)(lua_touserdata(L, 1)); - delete o; - return 0; } @@ -36,7 +43,7 @@ int LuaPerlinNoise::l_get2d(lua_State *L) { NO_MAP_LOCK_REQUIRED; LuaPerlinNoise *o = checkobject(L, 1); - v2f p = read_v2f(L, 2); + v2f p = check_v2f(L, 2); lua_Number val = NoisePerlin2D(&o->np, p.X, p.Y, 0); lua_pushnumber(L, val); return 1; @@ -47,26 +54,13 @@ int LuaPerlinNoise::l_get3d(lua_State *L) { NO_MAP_LOCK_REQUIRED; LuaPerlinNoise *o = checkobject(L, 1); - v3f p = read_v3f(L, 2); + v3f p = check_v3f(L, 2); lua_Number val = NoisePerlin3D(&o->np, p.X, p.Y, p.Z, 0); lua_pushnumber(L, val); return 1; } -LuaPerlinNoise::LuaPerlinNoise(NoiseParams *params) : - np(*params) -{ -} - - -LuaPerlinNoise::~LuaPerlinNoise() -{ -} - - -// LuaPerlinNoise(seed, octaves, persistence, scale) -// Creates an LuaPerlinNoise and leaves it on top of stack int LuaPerlinNoise::create_object(lua_State *L) { NO_MAP_LOCK_REQUIRED; @@ -91,14 +85,22 @@ int LuaPerlinNoise::create_object(lua_State *L) } -LuaPerlinNoise* LuaPerlinNoise::checkobject(lua_State *L, int narg) +int LuaPerlinNoise::gc_object(lua_State *L) +{ + LuaPerlinNoise *o = *(LuaPerlinNoise **)(lua_touserdata(L, 1)); + delete o; + return 0; +} + + +LuaPerlinNoise *LuaPerlinNoise::checkobject(lua_State *L, int narg) { NO_MAP_LOCK_REQUIRED; luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); if (!ud) luaL_typerror(L, narg, className); - return *(LuaPerlinNoise**)ud; // unbox pointer + return *(LuaPerlinNoise **)ud; } @@ -111,7 +113,7 @@ void LuaPerlinNoise::Register(lua_State *L) lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from Lua getmetatable() + lua_settable(L, metatable); lua_pushliteral(L, "__index"); lua_pushvalue(L, methodtable); @@ -121,12 +123,11 @@ void LuaPerlinNoise::Register(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); + lua_pop(L, 1); - // Can be created from Lua (PerlinNoise(seed, octaves, persistence) lua_register(L, className, create_object); } @@ -138,16 +139,26 @@ const luaL_reg LuaPerlinNoise::methods[] = { {0,0} }; - +/////////////////////////////////////// /* - PerlinNoiseMap - */ + LuaPerlinNoiseMap +*/ -int LuaPerlinNoiseMap::gc_object(lua_State *L) +LuaPerlinNoiseMap::LuaPerlinNoiseMap(NoiseParams *params, int seed, v3s16 size) { - LuaPerlinNoiseMap *o = *(LuaPerlinNoiseMap **)(lua_touserdata(L, 1)); - delete o; - return 0; + m_is3d = size.Z > 1; + np = *params; + try { + noise = new Noise(&np, seed, size.X, size.Y, size.Z); + } catch (InvalidNoiseParamsException &e) { + throw LuaError(e.what()); + } +} + + +LuaPerlinNoiseMap::~LuaPerlinNoiseMap() +{ + delete noise; } @@ -157,15 +168,15 @@ int LuaPerlinNoiseMap::l_get2dMap(lua_State *L) size_t i = 0; LuaPerlinNoiseMap *o = checkobject(L, 1); - v2f p = read_v2f(L, 2); + v2f p = check_v2f(L, 2); Noise *n = o->noise; n->perlinMap2D(p.X, p.Y); lua_newtable(L); - for (int y = 0; y != n->sy; y++) { + for (u32 y = 0; y != n->sy; y++) { lua_newtable(L); - for (int x = 0; x != n->sx; x++) { + for (u32 x = 0; x != n->sx; x++) { lua_pushnumber(L, n->result[i++]); lua_rawseti(L, -2, x + 1); } @@ -180,14 +191,19 @@ int LuaPerlinNoiseMap::l_get2dMap_flat(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaPerlinNoiseMap *o = checkobject(L, 1); - v2f p = read_v2f(L, 2); + v2f p = check_v2f(L, 2); + bool use_buffer = lua_istable(L, 3); Noise *n = o->noise; n->perlinMap2D(p.X, p.Y); size_t maplen = n->sx * n->sy; - lua_newtable(L); + if (use_buffer) + lua_pushvalue(L, 3); + else + lua_newtable(L); + for (size_t i = 0; i != maplen; i++) { lua_pushnumber(L, n->result[i]); lua_rawseti(L, -2, i + 1); @@ -202,7 +218,7 @@ int LuaPerlinNoiseMap::l_get3dMap(lua_State *L) size_t i = 0; LuaPerlinNoiseMap *o = checkobject(L, 1); - v3f p = read_v3f(L, 2); + v3f p = check_v3f(L, 2); if (!o->m_is3d) return 0; @@ -211,11 +227,11 @@ int LuaPerlinNoiseMap::l_get3dMap(lua_State *L) n->perlinMap3D(p.X, p.Y, p.Z); lua_newtable(L); - for (int z = 0; z != n->sz; z++) { + for (u32 z = 0; z != n->sz; z++) { lua_newtable(L); - for (int y = 0; y != n->sy; y++) { + for (u32 y = 0; y != n->sy; y++) { lua_newtable(L); - for (int x = 0; x != n->sx; x++) { + for (u32 x = 0; x != n->sx; x++) { lua_pushnumber(L, n->result[i++]); lua_rawseti(L, -2, x + 1); } @@ -232,7 +248,8 @@ int LuaPerlinNoiseMap::l_get3dMap_flat(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaPerlinNoiseMap *o = checkobject(L, 1); - v3f p = read_v3f(L, 2); + v3f p = check_v3f(L, 2); + bool use_buffer = lua_istable(L, 3); if (!o->m_is3d) return 0; @@ -242,7 +259,11 @@ int LuaPerlinNoiseMap::l_get3dMap_flat(lua_State *L) size_t maplen = n->sx * n->sy * n->sz; - lua_newtable(L); + if (use_buffer) + lua_pushvalue(L, 3); + else + lua_newtable(L); + for (size_t i = 0; i != maplen; i++) { lua_pushnumber(L, n->result[i]); lua_rawseti(L, -2, i + 1); @@ -251,26 +272,61 @@ int LuaPerlinNoiseMap::l_get3dMap_flat(lua_State *L) } -LuaPerlinNoiseMap::LuaPerlinNoiseMap(NoiseParams *params, int seed, v3s16 size) +int LuaPerlinNoiseMap::l_calc2dMap(lua_State *L) { - m_is3d = size.Z > 1; - np = *params; - try { - noise = new Noise(&np, seed, size.X, size.Y, size.Z); - } catch (InvalidNoiseParamsException &e) { - throw LuaError(e.what()); - } + NO_MAP_LOCK_REQUIRED; + + LuaPerlinNoiseMap *o = checkobject(L, 1); + v2f p = check_v2f(L, 2); + + Noise *n = o->noise; + n->perlinMap2D(p.X, p.Y); + + return 0; } +int LuaPerlinNoiseMap::l_calc3dMap(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; -LuaPerlinNoiseMap::~LuaPerlinNoiseMap() + LuaPerlinNoiseMap *o = checkobject(L, 1); + v3f p = check_v3f(L, 2); + + if (!o->m_is3d) + return 0; + + Noise *n = o->noise; + n->perlinMap3D(p.X, p.Y, p.Z); + + return 0; +} + + +int LuaPerlinNoiseMap::l_getMapSlice(lua_State *L) { - delete noise; + NO_MAP_LOCK_REQUIRED; + + LuaPerlinNoiseMap *o = checkobject(L, 1); + v3s16 slice_offset = read_v3s16(L, 2); + v3s16 slice_size = read_v3s16(L, 3); + bool use_buffer = lua_istable(L, 4); + + Noise *n = o->noise; + + if (use_buffer) + lua_pushvalue(L, 3); + else + lua_newtable(L); + + write_array_slice_float(L, lua_gettop(L), n->result, + v3u16(n->sx, n->sy, n->sz), + v3u16(slice_offset.X, slice_offset.Y, slice_offset.Z), + v3u16(slice_size.X, slice_size.Y, slice_size.Z)); + + return 1; } -// LuaPerlinNoiseMap(np, size) -// Creates an LuaPerlinNoiseMap and leaves it on top of stack int LuaPerlinNoiseMap::create_object(lua_State *L) { NoiseParams np; @@ -286,6 +342,14 @@ int LuaPerlinNoiseMap::create_object(lua_State *L) } +int LuaPerlinNoiseMap::gc_object(lua_State *L) +{ + LuaPerlinNoiseMap *o = *(LuaPerlinNoiseMap **)(lua_touserdata(L, 1)); + delete o; + return 0; +} + + LuaPerlinNoiseMap *LuaPerlinNoiseMap::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); @@ -294,7 +358,7 @@ LuaPerlinNoiseMap *LuaPerlinNoiseMap::checkobject(lua_State *L, int narg) if (!ud) luaL_typerror(L, narg, className); - return *(LuaPerlinNoiseMap **)ud; // unbox pointer + return *(LuaPerlinNoiseMap **)ud; } @@ -307,7 +371,7 @@ void LuaPerlinNoiseMap::Register(lua_State *L) lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from Lua getmetatable() + lua_settable(L, metatable); lua_pushliteral(L, "__index"); lua_pushvalue(L, methodtable); @@ -317,12 +381,11 @@ void LuaPerlinNoiseMap::Register(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); + lua_pop(L, 1); - // Can be created from Lua (PerlinNoiseMap(np, size) lua_register(L, className, create_object); } @@ -331,37 +394,31 @@ const char LuaPerlinNoiseMap::className[] = "PerlinNoiseMap"; const luaL_reg LuaPerlinNoiseMap::methods[] = { luamethod(LuaPerlinNoiseMap, get2dMap), luamethod(LuaPerlinNoiseMap, get2dMap_flat), + luamethod(LuaPerlinNoiseMap, calc2dMap), luamethod(LuaPerlinNoiseMap, get3dMap), luamethod(LuaPerlinNoiseMap, get3dMap_flat), + luamethod(LuaPerlinNoiseMap, calc3dMap), + luamethod(LuaPerlinNoiseMap, getMapSlice), {0,0} }; +/////////////////////////////////////// /* LuaPseudoRandom */ -// garbage collector -int LuaPseudoRandom::gc_object(lua_State *L) -{ - LuaPseudoRandom *o = *(LuaPseudoRandom **)(lua_touserdata(L, 1)); - delete o; - return 0; -} - - -// next(self, min=0, max=32767) -> get next value int LuaPseudoRandom::l_next(lua_State *L) { NO_MAP_LOCK_REQUIRED; LuaPseudoRandom *o = checkobject(L, 1); int min = 0; int max = 32767; - lua_settop(L, 3); // Fill 2 and 3 with nil if they don't exist - if(!lua_isnil(L, 2)) + lua_settop(L, 3); + if (lua_isnumber(L, 2)) min = luaL_checkinteger(L, 2); - if(!lua_isnil(L, 3)) + if (lua_isnumber(L, 3)) max = luaL_checkinteger(L, 3); - if(max < min){ + if (max < min) { errorstream<<"PseudoRandom.next(): max="<<max<<" min="<<min<<std::endl; throw LuaError("PseudoRandom.next(): max < min"); } @@ -378,34 +435,107 @@ int LuaPseudoRandom::l_next(lua_State *L) } -LuaPseudoRandom::LuaPseudoRandom(int seed): - m_pseudo(seed) +int LuaPseudoRandom::create_object(lua_State *L) { + int seed = luaL_checknumber(L, 1); + LuaPseudoRandom *o = new LuaPseudoRandom(seed); + *(void **)(lua_newuserdata(L, sizeof(void *))) = o; + luaL_getmetatable(L, className); + lua_setmetatable(L, -2); + return 1; } -LuaPseudoRandom::~LuaPseudoRandom() +int LuaPseudoRandom::gc_object(lua_State *L) { + LuaPseudoRandom *o = *(LuaPseudoRandom **)(lua_touserdata(L, 1)); + delete o; + return 0; } -const PseudoRandom& LuaPseudoRandom::getItem() const +LuaPseudoRandom *LuaPseudoRandom::checkobject(lua_State *L, int narg) { - return m_pseudo; + luaL_checktype(L, narg, LUA_TUSERDATA); + void *ud = luaL_checkudata(L, narg, className); + if (!ud) + luaL_typerror(L, narg, className); + return *(LuaPseudoRandom **)ud; } -PseudoRandom& LuaPseudoRandom::getItem() + +void LuaPseudoRandom::Register(lua_State *L) { - return m_pseudo; + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, className); + int metatable = lua_gettop(L); + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); + + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, gc_object); + lua_settable(L, metatable); + + lua_pop(L, 1); + + luaL_openlib(L, 0, methods, 0); + lua_pop(L, 1); + + lua_register(L, className, create_object); } -// LuaPseudoRandom(seed) -// Creates an LuaPseudoRandom and leaves it on top of stack -int LuaPseudoRandom::create_object(lua_State *L) +const char LuaPseudoRandom::className[] = "PseudoRandom"; +const luaL_reg LuaPseudoRandom::methods[] = { + luamethod(LuaPseudoRandom, next), + {0,0} +}; + +/////////////////////////////////////// +/* + LuaPcgRandom +*/ + +int LuaPcgRandom::l_next(lua_State *L) { - int seed = luaL_checknumber(L, 1); - LuaPseudoRandom *o = new LuaPseudoRandom(seed); + NO_MAP_LOCK_REQUIRED; + + LuaPcgRandom *o = checkobject(L, 1); + u32 min = lua_isnumber(L, 2) ? lua_tointeger(L, 2) : o->m_rnd.RANDOM_MIN; + u32 max = lua_isnumber(L, 3) ? lua_tointeger(L, 3) : o->m_rnd.RANDOM_MAX; + + lua_pushinteger(L, o->m_rnd.range(min, max)); + return 1; +} + + +int LuaPcgRandom::l_rand_normal_dist(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + + LuaPcgRandom *o = checkobject(L, 1); + u32 min = lua_isnumber(L, 2) ? lua_tointeger(L, 2) : o->m_rnd.RANDOM_MIN; + u32 max = lua_isnumber(L, 3) ? lua_tointeger(L, 3) : o->m_rnd.RANDOM_MAX; + int num_trials = lua_isnumber(L, 4) ? lua_tointeger(L, 4) : 6; + + lua_pushinteger(L, o->m_rnd.randNormalDist(min, max, num_trials)); + return 1; +} + + +int LuaPcgRandom::create_object(lua_State *L) +{ + lua_Integer seed = luaL_checknumber(L, 1); + LuaPcgRandom *o = lua_isnumber(L, 2) ? + new LuaPcgRandom(seed, lua_tointeger(L, 2)) : + new LuaPcgRandom(seed); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); lua_setmetatable(L, -2); @@ -413,17 +543,25 @@ int LuaPseudoRandom::create_object(lua_State *L) } -LuaPseudoRandom* LuaPseudoRandom::checkobject(lua_State *L, int narg) +int LuaPcgRandom::gc_object(lua_State *L) +{ + LuaPcgRandom *o = *(LuaPcgRandom **)(lua_touserdata(L, 1)); + delete o; + return 0; +} + + +LuaPcgRandom *LuaPcgRandom::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); if (!ud) luaL_typerror(L, narg, className); - return *(LuaPseudoRandom**)ud; // unbox pointer + return *(LuaPcgRandom **)ud; } -void LuaPseudoRandom::Register(lua_State *L) +void LuaPcgRandom::Register(lua_State *L) { lua_newtable(L); int methodtable = lua_gettop(L); @@ -432,7 +570,7 @@ void LuaPseudoRandom::Register(lua_State *L) lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methodtable); - lua_settable(L, metatable); // hide metatable from Lua getmetatable() + lua_settable(L, metatable); lua_pushliteral(L, "__index"); lua_pushvalue(L, methodtable); @@ -442,18 +580,18 @@ void LuaPseudoRandom::Register(lua_State *L) lua_pushcfunction(L, gc_object); lua_settable(L, metatable); - lua_pop(L, 1); // drop metatable + lua_pop(L, 1); - luaL_openlib(L, 0, methods, 0); // fill methodtable - lua_pop(L, 1); // drop methodtable + luaL_openlib(L, 0, methods, 0); + lua_pop(L, 1); - // Can be created from Lua (LuaPseudoRandom(seed)) lua_register(L, className, create_object); } -const char LuaPseudoRandom::className[] = "PseudoRandom"; -const luaL_reg LuaPseudoRandom::methods[] = { - luamethod(LuaPseudoRandom, next), +const char LuaPcgRandom::className[] = "PcgRandom"; +const luaL_reg LuaPcgRandom::methods[] = { + luamethod(LuaPcgRandom, next), + luamethod(LuaPcgRandom, rand_normal_dist), {0,0} }; diff --git a/src/script/lua_api/l_noise.h b/src/script/lua_api/l_noise.h index 3e22ac7a0..e958c5a23 100644 --- a/src/script/lua_api/l_noise.h +++ b/src/script/lua_api/l_noise.h @@ -64,6 +64,9 @@ class LuaPerlinNoiseMap : public ModApiBase { static const char className[]; static const luaL_reg methods[]; + // Exported functions + + // garbage collector static int gc_object(lua_State *L); static int l_get2dMap(lua_State *L); @@ -71,6 +74,10 @@ class LuaPerlinNoiseMap : public ModApiBase { static int l_get3dMap(lua_State *L); static int l_get3dMap_flat(lua_State *L); + static int l_calc2dMap(lua_State *L); + static int l_calc3dMap(lua_State *L); + static int l_getMapSlice(lua_State *L); + public: LuaPerlinNoiseMap(NoiseParams *np, int seed, v3s16 size); @@ -104,18 +111,51 @@ private: static int l_next(lua_State *L); public: - LuaPseudoRandom(int seed); - - ~LuaPseudoRandom(); - - const PseudoRandom& getItem() const; - PseudoRandom& getItem(); + LuaPseudoRandom(int seed) : + m_pseudo(seed) {} // LuaPseudoRandom(seed) // Creates an LuaPseudoRandom and leaves it on top of stack static int create_object(lua_State *L); - static LuaPseudoRandom* checkobject(lua_State *L, int narg); + static LuaPseudoRandom *checkobject(lua_State *L, int narg); + + static void Register(lua_State *L); +}; + +/* + LuaPcgRandom +*/ +class LuaPcgRandom : public ModApiBase { +private: + PcgRandom m_rnd; + + static const char className[]; + static const luaL_reg methods[]; + + // Exported functions + + // garbage collector + static int gc_object(lua_State *L); + + // next(self, min=-2147483648, max=2147483647) -> get next value + static int l_next(lua_State *L); + + // rand_normal_dist(self, min=-2147483648, max=2147483647, num_trials=6) -> + // get next normally distributed random value + static int l_rand_normal_dist(lua_State *L); + +public: + LuaPcgRandom(u64 seed) : + m_rnd(seed) {} + LuaPcgRandom(u64 seed, u64 seq) : + m_rnd(seed, seq) {} + + // LuaPcgRandom(seed) + // Creates an LuaPcgRandom and leaves it on top of stack + static int create_object(lua_State *L); + + static LuaPcgRandom *checkobject(lua_State *L, int narg); static void Register(lua_State *L); }; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 4286840fe..3ac8eeefb 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -26,11 +26,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "tool.h" #include "serverobject.h" -#include "content_object.h" #include "content_sao.h" #include "server.h" #include "hud.h" +#include "scripting_game.h" +#define GET_ENV_PTR ServerEnvironment* env = \ + dynamic_cast<ServerEnvironment*>(getEnv(L)); \ + if (env == NULL) return 0 struct EnumString es_HudElementType[] = { @@ -65,6 +68,7 @@ struct EnumString es_HudBuiltinElement[] = {HUD_FLAG_CROSSHAIR_VISIBLE, "crosshair"}, {HUD_FLAG_WIELDITEM_VISIBLE, "wielditem"}, {HUD_FLAG_BREATHBAR_VISIBLE, "breathbar"}, + {HUD_FLAG_MINIMAP_VISIBLE, "minimap"}, {0, NULL}, }; @@ -77,7 +81,7 @@ ObjectRef* ObjectRef::checkobject(lua_State *L, int narg) { luaL_checktype(L, narg, LUA_TUSERDATA); void *ud = luaL_checkudata(L, narg, className); - if(!ud) luaL_typerror(L, narg, className); + if (!ud) luaL_typerror(L, narg, className); return *(ObjectRef**)ud; // unbox pointer } @@ -90,9 +94,9 @@ ServerActiveObject* ObjectRef::getobject(ObjectRef *ref) LuaEntitySAO* ObjectRef::getluaobject(ObjectRef *ref) { ServerActiveObject *obj = getobject(ref); - if(obj == NULL) + if (obj == NULL) return NULL; - if(obj->getType() != ACTIVEOBJECT_TYPE_LUAENTITY) + if (obj->getType() != ACTIVEOBJECT_TYPE_LUAENTITY) return NULL; return (LuaEntitySAO*)obj; } @@ -100,9 +104,9 @@ LuaEntitySAO* ObjectRef::getluaobject(ObjectRef *ref) PlayerSAO* ObjectRef::getplayersao(ObjectRef *ref) { ServerActiveObject *obj = getobject(ref); - if(obj == NULL) + if (obj == NULL) return NULL; - if(obj->getType() != ACTIVEOBJECT_TYPE_PLAYER) + if (obj->getType() != ACTIVEOBJECT_TYPE_PLAYER) return NULL; return (PlayerSAO*)obj; } @@ -110,7 +114,7 @@ PlayerSAO* ObjectRef::getplayersao(ObjectRef *ref) Player* ObjectRef::getplayer(ObjectRef *ref) { PlayerSAO *playersao = getplayersao(ref); - if(playersao == NULL) + if (playersao == NULL) return NULL; return playersao->getPlayer(); } @@ -129,9 +133,22 @@ int ObjectRef::gc_object(lua_State *L) { int ObjectRef::l_remove(lua_State *L) { NO_MAP_LOCK_REQUIRED; + GET_ENV_PTR; + ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) + return 0; + if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER) + return 0; + + std::set<int> child_ids = co->getAttachmentChildIds(); + std::set<int>::iterator it; + for (it = child_ids.begin(); it != child_ids.end(); ++it) { + ServerActiveObject *child = env->getActiveObject(*it); + child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0)); + } + verbosestream<<"ObjectRef::l_remove(): id="<<co->getId()<<std::endl; co->m_removed = true; return 0; @@ -144,7 +161,7 @@ int ObjectRef::l_getpos(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; v3f pos = co->getBasePosition() / BS; lua_newtable(L); lua_pushnumber(L, pos.X); @@ -163,7 +180,7 @@ int ObjectRef::l_setpos(lua_State *L) ObjectRef *ref = checkobject(L, 1); //LuaEntitySAO *co = getluaobject(ref); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // pos v3f pos = checkFloatPos(L, 2); // Do it @@ -178,7 +195,7 @@ int ObjectRef::l_moveto(lua_State *L) ObjectRef *ref = checkobject(L, 1); //LuaEntitySAO *co = getluaobject(ref); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // pos v3f pos = checkFloatPos(L, 2); // continuous @@ -196,20 +213,36 @@ 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 == NULL) return 0; + if (puncher == NULL) return 0; v3f dir; - if(lua_type(L, 5) != LUA_TTABLE) + if (lua_type(L, 5) != LUA_TTABLE) dir = co->getBasePosition() - puncher->getBasePosition(); else dir = read_v3f(L, 5); float time_from_last_punch = 1000000; - if(lua_isnumber(L, 3)) + if (lua_isnumber(L, 3)) time_from_last_punch = lua_tonumber(L, 3); ToolCapabilities toolcap = read_tool_capabilities(L, 4); dir.normalize(); + + s16 src_original_hp = co->getHP(); + s16 dst_origin_hp = puncher->getHP(); + // Do it co->punch(dir, &toolcap, puncher, time_from_last_punch); + + // 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); + } + + // If the puncher is a player, and its HP changed + if (dst_origin_hp != puncher->getHP() && + puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + getServer(L)->SendPlayerHPOrDie((PlayerSAO *)puncher); + } return 0; } @@ -221,8 +254,8 @@ int ObjectRef::l_right_click(lua_State *L) ObjectRef *ref2 = checkobject(L, 2); ServerActiveObject *co = getobject(ref); ServerActiveObject *co2 = getobject(ref2); - if(co == NULL) return 0; - if(co2 == NULL) return 0; + if (co == NULL) return 0; + if (co2 == NULL) return 0; // Do it co->rightClick(co2); return 0; @@ -237,12 +270,15 @@ int ObjectRef::l_set_hp(lua_State *L) ObjectRef *ref = checkobject(L, 1); luaL_checknumber(L, 2); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; int hp = lua_tonumber(L, 2); /*infostream<<"ObjectRef::l_set_hp(): id="<<co->getId() <<" hp="<<hp<<std::endl;*/ // Do it co->setHP(hp); + if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER) + getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co); + // Return return 0; } @@ -255,7 +291,7 @@ int ObjectRef::l_get_hp(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if(co == NULL){ + if (co == NULL) { // Default hp is 1 lua_pushnumber(L, 1); return 1; @@ -274,10 +310,10 @@ int ObjectRef::l_get_inventory(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it InventoryLocation loc = co->getInventoryLocation(); - if(getServer(L)->getInventory(loc) != NULL) + if (getServer(L)->getInventory(loc) != NULL) InvRef::create(L, loc); else lua_pushnil(L); // An object may have no inventory (nil) @@ -290,7 +326,7 @@ 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; + if (co == NULL) return 0; // Do it lua_pushstring(L, co->getWieldList().c_str()); return 1; @@ -302,7 +338,7 @@ 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; + if (co == NULL) return 0; // Do it lua_pushinteger(L, co->getWieldIndex() + 1); return 1; @@ -314,7 +350,7 @@ 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 == NULL) { // Empty ItemStack LuaItemStack::create(L, ItemStack()); return 1; @@ -330,10 +366,13 @@ int ObjectRef::l_set_wielded_item(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it ItemStack item = read_item(L, 2, getServer(L)); bool success = co->setWieldedItem(item); + if (success && co->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + getServer(L)->SendInventory(((PlayerSAO*)co)); + } lua_pushboolean(L, success); return 1; } @@ -344,7 +383,7 @@ int ObjectRef::l_set_armor_groups(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it ItemGroupList groups; read_groups(L, 2, groups); @@ -352,13 +391,27 @@ int ObjectRef::l_set_armor_groups(lua_State *L) return 0; } +// get_armor_groups(self) +int ObjectRef::l_get_armor_groups(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + ServerActiveObject *co = getobject(ref); + if (co == NULL) + return 0; + // Do it + ItemGroupList groups = co->getArmorGroups(); + push_groups(L, groups); + return 1; +} + // set_physics_override(self, physics_override_speed, physics_override_jump, // physics_override_gravity, sneak, sneak_glitch) int ObjectRef::l_set_physics_override(lua_State *L) { ObjectRef *ref = checkobject(L, 1); PlayerSAO *co = (PlayerSAO *) getobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it if (lua_istable(L, 2)) { co->m_physics_override_speed = getfloatfield_default(L, 2, "speed", co->m_physics_override_speed); @@ -369,15 +422,15 @@ int ObjectRef::l_set_physics_override(lua_State *L) co->m_physics_override_sent = false; } else { // old, non-table format - if(!lua_isnil(L, 2)){ + if (!lua_isnil(L, 2)) { co->m_physics_override_speed = lua_tonumber(L, 2); co->m_physics_override_sent = false; } - if(!lua_isnil(L, 3)){ + if (!lua_isnil(L, 3)) { co->m_physics_override_jump = lua_tonumber(L, 3); co->m_physics_override_sent = false; } - if(!lua_isnil(L, 4)){ + if (!lua_isnil(L, 4)) { co->m_physics_override_gravity = lua_tonumber(L, 4); co->m_physics_override_sent = false; } @@ -385,27 +438,74 @@ int ObjectRef::l_set_physics_override(lua_State *L) return 0; } -// set_animation(self, frame_range, frame_speed, frame_blend) +// get_physics_override(self) +int ObjectRef::l_get_physics_override(lua_State *L) +{ + ObjectRef *ref = checkobject(L, 1); + PlayerSAO *co = (PlayerSAO *)getobject(ref); + if (co == NULL) + return 0; + // Do it + lua_newtable(L); + lua_pushnumber(L, co->m_physics_override_speed); + lua_setfield(L, -2, "speed"); + lua_pushnumber(L, co->m_physics_override_jump); + lua_setfield(L, -2, "jump"); + lua_pushnumber(L, co->m_physics_override_gravity); + lua_setfield(L, -2, "gravity"); + lua_pushboolean(L, co->m_physics_override_sneak); + lua_setfield(L, -2, "sneak"); + lua_pushboolean(L, co->m_physics_override_sneak_glitch); + lua_setfield(L, -2, "sneak_glitch"); + return 1; +} + +// set_animation(self, frame_range, frame_speed, frame_blend, frame_loop) int ObjectRef::l_set_animation(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it v2f frames = v2f(1, 1); - if(!lua_isnil(L, 2)) + if (!lua_isnil(L, 2)) frames = read_v2f(L, 2); float frame_speed = 15; - if(!lua_isnil(L, 3)) + if (!lua_isnil(L, 3)) frame_speed = lua_tonumber(L, 3); float frame_blend = 0; - if(!lua_isnil(L, 4)) + if (!lua_isnil(L, 4)) frame_blend = lua_tonumber(L, 4); - co->setAnimation(frames, frame_speed, frame_blend); + bool frame_loop = true; + if (lua_isboolean(L, 5)) + frame_loop = lua_toboolean(L, 5); + co->setAnimation(frames, frame_speed, frame_blend, frame_loop); return 0; } +// get_animation(self) +int ObjectRef::l_get_animation(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + ServerActiveObject *co = getobject(ref); + if (co == NULL) + return 0; + // Do it + v2f frames = v2f(1,1); + float frame_speed = 15; + float frame_blend = 0; + bool frame_loop = true; + co->getAnimation(&frames, &frame_speed, &frame_blend, &frame_loop); + + push_v2f(L, frames); + lua_pushnumber(L, frame_speed); + lua_pushnumber(L, frame_blend); + lua_pushboolean(L, frame_loop); + return 4; +} + // set_local_animation(self, {stand/idle}, {walk}, {dig}, {walk+dig}, frame_speed) int ObjectRef::l_set_local_animation(lua_State *L) { @@ -417,11 +517,11 @@ int ObjectRef::l_set_local_animation(lua_State *L) // Do it v2s32 frames[4]; for (int i=0;i<4;i++) { - if(!lua_isnil(L, 2+1)) + if (!lua_isnil(L, 2+1)) frames[i] = read_v2s32(L, 2+i); } float frame_speed = 30; - if(!lua_isnil(L, 6)) + if (!lua_isnil(L, 6)) frame_speed = lua_tonumber(L, 6); if (!getServer(L)->setLocalPlayerAnimations(player, frames, frame_speed)) @@ -431,6 +531,27 @@ int ObjectRef::l_set_local_animation(lua_State *L) return 0; } +// get_local_animation(self) +int ObjectRef::l_get_local_animation(lua_State *L) +{ + //NO_MAP_LOCK_REQUIRED + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + if (player == NULL) + return 0; + + v2s32 frames[4]; + float frame_speed; + player->getLocalAnimations(frames, &frame_speed); + + for (int i = 0; i < 4; i++) { + push_v2s32(L, frames[i]); + } + + lua_pushnumber(L, frame_speed); + return 5; +} + // set_eye_offset(self, v3f first pv, v3f third pv) int ObjectRef::l_set_eye_offset(lua_State *L) { @@ -443,9 +564,9 @@ int ObjectRef::l_set_eye_offset(lua_State *L) v3f offset_first = v3f(0, 0, 0); v3f offset_third = v3f(0, 0, 0); - if(!lua_isnil(L, 2)) + if (!lua_isnil(L, 2)) offset_first = read_v3f(L, 2); - if(!lua_isnil(L, 3)) + if (!lua_isnil(L, 3)) offset_third = read_v3f(L, 3); // Prevent abuse of offset values (keep player always visible) @@ -461,60 +582,154 @@ int ObjectRef::l_set_eye_offset(lua_State *L) return 0; } +// get_eye_offset(self) +int ObjectRef::l_get_eye_offset(lua_State *L) +{ + //NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + if (player == NULL) + return 0; + // Do it + push_v3f(L, player->eye_offset_first); + push_v3f(L, player->eye_offset_third); + return 2; +} + // set_bone_position(self, std::string bone, v3f position, v3f rotation) int ObjectRef::l_set_bone_position(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it std::string bone = ""; - if(!lua_isnil(L, 2)) + if (!lua_isnil(L, 2)) bone = lua_tostring(L, 2); v3f position = v3f(0, 0, 0); - if(!lua_isnil(L, 3)) + if (!lua_isnil(L, 3)) position = read_v3f(L, 3); v3f rotation = v3f(0, 0, 0); - if(!lua_isnil(L, 4)) + if (!lua_isnil(L, 4)) rotation = read_v3f(L, 4); co->setBonePosition(bone, position, rotation); return 0; } +// get_bone_position(self, bone) +int ObjectRef::l_get_bone_position(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + ServerActiveObject *co = getobject(ref); + if (co == NULL) + return 0; + // Do it + std::string bone = ""; + if (!lua_isnil(L, 2)) + bone = lua_tostring(L, 2); + + v3f position = v3f(0, 0, 0); + v3f rotation = v3f(0, 0, 0); + co->getBonePosition(bone, &position, &rotation); + + push_v3f(L, position); + push_v3f(L, rotation); + return 2; +} + // set_attach(self, parent, bone, position, rotation) int ObjectRef::l_set_attach(lua_State *L) { NO_MAP_LOCK_REQUIRED; + GET_ENV_PTR; + ObjectRef *ref = checkobject(L, 1); ObjectRef *parent_ref = checkobject(L, 2); ServerActiveObject *co = getobject(ref); ServerActiveObject *parent = getobject(parent_ref); - if(co == NULL) return 0; - if(parent == NULL) return 0; + if (co == NULL) + return 0; + if (parent == NULL) + return 0; // Do it + int parent_id = 0; std::string bone = ""; - if(!lua_isnil(L, 3)) - bone = lua_tostring(L, 3); v3f position = v3f(0, 0, 0); - if(!lua_isnil(L, 4)) - position = read_v3f(L, 4); v3f rotation = v3f(0, 0, 0); - if(!lua_isnil(L, 5)) + co->getAttachment(&parent_id, &bone, &position, &rotation); + if (parent_id) { + ServerActiveObject *old_parent = env->getActiveObject(parent_id); + old_parent->removeAttachmentChild(co->getId()); + } + + bone = ""; + if (!lua_isnil(L, 3)) + bone = lua_tostring(L, 3); + position = v3f(0, 0, 0); + if (!lua_isnil(L, 4)) + position = read_v3f(L, 4); + rotation = v3f(0, 0, 0); + if (!lua_isnil(L, 5)) rotation = read_v3f(L, 5); co->setAttachment(parent->getId(), bone, position, rotation); + parent->addAttachmentChild(co->getId()); return 0; } +// get_attach(self) +int ObjectRef::l_get_attach(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + GET_ENV_PTR; + + ObjectRef *ref = checkobject(L, 1); + ServerActiveObject *co = getobject(ref); + if (co == NULL) + return 0; + + // Do it + int parent_id = 0; + std::string bone = ""; + v3f position = v3f(0, 0, 0); + v3f rotation = v3f(0, 0, 0); + co->getAttachment(&parent_id, &bone, &position, &rotation); + if (!parent_id) + return 0; + ServerActiveObject *parent = env->getActiveObject(parent_id); + + getScriptApiBase(L)->objectrefGetOrCreate(L, parent); + lua_pushlstring(L, bone.c_str(), bone.size()); + push_v3f(L, position); + push_v3f(L, rotation); + return 4; +} + // set_detach(self) int ObjectRef::l_set_detach(lua_State *L) { NO_MAP_LOCK_REQUIRED; + GET_ENV_PTR; + ObjectRef *ref = checkobject(L, 1); ServerActiveObject *co = getobject(ref); - if(co == NULL) return 0; + if (co == NULL) + return 0; + + int parent_id = 0; + std::string bone = ""; + v3f position; + v3f rotation; + co->getAttachment(&parent_id, &bone, &position, &rotation); + ServerActiveObject *parent = NULL; + if (parent_id) + parent = env->getActiveObject(parent_id); + // Do it co->setAttachment(0, "", v3f(0,0,0), v3f(0,0,0)); + if (parent != NULL) + parent->removeAttachmentChild(co->getId()); return 0; } @@ -524,15 +739,40 @@ 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 == NULL) return 0; ObjectProperties *prop = co->accessObjectProperties(); - if(!prop) + if (!prop) return 0; read_object_properties(L, 2, prop); co->notifyObjectPropertiesModified(); return 0; } +// get_properties(self) +int ObjectRef::l_get_properties(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + ServerActiveObject *co = getobject(ref); + if (co == NULL) + return 0; + ObjectProperties *prop = co->accessObjectProperties(); + if (!prop) + return 0; + push_object_properties(L, prop); + return 1; +} + +// is_player(self) +int ObjectRef::l_is_player(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + lua_pushboolean(L, (player != NULL)); + return 1; +} + /* LuaEntitySAO-only */ // setvelocity(self, {x=num, y=num, z=num}) @@ -541,7 +781,7 @@ int ObjectRef::l_setvelocity(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; v3f pos = checkFloatPos(L, 2); // Do it co->setVelocity(pos); @@ -554,7 +794,7 @@ int ObjectRef::l_getvelocity(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it v3f v = co->getVelocity(); pushFloatPos(L, v); @@ -567,7 +807,7 @@ int ObjectRef::l_setacceleration(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // pos v3f pos = checkFloatPos(L, 2); // Do it @@ -581,7 +821,7 @@ int ObjectRef::l_getacceleration(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it v3f v = co->getAcceleration(); pushFloatPos(L, v); @@ -594,7 +834,7 @@ int ObjectRef::l_setyaw(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; float yaw = luaL_checknumber(L, 2) * core::RADTODEG; // Do it co->setYaw(yaw); @@ -607,7 +847,7 @@ int ObjectRef::l_getyaw(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it float yaw = co->getYaw() * core::DEGTORAD; lua_pushnumber(L, yaw); @@ -620,7 +860,7 @@ int ObjectRef::l_settexturemod(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it std::string mod = luaL_checkstring(L, 2); co->setTextureMod(mod); @@ -634,19 +874,19 @@ int ObjectRef::l_setsprite(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it v2s16 p(0,0); - if(!lua_isnil(L, 2)) + if (!lua_isnil(L, 2)) p = read_v2s16(L, 2); int num_frames = 1; - if(!lua_isnil(L, 3)) + if (!lua_isnil(L, 3)) num_frames = lua_tonumber(L, 3); float framelength = 0.2; - if(!lua_isnil(L, 4)) + if (!lua_isnil(L, 4)) framelength = lua_tonumber(L, 4); bool select_horiz_by_yawpitch = false; - if(!lua_isnil(L, 5)) + if (!lua_isnil(L, 5)) select_horiz_by_yawpitch = lua_toboolean(L, 5); co->setSprite(p, num_frames, framelength, select_horiz_by_yawpitch); return 0; @@ -660,7 +900,7 @@ int ObjectRef::l_get_entity_name(lua_State *L) ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); log_deprecated(L,"Deprecated call to \"get_entity_name"); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it std::string name = co->getName(); lua_pushstring(L, name.c_str()); @@ -673,7 +913,7 @@ int ObjectRef::l_get_luaentity(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); LuaEntitySAO *co = getluaobject(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it luaentity_get(L, co->getId()); return 1; @@ -681,16 +921,6 @@ int ObjectRef::l_get_luaentity(lua_State *L) /* Player-only */ -// is_player(self) -int ObjectRef::l_is_player(lua_State *L) -{ - NO_MAP_LOCK_REQUIRED; - ObjectRef *ref = checkobject(L, 1); - Player *player = getplayer(ref); - lua_pushboolean(L, (player != NULL)); - return 1; -} - // is_player_connected(self) int ObjectRef::l_is_player_connected(lua_State *L) { @@ -707,7 +937,7 @@ int ObjectRef::l_get_player_name(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); Player *player = getplayer(ref); - if(player == NULL){ + if (player == NULL) { lua_pushlstring(L, "", 0); return 1; } @@ -716,13 +946,28 @@ int ObjectRef::l_get_player_name(lua_State *L) return 1; } +// get_player_velocity(self) +int ObjectRef::l_get_player_velocity(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + if (player == NULL) { + lua_pushnil(L); + return 1; + } + // Do it + push_v3f(L, player->getSpeed() / BS); + return 1; +} + // get_look_dir(self) int ObjectRef::l_get_look_dir(lua_State *L) { NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); Player *player = getplayer(ref); - if(player == NULL) return 0; + if (player == NULL) return 0; // Do it float pitch = player->getRadPitch(); float yaw = player->getRadYaw(); @@ -737,7 +982,7 @@ int ObjectRef::l_get_look_pitch(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); Player *player = getplayer(ref); - if(player == NULL) return 0; + if (player == NULL) return 0; // Do it lua_pushnumber(L, player->getRadPitch()); return 1; @@ -749,7 +994,7 @@ int ObjectRef::l_get_look_yaw(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); Player *player = getplayer(ref); - if(player == NULL) return 0; + if (player == NULL) return 0; // Do it lua_pushnumber(L, player->getRadYaw()); return 1; @@ -761,7 +1006,7 @@ int ObjectRef::l_set_look_pitch(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); PlayerSAO* co = getplayersao(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; float pitch = luaL_checknumber(L, 2) * core::RADTODEG; // Do it co->setPitch(pitch); @@ -774,7 +1019,7 @@ int ObjectRef::l_set_look_yaw(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); PlayerSAO* co = getplayersao(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; float yaw = luaL_checknumber(L, 2) * core::RADTODEG; // Do it co->setYaw(yaw); @@ -787,11 +1032,15 @@ int ObjectRef::l_set_breath(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); PlayerSAO* co = getplayersao(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; u16 breath = luaL_checknumber(L, 2); // Do it co->setBreath(breath); - co->m_breath_not_sent = true; + + // If the object is a player sent the breath to client + if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER) + getServer(L)->SendPlayerBreath(((PlayerSAO*)co)->getPeerID()); + return 0; } @@ -801,7 +1050,7 @@ int ObjectRef::l_get_breath(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); PlayerSAO* co = getplayersao(ref); - if(co == NULL) return 0; + if (co == NULL) return 0; // Do it u16 breath = co->getBreath(); lua_pushinteger (L, breath); @@ -814,7 +1063,7 @@ int ObjectRef::l_set_inventory_formspec(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); Player *player = getplayer(ref); - if(player == NULL) return 0; + if (player == NULL) return 0; std::string formspec = luaL_checkstring(L, 2); player->inventory_formspec = formspec; @@ -829,7 +1078,7 @@ int ObjectRef::l_get_inventory_formspec(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); Player *player = getplayer(ref); - if(player == NULL) return 0; + if (player == NULL) return 0; std::string formspec = player->inventory_formspec; lua_pushlstring(L, formspec.c_str(), formspec.size()); @@ -842,7 +1091,7 @@ int ObjectRef::l_get_player_control(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); Player *player = getplayer(ref); - if(player == NULL){ + if (player == NULL) { lua_pushlstring(L, "", 0); return 1; } @@ -876,7 +1125,7 @@ int ObjectRef::l_get_player_control_bits(lua_State *L) NO_MAP_LOCK_REQUIRED; ObjectRef *ref = checkobject(L, 1); Player *player = getplayer(ref); - if(player == NULL){ + if (player == NULL) { lua_pushlstring(L, "", 0); return 1; } @@ -1136,6 +1385,8 @@ int ObjectRef::l_hud_get_flags(lua_State *L) lua_setfield(L, -2, "wielditem"); lua_pushboolean(L, player->hud_flags & HUD_FLAG_BREATHBAR_VISIBLE); lua_setfield(L, -2, "breathbar"); + lua_pushboolean(L, player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE); + lua_setfield(L, -2, "minimap"); return 1; } @@ -1157,6 +1408,20 @@ int ObjectRef::l_hud_set_hotbar_itemcount(lua_State *L) return 1; } +// hud_get_hotbar_itemcount(self) +int ObjectRef::l_hud_get_hotbar_itemcount(lua_State *L) +{ + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + if (player == NULL) + return 0; + + s32 hotbar_itemcount = getServer(L)->hudGetHotbarItemcount(player); + + lua_pushnumber(L, hotbar_itemcount); + return 1; +} + // hud_set_hotbar_image(self, name) int ObjectRef::l_hud_set_hotbar_image(lua_State *L) { @@ -1171,6 +1436,19 @@ int ObjectRef::l_hud_set_hotbar_image(lua_State *L) return 1; } +// hud_get_hotbar_image(self) +int ObjectRef::l_hud_get_hotbar_image(lua_State *L) +{ + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + if (player == NULL) + return 0; + + std::string name = getServer(L)->hudGetHotbarImage(player); + lua_pushlstring(L, name.c_str(), name.size()); + return 1; +} + // hud_set_hotbar_selected_image(self, name) int ObjectRef::l_hud_set_hotbar_selected_image(lua_State *L) { @@ -1185,6 +1463,19 @@ int ObjectRef::l_hud_set_hotbar_selected_image(lua_State *L) return 1; } +// hud_get_hotbar_selected_image(self) +int ObjectRef::l_hud_get_hotbar_selected_image(lua_State *L) +{ + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + if (player == NULL) + return 0; + + std::string name = getServer(L)->hudGetHotbarSelectedImage(player); + lua_pushlstring(L, name.c_str(), name.size()); + return 1; +} + // set_sky(self, bgcolor, type, list) int ObjectRef::l_set_sky(lua_State *L) { @@ -1194,8 +1485,7 @@ int ObjectRef::l_set_sky(lua_State *L) return 0; video::SColor bgcolor(255,255,255,255); - if (!lua_isnil(L, 2)) - bgcolor = readARGB8(L, 2); + read_color(L, 2, &bgcolor); std::string type = luaL_checkstring(L, 3); @@ -1224,6 +1514,33 @@ int ObjectRef::l_set_sky(lua_State *L) return 1; } +// get_sky(self) +int ObjectRef::l_get_sky(lua_State *L) +{ + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + if (player == NULL) + return 0; + video::SColor bgcolor(255, 255, 255, 255); + std::string type; + std::vector<std::string> params; + + player->getSky(&bgcolor, &type, ¶ms); + type = type == "" ? "regular" : type; + + push_ARGB8(L, bgcolor); + lua_pushlstring(L, type.c_str(), type.size()); + lua_newtable(L); + s16 i = 1; + for (std::vector<std::string>::iterator it = params.begin(); + it != params.end(); ++it) { + lua_pushlstring(L, it->c_str(), it->size()); + lua_rawseti(L, -2, i); + i++; + } + return 3; +} + // override_day_night_ratio(self, brightness=0...1) int ObjectRef::l_override_day_night_ratio(lua_State *L) { @@ -1234,7 +1551,7 @@ int ObjectRef::l_override_day_night_ratio(lua_State *L) bool do_override = false; float ratio = 0.0f; - if (!lua_isnil(L, 2)){ + if (!lua_isnil(L, 2)) { do_override = true; ratio = luaL_checknumber(L, 2); } @@ -1246,6 +1563,65 @@ int ObjectRef::l_override_day_night_ratio(lua_State *L) return 1; } +// get_day_night_ratio(self) +int ObjectRef::l_get_day_night_ratio(lua_State *L) +{ + ObjectRef *ref = checkobject(L, 1); + Player *player = getplayer(ref); + if (player == NULL) + return 0; + + bool do_override; + float ratio; + player->getDayNightRatio(&do_override, &ratio); + + if (do_override) + lua_pushnumber(L, ratio); + else + lua_pushnil(L); + + return 1; +} + +// set_nametag_attributes(self, attributes) +int ObjectRef::l_set_nametag_attributes(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + PlayerSAO *playersao = getplayersao(ref); + if (playersao == NULL) + return 0; + + lua_getfield(L, 2, "color"); + if (!lua_isnil(L, -1)) { + video::SColor color = playersao->getNametagColor(); + if (!read_color(L, -1, &color)) + return 0; + playersao->setNametagColor(color); + } + + lua_pushboolean(L, true); + return 1; +} + +// get_nametag_attributes(self) +int ObjectRef::l_get_nametag_attributes(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkobject(L, 1); + PlayerSAO *playersao = getplayersao(ref); + if (playersao == NULL) + return 0; + + video::SColor color = playersao->getNametagColor(); + + lua_newtable(L); + push_ARGB8(L, color); + lua_setfield(L, -2, "color"); + + return 1; +} + ObjectRef::ObjectRef(ServerActiveObject *object): m_object(object) { @@ -1254,7 +1630,7 @@ ObjectRef::ObjectRef(ServerActiveObject *object): ObjectRef::~ObjectRef() { - /*if(m_object) + /*if (m_object) infostream<<"ObjectRef destructing for id=" <<m_object->getId()<<std::endl; else @@ -1323,12 +1699,16 @@ const luaL_reg ObjectRef::methods[] = { luamethod(ObjectRef, get_wielded_item), luamethod(ObjectRef, set_wielded_item), luamethod(ObjectRef, set_armor_groups), - luamethod(ObjectRef, set_physics_override), + luamethod(ObjectRef, get_armor_groups), luamethod(ObjectRef, set_animation), + luamethod(ObjectRef, get_animation), luamethod(ObjectRef, set_bone_position), + luamethod(ObjectRef, get_bone_position), luamethod(ObjectRef, set_attach), + luamethod(ObjectRef, get_attach), luamethod(ObjectRef, set_detach), luamethod(ObjectRef, set_properties), + luamethod(ObjectRef, get_properties), // LuaEntitySAO-only luamethod(ObjectRef, setvelocity), luamethod(ObjectRef, getvelocity), @@ -1344,6 +1724,7 @@ const luaL_reg ObjectRef::methods[] = { luamethod(ObjectRef, is_player), luamethod(ObjectRef, is_player_connected), luamethod(ObjectRef, get_player_name), + luamethod(ObjectRef, get_player_velocity), luamethod(ObjectRef, get_look_dir), luamethod(ObjectRef, get_look_pitch), luamethod(ObjectRef, get_look_yaw), @@ -1355,6 +1736,8 @@ const luaL_reg ObjectRef::methods[] = { luamethod(ObjectRef, get_inventory_formspec), luamethod(ObjectRef, get_player_control), luamethod(ObjectRef, get_player_control_bits), + luamethod(ObjectRef, set_physics_override), + luamethod(ObjectRef, get_physics_override), luamethod(ObjectRef, hud_add), luamethod(ObjectRef, hud_remove), luamethod(ObjectRef, hud_change), @@ -1362,11 +1745,20 @@ const luaL_reg ObjectRef::methods[] = { luamethod(ObjectRef, hud_set_flags), luamethod(ObjectRef, hud_get_flags), luamethod(ObjectRef, hud_set_hotbar_itemcount), + luamethod(ObjectRef, hud_get_hotbar_itemcount), luamethod(ObjectRef, hud_set_hotbar_image), + luamethod(ObjectRef, hud_get_hotbar_image), luamethod(ObjectRef, hud_set_hotbar_selected_image), + luamethod(ObjectRef, hud_get_hotbar_selected_image), luamethod(ObjectRef, set_sky), + luamethod(ObjectRef, get_sky), luamethod(ObjectRef, override_day_night_ratio), + luamethod(ObjectRef, get_day_night_ratio), luamethod(ObjectRef, set_local_animation), + luamethod(ObjectRef, get_local_animation), luamethod(ObjectRef, set_eye_offset), + luamethod(ObjectRef, get_eye_offset), + luamethod(ObjectRef, set_nametag_attributes), + luamethod(ObjectRef, get_nametag_attributes), {0,0} }; diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index d51ca379f..a4457cc05 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -101,25 +101,46 @@ private: // set_armor_groups(self, groups) static int l_set_armor_groups(lua_State *L); + // get_armor_groups(self) + static int l_get_armor_groups(lua_State *L); + // set_physics_override(self, physics_override_speed, physics_override_jump, // physics_override_gravity, sneak, sneak_glitch) static int l_set_physics_override(lua_State *L); - // set_animation(self, frame_range, frame_speed, frame_blend) + // get_physics_override(self) + static int l_get_physics_override(lua_State *L); + + // set_animation(self, frame_range, frame_speed, frame_blend, frame_loop) static int l_set_animation(lua_State *L); + // get_animation(self) + static int l_get_animation(lua_State *L); + // set_bone_position(self, std::string bone, v3f position, v3f rotation) static int l_set_bone_position(lua_State *L); + // get_bone_position(self, bone) + static int l_get_bone_position(lua_State *L); + // set_attach(self, parent, bone, position, rotation) static int l_set_attach(lua_State *L); + // get_attach(self) + static int l_get_attach(lua_State *L); + // set_detach(self) static int l_set_detach(lua_State *L); // set_properties(self, properties) static int l_set_properties(lua_State *L); + // get_properties(self) + static int l_get_properties(lua_State *L); + + // is_player(self) + static int l_is_player(lua_State *L); + /* LuaEntitySAO-only */ // setvelocity(self, {x=num, y=num, z=num}) @@ -156,15 +177,15 @@ private: /* Player-only */ - // is_player(self) - static int l_is_player(lua_State *L); - // is_player_connected(self) static int l_is_player_connected(lua_State *L); // get_player_name(self) static int l_get_player_name(lua_State *L); + // get_player_velocity(self) + static int l_get_player_velocity(lua_State *L); + // get_look_dir(self) static int l_get_look_dir(lua_State *L); @@ -222,24 +243,51 @@ private: // hud_set_hotbar_itemcount(self, hotbar_itemcount) static int l_hud_set_hotbar_itemcount(lua_State *L); + // hud_get_hotbar_itemcount(self) + static int l_hud_get_hotbar_itemcount(lua_State *L); + // hud_set_hotbar_image(self, name) static int l_hud_set_hotbar_image(lua_State *L); + // hud_get_hotbar_image(self) + static int l_hud_get_hotbar_image(lua_State *L); + // hud_set_hotbar_selected_image(self, name) static int l_hud_set_hotbar_selected_image(lua_State *L); + // hud_get_hotbar_selected_image(self) + static int l_hud_get_hotbar_selected_image(lua_State *L); + // set_sky(self, type, list) static int l_set_sky(lua_State *L); - // override_day_night_ratio(self, type, list) + // get_sky(self, type, list) + static int l_get_sky(lua_State *L); + + // override_day_night_ratio(self, type) static int l_override_day_night_ratio(lua_State *L); + // get_day_night_ratio(self) + static int l_get_day_night_ratio(lua_State *L); + // set_local_animation(self, {stand/idle}, {walk}, {dig}, {walk+dig}, frame_speed) static int l_set_local_animation(lua_State *L); + // get_local_animation(self) + static int l_get_local_animation(lua_State *L); + // set_eye_offset(self, v3f first pv, v3f third pv) static int l_set_eye_offset(lua_State *L); + // get_eye_offset(self) + static int l_get_eye_offset(lua_State *L); + + // set_nametag_attributes(self, attributes) + static int l_set_nametag_attributes(lua_State *L); + + // get_nametag_attributes(self) + static int l_get_nametag_attributes(lua_State *L); + public: ObjectRef(ServerActiveObject *object); diff --git a/src/script/lua_api/l_particles.cpp b/src/script/lua_api/l_particles.cpp index 6769f5c23..2532b2b08 100644 --- a/src/script/lua_api/l_particles.cpp +++ b/src/script/lua_api/l_particles.cpp @@ -34,17 +34,20 @@ int ModApiParticles::l_add_particle(lua_State *L) { // Get parameters v3f pos, vel, acc; - pos= vel= acc= v3f(0, 0, 0); + pos = vel = acc = v3f(0, 0, 0); + float expirationtime, size; - expirationtime= size= 1; + expirationtime = size = 1; + bool collisiondetection, vertical; - collisiondetection= vertical= false; + collisiondetection = vertical = false; + std::string texture = ""; - const char *playername = ""; + std::string playername = ""; if (lua_gettop(L) > 1) // deprecated { - log_deprecated(L,"Deprecated add_particle call with individual parameters instead of definition"); + log_deprecated(L, "Deprecated add_particle call with individual parameters instead of definition"); pos = check_v3f(L, 1); vel = check_v3f(L, 2); acc = check_v3f(L, 3); @@ -57,44 +60,44 @@ int ModApiParticles::l_add_particle(lua_State *L) } else if (lua_istable(L, 1)) { - int table = lua_gettop(L); - lua_pushnil(L); - while (lua_next(L, table) != 0) - { - const char *key = lua_tostring(L, -2); - if(strcmp(key,"pos")==0){ - pos=check_v3f(L, -1); - }else if(strcmp(key,"vel")==0){ - vel=check_v3f(L, -1); - }else if(strcmp(key,"acc")==0){ - acc=check_v3f(L, -1); - }else if(strcmp(key,"expirationtime")==0){ - expirationtime=luaL_checknumber(L, -1); - }else if(strcmp(key,"size")==0){ - size=luaL_checknumber(L, -1); - }else if(strcmp(key,"collisiondetection")==0){ - collisiondetection=lua_toboolean(L, -1); - }else if(strcmp(key,"vertical")==0){ - vertical=lua_toboolean(L, -1); - }else if(strcmp(key,"texture")==0){ - texture=luaL_checkstring(L, -1); - }else if(strcmp(key,"playername")==0){ - playername=luaL_checkstring(L, -1); - } - lua_pop(L, 1); + lua_getfield(L, 1, "pos"); + pos = lua_istable(L, -1) ? check_v3f(L, -1) : v3f(); + lua_pop(L, 1); + + lua_getfield(L, 1, "vel"); + if (lua_istable(L, -1)) { + vel = check_v3f(L, -1); + log_deprecated(L, "The use of vel is deprecated. " + "Use velocity instead"); } + lua_pop(L, 1); + + lua_getfield(L, 1, "velocity"); + vel = lua_istable(L, -1) ? check_v3f(L, -1) : vel; + lua_pop(L, 1); + + lua_getfield(L, 1, "acc"); + if (lua_istable(L, -1)) { + acc = check_v3f(L, -1); + log_deprecated(L, "The use of acc is deprecated. " + "Use acceleration instead"); + } + lua_pop(L, 1); + + lua_getfield(L, 1, "acceleration"); + acc = lua_istable(L, -1) ? check_v3f(L, -1) : acc; + lua_pop(L, 1); + + expirationtime = getfloatfield_default(L, 1, "expirationtime", 1); + size = getfloatfield_default(L, 1, "size", 1); + collisiondetection = getboolfield_default(L, 1, + "collisiondetection", collisiondetection); + vertical = getboolfield_default(L, 1, "vertical", vertical); + texture = getstringfield_default(L, 1, "texture", ""); + playername = getstringfield_default(L, 1, "playername", ""); } - if (strcmp(playername, "")==0) // spawn for all players - { - getServer(L)->spawnParticleAll(pos, vel, acc, + getServer(L)->spawnParticle(playername, pos, vel, acc, expirationtime, size, collisiondetection, vertical, texture); - } - else - { - getServer(L)->spawnParticle(playername, - pos, vel, acc, expirationtime, - size, collisiondetection, vertical, texture); - } return 1; } @@ -125,7 +128,7 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) bool collisiondetection, vertical; collisiondetection= vertical= false; std::string texture = ""; - const char *playername = ""; + std::string playername = ""; if (lua_gettop(L) > 1) //deprecated { @@ -149,74 +152,55 @@ int ModApiParticles::l_add_particlespawner(lua_State *L) } else if (lua_istable(L, 1)) { - int table = lua_gettop(L); - lua_pushnil(L); - while (lua_next(L, table) != 0) - { - const char *key = lua_tostring(L, -2); - if(strcmp(key,"amount")==0){ - amount=luaL_checknumber(L, -1); - }else if(strcmp(key,"time")==0){ - time=luaL_checknumber(L, -1); - }else if(strcmp(key,"minpos")==0){ - minpos=check_v3f(L, -1); - }else if(strcmp(key,"maxpos")==0){ - maxpos=check_v3f(L, -1); - }else if(strcmp(key,"minvel")==0){ - minvel=check_v3f(L, -1); - }else if(strcmp(key,"maxvel")==0){ - maxvel=check_v3f(L, -1); - }else if(strcmp(key,"minacc")==0){ - minacc=check_v3f(L, -1); - }else if(strcmp(key,"maxacc")==0){ - maxacc=check_v3f(L, -1); - }else if(strcmp(key,"minexptime")==0){ - minexptime=luaL_checknumber(L, -1); - }else if(strcmp(key,"maxexptime")==0){ - maxexptime=luaL_checknumber(L, -1); - }else if(strcmp(key,"minsize")==0){ - minsize=luaL_checknumber(L, -1); - }else if(strcmp(key,"maxsize")==0){ - maxsize=luaL_checknumber(L, -1); - }else if(strcmp(key,"collisiondetection")==0){ - collisiondetection=lua_toboolean(L, -1); - }else if(strcmp(key,"vertical")==0){ - vertical=lua_toboolean(L, -1); - }else if(strcmp(key,"texture")==0){ - texture=luaL_checkstring(L, -1); - }else if(strcmp(key,"playername")==0){ - playername=luaL_checkstring(L, -1); - } - lua_pop(L, 1); - } - } - if (strcmp(playername, "")==0) //spawn for all players - { - u32 id = getServer(L)->addParticleSpawnerAll( amount, time, - minpos, maxpos, - minvel, maxvel, - minacc, maxacc, - minexptime, maxexptime, - minsize, maxsize, - collisiondetection, - vertical, - texture); - lua_pushnumber(L, id); - } - else - { - u32 id = getServer(L)->addParticleSpawner(playername, - amount, time, - minpos, maxpos, - minvel, maxvel, - minacc, maxacc, - minexptime, maxexptime, - minsize, maxsize, - collisiondetection, - vertical, - texture); - lua_pushnumber(L, id); + amount = getintfield_default(L, 1, "amount", amount); + time = getfloatfield_default(L, 1, "time", time); + + lua_getfield(L, 1, "minpos"); + minpos = lua_istable(L, -1) ? check_v3f(L, -1) : minpos; + lua_pop(L, 1); + + lua_getfield(L, 1, "maxpos"); + maxpos = lua_istable(L, -1) ? check_v3f(L, -1) : maxpos; + lua_pop(L, 1); + + lua_getfield(L, 1, "minvel"); + minvel = lua_istable(L, -1) ? check_v3f(L, -1) : minvel; + lua_pop(L, 1); + + lua_getfield(L, 1, "maxvel"); + maxvel = lua_istable(L, -1) ? check_v3f(L, -1) : maxvel; + lua_pop(L, 1); + + lua_getfield(L, 1, "minacc"); + minacc = lua_istable(L, -1) ? check_v3f(L, -1) : minacc; + lua_pop(L, 1); + + lua_getfield(L, 1, "maxacc"); + maxacc = lua_istable(L, -1) ? check_v3f(L, -1) : maxacc; + lua_pop(L, 1); + + minexptime = getfloatfield_default(L, 1, "minexptime", minexptime); + maxexptime = getfloatfield_default(L, 1, "maxexptime", maxexptime); + minsize = getfloatfield_default(L, 1, "minsize", minsize); + maxsize = getfloatfield_default(L, 1, "maxsize", maxsize); + collisiondetection = getboolfield_default(L, 1, + "collisiondetection", collisiondetection); + vertical = getboolfield_default(L, 1, "vertical", vertical); + texture = getstringfield_default(L, 1, "texture", ""); + playername = getstringfield_default(L, 1, "playername", ""); } + + u32 id = getServer(L)->addParticleSpawner(amount, time, + minpos, maxpos, + minvel, maxvel, + minacc, maxacc, + minexptime, maxexptime, + minsize, maxsize, + collisiondetection, + vertical, + texture, playername); + lua_pushnumber(L, id); + return 1; } @@ -226,16 +210,12 @@ int ModApiParticles::l_delete_particlespawner(lua_State *L) { // Get parameters u32 id = luaL_checknumber(L, 1); - - if (lua_gettop(L) == 2) // only delete for one player - { - const char *playername = luaL_checkstring(L, 2); - getServer(L)->deleteParticleSpawner(playername, id); - } - else // delete for all players - { - getServer(L)->deleteParticleSpawnerAll(id); + std::string playername = ""; + if (lua_gettop(L) == 2) { + playername = luaL_checkstring(L, 2); } + + getServer(L)->deleteParticleSpawner(playername, id); return 1; } diff --git a/src/script/lua_api/l_server.cpp b/src/script/lua_api/l_server.cpp index 8d7f6512e..73eca9d60 100644 --- a/src/script/lua_api/l_server.cpp +++ b/src/script/lua_api/l_server.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_internal.h" #include "common/c_converter.h" #include "common/c_content.h" +#include "cpp_api/s_base.h" #include "server.h" #include "environment.h" #include "player.h" @@ -29,7 +30,9 @@ with this program; if not, write to the Free Software Foundation, Inc., // request_shutdown() int ModApiServer::l_request_shutdown(lua_State *L) { - getServer(L)->requestShutdown(); + const char *msg = lua_tolstring(L, 1, NULL); + bool reconnect = lua_toboolean(L, 2); + getServer(L)->requestShutdown(msg ? msg : "", reconnect); return 0; } @@ -306,7 +309,7 @@ int ModApiServer::l_kick_player(lua_State *L) lua_pushboolean(L, false); // No such player return 1; } - getServer(L)->DenyAccess(player->peer_id, narrow_to_wide(message)); + getServer(L)->DenyAccess_Legacy(player->peer_id, utf8_to_wide(message)); lua_pushboolean(L, true); return 1; } @@ -342,7 +345,7 @@ int ModApiServer::l_show_formspec(lua_State *L) int ModApiServer::l_get_current_modname(lua_State *L) { NO_MAP_LOCK_REQUIRED; - lua_getfield(L, LUA_REGISTRYINDEX, "current_modname"); + lua_getfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); return 1; } @@ -367,33 +370,18 @@ int ModApiServer::l_get_modnames(lua_State *L) NO_MAP_LOCK_REQUIRED; // Get a list of mods - std::list<std::string> mods_unsorted, mods_sorted; - getServer(L)->getModNames(mods_unsorted); + std::vector<std::string> modlist; + getServer(L)->getModNames(modlist); // Take unsorted items from mods_unsorted and sort them into // mods_sorted; not great performance but the number of mods on a // server will likely be small. - for(std::list<std::string>::iterator i = mods_unsorted.begin(); - i != mods_unsorted.end(); ++i) { - bool added = false; - for(std::list<std::string>::iterator x = mods_sorted.begin(); - x != mods_sorted.end(); ++x) { - // I doubt anybody using Minetest will be using - // anything not ASCII based :) - if(i->compare(*x) <= 0) { - mods_sorted.insert(x, *i); - added = true; - break; - } - } - if(!added) - mods_sorted.push_back(*i); - } + std::sort(modlist.begin(), modlist.end()); // Package them up for Lua - lua_createtable(L, mods_sorted.size(), 0); - std::list<std::string>::iterator iter = mods_sorted.begin(); - for (u16 i = 0; iter != mods_sorted.end(); iter++) { + lua_createtable(L, modlist.size(), 0); + std::vector<std::string>::iterator iter = modlist.begin(); + for (u16 i = 0; iter != modlist.end(); iter++) { lua_pushstring(L, iter->c_str()); lua_rawseti(L, -2, ++i); } @@ -450,6 +438,31 @@ int ModApiServer::l_notify_authentication_modified(lua_State *L) return 0; } +// get_last_run_mod() +int ModApiServer::l_get_last_run_mod(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + lua_getfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); + const char *current_mod = lua_tostring(L, -1); + if (current_mod == NULL || current_mod[0] == '\0') { + lua_pop(L, 1); + lua_pushstring(L, getScriptApiBase(L)->getOrigin().c_str()); + } + return 1; +} + +// set_last_run_mod(modname) +int ModApiServer::l_set_last_run_mod(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; +#ifdef SCRIPTAPI_DEBUG + const char *mod = lua_tostring(L, 1); + getScriptApiBase(L)->setOriginDirect(mod); + //printf(">>>> last mod set from Lua: %s\n", mod); +#endif + return 0; +} + #ifndef NDEBUG // cause_error(type_of_error) int ModApiServer::l_cause_error(lua_State *L) @@ -507,6 +520,8 @@ void ModApiServer::Initialize(lua_State *L, int top) API_FCT(unban_player_or_ip); API_FCT(notify_authentication_modified); + API_FCT(get_last_run_mod); + API_FCT(set_last_run_mod); #ifndef NDEBUG API_FCT(cause_error); #endif diff --git a/src/script/lua_api/l_server.h b/src/script/lua_api/l_server.h index fd85a8975..df31f325f 100644 --- a/src/script/lua_api/l_server.h +++ b/src/script/lua_api/l_server.h @@ -24,7 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., class ModApiServer : public ModApiBase { private: - // request_shutdown() + // request_shutdown([message], [reconnect]) static int l_request_shutdown(lua_State *L); // get_server_status() @@ -88,6 +88,12 @@ private: // notify_authentication_modified(name) static int l_notify_authentication_modified(lua_State *L); + // get_last_run_mod() + static int l_get_last_run_mod(lua_State *L); + + // set_last_run_mod(modname) + static int l_set_last_run_mod(lua_State *L); + #ifndef NDEBUG // cause_error(type_of_error) static int l_cause_error(lua_State *L); diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 9c88a3e05..35b82b435 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_settings.h" #include "lua_api/l_internal.h" +#include "cpp_api/s_security.h" #include "settings.h" #include "log.h" @@ -188,6 +189,7 @@ int LuaSettings::create_object(lua_State* L) { NO_MAP_LOCK_REQUIRED; const char* filename = luaL_checkstring(L, 1); + CHECK_SECURE_PATH_OPTIONAL(L, filename); LuaSettings* o = new LuaSettings(filename); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; luaL_getmetatable(L, className); diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp index eb6c1835d..12146e80a 100644 --- a/src/script/lua_api/l_util.cpp +++ b/src/script/lua_api/l_util.cpp @@ -24,13 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_async.h" #include "serialization.h" #include "json/json.h" +#include "cpp_api/s_security.h" +#include "areastore.h" #include "debug.h" #include "porting.h" #include "log.h" #include "tool.h" #include "filesys.h" #include "settings.h" -#include "main.h" //required for g_settings, g_settings_path +#include "util/auth.h" +#include <algorithm> // debug(...) // Writes a line to dstream @@ -92,12 +95,19 @@ int ModApiUtil::l_log(lua_State *L) return 0; } +#define CHECK_SECURE_SETTING(L, name) \ + if (name.compare(0, 7, "secure.") == 0) {\ + lua_pushliteral(L, "Attempt to set secure setting.");\ + lua_error(L);\ + } + // setting_set(name, value) int ModApiUtil::l_setting_set(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const char *name = luaL_checkstring(L, 1); - const char *value = luaL_checkstring(L, 2); + std::string name = luaL_checkstring(L, 1); + std::string value = luaL_checkstring(L, 2); + CHECK_SECURE_SETTING(L, name); g_settings->set(name, value); return 0; } @@ -120,8 +130,9 @@ int ModApiUtil::l_setting_get(lua_State *L) int ModApiUtil::l_setting_setbool(lua_State *L) { NO_MAP_LOCK_REQUIRED; - const char *name = luaL_checkstring(L, 1); + std::string name = luaL_checkstring(L, 1); bool value = lua_toboolean(L, 2); + CHECK_SECURE_SETTING(L, name); g_settings->setBool(name, value); return 0; } @@ -256,8 +267,7 @@ int ModApiUtil::l_get_password_hash(lua_State *L) NO_MAP_LOCK_REQUIRED; std::string name = luaL_checkstring(L, 1); std::string raw_password = luaL_checkstring(L, 2); - std::string hash = translatePassword(name, - narrow_to_wide(raw_password)); + std::string hash = translatePassword(name, raw_password); lua_pushstring(L, hash.c_str()); return 1; } @@ -308,7 +318,7 @@ int ModApiUtil::l_compress(lua_State *L) int ModApiUtil::l_decompress(lua_State *L) { size_t size; - const char * data = luaL_checklstring(L, 1, &size); + const char *data = luaL_checklstring(L, 1, &size); std::istringstream is(std::string(data, size)); std::ostringstream os; @@ -320,6 +330,64 @@ int ModApiUtil::l_decompress(lua_State *L) return 1; } +// mkdir(path) +int ModApiUtil::l_mkdir(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + const char *path = luaL_checkstring(L, 1); + CHECK_SECURE_PATH_OPTIONAL(L, path); + lua_pushboolean(L, fs::CreateAllDirs(path)); + return 1; +} + +// get_dir_list(path, is_dir) +int ModApiUtil::l_get_dir_list(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + const char *path = luaL_checkstring(L, 1); + short is_dir = lua_isboolean(L, 2) ? lua_toboolean(L, 2) : -1; + + CHECK_SECURE_PATH_OPTIONAL(L, path); + + std::vector<fs::DirListNode> list = fs::GetDirListing(path); + + int index = 0; + lua_newtable(L); + + for (size_t i = 0; i < list.size(); i++) { + if (is_dir == -1 || is_dir == list[i].dir) { + lua_pushstring(L, list[i].name.c_str()); + lua_rawseti(L, -2, ++index); + } + } + + return 1; +} + +int ModApiUtil::l_request_insecure_environment(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + if (!ScriptApiSecurity::isSecure(L)) { + lua_getglobal(L, "_G"); + return 1; + } + lua_getfield(L, LUA_REGISTRYINDEX, SCRIPT_MOD_NAME_FIELD); + if (!lua_isstring(L, -1)) { + lua_pushnil(L); + return 1; + } + const char *mod_name = lua_tostring(L, -1); + std::string trusted_mods = g_settings->get("secure.trusted_mods"); + std::vector<std::string> mod_list = str_split(trusted_mods, ','); + if (std::find(mod_list.begin(), mod_list.end(), mod_name) == mod_list.end()) { + lua_pushnil(L); + return 1; + } + lua_getfield(L, LUA_REGISTRYINDEX, "globals_backup"); + return 1; +} + + void ModApiUtil::Initialize(lua_State *L, int top) { API_FCT(debug); @@ -345,6 +413,11 @@ void ModApiUtil::Initialize(lua_State *L, int top) API_FCT(compress); API_FCT(decompress); + + API_FCT(mkdir); + API_FCT(get_dir_list); + + API_FCT(request_insecure_environment); } void ModApiUtil::InitializeAsync(AsyncEngine& engine) @@ -367,5 +440,8 @@ void ModApiUtil::InitializeAsync(AsyncEngine& engine) ASYNC_API_FCT(compress); ASYNC_API_FCT(decompress); + + ASYNC_API_FCT(mkdir); + ASYNC_API_FCT(get_dir_list); } diff --git a/src/script/lua_api/l_util.h b/src/script/lua_api/l_util.h index e82432381..e75aa28cb 100644 --- a/src/script/lua_api/l_util.h +++ b/src/script/lua_api/l_util.h @@ -78,7 +78,7 @@ private: // is_yes(arg) static int l_is_yes(lua_State *L); - // get_scriptdir() + // get_builtin_path() static int l_get_builtin_path(lua_State *L); // compress(data, method, ...) @@ -87,6 +87,15 @@ private: // decompress(data, method, ...) static int l_decompress(lua_State *L); + // mkdir(path) + static int l_mkdir(lua_State *L); + + // get_dir_list(path, is_dir) + static int l_get_dir_list(lua_State *L); + + // request_insecure_environment() + static int l_request_insecure_environment(lua_State *L); + public: static void Initialize(lua_State *L, int top); @@ -95,3 +104,4 @@ public: }; #endif /* L_UTIL_H_ */ + diff --git a/src/script/lua_api/l_vmanip.cpp b/src/script/lua_api/l_vmanip.cpp index de7612115..ac6c10303 100644 --- a/src/script/lua_api/l_vmanip.cpp +++ b/src/script/lua_api/l_vmanip.cpp @@ -46,8 +46,8 @@ int LuaVoxelManip::l_read_from_map(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); MMVManip *vm = o->vm; - v3s16 bp1 = getNodeBlockPos(read_v3s16(L, 2)); - v3s16 bp2 = getNodeBlockPos(read_v3s16(L, 3)); + v3s16 bp1 = getNodeBlockPos(check_v3s16(L, 2)); + v3s16 bp2 = getNodeBlockPos(check_v3s16(L, 3)); sortBoxVerticies(bp1, bp2); vm->initialEmerge(bp1, bp2); @@ -63,12 +63,18 @@ int LuaVoxelManip::l_get_data(lua_State *L) NO_MAP_LOCK_REQUIRED; LuaVoxelManip *o = checkobject(L, 1); + bool use_buffer = lua_istable(L, 2); + MMVManip *vm = o->vm; - int volume = vm->m_area.getVolume(); + u32 volume = vm->m_area.getVolume(); - lua_newtable(L); - for (int i = 0; i != volume; i++) { + if (use_buffer) + lua_pushvalue(L, 2); + else + lua_newtable(L); + + for (u32 i = 0; i != volume; i++) { lua_Integer cid = vm->m_data[i].getContent(); lua_pushinteger(L, cid); lua_rawseti(L, -2, i + 1); @@ -87,8 +93,8 @@ int LuaVoxelManip::l_set_data(lua_State *L) if (!lua_istable(L, 2)) return 0; - int volume = vm->m_area.getVolume(); - for (int i = 0; i != volume; i++) { + u32 volume = vm->m_area.getVolume(); + for (u32 i = 0; i != volume; i++) { lua_rawgeti(L, 2, i + 1); content_t c = lua_tointeger(L, -1); @@ -116,7 +122,7 @@ int LuaVoxelManip::l_get_node_at(lua_State *L) GET_ENV_PTR; LuaVoxelManip *o = checkobject(L, 1); - v3s16 pos = read_v3s16(L, 2); + v3s16 pos = check_v3s16(L, 2); pushnode(L, o->vm->getNodeNoExNoEmerge(pos), env->getGameDef()->ndef()); return 1; @@ -128,7 +134,7 @@ int LuaVoxelManip::l_set_node_at(lua_State *L) GET_ENV_PTR; LuaVoxelManip *o = checkobject(L, 1); - v3s16 pos = read_v3s16(L, 2); + v3s16 pos = check_v3s16(L, 2); MapNode n = readnode(L, 3, env->getGameDef()->ndef()); o->vm->setNodeNoEmerge(pos, n); @@ -171,8 +177,8 @@ int LuaVoxelManip::l_calc_lighting(lua_State *L) v3s16 yblock = v3s16(0, 1, 0) * MAP_BLOCKSIZE; v3s16 fpmin = vm->m_area.MinEdge; v3s16 fpmax = vm->m_area.MaxEdge; - v3s16 pmin = lua_istable(L, 2) ? read_v3s16(L, 2) : fpmin + yblock; - v3s16 pmax = lua_istable(L, 3) ? read_v3s16(L, 3) : fpmax - yblock; + v3s16 pmin = lua_istable(L, 2) ? check_v3s16(L, 2) : fpmin + yblock; + v3s16 pmax = lua_istable(L, 3) ? check_v3s16(L, 3) : fpmax - yblock; sortBoxVerticies(pmin, pmax); if (!vm->m_area.contains(VoxelArea(pmin, pmax))) @@ -206,8 +212,8 @@ int LuaVoxelManip::l_set_lighting(lua_State *L) MMVManip *vm = o->vm; v3s16 yblock = v3s16(0, 1, 0) * MAP_BLOCKSIZE; - v3s16 pmin = lua_istable(L, 3) ? read_v3s16(L, 3) : vm->m_area.MinEdge + yblock; - v3s16 pmax = lua_istable(L, 4) ? read_v3s16(L, 4) : vm->m_area.MaxEdge - yblock; + v3s16 pmin = lua_istable(L, 3) ? check_v3s16(L, 3) : vm->m_area.MinEdge + yblock; + v3s16 pmax = lua_istable(L, 4) ? check_v3s16(L, 4) : vm->m_area.MaxEdge - yblock; sortBoxVerticies(pmin, pmax); if (!vm->m_area.contains(VoxelArea(pmin, pmax))) @@ -228,10 +234,10 @@ int LuaVoxelManip::l_get_light_data(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); MMVManip *vm = o->vm; - int volume = vm->m_area.getVolume(); + u32 volume = vm->m_area.getVolume(); lua_newtable(L); - for (int i = 0; i != volume; i++) { + for (u32 i = 0; i != volume; i++) { lua_Integer light = vm->m_data[i].param1; lua_pushinteger(L, light); lua_rawseti(L, -2, i + 1); @@ -250,8 +256,8 @@ int LuaVoxelManip::l_set_light_data(lua_State *L) if (!lua_istable(L, 2)) return 0; - int volume = vm->m_area.getVolume(); - for (int i = 0; i != volume; i++) { + u32 volume = vm->m_area.getVolume(); + for (u32 i = 0; i != volume; i++) { lua_rawgeti(L, 2, i + 1); u8 light = lua_tointeger(L, -1); @@ -270,10 +276,10 @@ int LuaVoxelManip::l_get_param2_data(lua_State *L) LuaVoxelManip *o = checkobject(L, 1); MMVManip *vm = o->vm; - int volume = vm->m_area.getVolume(); + u32 volume = vm->m_area.getVolume(); lua_newtable(L); - for (int i = 0; i != volume; i++) { + for (u32 i = 0; i != volume; i++) { lua_Integer param2 = vm->m_data[i].param2; lua_pushinteger(L, param2); lua_rawseti(L, -2, i + 1); @@ -292,8 +298,8 @@ int LuaVoxelManip::l_set_param2_data(lua_State *L) if (!lua_istable(L, 2)) return 0; - int volume = vm->m_area.getVolume(); - for (int i = 0; i != volume; i++) { + u32 volume = vm->m_area.getVolume(); + for (u32 i = 0; i != volume; i++) { lua_rawgeti(L, 2, i + 1); u8 param2 = lua_tointeger(L, -1); @@ -402,7 +408,7 @@ int LuaVoxelManip::create_object(lua_State *L) Map *map = &(env->getMap()); LuaVoxelManip *o = (lua_istable(L, 1) && lua_istable(L, 2)) ? - new LuaVoxelManip(map, read_v3s16(L, 1), read_v3s16(L, 2)) : + new LuaVoxelManip(map, check_v3s16(L, 1), check_v3s16(L, 2)) : new LuaVoxelManip(map); *(void **)(lua_newuserdata(L, sizeof(void *))) = o; diff --git a/src/script/scripting_game.cpp b/src/script/scripting_game.cpp index e716bc979..4f0350d41 100644 --- a/src/script/scripting_game.cpp +++ b/src/script/scripting_game.cpp @@ -20,7 +20,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "scripting_game.h" #include "server.h" #include "log.h" +#include "settings.h" #include "cpp_api/s_internal.h" +#include "lua_api/l_areastore.h" #include "lua_api/l_base.h" #include "lua_api/l_craft.h" #include "lua_api/l_env.h" @@ -49,10 +51,12 @@ GameScripting::GameScripting(Server* server) // setEnv(env) is called by ScriptApiEnv::initializeEnvironment() // once the environment has been created - //TODO add security - SCRIPTAPI_PRECHECKHEADER + if (g_settings->getBool("secure.enable_security")) { + initializeSecurity(); + } + lua_getglobal(L, "core"); int top = lua_gettop(L); @@ -88,10 +92,12 @@ void GameScripting::InitializeModApi(lua_State *L, int top) // Register reference classes (userdata) InvRef::Register(L); + LuaAreaStore::Register(L); LuaItemStack::Register(L); LuaPerlinNoise::Register(L); LuaPerlinNoiseMap::Register(L); LuaPseudoRandom::Register(L); + LuaPcgRandom::Register(L); LuaVoxelManip::Register(L); NodeMetaRef::Register(L); NodeTimerRef::Register(L); @@ -99,7 +105,7 @@ void GameScripting::InitializeModApi(lua_State *L, int top) LuaSettings::Register(L); } -void log_deprecated(std::string message) +void log_deprecated(const std::string &message) { - log_deprecated(NULL,message); + log_deprecated(NULL, message); } diff --git a/src/script/scripting_game.h b/src/script/scripting_game.h index 14dbd9170..970b3e80d 100644 --- a/src/script/scripting_game.h +++ b/src/script/scripting_game.h @@ -27,19 +27,21 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_node.h" #include "cpp_api/s_player.h" #include "cpp_api/s_server.h" +#include "cpp_api/s_security.h" /*****************************************************************************/ /* Scripting <-> Game Interface */ /*****************************************************************************/ -class GameScripting - : virtual public ScriptApiBase, - public ScriptApiDetached, - public ScriptApiEntity, - public ScriptApiEnv, - public ScriptApiNode, - public ScriptApiPlayer, - public ScriptApiServer +class GameScripting : + virtual public ScriptApiBase, + public ScriptApiDetached, + public ScriptApiEntity, + public ScriptApiEnv, + public ScriptApiNode, + public ScriptApiPlayer, + public ScriptApiServer, + public ScriptApiSecurity { public: GameScripting(Server* server); @@ -50,6 +52,6 @@ private: void InitializeModApi(lua_State *L, int top); }; -void log_deprecated(std::string message); +void log_deprecated(const std::string &message); #endif /* SCRIPTING_GAME_H_ */ diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index 54b3133c5..c74c18edc 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -38,8 +38,6 @@ MainMenuScripting::MainMenuScripting(GUIEngine* guiengine) { setGuiEngine(guiengine); - //TODO add security - SCRIPTAPI_PRECHECKHEADER lua_getglobal(L, "core"); diff --git a/src/server.cpp b/src/server.cpp index 2d84ad032..dc7b101a6 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -21,12 +21,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iostream> #include <queue> #include <algorithm> -#include "clientserver.h" +#include "network/networkprotocol.h" +#include "network/serveropcodes.h" #include "ban.h" #include "environment.h" #include "map.h" #include "jthread/jmutexautolock.h" -#include "main.h" #include "constants.h" #include "voxel.h" #include "config.h" @@ -50,20 +50,18 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "content_abm.h" #include "content_sao.h" #include "mods.h" -#include "sha1.h" -#include "base64.h" -#include "tool.h" #include "sound.h" // dummySoundManager #include "event_manager.h" -#include "hex.h" #include "serverlist.h" #include "util/string.h" -#include "util/pointedthing.h" #include "util/mathconstants.h" #include "rollback.h" #include "util/serialize.h" #include "util/thread.h" #include "defaultsettings.h" +#include "util/base64.h" +#include "util/sha1.h" +#include "util/hex.h" class ClientNotFoundException : public BaseException { @@ -88,7 +86,7 @@ public: void * Thread(); }; -void * ServerThread::Thread() +void *ServerThread::Thread() { log_register_thread("ServerThread"); @@ -101,33 +99,22 @@ void * ServerThread::Thread() porting::setThreadName("ServerThread"); - while(!StopRequested()) - { - try{ + while (!StopRequested()) { + try { //TimeTaker timer("AsyncRunStep() + Receive()"); m_server->AsyncRunStep(); m_server->Receive(); - } - catch(con::NoIncomingDataException &e) - { - } - catch(con::PeerNotFoundException &e) - { + } catch (con::NoIncomingDataException &e) { + } catch (con::PeerNotFoundException &e) { infostream<<"Server: PeerNotFoundException"<<std::endl; - } - catch(ClientNotFoundException &e) - { - } - catch(con::ConnectionBindFailed &e) - { - m_server->setAsyncFatalError(e.what()); - } - catch(LuaError &e) - { + } catch (ClientNotFoundException &e) { + } catch (con::ConnectionBindFailed &e) { m_server->setAsyncFatalError(e.what()); + } catch (LuaError &e) { + m_server->setAsyncFatalError("Lua: " + std::string(e.what())); } } @@ -193,6 +180,7 @@ Server::Server( m_uptime(0), m_clients(&m_con), m_shutdown_requested(false), + m_shutdown_ask_reconnect(false), m_ignore_map_edit_events(false), m_ignore_map_edit_events_peer_id(0), m_next_sound_id(0) @@ -223,12 +211,9 @@ Server::Server( infostream<<"- world: "<<m_path_world<<std::endl; infostream<<"- game: "<<m_gamespec.path<<std::endl; - // Initialize default settings and override defaults with those provided - // by the game - set_default_settings(g_settings); - Settings gamedefaults; - getGameMinetestConfig(gamespec.path, gamedefaults); - override_default_settings(g_settings, &gamedefaults); + // Create world if it doesn't exist + if(!loadGameConfAndInitWorld(m_path_world, m_gamespec)) + throw ServerError("Failed to initialize world"); // Create server thread m_thread = new ServerThread(this); @@ -236,10 +221,6 @@ Server::Server( // Create emerge manager m_emerge = new EmergeManager(this); - // Create world if it doesn't exist - if(!initializeWorld(m_path_world, m_gamespec.id)) - throw ServerError("Failed to initialize world"); - // Create ban manager std::string ban_path = m_path_world + DIR_DELIM "ipban.txt"; m_banmanager = new BanManager(ban_path); @@ -248,11 +229,9 @@ Server::Server( m_mods = modconf.getMods(); std::vector<ModSpec> unsatisfied_mods = modconf.getUnsatisfiedMods(); // complain about mods with unsatisfied dependencies - if(!modconf.isConsistent()) - { + if(!modconf.isConsistent()) { for(std::vector<ModSpec>::iterator it = unsatisfied_mods.begin(); - it != unsatisfied_mods.end(); ++it) - { + it != unsatisfied_mods.end(); ++it) { ModSpec mod = *it; errorstream << "mod \"" << mod.name << "\" has unsatisfied dependencies: "; for(std::set<std::string>::iterator dep_it = mod.unsatisfied_depends.begin(); @@ -268,8 +247,7 @@ Server::Server( std::vector<std::string> names = worldmt_settings.getNames(); std::set<std::string> load_mod_names; for(std::vector<std::string>::iterator it = names.begin(); - it != names.end(); ++it) - { + it != names.end(); ++it) { std::string name = *it; if(name.compare(0,9,"load_mod_")==0 && worldmt_settings.getBool(name)) load_mod_names.insert(name.substr(9)); @@ -281,8 +259,7 @@ Server::Server( for(std::vector<ModSpec>::iterator it = unsatisfied_mods.begin(); it != unsatisfied_mods.end(); ++it) load_mod_names.erase((*it).name); - if(!load_mod_names.empty()) - { + if(!load_mod_names.empty()) { errorstream << "The following mods could not be found:"; for(std::set<std::string>::iterator it = load_mod_names.begin(); it != load_mod_names.end(); ++it) @@ -304,31 +281,41 @@ Server::Server( m_script = new GameScripting(this); - std::string scriptpath = getBuiltinLuaPath() + DIR_DELIM "init.lua"; + std::string script_path = getBuiltinLuaPath() + DIR_DELIM "init.lua"; + std::string error_msg; - if (!m_script->loadScript(scriptpath)) - throw ModError("Failed to load and run " + scriptpath); + if (!m_script->loadMod(script_path, BUILTIN_MOD_NAME, &error_msg)) + throw ModError("Failed to load and run " + script_path + + "\nError from Lua:\n" + error_msg); - // Print 'em - infostream<<"Server: Loading mods: "; + // Print mods + infostream << "Server: Loading mods: "; for(std::vector<ModSpec>::iterator i = m_mods.begin(); - i != m_mods.end(); i++){ + i != m_mods.end(); i++) { const ModSpec &mod = *i; - infostream<<mod.name<<" "; + infostream << mod.name << " "; } - infostream<<std::endl; + infostream << std::endl; // Load and run "mod" scripts - for(std::vector<ModSpec>::iterator i = m_mods.begin(); - i != m_mods.end(); i++){ + for (std::vector<ModSpec>::iterator i = m_mods.begin(); + i != m_mods.end(); i++) { const ModSpec &mod = *i; - std::string scriptpath = mod.path + DIR_DELIM + "init.lua"; - infostream<<" ["<<padStringRight(mod.name, 12)<<"] [\"" - <<scriptpath<<"\"]"<<std::endl; - bool success = m_script->loadMod(scriptpath, mod.name); - if(!success){ - errorstream<<"Server: Failed to load and run " - <<scriptpath<<std::endl; - throw ModError("Failed to load and run "+scriptpath); + if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) { + std::ostringstream err; + err << "Error loading mod \"" << mod.name + << "\": mod_name does not follow naming conventions: " + << "Only chararacters [a-z0-9_] are allowed." << std::endl; + errorstream << err.str().c_str(); + throw ModError(err.str()); + } + std::string script_path = mod.path + DIR_DELIM "init.lua"; + infostream << " [" << padStringRight(mod.name, 12) << "] [\"" + << script_path << "\"]" << std::endl; + if (!m_script->loadMod(script_path, mod.name, &error_msg)) { + errorstream << "Server: Failed to load and run " + << script_path << std::endl; + throw ModError("Failed to load and run " + script_path + + "\nError from Lua:\n" + error_msg); } } @@ -338,10 +325,18 @@ Server::Server( // Apply item aliases in the node definition manager m_nodedef->updateAliases(m_itemdef); + // Apply texture overrides from texturepack/override.txt + std::string texture_path = g_settings->get("texture_path"); + if (texture_path != "" && fs::IsDir(texture_path)) + m_nodedef->applyTextureOverrides(texture_path + DIR_DELIM + "override.txt"); + m_nodedef->setNodeRegistrationStatus(true); // Perform pending node name resolutions - m_nodedef->runNodeResolverCallbacks(); + m_nodedef->runNodeResolveCallbacks(); + + // init the recipe hashes to speed up crafting + m_craftdef->initHashes(this); // Initialize Environment m_env = new ServerEnvironment(servermap, m_script, this, m_path_world); @@ -389,10 +384,23 @@ Server::~Server() // Execute script shutdown hooks m_script->on_shutdown(); - infostream<<"Server: Saving players"<<std::endl; + infostream << "Server: Saving players" << std::endl; m_env->saveLoadedPlayers(); - infostream<<"Server: Saving environment metadata"<<std::endl; + infostream << "Server: Kicking players" << std::endl; + std::string kick_msg; + bool reconnect = false; + if (getShutdownRequested()) { + reconnect = m_shutdown_ask_reconnect; + kick_msg = m_shutdown_msg; + } + if (kick_msg == "") { + kick_msg = g_settings->get("kick_msg_shutdown"); + } + m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, + kick_msg, reconnect); + + infostream << "Server: Saving environment metadata" << std::endl; m_env->saveMeta(); } @@ -489,8 +497,19 @@ void Server::step(float dtime) } // Throw if fatal error occurred in thread std::string async_err = m_async_fatal_error.get(); - if(async_err != ""){ - throw ServerError(async_err); + if(async_err != "") { + if (m_simple_singleplayer_mode) { + throw ServerError(async_err); + } + else { + m_env->kickAllPlayers(SERVER_ACCESSDENIED_CRASH, + g_settings->get("kick_msg_crash"), + g_settings->getBool("ask_reconnect_on_crash")); + errorstream << "UNRECOVERABLE error occurred. Stopping server. " + << "Please fix the following error:" << std::endl + << async_err << std::endl; + FATAL_ERROR(async_err.c_str()); + } } } @@ -536,23 +555,18 @@ void Server::AsyncRunStep(bool initial_step) /* Update time of day and overall game time */ - { - JMutexAutoLock envlock(m_env_mutex); + m_env->setTimeOfDaySpeed(g_settings->getFloat("time_speed")); - m_env->setTimeOfDaySpeed(g_settings->getFloat("time_speed")); - - /* - Send to clients at constant intervals - */ + /* + Send to clients at constant intervals + */ - m_time_of_day_send_timer -= dtime; - if(m_time_of_day_send_timer < 0.0) - { - m_time_of_day_send_timer = g_settings->getFloat("time_send_interval"); - u16 time = m_env->getTimeOfDay(); - float time_speed = g_settings->getFloat("time_speed"); - SendTimeOfDay(PEER_ID_INEXISTENT, time, time_speed); - } + m_time_of_day_send_timer -= dtime; + if(m_time_of_day_send_timer < 0.0) { + m_time_of_day_send_timer = g_settings->getFloat("time_send_interval"); + u16 time = m_env->getTimeOfDay(); + float time_speed = g_settings->getFloat("time_speed"); + SendTimeOfDay(PEER_ID_INEXISTENT, time, time_speed); } { @@ -580,63 +594,14 @@ void Server::AsyncRunStep(bool initial_step) // Run Map's timers and unload unused data ScopeProfiler sp(g_profiler, "Server: map timer and unload"); m_env->getMap().timerUpdate(map_timer_and_unload_dtime, - g_settings->getFloat("server_unload_unused_data_timeout")); + g_settings->getFloat("server_unload_unused_data_timeout"), + (u32)-1); } /* Do background stuff */ - /* - Handle players - */ - { - JMutexAutoLock lock(m_env_mutex); - - std::list<u16> clientids = m_clients.getClientIDs(); - - ScopeProfiler sp(g_profiler, "Server: handle players"); - - for(std::list<u16>::iterator - i = clientids.begin(); - i != clientids.end(); ++i) - { - PlayerSAO *playersao = getPlayerSAO(*i); - if(playersao == NULL) - continue; - - /* - Handle player HPs (die if hp=0) - */ - if(playersao->m_hp_not_sent && g_settings->getBool("enable_damage")) - { - if(playersao->getHP() == 0) - DiePlayer(*i); - else - SendPlayerHP(*i); - } - - /* - Send player breath if changed - */ - if(playersao->m_breath_not_sent) { - SendPlayerBreath(*i); - } - - /* - Send player inventories if necessary - */ - if(playersao->m_moved){ - SendMovePlayer(*i); - playersao->m_moved = false; - } - if(playersao->m_inventory_not_sent){ - UpdateCrafting(*i); - SendInventory(*i); - } - } - } - /* Transform liquids */ m_liquid_transform_timer += dtime; if(m_liquid_transform_timer >= m_liquid_transform_every) @@ -821,46 +786,13 @@ void Server::AsyncRunStep(bool initial_step) obj->m_known_by_count++; } - // Send packet - SharedBuffer<u8> reply(2 + data_buffer.size()); - writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD); - memcpy((char*)&reply[2], data_buffer.c_str(), - data_buffer.size()); - // Send as reliable - m_clients.send(client->peer_id, 0, reply, true); - - verbosestream<<"Server: Sent object remove/add: " - <<removed_objects.size()<<" removed, " - <<added_objects.size()<<" added, " - <<"packet size is "<<reply.getSize()<<std::endl; + 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; } m_clients.Unlock(); -#if 0 - /* - Collect a list of all the objects known by the clients - and report it back to the environment. - */ - - core::map<u16, bool> all_known_objects; - - for(core::map<u16, RemoteClient*>::Iterator - i = m_clients.getIterator(); - i.atEnd() == false; i++) - { - RemoteClient *client = i.getNode()->getValue(); - // Go through all known objects of client - for(core::map<u16, bool>::Iterator - i = client->m_known_objects.getIterator(); - i.atEnd()==false; i++) - { - u16 id = i.getNode()->getKey(); - all_known_objects[id] = true; - } - } - - m_env->setKnownActiveObjects(whatever); -#endif - } /* @@ -872,25 +804,22 @@ void Server::AsyncRunStep(bool initial_step) // Key = object id // Value = data sent by object - std::map<u16, std::list<ActiveObjectMessage>* > buffered_messages; + std::map<u16, std::vector<ActiveObjectMessage>* > buffered_messages; // Get active object messages from environment - for(;;) - { + for(;;) { ActiveObjectMessage aom = m_env->getActiveObjectMessage(); - if(aom.id == 0) + if (aom.id == 0) break; - std::list<ActiveObjectMessage>* message_list = NULL; - std::map<u16, std::list<ActiveObjectMessage>* >::iterator n; + std::vector<ActiveObjectMessage>* message_list = NULL; + std::map<u16, std::vector<ActiveObjectMessage>* >::iterator n; n = buffered_messages.find(aom.id); - if(n == buffered_messages.end()) - { - message_list = new std::list<ActiveObjectMessage>; + if (n == buffered_messages.end()) { + message_list = new std::vector<ActiveObjectMessage>; buffered_messages[aom.id] = message_list; } - else - { + else { message_list = n->second; } message_list->push_back(aom); @@ -899,28 +828,26 @@ void Server::AsyncRunStep(bool initial_step) m_clients.Lock(); std::map<u16, RemoteClient*> clients = m_clients.getClientList(); // Route data to every client - for(std::map<u16, RemoteClient*>::iterator + for (std::map<u16, RemoteClient*>::iterator i = clients.begin(); - i != clients.end(); ++i) - { + i != clients.end(); ++i) { RemoteClient *client = i->second; std::string reliable_data; std::string unreliable_data; // Go through all objects in message buffer - for(std::map<u16, std::list<ActiveObjectMessage>* >::iterator + for (std::map<u16, std::vector<ActiveObjectMessage>* >::iterator j = buffered_messages.begin(); - j != buffered_messages.end(); ++j) - { + j != buffered_messages.end(); ++j) { // If object is not known by client, skip it u16 id = j->first; - if(client->m_known_objects.find(id) == client->m_known_objects.end()) + if (client->m_known_objects.find(id) == client->m_known_objects.end()) continue; + // Get message list of object - std::list<ActiveObjectMessage>* list = j->second; + std::vector<ActiveObjectMessage>* list = j->second; // Go through every message - for(std::list<ActiveObjectMessage>::iterator - k = list->begin(); k != list->end(); ++k) - { + for (std::vector<ActiveObjectMessage>::iterator + k = list->begin(); k != list->end(); ++k) { // Compose the full new data with header ActiveObjectMessage aom = *k; std::string new_data; @@ -941,40 +868,20 @@ void Server::AsyncRunStep(bool initial_step) reliable_data and unreliable_data are now ready. Send them. */ - if(reliable_data.size() > 0) - { - SharedBuffer<u8> reply(2 + reliable_data.size()); - writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_MESSAGES); - memcpy((char*)&reply[2], reliable_data.c_str(), - reliable_data.size()); - // Send as reliable - m_clients.send(client->peer_id, 0, reply, true); - } - if(unreliable_data.size() > 0) - { - SharedBuffer<u8> reply(2 + unreliable_data.size()); - writeU16(&reply[0], TOCLIENT_ACTIVE_OBJECT_MESSAGES); - memcpy((char*)&reply[2], unreliable_data.c_str(), - unreliable_data.size()); - // Send as unreliable - m_clients.send(client->peer_id, 1, reply, false); + if(reliable_data.size() > 0) { + SendActiveObjectMessages(client->peer_id, reliable_data); } - /*if(reliable_data.size() > 0 || unreliable_data.size() > 0) - { - infostream<<"Server: Size of object message data: " - <<"reliable: "<<reliable_data.size() - <<", unreliable: "<<unreliable_data.size() - <<std::endl; - }*/ + if(unreliable_data.size() > 0) { + SendActiveObjectMessages(client->peer_id, unreliable_data, false); + } } m_clients.Unlock(); // Clear buffered_messages - for(std::map<u16, std::list<ActiveObjectMessage>* >::iterator + for(std::map<u16, std::vector<ActiveObjectMessage>* >::iterator i = buffered_messages.begin(); - i != buffered_messages.end(); ++i) - { + i != buffered_messages.end(); ++i) { delete i->second; } } @@ -1001,83 +908,67 @@ void Server::AsyncRunStep(bool initial_step) while(m_unsent_map_edit_queue.size() != 0) { - MapEditEvent* event = m_unsent_map_edit_queue.pop_front(); + MapEditEvent* event = m_unsent_map_edit_queue.front(); + m_unsent_map_edit_queue.pop(); // Players far away from the change are stored here. // Instead of sending the changes, MapBlocks are set not sent // for them. - std::list<u16> far_players; + std::vector<u16> far_players; - if(event->type == MEET_ADDNODE || event->type == MEET_SWAPNODE) - { - //infostream<<"Server: MEET_ADDNODE"<<std::endl; + switch (event->type) { + case MEET_ADDNODE: + case MEET_SWAPNODE: prof.add("MEET_ADDNODE", 1); - if(disable_single_change_sending) - sendAddNode(event->p, event->n, event->already_known_by_peer, - &far_players, 5, event->type == MEET_ADDNODE); - else - sendAddNode(event->p, event->n, event->already_known_by_peer, - &far_players, 30, event->type == MEET_ADDNODE); - } - else if(event->type == MEET_REMOVENODE) - { - //infostream<<"Server: MEET_REMOVENODE"<<std::endl; + sendAddNode(event->p, event->n, event->already_known_by_peer, + &far_players, disable_single_change_sending ? 5 : 30, + event->type == MEET_ADDNODE); + break; + case MEET_REMOVENODE: prof.add("MEET_REMOVENODE", 1); - if(disable_single_change_sending) - sendRemoveNode(event->p, event->already_known_by_peer, - &far_players, 5); - else - sendRemoveNode(event->p, event->already_known_by_peer, - &far_players, 30); - } - else if(event->type == MEET_BLOCK_NODE_METADATA_CHANGED) - { - infostream<<"Server: MEET_BLOCK_NODE_METADATA_CHANGED"<<std::endl; - prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1); - setBlockNotSent(event->p); - } - else if(event->type == MEET_OTHER) - { - infostream<<"Server: MEET_OTHER"<<std::endl; + sendRemoveNode(event->p, event->already_known_by_peer, + &far_players, disable_single_change_sending ? 5 : 30); + break; + case MEET_BLOCK_NODE_METADATA_CHANGED: + infostream << "Server: MEET_BLOCK_NODE_METADATA_CHANGED" << std::endl; + prof.add("MEET_BLOCK_NODE_METADATA_CHANGED", 1); + setBlockNotSent(event->p); + break; + case MEET_OTHER: + infostream << "Server: MEET_OTHER" << std::endl; prof.add("MEET_OTHER", 1); for(std::set<v3s16>::iterator i = event->modified_blocks.begin(); - i != event->modified_blocks.end(); ++i) - { + i != event->modified_blocks.end(); ++i) { setBlockNotSent(*i); } - } - else - { + break; + default: prof.add("unknown", 1); - infostream<<"WARNING: Server: Unknown MapEditEvent " - <<((u32)event->type)<<std::endl; + infostream << "WARNING: Server: Unknown MapEditEvent " + << ((u32)event->type) << std::endl; + break; } /* Set blocks not sent to far players */ - if(!far_players.empty()) - { + if(!far_players.empty()) { // Convert list format to that wanted by SetBlocksNotSent std::map<v3s16, MapBlock*> modified_blocks2; for(std::set<v3s16>::iterator i = event->modified_blocks.begin(); - i != event->modified_blocks.end(); ++i) - { + i != event->modified_blocks.end(); ++i) { modified_blocks2[*i] = m_env->getMap().getBlockNoCreateNoEx(*i); } + // Set blocks not sent - for(std::list<u16>::iterator + for(std::vector<u16>::iterator i = far_players.begin(); - i != far_players.end(); ++i) - { - u16 peer_id = *i; - RemoteClient *client = getClient(peer_id); - if(client==NULL) - continue; - client->SetBlocksNotSent(modified_blocks2); + i != far_players.end(); ++i) { + if(RemoteClient *client = getClient(*i)) + client->SetBlocksNotSent(modified_blocks2); } } @@ -1147,13 +1038,13 @@ void Server::Receive() DSTACK(__FUNCTION_NAME); SharedBuffer<u8> data; u16 peer_id; - u32 datasize; - try{ - datasize = m_con.Receive(peer_id,data); - ProcessData(*data, datasize, peer_id); + try { + NetworkPacket pkt; + m_con.Receive(&pkt); + peer_id = pkt.getPeerId(); + ProcessData(&pkt); } - catch(con::InvalidIncomingDataException &e) - { + catch(con::InvalidIncomingDataException &e) { infostream<<"Server::Receive(): " "InvalidIncomingDataException: what()=" <<e.what()<<std::endl; @@ -1163,14 +1054,12 @@ void Server::Receive() "SerializationError: what()=" <<e.what()<<std::endl; } - catch(ClientStateError &e) - { + catch(ClientStateError &e) { errorstream << "ProcessData: peer=" << peer_id << e.what() << std::endl; - DenyAccess(peer_id, L"Your client sent something server didn't expect." + DenyAccess_Legacy(peer_id, L"Your client sent something server didn't expect." L"Try reconnecting or updating your client"); } - catch(con::PeerNotFoundException &e) - { + catch(con::PeerNotFoundException &e) { // Do nothing } } @@ -1184,7 +1073,7 @@ PlayerSAO* Server::StageTwoClientInit(u16 peer_id) RemoteClient* client = m_clients.lockedGetClientNoEx(peer_id, CS_InitDone); if (client != NULL) { playername = client->getName(); - playersao = emergePlayer(playername.c_str(), peer_id); + playersao = emergePlayer(playername.c_str(), peer_id, client->net_proto_version); } } catch (std::exception &e) { m_clients.Unlock(); @@ -1196,18 +1085,17 @@ PlayerSAO* Server::StageTwoClientInit(u16 peer_id) static_cast<RemotePlayer*>(m_env->getPlayer(playername.c_str())); // If failed, cancel - if((playersao == NULL) || (player == NULL)) - { - if(player && player->peer_id != 0){ - errorstream<<"Server: "<<playername<<": Failed to emerge player" - <<" (player allocated to an another client)"<<std::endl; - DenyAccess(peer_id, L"Another client is connected with this " + if ((playersao == NULL) || (player == NULL)) { + if (player && player->peer_id != 0) { + actionstream << "Server: Failed to emerge player \"" << playername + << "\" (player allocated to an another client)" << std::endl; + DenyAccess_Legacy(peer_id, L"Another client is connected with this " L"name. If your client closed unexpectedly, try again in " L"a minute."); } else { - errorstream<<"Server: "<<playername<<": Failed to emerge player" - <<std::endl; - DenyAccess(peer_id, L"Could not allocate player."); + errorstream << "Server: " << playername << ": Failed to emerge player" + << std::endl; + DenyAccess_Legacy(peer_id, L"Could not allocate player."); } return NULL; } @@ -1224,23 +1112,20 @@ PlayerSAO* Server::StageTwoClientInit(u16 peer_id) SendPlayerInventoryFormspec(peer_id); // Send inventory - UpdateCrafting(peer_id); - SendInventory(peer_id); + SendInventory(playersao); // Send HP - if(g_settings->getBool("enable_damage")) - SendPlayerHP(peer_id); + SendPlayerHPOrDie(playersao); // Send Breath SendPlayerBreath(peer_id); // Show death screen if necessary - if(player->hp == 0) + if(player->isDead()) SendDeathscreen(peer_id, false, v3f(0,0,0)); // Note things in chat if not in simple singleplayer mode - if(!m_simple_singleplayer_mode) - { + if(!m_simple_singleplayer_mode) { // Send information about server to player in chat SendChatMessage(peer_id, getStatusString()); @@ -1270,8 +1155,7 @@ PlayerSAO* Server::StageTwoClientInit(u16 peer_id) actionstream<<player->getName() <<" joins game. List of players: "; for (std::vector<std::string>::iterator i = names.begin(); - i != names.end(); i++) - { + i != names.end(); i++) { actionstream << *i << " "; } @@ -1280,1467 +1164,96 @@ PlayerSAO* Server::StageTwoClientInit(u16 peer_id) return playersao; } -void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) +inline void Server::handleCommand(NetworkPacket* pkt) +{ + const ToServerCommandHandler& opHandle = toServerCommandTable[pkt->getCommand()]; + (this->*opHandle.handler)(pkt); +} + +void Server::ProcessData(NetworkPacket *pkt) { DSTACK(__FUNCTION_NAME); // Environment is locked first. JMutexAutoLock envlock(m_env_mutex); ScopeProfiler sp(g_profiler, "Server::ProcessData"); + u32 peer_id = pkt->getPeerId(); - std::string addr_s; - try{ + try { Address address = getPeerAddress(peer_id); - addr_s = address.serializeString(); + std::string addr_s = address.serializeString(); - // drop player if is ip is banned - if(m_banmanager->isIpBanned(addr_s)){ + if(m_banmanager->isIpBanned(addr_s)) { std::string ban_name = m_banmanager->getBanName(addr_s); - infostream<<"Server: A banned client tried to connect from " - <<addr_s<<"; banned name was " - <<ban_name<<std::endl; + infostream << "Server: A banned client tried to connect from " + << addr_s << "; banned name was " + << ban_name << std::endl; // This actually doesn't seem to transfer to the client - DenyAccess(peer_id, L"Your ip is banned. Banned name was " - +narrow_to_wide(ban_name)); + DenyAccess_Legacy(peer_id, L"Your ip is banned. Banned name was " + + utf8_to_wide(ban_name)); return; } } - catch(con::PeerNotFoundException &e) - { + catch(con::PeerNotFoundException &e) { /* * no peer for this packet found * most common reason is peer timeout, e.g. peer didn't * respond for some time, your server was overloaded or * things like that. */ - infostream<<"Server::ProcessData(): Cancelling: peer " - <<peer_id<<" not found"<<std::endl; - return; - } - - try - { - - if(datasize < 2) - return; - - ToServerCommand command = (ToServerCommand)readU16(&data[0]); - - if(command == TOSERVER_INIT) - { - // [0] u16 TOSERVER_INIT - // [2] u8 SER_FMT_VER_HIGHEST_READ - // [3] u8[20] player_name - // [23] u8[28] password <--- can be sent without this, from old versions - - if(datasize < 2+1+PLAYERNAME_SIZE) - return; - - RemoteClient* client = getClient(peer_id, CS_Created); - - // If net_proto_version is set, this client has already been handled - if(client->getState() > CS_Created) - { - verbosestream<<"Server: Ignoring multiple TOSERVER_INITs from " - <<addr_s<<" (peer_id="<<peer_id<<")"<<std::endl; - return; - } - - verbosestream<<"Server: Got TOSERVER_INIT from "<<addr_s<<" (peer_id=" - <<peer_id<<")"<<std::endl; - - // Do not allow multiple players in simple singleplayer mode. - // This isn't a perfect way to do it, but will suffice for now - if(m_simple_singleplayer_mode && m_clients.getClientIDs().size() > 1){ - infostream<<"Server: Not allowing another client ("<<addr_s - <<") to connect in simple singleplayer mode"<<std::endl; - DenyAccess(peer_id, L"Running in simple singleplayer mode."); - return; - } - - // First byte after command is maximum supported - // serialization version - u8 client_max = data[2]; - u8 our_max = SER_FMT_VER_HIGHEST_READ; - // Use the highest version supported by both - int deployed = std::min(client_max, our_max); - // If it's lower than the lowest supported, give up. - if(deployed < SER_FMT_CLIENT_VER_LOWEST) - deployed = SER_FMT_VER_INVALID; - - if(deployed == SER_FMT_VER_INVALID) - { - actionstream<<"Server: A mismatched client tried to connect from " - <<addr_s<<std::endl; - infostream<<"Server: Cannot negotiate serialization version with " - <<addr_s<<std::endl; - DenyAccess(peer_id, std::wstring( - L"Your client's version is not supported.\n" - L"Server version is ") - + narrow_to_wide(minetest_version_simple) + L"." - ); - return; - } - - client->setPendingSerializationVersion(deployed); - - /* - Read and check network protocol version - */ - - u16 min_net_proto_version = 0; - if(datasize >= 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2) - min_net_proto_version = readU16(&data[2+1+PLAYERNAME_SIZE+PASSWORD_SIZE]); - - // Use same version as minimum and maximum if maximum version field - // doesn't exist (backwards compatibility) - u16 max_net_proto_version = min_net_proto_version; - if(datasize >= 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2+2) - max_net_proto_version = readU16(&data[2+1+PLAYERNAME_SIZE+PASSWORD_SIZE+2]); - - // Start with client's maximum version - u16 net_proto_version = max_net_proto_version; - - // Figure out a working version if it is possible at all - if(max_net_proto_version >= SERVER_PROTOCOL_VERSION_MIN || - min_net_proto_version <= SERVER_PROTOCOL_VERSION_MAX) - { - // If maximum is larger than our maximum, go with our maximum - if(max_net_proto_version > SERVER_PROTOCOL_VERSION_MAX) - net_proto_version = SERVER_PROTOCOL_VERSION_MAX; - // Else go with client's maximum - else - net_proto_version = max_net_proto_version; - } - - verbosestream<<"Server: "<<addr_s<<": Protocol version: min: " - <<min_net_proto_version<<", max: "<<max_net_proto_version - <<", chosen: "<<net_proto_version<<std::endl; - - client->net_proto_version = net_proto_version; - - if(net_proto_version < SERVER_PROTOCOL_VERSION_MIN || - net_proto_version > SERVER_PROTOCOL_VERSION_MAX) - { - actionstream<<"Server: A mismatched client tried to connect from " - <<addr_s<<std::endl; - DenyAccess(peer_id, std::wstring( - L"Your client's version is not supported.\n" - L"Server version is ") - + narrow_to_wide(minetest_version_simple) + L",\n" - + L"server's PROTOCOL_VERSION is " - + narrow_to_wide(itos(SERVER_PROTOCOL_VERSION_MIN)) - + L"..." - + narrow_to_wide(itos(SERVER_PROTOCOL_VERSION_MAX)) - + L", client's PROTOCOL_VERSION is " - + narrow_to_wide(itos(min_net_proto_version)) - + L"..." - + narrow_to_wide(itos(max_net_proto_version)) - ); - return; - } - - if(g_settings->getBool("strict_protocol_version_checking")) - { - if(net_proto_version != LATEST_PROTOCOL_VERSION) - { - actionstream<<"Server: A mismatched (strict) client tried to " - <<"connect from "<<addr_s<<std::endl; - DenyAccess(peer_id, std::wstring( - L"Your client's version is not supported.\n" - L"Server version is ") - + narrow_to_wide(minetest_version_simple) + L",\n" - + L"server's PROTOCOL_VERSION (strict) is " - + narrow_to_wide(itos(LATEST_PROTOCOL_VERSION)) - + L", client's PROTOCOL_VERSION is " - + narrow_to_wide(itos(min_net_proto_version)) - + L"..." - + narrow_to_wide(itos(max_net_proto_version)) - ); - return; - } - } - - /* - Set up player - */ - char playername[PLAYERNAME_SIZE]; - unsigned int playername_length = 0; - for (; playername_length < PLAYERNAME_SIZE; playername_length++ ) { - playername[playername_length] = data[3+playername_length]; - if (data[3+playername_length] == 0) - break; - } - - if (playername_length == PLAYERNAME_SIZE) { - actionstream<<"Server: Player with name exceeding max length " - <<"tried to connect from "<<addr_s<<std::endl; - DenyAccess(peer_id, L"Name too long"); - return; - } - - - if(playername[0]=='\0') - { - actionstream<<"Server: Player with an empty name " - <<"tried to connect from "<<addr_s<<std::endl; - DenyAccess(peer_id, L"Empty name"); - return; - } - - if(string_allowed(playername, PLAYERNAME_ALLOWED_CHARS)==false) - { - actionstream<<"Server: Player with an invalid name " - <<"tried to connect from "<<addr_s<<std::endl; - DenyAccess(peer_id, L"Name contains unallowed characters"); - return; - } - - if(!isSingleplayer() && strcasecmp(playername, "singleplayer") == 0) - { - actionstream<<"Server: Player with the name \"singleplayer\" " - <<"tried to connect from "<<addr_s<<std::endl; - DenyAccess(peer_id, L"Name is not allowed"); - return; - } - - { - std::string reason; - if(m_script->on_prejoinplayer(playername, addr_s, reason)) - { - actionstream<<"Server: Player with the name \""<<playername<<"\" " - <<"tried to connect from "<<addr_s<<" " - <<"but it was disallowed for the following reason: " - <<reason<<std::endl; - DenyAccess(peer_id, narrow_to_wide(reason)); - return; - } - } - - infostream<<"Server: New connection: \""<<playername<<"\" from " - <<addr_s<<" (peer_id="<<peer_id<<")"<<std::endl; - - // Get password - char given_password[PASSWORD_SIZE]; - if(datasize < 2+1+PLAYERNAME_SIZE+PASSWORD_SIZE) - { - // old version - assume blank password - given_password[0] = 0; - } - else - { - for(u32 i=0; i<PASSWORD_SIZE-1; i++) - { - given_password[i] = data[23+i]; - } - given_password[PASSWORD_SIZE-1] = 0; - } - - if(!base64_is_valid(given_password)){ - actionstream<<"Server: "<<playername - <<" supplied invalid password hash"<<std::endl; - DenyAccess(peer_id, L"Invalid password hash"); - return; - } - - // Enforce user limit. - // Don't enforce for users that have some admin right - if(m_clients.getClientIDs(CS_Created).size() >= g_settings->getU16("max_users") && - !checkPriv(playername, "server") && - !checkPriv(playername, "ban") && - !checkPriv(playername, "privs") && - !checkPriv(playername, "password") && - playername != g_settings->get("name")) - { - actionstream<<"Server: "<<playername<<" tried to join, but there" - <<" are already max_users=" - <<g_settings->getU16("max_users")<<" players."<<std::endl; - DenyAccess(peer_id, L"Too many users."); - return; - } - - std::string checkpwd; // Password hash to check against - bool has_auth = m_script->getAuth(playername, &checkpwd, NULL); - - // If no authentication info exists for user, create it - if(!has_auth){ - if(!isSingleplayer() && - g_settings->getBool("disallow_empty_password") && - std::string(given_password) == ""){ - actionstream<<"Server: "<<playername - <<" supplied empty password"<<std::endl; - DenyAccess(peer_id, L"Empty passwords are " - L"disallowed. Set a password and try again."); - return; - } - std::wstring raw_default_password = - narrow_to_wide(g_settings->get("default_password")); - std::string initial_password = - translatePassword(playername, raw_default_password); - - // If default_password is empty, allow any initial password - if (raw_default_password.length() == 0) - initial_password = given_password; - - m_script->createAuth(playername, initial_password); - } - - has_auth = m_script->getAuth(playername, &checkpwd, NULL); - - if(!has_auth){ - actionstream<<"Server: "<<playername<<" cannot be authenticated" - <<" (auth handler does not work?)"<<std::endl; - DenyAccess(peer_id, L"Not allowed to login"); - return; - } - - if(given_password != checkpwd){ - actionstream<<"Server: "<<playername<<" supplied wrong password" - <<std::endl; - DenyAccess(peer_id, L"Wrong password"); - return; - } - - RemotePlayer *player = - static_cast<RemotePlayer*>(m_env->getPlayer(playername)); - - if(player && player->peer_id != 0){ - errorstream<<"Server: "<<playername<<": Failed to emerge player" - <<" (player allocated to an another client)"<<std::endl; - DenyAccess(peer_id, L"Another client is connected with this " - L"name. If your client closed unexpectedly, try again in " - L"a minute."); - } - - m_clients.setPlayerName(peer_id,playername); - - /* - Answer with a TOCLIENT_INIT - */ - { - SharedBuffer<u8> reply(2+1+6+8+4); - writeU16(&reply[0], TOCLIENT_INIT); - writeU8(&reply[2], deployed); - //send dummy pos for legacy reasons only - writeV3S16(&reply[2+1], floatToInt(v3f(0,0,0), BS)); - writeU64(&reply[2+1+6], m_env->getServerMap().getSeed()); - writeF1000(&reply[2+1+6+8], g_settings->getFloat("dedicated_server_step")); - - // Send as reliable - m_clients.send(peer_id, 0, reply, true); - m_clients.event(peer_id, CSE_Init); - } - - return; - } - - if(command == TOSERVER_INIT2) - { - - verbosestream<<"Server: Got TOSERVER_INIT2 from " - <<peer_id<<std::endl; - - m_clients.event(peer_id, CSE_GotInit2); - u16 protocol_version = m_clients.getProtocolVersion(peer_id); - - - ///// begin compatibility code - PlayerSAO* playersao = NULL; - if (protocol_version <= 22) { - playersao = StageTwoClientInit(peer_id); - - if (playersao == NULL) { - errorstream - << "TOSERVER_INIT2 stage 2 client init failed for peer " - << peer_id << std::endl; - return; - } - } - ///// end compatibility code - - /* - Send some initialization data - */ - - infostream<<"Server: Sending content to " - <<getPlayerName(peer_id)<<std::endl; - - // Send player movement settings - SendMovement(peer_id); - - // Send item definitions - SendItemDef(peer_id, m_itemdef, protocol_version); - - // Send node definitions - SendNodeDef(peer_id, m_nodedef, protocol_version); - - m_clients.event(peer_id, CSE_SetDefinitionsSent); - - // Send media announcement - sendMediaAnnouncement(peer_id); - - // Send detached inventories - sendDetachedInventories(peer_id); - - // Send time of day - u16 time = m_env->getTimeOfDay(); - float time_speed = g_settings->getFloat("time_speed"); - SendTimeOfDay(peer_id, time, time_speed); - - ///// begin compatibility code - if (protocol_version <= 22) { - m_clients.event(peer_id, CSE_SetClientReady); - m_script->on_joinplayer(playersao); - } - ///// end compatibility code - - // Warnings about protocol version can be issued here - if(getClient(peer_id)->net_proto_version < LATEST_PROTOCOL_VERSION) - { - SendChatMessage(peer_id, L"# Server: WARNING: YOUR CLIENT'S " - L"VERSION MAY NOT BE FULLY COMPATIBLE WITH THIS SERVER!"); - } - - return; - } - - u8 peer_ser_ver = getClient(peer_id, CS_InitDone)->serialization_version; - u16 peer_proto_ver = getClient(peer_id, CS_InitDone)->net_proto_version; - - if(peer_ser_ver == SER_FMT_VER_INVALID) - { - errorstream<<"Server::ProcessData(): Cancelling: Peer" - " serialization format invalid or not initialized." - " Skipping incoming command="<<command<<std::endl; + infostream << "Server::ProcessData(): Canceling: peer " + << peer_id << " not found" << std::endl; return; } - /* Handle commands relate to client startup */ - if(command == TOSERVER_REQUEST_MEDIA) { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - std::list<std::string> tosend; - u16 numfiles = readU16(is); - - infostream<<"Sending "<<numfiles<<" files to " - <<getPlayerName(peer_id)<<std::endl; - verbosestream<<"TOSERVER_REQUEST_MEDIA: "<<std::endl; - - for(int i = 0; i < numfiles; i++) { - std::string name = deSerializeString(is); - tosend.push_back(name); - verbosestream<<"TOSERVER_REQUEST_MEDIA: requested file " - <<name<<std::endl; - } - - sendRequestedMedia(peer_id, tosend); - return; - } - else if(command == TOSERVER_RECEIVED_MEDIA) { - return; - } - else if(command == TOSERVER_CLIENT_READY) { - // clients <= protocol version 22 did not send ready message, - // they're already initialized - if (peer_proto_ver <= 22) { - infostream << "Client sent message not expected by a " - << "client using protocol version <= 22," - << "disconnecing peer_id: " << peer_id << std::endl; - m_con.DisconnectPeer(peer_id); - return; - } - - PlayerSAO* playersao = StageTwoClientInit(peer_id); - - if (playersao == NULL) { - errorstream - << "TOSERVER_CLIENT_READY stage 2 client init failed for peer_id: " - << peer_id << std::endl; - m_con.DisconnectPeer(peer_id); - return; - } - - - if(datasize < 2+8) { - errorstream - << "TOSERVER_CLIENT_READY client sent inconsistent data, disconnecting peer_id: " - << peer_id << std::endl; - m_con.DisconnectPeer(peer_id); - return; - } - - m_clients.setClientVersion( - peer_id, - data[2], data[3], data[4], - std::string((char*) &data[8],(u16) data[6])); - - m_clients.event(peer_id, CSE_SetClientReady); - m_script->on_joinplayer(playersao); - - } - else if(command == TOSERVER_GOTBLOCKS) - { - if(datasize < 2+1) - return; - - /* - [0] u16 command - [2] u8 count - [3] v3s16 pos_0 - [3+6] v3s16 pos_1 - ... - */ - - u16 count = data[2]; - for(u16 i=0; i<count; i++) - { - if((s16)datasize < 2+1+(i+1)*6) - throw con::InvalidIncomingDataException - ("GOTBLOCKS length is too short"); - v3s16 p = readV3S16(&data[2+1+i*6]); - /*infostream<<"Server: GOTBLOCKS (" - <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/ - RemoteClient *client = getClient(peer_id); - client->GotBlock(p); - } - return; - } - - if (m_clients.getClientState(peer_id) < CS_Active) - { - if (command == TOSERVER_PLAYERPOS) return; - - errorstream<<"Got packet command: " << command << " for peer id " - << peer_id << " but client isn't active yet. Dropping packet " - <<std::endl; - return; - } - - Player *player = m_env->getPlayer(peer_id); - if(player == NULL) { - errorstream<<"Server::ProcessData(): Cancelling: " - "No player for peer_id="<<peer_id - << " disconnecting peer!" <<std::endl; - m_con.DisconnectPeer(peer_id); - return; - } - - PlayerSAO *playersao = player->getPlayerSAO(); - if(playersao == NULL) { - errorstream<<"Server::ProcessData(): Cancelling: " - "No player object for peer_id="<<peer_id - << " disconnecting peer!" <<std::endl; - m_con.DisconnectPeer(peer_id); - return; - } - - if(command == TOSERVER_PLAYERPOS) - { - if(datasize < 2+12+12+4+4) - return; - - u32 start = 0; - v3s32 ps = readV3S32(&data[start+2]); - v3s32 ss = readV3S32(&data[start+2+12]); - f32 pitch = (f32)readS32(&data[2+12+12]) / 100.0; - f32 yaw = (f32)readS32(&data[2+12+12+4]) / 100.0; - u32 keyPressed = 0; - if(datasize >= 2+12+12+4+4+4) - keyPressed = (u32)readU32(&data[2+12+12+4+4]); - v3f position((f32)ps.X/100., (f32)ps.Y/100., (f32)ps.Z/100.); - v3f speed((f32)ss.X/100., (f32)ss.Y/100., (f32)ss.Z/100.); - pitch = wrapDegrees(pitch); - yaw = wrapDegrees(yaw); - - player->setPosition(position); - player->setSpeed(speed); - player->setPitch(pitch); - player->setYaw(yaw); - player->keyPressed=keyPressed; - player->control.up = (bool)(keyPressed&1); - player->control.down = (bool)(keyPressed&2); - player->control.left = (bool)(keyPressed&4); - player->control.right = (bool)(keyPressed&8); - player->control.jump = (bool)(keyPressed&16); - player->control.aux1 = (bool)(keyPressed&32); - player->control.sneak = (bool)(keyPressed&64); - player->control.LMB = (bool)(keyPressed&128); - player->control.RMB = (bool)(keyPressed&256); - - bool cheated = playersao->checkMovementCheat(); - if(cheated){ - // Call callbacks - m_script->on_cheat(playersao, "moved_too_fast"); - } - - /*infostream<<"Server::ProcessData(): Moved player "<<peer_id<<" to " - <<"("<<position.X<<","<<position.Y<<","<<position.Z<<")" - <<" pitch="<<pitch<<" yaw="<<yaw<<std::endl;*/ - } - else if(command == TOSERVER_DELETEDBLOCKS) - { - if(datasize < 2+1) - return; - - /* - [0] u16 command - [2] u8 count - [3] v3s16 pos_0 - [3+6] v3s16 pos_1 - ... - */ - - u16 count = data[2]; - for(u16 i=0; i<count; i++) - { - if((s16)datasize < 2+1+(i+1)*6) - throw con::InvalidIncomingDataException - ("DELETEDBLOCKS length is too short"); - v3s16 p = readV3S16(&data[2+1+i*6]); - /*infostream<<"Server: DELETEDBLOCKS (" - <<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/ - RemoteClient *client = getClient(peer_id); - client->SetBlockNotSent(p); - } - } - else if(command == TOSERVER_CLICK_OBJECT) - { - infostream<<"Server: CLICK_OBJECT not supported anymore"<<std::endl; - return; - } - else if(command == TOSERVER_CLICK_ACTIVEOBJECT) - { - infostream<<"Server: CLICK_ACTIVEOBJECT not supported anymore"<<std::endl; - return; - } - else if(command == TOSERVER_GROUND_ACTION) - { - infostream<<"Server: GROUND_ACTION not supported anymore"<<std::endl; - return; - - } - else if(command == TOSERVER_RELEASE) - { - infostream<<"Server: RELEASE not supported anymore"<<std::endl; - return; - } - else if(command == TOSERVER_SIGNTEXT) - { - infostream<<"Server: SIGNTEXT not supported anymore" - <<std::endl; - return; - } - else if(command == TOSERVER_SIGNNODETEXT) - { - infostream<<"Server: SIGNNODETEXT not supported anymore" - <<std::endl; - return; - } - else if(command == TOSERVER_INVENTORY_ACTION) - { - // Strip command and create a stream - std::string datastring((char*)&data[2], datasize-2); - verbosestream<<"TOSERVER_INVENTORY_ACTION: data="<<datastring<<std::endl; - std::istringstream is(datastring, std::ios_base::binary); - // Create an action - InventoryAction *a = InventoryAction::deSerialize(is); - if(a == NULL) - { - infostream<<"TOSERVER_INVENTORY_ACTION: " - <<"InventoryAction::deSerialize() returned NULL" - <<std::endl; - return; - } - - // If something goes wrong, this player is to blame - RollbackScopeActor rollback_scope(m_rollback, - std::string("player:")+player->getName()); - - /* - Note: Always set inventory not sent, to repair cases - where the client made a bad prediction. - */ - - /* - Handle restrictions and special cases of the move action - */ - if(a->getType() == IACTION_MOVE) - { - IMoveAction *ma = (IMoveAction*)a; - - ma->from_inv.applyCurrentPlayer(player->getName()); - ma->to_inv.applyCurrentPlayer(player->getName()); - - setInventoryModified(ma->from_inv); - setInventoryModified(ma->to_inv); - - bool from_inv_is_current_player = - (ma->from_inv.type == InventoryLocation::PLAYER) && - (ma->from_inv.name == player->getName()); - - bool to_inv_is_current_player = - (ma->to_inv.type == InventoryLocation::PLAYER) && - (ma->to_inv.name == player->getName()); - - /* - Disable moving items out of craftpreview - */ - if(ma->from_list == "craftpreview") - { - infostream<<"Ignoring IMoveAction from " - <<(ma->from_inv.dump())<<":"<<ma->from_list - <<" to "<<(ma->to_inv.dump())<<":"<<ma->to_list - <<" because src is "<<ma->from_list<<std::endl; - delete a; - return; - } - - /* - Disable moving items into craftresult and craftpreview - */ - if(ma->to_list == "craftpreview" || ma->to_list == "craftresult") - { - infostream<<"Ignoring IMoveAction from " - <<(ma->from_inv.dump())<<":"<<ma->from_list - <<" to "<<(ma->to_inv.dump())<<":"<<ma->to_list - <<" because dst is "<<ma->to_list<<std::endl; - delete a; - return; - } - - // Disallow moving items in elsewhere than player's inventory - // if not allowed to interact - if(!checkPriv(player->getName(), "interact") && - (!from_inv_is_current_player || - !to_inv_is_current_player)) - { - infostream<<"Cannot move outside of player's inventory: " - <<"No interact privilege"<<std::endl; - delete a; - return; - } - } - /* - Handle restrictions and special cases of the drop action - */ - else if(a->getType() == IACTION_DROP) - { - IDropAction *da = (IDropAction*)a; - - da->from_inv.applyCurrentPlayer(player->getName()); - - setInventoryModified(da->from_inv); - - /* - Disable dropping items out of craftpreview - */ - if(da->from_list == "craftpreview") - { - infostream<<"Ignoring IDropAction from " - <<(da->from_inv.dump())<<":"<<da->from_list - <<" because src is "<<da->from_list<<std::endl; - delete a; - return; - } - - // Disallow dropping items if not allowed to interact - if(!checkPriv(player->getName(), "interact")) - { - delete a; - return; - } - } - /* - Handle restrictions and special cases of the craft action - */ - else if(a->getType() == IACTION_CRAFT) - { - ICraftAction *ca = (ICraftAction*)a; - - ca->craft_inv.applyCurrentPlayer(player->getName()); - - setInventoryModified(ca->craft_inv); - - //bool craft_inv_is_current_player = - // (ca->craft_inv.type == InventoryLocation::PLAYER) && - // (ca->craft_inv.name == player->getName()); - - // Disallow crafting if not allowed to interact - if(!checkPriv(player->getName(), "interact")) - { - infostream<<"Cannot craft: " - <<"No interact privilege"<<std::endl; - delete a; - return; - } - } - - // Do the action - a->apply(this, playersao, this); - // Eat the action - delete a; - } - else if(command == TOSERVER_CHAT_MESSAGE) - { - /* - u16 command - u16 length - wstring message - */ - u8 buf[6]; - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - // Read stuff - is.read((char*)buf, 2); - u16 len = readU16(buf); - - std::wstring message; - for(u16 i=0; i<len; i++) - { - is.read((char*)buf, 2); - message += (wchar_t)readU16(buf); - } - - // If something goes wrong, this player is to blame - RollbackScopeActor rollback_scope(m_rollback, - std::string("player:")+player->getName()); - - // Get player name of this client - std::wstring name = narrow_to_wide(player->getName()); - - // Run script hook - bool ate = m_script->on_chat_message(player->getName(), - wide_to_narrow(message)); - // If script ate the message, don't proceed - if(ate) - return; - - // Line to send to players - std::wstring line; - // Whether to send to the player that sent the line - bool send_to_sender_only = false; - - // Commands are implemented in Lua, so only catch invalid - // commands that were not "eaten" and send an error back - if(message[0] == L'/') - { - message = message.substr(1); - send_to_sender_only = true; - if(message.length() == 0) - line += L"-!- Empty command"; - else - line += L"-!- Invalid command: " + str_split(message, L' ')[0]; - } - else - { - if(checkPriv(player->getName(), "shout")){ - line += L"<"; - line += name; - line += L"> "; - line += message; - } else { - line += L"-!- You don't have permission to shout."; - send_to_sender_only = true; - } - } - - if(line != L"") - { - /* - Send the message to sender - */ - if (send_to_sender_only) - { - SendChatMessage(peer_id, line); - } - /* - Send the message to others - */ - else - { - actionstream<<"CHAT: "<<wide_to_narrow(line)<<std::endl; - - std::list<u16> clients = m_clients.getClientIDs(); - - for(std::list<u16>::iterator - i = clients.begin(); - i != clients.end(); ++i) - { - if (*i != peer_id) - SendChatMessage(*i, line); - } - } - } - } - else if(command == TOSERVER_DAMAGE) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - u8 damage = readU8(is); - - if(g_settings->getBool("enable_damage")) - { - actionstream<<player->getName()<<" damaged by " - <<(int)damage<<" hp at "<<PP(player->getPosition()/BS) - <<std::endl; - - playersao->setHP(playersao->getHP() - damage); - - if(playersao->getHP() == 0 && playersao->m_hp_not_sent) - DiePlayer(peer_id); - - if(playersao->m_hp_not_sent) - SendPlayerHP(peer_id); - } - } - else if(command == TOSERVER_BREATH) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - u16 breath = readU16(is); - playersao->setBreath(breath); - m_script->player_event(playersao,"breath_changed"); - } - else if(command == TOSERVER_PASSWORD) - { - /* - [0] u16 TOSERVER_PASSWORD - [2] u8[28] old password - [30] u8[28] new password - */ + try { + ToServerCommand command = (ToServerCommand) pkt->getCommand(); - if(datasize != 2+PASSWORD_SIZE*2) + // Command must be handled into ToServerCommandHandler + if (command >= TOSERVER_NUM_MSG_TYPES) { + infostream << "Server: Ignoring unknown command " + << command << std::endl; return; - /*char password[PASSWORD_SIZE]; - for(u32 i=0; i<PASSWORD_SIZE-1; i++) - password[i] = data[2+i]; - password[PASSWORD_SIZE-1] = 0;*/ - std::string oldpwd; - for(u32 i=0; i<PASSWORD_SIZE-1; i++) - { - char c = data[2+i]; - if(c == 0) - break; - oldpwd += c; - } - std::string newpwd; - for(u32 i=0; i<PASSWORD_SIZE-1; i++) - { - char c = data[2+PASSWORD_SIZE+i]; - if(c == 0) - break; - newpwd += c; } - if(!base64_is_valid(newpwd)){ - infostream<<"Server: "<<player->getName()<<" supplied invalid password hash"<<std::endl; - // Wrong old password supplied!! - SendChatMessage(peer_id, L"Invalid new password hash supplied. Password NOT changed."); + if (toServerCommandTable[command].state == TOSERVER_STATE_NOT_CONNECTED) { + handleCommand(pkt); return; } - infostream<<"Server: Client requests a password change from " - <<"'"<<oldpwd<<"' to '"<<newpwd<<"'"<<std::endl; + u8 peer_ser_ver = getClient(peer_id, CS_InitDone)->serialization_version; - std::string playername = player->getName(); - - std::string checkpwd; - m_script->getAuth(playername, &checkpwd, NULL); - - if(oldpwd != checkpwd) - { - infostream<<"Server: invalid old password"<<std::endl; - // Wrong old password supplied!! - SendChatMessage(peer_id, L"Invalid old password supplied. Password NOT changed."); + if(peer_ser_ver == SER_FMT_VER_INVALID) { + errorstream << "Server::ProcessData(): Cancelling: Peer" + " serialization format invalid or not initialized." + " Skipping incoming command=" << command << std::endl; return; } - bool success = m_script->setPassword(playername, newpwd); - if(success){ - actionstream<<player->getName()<<" changes password"<<std::endl; - SendChatMessage(peer_id, L"Password change successful."); - } else { - actionstream<<player->getName()<<" tries to change password but " - <<"it fails"<<std::endl; - SendChatMessage(peer_id, L"Password change failed or inavailable."); - } - } - else if(command == TOSERVER_PLAYERITEM) - { - if (datasize < 2+2) - return; - - u16 item = readU16(&data[2]); - playersao->setWieldIndex(item); - } - else if(command == TOSERVER_RESPAWN) - { - if(player->hp != 0 || !g_settings->getBool("enable_damage")) + /* Handle commands related to client startup */ + if (toServerCommandTable[command].state == TOSERVER_STATE_STARTUP) { + handleCommand(pkt); return; - - RespawnPlayer(peer_id); - - actionstream<<player->getName()<<" respawns at " - <<PP(player->getPosition()/BS)<<std::endl; - - // ActiveObject is added to environment in AsyncRunStep after - // the previous addition has been succesfully removed - } - else if(command == TOSERVER_INTERACT) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - /* - [0] u16 command - [2] u8 action - [3] u16 item - [5] u32 length of the next item - [9] serialized PointedThing - 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 - */ - u8 action = readU8(is); - u16 item_i = readU16(is); - std::istringstream tmp_is(deSerializeLongString(is), std::ios::binary); - PointedThing pointed; - pointed.deSerialize(tmp_is); - - verbosestream<<"TOSERVER_INTERACT: action="<<(int)action<<", item=" - <<item_i<<", pointed="<<pointed.dump()<<std::endl; - - if(player->hp == 0) - { - verbosestream<<"TOSERVER_INTERACT: "<<player->getName() - <<" tried to interact, but is dead!"<<std::endl; - return; - } - - v3f player_pos = playersao->getLastGoodPosition(); - - // Update wielded item - playersao->setWieldIndex(item_i); - - // Get pointed to node (undefined if not POINTEDTYPE_NODE) - v3s16 p_under = pointed.node_undersurface; - v3s16 p_above = pointed.node_abovesurface; - - // Get pointed to object (NULL if not POINTEDTYPE_OBJECT) - ServerActiveObject *pointed_object = NULL; - if(pointed.type == POINTEDTHING_OBJECT) - { - pointed_object = m_env->getActiveObject(pointed.object_id); - if(pointed_object == NULL) - { - verbosestream<<"TOSERVER_INTERACT: " - "pointed object is NULL"<<std::endl; - return; - } - - } - - v3f pointed_pos_under = player_pos; - v3f pointed_pos_above = player_pos; - if(pointed.type == POINTEDTHING_NODE) - { - pointed_pos_under = intToFloat(p_under, BS); - pointed_pos_above = intToFloat(p_above, BS); - } - else if(pointed.type == POINTEDTHING_OBJECT) - { - pointed_pos_under = pointed_object->getBasePosition(); - pointed_pos_above = pointed_pos_under; } - /* - Check that target is reasonably close - (only when digging or placing things) - */ - if(action == 0 || action == 2 || action == 3) - { - float d = player_pos.getDistanceFrom(pointed_pos_under); - float max_d = BS * 14; // Just some large enough value - if(d > max_d){ - actionstream<<"Player "<<player->getName() - <<" tried to access "<<pointed.dump() - <<" from too far: " - <<"d="<<d<<", max_d="<<max_d - <<". ignoring."<<std::endl; - // Re-send block to revert change on client-side - RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); - client->SetBlockNotSent(blockpos); - // Call callbacks - m_script->on_cheat(playersao, "interacted_too_far"); - // Do nothing else - return; - } - } + if (m_clients.getClientState(peer_id) < CS_Active) { + if (command == TOSERVER_PLAYERPOS) return; - /* - 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; - // Re-send block to revert change on client-side - RemoteClient *client = getClient(peer_id); - // Digging completed -> under - if(action == 2){ - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); - client->SetBlockNotSent(blockpos); - } - // Placement -> above - if(action == 3){ - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS)); - client->SetBlockNotSent(blockpos); - } + errorstream << "Got packet command: " << command << " for peer id " + << peer_id << " but client isn't active yet. Dropping packet " + << std::endl; return; } - /* - If something goes wrong, this player is to blame - */ - RollbackScopeActor rollback_scope(m_rollback, - std::string("player:")+player->getName()); - - /* - 0: start digging or punch object - */ - if(action == 0) - { - if(pointed.type == POINTEDTHING_NODE) - { - /* - NOTE: This can be used in the future to check if - somebody is cheating, by checking the timing. - */ - MapNode n(CONTENT_IGNORE); - bool pos_ok; - n = m_env->getMap().getNodeNoEx(p_under, &pos_ok); - if (pos_ok) - n = m_env->getMap().getNodeNoEx(p_under, &pos_ok); - - if (!pos_ok) { - infostream<<"Server: Not punching: Node not found." - <<" Adding block to emerge queue." - <<std::endl; - m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), false); - } - - if(n.getContent() != CONTENT_IGNORE) - m_script->node_on_punch(p_under, n, playersao, pointed); - // Cheat prevention - playersao->noCheatDigStart(p_under); - } - else if(pointed.type == POINTEDTHING_OBJECT) - { - // Skip if object has been removed - if(pointed_object->m_removed) - return; - - actionstream<<player->getName()<<" punches object " - <<pointed.object_id<<": " - <<pointed_object->getDescription()<<std::endl; - - ItemStack punchitem = playersao->getWieldedItem(); - ToolCapabilities toolcap = - punchitem.getToolCapabilities(m_itemdef); - v3f dir = (pointed_object->getBasePosition() - - (player->getPosition() + player->getEyeOffset()) - ).normalize(); - float time_from_last_punch = - playersao->resetTimeFromLastPunch(); - pointed_object->punch(dir, &toolcap, playersao, - time_from_last_punch); - } - - } // action == 0 - - /* - 1: stop digging - */ - else if(action == 1) - { - } // action == 1 - - /* - 2: Digging completed - */ - else if(action == 2) - { - // Only digging of nodes - if(pointed.type == POINTEDTHING_NODE) - { - bool pos_ok; - MapNode n = m_env->getMap().getNodeNoEx(p_under, &pos_ok); - if (!pos_ok) { - infostream << "Server: Not finishing digging: Node not found." - << " Adding block to emerge queue." - << std::endl; - m_emerge->enqueueBlockEmerge(peer_id, getNodeBlockPos(p_above), false); - } - - /* Cheat prevention */ - bool is_valid_dig = true; - if(!isSingleplayer() && !g_settings->getBool("disable_anticheat")) - { - v3s16 nocheat_p = playersao->getNoCheatDigPos(); - float nocheat_t = playersao->getNoCheatDigTime(); - playersao->noCheatDigEnd(); - // If player didn't start digging this, ignore dig - if(nocheat_p != p_under){ - infostream<<"Server: NoCheat: "<<player->getName() - <<" started digging " - <<PP(nocheat_p)<<" and completed digging " - <<PP(p_under)<<"; not digging."<<std::endl; - is_valid_dig = false; - // Call callbacks - m_script->on_cheat(playersao, "finished_unknown_dig"); - } - // Get player's wielded item - ItemStack playeritem; - InventoryList *mlist = playersao->getInventory()->getList("main"); - if(mlist != NULL) - playeritem = mlist->getItem(playersao->getWieldIndex()); - ToolCapabilities playeritem_toolcap = - playeritem.getToolCapabilities(m_itemdef); - // Get diggability and expected digging time - DigParams params = getDigParams(m_nodedef->get(n).groups, - &playeritem_toolcap); - // If can't dig, try hand - if(!params.diggable){ - const ItemDefinition &hand = m_itemdef->get(""); - const ToolCapabilities *tp = hand.tool_capabilities; - if(tp) - params = getDigParams(m_nodedef->get(n).groups, tp); - } - // If can't dig, ignore dig - if(!params.diggable){ - infostream<<"Server: NoCheat: "<<player->getName() - <<" completed digging "<<PP(p_under) - <<", which is not diggable with tool. not digging." - <<std::endl; - is_valid_dig = false; - // Call callbacks - m_script->on_cheat(playersao, "dug_unbreakable"); - } - // Check digging time - // If already invalidated, we don't have to - if(!is_valid_dig){ - // Well not our problem then - } - // Clean and long dig - else if(params.time > 2.0 && nocheat_t * 1.2 > params.time){ - // All is good, but grab time from pool; don't care if - // it's actually available - playersao->getDigPool().grab(params.time); - } - // Short or laggy dig - // Try getting the time from pool - else if(playersao->getDigPool().grab(params.time)){ - // All is good - } - // Dig not possible - else{ - infostream<<"Server: NoCheat: "<<player->getName() - <<" completed digging "<<PP(p_under) - <<"too fast; not digging."<<std::endl; - is_valid_dig = false; - // Call callbacks - m_script->on_cheat(playersao, "dug_too_fast"); - } - } - - /* Actually dig node */ - - if(is_valid_dig && n.getContent() != CONTENT_IGNORE) - m_script->node_on_dig(p_under, n, playersao); - - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); - RemoteClient *client = getClient(peer_id); - // Send unusual result (that is, node not being removed) - if(m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR) - { - // Re-send block to revert change on client-side - client->SetBlockNotSent(blockpos); - } - else { - client->ResendBlockIfOnWire(blockpos); - } - } - } // action == 2 - - /* - 3: place block or right-click object - */ - else if(action == 3) - { - ItemStack item = playersao->getWieldedItem(); - - // Reset build time counter - if(pointed.type == POINTEDTHING_NODE && - item.getDefinition(m_itemdef).type == ITEM_NODE) - getClient(peer_id)->m_time_from_building = 0.0; - - if(pointed.type == POINTEDTHING_OBJECT) - { - // Right click object - - // Skip if object has been removed - if(pointed_object->m_removed) - return; - - actionstream<<player->getName()<<" right-clicks object " - <<pointed.object_id<<": " - <<pointed_object->getDescription()<<std::endl; - - // Do stuff - pointed_object->rightClick(playersao); - } - else if(m_script->item_OnPlace( - item, playersao, pointed)) - { - // Placement was handled in lua - - // Apply returned ItemStack - playersao->setWieldedItem(item); - } - - // If item has node placement prediction, always send the - // blocks to make sure the client knows what exactly happened - RemoteClient *client = getClient(peer_id); - v3s16 blockpos = getNodeBlockPos(floatToInt(pointed_pos_above, BS)); - v3s16 blockpos2 = getNodeBlockPos(floatToInt(pointed_pos_under, BS)); - if(item.getDefinition(m_itemdef).node_placement_prediction != "") { - client->SetBlockNotSent(blockpos); - if(blockpos2 != blockpos) { - client->SetBlockNotSent(blockpos2); - } - } - else { - client->ResendBlockIfOnWire(blockpos); - if(blockpos2 != blockpos) { - client->ResendBlockIfOnWire(blockpos2); - } - } - } // action == 3 - - /* - 4: use - */ - else if(action == 4) - { - ItemStack item = playersao->getWieldedItem(); - - actionstream<<player->getName()<<" uses "<<item.name - <<", pointing at "<<pointed.dump()<<std::endl; - - if(m_script->item_OnUse( - item, playersao, pointed)) - { - // Apply returned ItemStack - playersao->setWieldedItem(item); - } - - } // action == 4 - - - /* - Catch invalid actions - */ - else - { - infostream<<"WARNING: Server: Invalid action " - <<action<<std::endl; - } - } - else if(command == TOSERVER_REMOVED_SOUNDS) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - int num = readU16(is); - for(int k=0; k<num; k++){ - s32 id = readS32(is); - std::map<s32, ServerPlayingSound>::iterator i = - m_playing_sounds.find(id); - if(i == m_playing_sounds.end()) - continue; - ServerPlayingSound &psound = i->second; - psound.clients.erase(peer_id); - if(psound.clients.empty()) - m_playing_sounds.erase(i++); - } - } - else if(command == TOSERVER_NODEMETA_FIELDS) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - v3s16 p = readV3S16(is); - std::string formname = deSerializeString(is); - int num = readU16(is); - std::map<std::string, std::string> fields; - for(int k=0; k<num; k++){ - std::string fieldname = deSerializeString(is); - std::string fieldvalue = deSerializeLongString(is); - fields[fieldname] = fieldvalue; - } - - // If something goes wrong, this player is to blame - RollbackScopeActor rollback_scope(m_rollback, - std::string("player:")+player->getName()); - - // Check the target node for rollback data; leave others unnoticed - RollbackNode rn_old(&m_env->getMap(), p, this); - - m_script->node_on_receive_fields(p, formname, fields,playersao); - - // Report rollback data - RollbackNode rn_new(&m_env->getMap(), p, this); - if(rollback() && rn_new != rn_old){ - RollbackAction action; - action.setSetNode(p, rn_old, rn_new); - rollback()->reportAction(action); - } - } - else if(command == TOSERVER_INVENTORY_FIELDS) - { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - - std::string formname = deSerializeString(is); - int num = readU16(is); - std::map<std::string, std::string> fields; - for(int k=0; k<num; k++){ - std::string fieldname = deSerializeString(is); - std::string fieldvalue = deSerializeLongString(is); - fields[fieldname] = fieldvalue; - } - - m_script->on_playerReceiveFields(playersao, formname, fields); - } - else - { - infostream<<"Server::ProcessData(): Ignoring " - "unknown command "<<command<<std::endl; - } - - } //try - catch(SendFailedException &e) - { - errorstream<<"Server::ProcessData(): SendFailedException: " - <<"what="<<e.what() - <<std::endl; + handleCommand(pkt); + } catch (SendFailedException &e) { + errorstream << "Server::ProcessData(): SendFailedException: " + << "what=" << e.what() + << std::endl; + } catch (PacketError &e) { + actionstream << "Server::ProcessData(): PacketError: " + << "what=" << e.what() + << std::endl; } } @@ -2752,24 +1265,20 @@ void Server::setTimeOfDay(u32 time) void Server::onMapEditEvent(MapEditEvent *event) { - //infostream<<"Server::onMapEditEvent()"<<std::endl; if(m_ignore_map_edit_events) return; if(m_ignore_map_edit_events_area.contains(event->getArea())) return; MapEditEvent *e = event->clone(); - m_unsent_map_edit_queue.push_back(e); + m_unsent_map_edit_queue.push(e); } Inventory* Server::getInventory(const InventoryLocation &loc) { - switch(loc.type){ + switch (loc.type) { case InventoryLocation::UNDEFINED: - {} - break; case InventoryLocation::CURRENT_PLAYER: - {} - break; + break; case InventoryLocation::PLAYER: { Player *player = m_env->getPlayer(loc.name.c_str()); @@ -2780,7 +1289,7 @@ Inventory* Server::getInventory(const InventoryLocation &loc) return NULL; return playersao->getInventory(); } - break; + break; case InventoryLocation::NODEMETA: { NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p); @@ -2788,37 +1297,40 @@ Inventory* Server::getInventory(const InventoryLocation &loc) return NULL; return meta->getInventory(); } - break; + break; case InventoryLocation::DETACHED: { if(m_detached_inventories.count(loc.name) == 0) return NULL; return m_detached_inventories[loc.name]; } - break; + break; default: - assert(0); + sanity_check(false); // abort + break; } return NULL; } -void Server::setInventoryModified(const InventoryLocation &loc) +void Server::setInventoryModified(const InventoryLocation &loc, bool playerSend) { switch(loc.type){ case InventoryLocation::UNDEFINED: - {} - break; + break; case InventoryLocation::PLAYER: { + if (!playerSend) + return; + Player *player = m_env->getPlayer(loc.name.c_str()); if(!player) return; PlayerSAO *playersao = player->getPlayerSAO(); if(!playersao) return; - playersao->m_inventory_not_sent = true; - playersao->m_wielded_item_not_sent = true; + + SendInventory(playersao); } - break; + break; case InventoryLocation::NODEMETA: { v3s16 blockpos = getNodeBlockPos(loc.p); @@ -2829,29 +1341,28 @@ void Server::setInventoryModified(const InventoryLocation &loc) setBlockNotSent(blockpos); } - break; + break; case InventoryLocation::DETACHED: { sendDetachedInventory(loc.name,PEER_ID_INEXISTENT); } - break; + break; default: - assert(0); + sanity_check(false); // abort + break; } } void Server::SetBlocksNotSent(std::map<v3s16, MapBlock *>& block) { - std::list<u16> clients = m_clients.getClientIDs(); + std::vector<u16> clients = m_clients.getClientIDs(); m_clients.Lock(); // Set the modified blocks unsent for all the clients - for (std::list<u16>::iterator - i = clients.begin(); + for (std::vector<u16>::iterator i = clients.begin(); i != clients.end(); ++i) { - RemoteClient *client = m_clients.lockedGetClientNoEx(*i); - if (client != NULL) + if (RemoteClient *client = m_clients.lockedGetClientNoEx(*i)) client->SetBlocksNotSent(block); - } + } m_clients.Unlock(); } @@ -2865,7 +1376,7 @@ void Server::peerAdded(con::Peer *peer) c.type = con::PEER_ADDED; c.peer_id = peer->id; c.timeout = false; - m_peer_change_queue.push_back(c); + m_peer_change_queue.push(c); } void Server::deletingPeer(con::Peer *peer, bool timeout) @@ -2879,7 +1390,7 @@ void Server::deletingPeer(con::Peer *peer, bool timeout) c.type = con::PEER_REMOVED; c.peer_id = peer->id; c.timeout = timeout; - m_peer_change_queue.push_back(c); + m_peer_change_queue.push(c); } bool Server::getClientConInfo(u16 peer_id, con::rtt_stat_type type, float* retval) @@ -2928,7 +1439,8 @@ void Server::handlePeerChanges() { while(m_peer_change_queue.size() > 0) { - con::PeerChange c = m_peer_change_queue.pop_front(); + con::PeerChange c = m_peer_change_queue.front(); + m_peer_change_queue.pop(); verbosestream<<"Server: Handling peer change: " <<"id="<<c.peer_id<<", timeout="<<c.timeout @@ -2945,219 +1457,197 @@ void Server::handlePeerChanges() break; default: - assert("Invalid peer change event received!" == 0); + FATAL_ERROR("Invalid peer change event received!"); break; } } } +void Server::Send(NetworkPacket* pkt) +{ + m_clients.send(pkt->getPeerId(), + clientCommandFactoryTable[pkt->getCommand()].channel, + pkt, + clientCommandFactoryTable[pkt->getCommand()].reliable); +} + void Server::SendMovement(u16 peer_id) { DSTACK(__FUNCTION_NAME); std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_MOVEMENT); - writeF1000(os, g_settings->getFloat("movement_acceleration_default")); - writeF1000(os, g_settings->getFloat("movement_acceleration_air")); - writeF1000(os, g_settings->getFloat("movement_acceleration_fast")); - writeF1000(os, g_settings->getFloat("movement_speed_walk")); - writeF1000(os, g_settings->getFloat("movement_speed_crouch")); - writeF1000(os, g_settings->getFloat("movement_speed_fast")); - writeF1000(os, g_settings->getFloat("movement_speed_climb")); - writeF1000(os, g_settings->getFloat("movement_speed_jump")); - writeF1000(os, g_settings->getFloat("movement_liquid_fluidity")); - writeF1000(os, g_settings->getFloat("movement_liquid_fluidity_smooth")); - writeF1000(os, g_settings->getFloat("movement_liquid_sink")); - writeF1000(os, g_settings->getFloat("movement_gravity")); + NetworkPacket pkt(TOCLIENT_MOVEMENT, 12 * sizeof(float), peer_id); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + pkt << g_settings->getFloat("movement_acceleration_default"); + pkt << g_settings->getFloat("movement_acceleration_air"); + pkt << g_settings->getFloat("movement_acceleration_fast"); + pkt << g_settings->getFloat("movement_speed_walk"); + pkt << g_settings->getFloat("movement_speed_crouch"); + pkt << g_settings->getFloat("movement_speed_fast"); + pkt << g_settings->getFloat("movement_speed_climb"); + pkt << g_settings->getFloat("movement_speed_jump"); + pkt << g_settings->getFloat("movement_liquid_fluidity"); + pkt << g_settings->getFloat("movement_liquid_fluidity_smooth"); + pkt << g_settings->getFloat("movement_liquid_sink"); + pkt << g_settings->getFloat("movement_gravity"); + + Send(&pkt); +} + +void Server::SendPlayerHPOrDie(PlayerSAO *playersao) +{ + if (!g_settings->getBool("enable_damage")) + return; + + u16 peer_id = playersao->getPeerID(); + bool is_alive = playersao->getHP() > 0; + + if (is_alive) + SendPlayerHP(peer_id); + else + DiePlayer(peer_id); } void Server::SendHP(u16 peer_id, u8 hp) { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - - writeU16(os, TOCLIENT_HP); - writeU8(os, hp); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + NetworkPacket pkt(TOCLIENT_HP, 1, peer_id); + pkt << hp; + Send(&pkt); } void Server::SendBreath(u16 peer_id, u16 breath) { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_BREATH); - writeU16(os, breath); + NetworkPacket pkt(TOCLIENT_BREATH, 2, peer_id); + pkt << (u16) breath; + Send(&pkt); +} - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); +void Server::SendAccessDenied(u16 peer_id, AccessDeniedCode reason, + const std::string &custom_reason, bool reconnect) +{ + assert(reason < SERVER_ACCESSDENIED_MAX); + + NetworkPacket pkt(TOCLIENT_ACCESS_DENIED, 1, peer_id); + pkt << (u8)reason; + if (reason == SERVER_ACCESSDENIED_CUSTOM_STRING) + pkt << custom_reason; + else if (reason == SERVER_ACCESSDENIED_SHUTDOWN || + reason == SERVER_ACCESSDENIED_CRASH) + pkt << custom_reason << (u8)reconnect; + Send(&pkt); } -void Server::SendAccessDenied(u16 peer_id,const std::wstring &reason) +void Server::SendAccessDenied_Legacy(u16 peer_id,const std::wstring &reason) { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - - writeU16(os, TOCLIENT_ACCESS_DENIED); - os<<serializeWideString(reason); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + NetworkPacket pkt(TOCLIENT_ACCESS_DENIED_LEGACY, 0, peer_id); + pkt << reason; + Send(&pkt); } void Server::SendDeathscreen(u16 peer_id,bool set_camera_point_target, v3f camera_point_target) { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - - writeU16(os, TOCLIENT_DEATHSCREEN); - writeU8(os, set_camera_point_target); - writeV3F1000(os, camera_point_target); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + NetworkPacket pkt(TOCLIENT_DEATHSCREEN, 1 + sizeof(v3f), peer_id); + pkt << set_camera_point_target << camera_point_target; + Send(&pkt); } void Server::SendItemDef(u16 peer_id, IItemDefManager *itemdef, u16 protocol_version) { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); + + NetworkPacket pkt(TOCLIENT_ITEMDEF, 0, peer_id); /* u16 command u32 length of the next item zlib-compressed serialized ItemDefManager */ - writeU16(os, TOCLIENT_ITEMDEF); std::ostringstream tmp_os(std::ios::binary); itemdef->serialize(tmp_os, protocol_version); std::ostringstream tmp_os2(std::ios::binary); compressZlib(tmp_os.str(), tmp_os2); - os<<serializeLongString(tmp_os2.str()); + pkt.putLongString(tmp_os2.str()); // Make data buffer - std::string s = os.str(); - verbosestream<<"Server: Sending item definitions to id("<<peer_id - <<"): size="<<s.size()<<std::endl; - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + verbosestream << "Server: Sending item definitions to id(" << peer_id + << "): size=" << pkt.getSize() << std::endl; + + Send(&pkt); } void Server::SendNodeDef(u16 peer_id, INodeDefManager *nodedef, u16 protocol_version) { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); + + NetworkPacket pkt(TOCLIENT_NODEDEF, 0, peer_id); /* u16 command u32 length of the next item zlib-compressed serialized NodeDefManager */ - writeU16(os, TOCLIENT_NODEDEF); std::ostringstream tmp_os(std::ios::binary); nodedef->serialize(tmp_os, protocol_version); std::ostringstream tmp_os2(std::ios::binary); compressZlib(tmp_os.str(), tmp_os2); - os<<serializeLongString(tmp_os2.str()); + + pkt.putLongString(tmp_os2.str()); // Make data buffer - std::string s = os.str(); - verbosestream<<"Server: Sending node definitions to id("<<peer_id - <<"): size="<<s.size()<<std::endl; - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + verbosestream << "Server: Sending node definitions to id(" << peer_id + << "): size=" << pkt.getSize() << std::endl; + + Send(&pkt); } /* Non-static send methods */ -void Server::SendInventory(u16 peer_id) +void Server::SendInventory(PlayerSAO* playerSAO) { DSTACK(__FUNCTION_NAME); - PlayerSAO *playersao = getPlayerSAO(peer_id); - assert(playersao); - - playersao->m_inventory_not_sent = false; + UpdateCrafting(playerSAO->getPlayer()); /* Serialize it */ + NetworkPacket pkt(TOCLIENT_INVENTORY, 0, playerSAO->getPeerID()); + std::ostringstream os; - playersao->getInventory()->serialize(os); + playerSAO->getInventory()->serialize(os); std::string s = os.str(); - SharedBuffer<u8> data(s.size()+2); - writeU16(&data[0], TOCLIENT_INVENTORY); - memcpy(&data[2], s.c_str(), s.size()); - - // Send as reliable - m_clients.send(peer_id, 0, data, true); + pkt.putRawString(s.c_str(), s.size()); + Send(&pkt); } void Server::SendChatMessage(u16 peer_id, const std::wstring &message) { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - u8 buf[12]; - - // Write command - writeU16(buf, TOCLIENT_CHAT_MESSAGE); - os.write((char*)buf, 2); + NetworkPacket pkt(TOCLIENT_CHAT_MESSAGE, 0, peer_id); + pkt << message; - // Write length - writeU16(buf, message.size()); - os.write((char*)buf, 2); - - // Write string - for(u32 i=0; i<message.size(); i++) - { - u16 w = message[i]; - writeU16(buf, w); - os.write((char*)buf, 2); - } - - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - - if (peer_id != PEER_ID_INEXISTENT) - { - // Send as reliable - m_clients.send(peer_id, 0, data, true); + if (peer_id != PEER_ID_INEXISTENT) { + Send(&pkt); } - else - { - m_clients.sendToAll(0,data,true); + else { + m_clients.sendToAll(0, &pkt, true); } } @@ -3166,21 +1656,12 @@ void Server::SendShowFormspecMessage(u16 peer_id, const std::string &formspec, { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - u8 buf[12]; + NetworkPacket pkt(TOCLIENT_SHOW_FORMSPEC, 0 , peer_id); + pkt.putLongString(FORMSPEC_VERSION_STRING + formspec); + pkt << formname; - // Write command - writeU16(buf, TOCLIENT_SHOW_FORMSPEC); - os.write((char*)buf, 2); - os<<serializeLongString(FORMSPEC_VERSION_STRING + formspec); - os<<serializeString(formname); - - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + Send(&pkt); } // Spawns a particle on peer with peer_id @@ -3190,29 +1671,18 @@ void Server::SendSpawnParticle(u16 peer_id, v3f pos, v3f velocity, v3f accelerat { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_SPAWN_PARTICLE); - writeV3F1000(os, pos); - writeV3F1000(os, velocity); - writeV3F1000(os, acceleration); - writeF1000(os, expirationtime); - writeF1000(os, size); - writeU8(os, collisiondetection); - os<<serializeLongString(texture); - writeU8(os, vertical); + NetworkPacket pkt(TOCLIENT_SPAWN_PARTICLE, 0, peer_id); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); + pkt << pos << velocity << acceleration << expirationtime + << size << collisiondetection; + pkt.putLongString(texture); + pkt << vertical; - if (peer_id != PEER_ID_INEXISTENT) - { - // Send as reliable - m_clients.send(peer_id, 0, data, true); + if (peer_id != PEER_ID_INEXISTENT) { + Send(&pkt); } - else - { - m_clients.sendToAll(0,data,true); + else { + m_clients.sendToAll(0, &pkt, true); } } @@ -3223,37 +1693,21 @@ void Server::SendAddParticleSpawner(u16 peer_id, u16 amount, float spawntime, v3 { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_ADD_PARTICLESPAWNER); - - writeU16(os, amount); - writeF1000(os, spawntime); - writeV3F1000(os, minpos); - writeV3F1000(os, maxpos); - writeV3F1000(os, minvel); - writeV3F1000(os, maxvel); - writeV3F1000(os, minacc); - writeV3F1000(os, maxacc); - writeF1000(os, minexptime); - writeF1000(os, maxexptime); - writeF1000(os, minsize); - writeF1000(os, maxsize); - writeU8(os, collisiondetection); - os<<serializeLongString(texture); - writeU32(os, id); - writeU8(os, vertical); + NetworkPacket pkt(TOCLIENT_ADD_PARTICLESPAWNER, 0, peer_id); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); + pkt << amount << spawntime << minpos << maxpos << minvel << maxvel + << minacc << maxacc << minexptime << maxexptime << minsize + << maxsize << collisiondetection; - if (peer_id != PEER_ID_INEXISTENT) - { - // Send as reliable - m_clients.send(peer_id, 0, data, true); + pkt.putLongString(texture); + + pkt << id << vertical; + + if (peer_id != PEER_ID_INEXISTENT) { + Send(&pkt); } else { - m_clients.sendToAll(0,data,true); + m_clients.sendToAll(0, &pkt, true); } } @@ -3261,197 +1715,124 @@ void Server::SendDeleteParticleSpawner(u16 peer_id, u32 id) { DSTACK(__FUNCTION_NAME); - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_DELETE_PARTICLESPAWNER); - - writeU16(os, id); + NetworkPacket pkt(TOCLIENT_DELETE_PARTICLESPAWNER_LEGACY, 2, peer_id); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); + // Ugly error in this packet + pkt << (u16) id; if (peer_id != PEER_ID_INEXISTENT) { - // Send as reliable - m_clients.send(peer_id, 0, data, true); + Send(&pkt); } else { - m_clients.sendToAll(0,data,true); + m_clients.sendToAll(0, &pkt, true); } } void Server::SendHUDAdd(u16 peer_id, u32 id, HudElement *form) { - std::ostringstream os(std::ios_base::binary); + NetworkPacket pkt(TOCLIENT_HUDADD, 0 , peer_id); - // Write command - writeU16(os, TOCLIENT_HUDADD); - writeU32(os, id); - writeU8(os, (u8)form->type); - writeV2F1000(os, form->pos); - os << serializeString(form->name); - writeV2F1000(os, form->scale); - os << serializeString(form->text); - writeU32(os, form->number); - writeU32(os, form->item); - writeU32(os, form->dir); - writeV2F1000(os, form->align); - writeV2F1000(os, form->offset); - writeV3F1000(os, form->world_pos); - writeV2S32(os,form->size); + pkt << id << (u8) form->type << form->pos << form->name << form->scale + << form->text << form->number << form->item << form->dir + << form->align << form->offset << form->world_pos << form->size; - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 1, data, true); + Send(&pkt); } void Server::SendHUDRemove(u16 peer_id, u32 id) { - std::ostringstream os(std::ios_base::binary); - - // Write command - writeU16(os, TOCLIENT_HUDRM); - writeU32(os, id); - - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - - m_clients.send(peer_id, 1, data, true); + NetworkPacket pkt(TOCLIENT_HUDRM, 4, peer_id); + pkt << id; + Send(&pkt); } void Server::SendHUDChange(u16 peer_id, u32 id, HudElementStat stat, void *value) { - std::ostringstream os(std::ios_base::binary); + NetworkPacket pkt(TOCLIENT_HUDCHANGE, 0, peer_id); + pkt << id << (u8) stat; - // Write command - writeU16(os, TOCLIENT_HUDCHANGE); - writeU32(os, id); - writeU8(os, (u8)stat); switch (stat) { case HUD_STAT_POS: case HUD_STAT_SCALE: case HUD_STAT_ALIGN: case HUD_STAT_OFFSET: - writeV2F1000(os, *(v2f *)value); + pkt << *(v2f *) value; break; case HUD_STAT_NAME: case HUD_STAT_TEXT: - os << serializeString(*(std::string *)value); + pkt << *(std::string *) value; break; case HUD_STAT_WORLD_POS: - writeV3F1000(os, *(v3f *)value); + pkt << *(v3f *) value; break; case HUD_STAT_SIZE: - writeV2S32(os,*(v2s32 *)value); + pkt << *(v2s32 *) value; break; case HUD_STAT_NUMBER: case HUD_STAT_ITEM: case HUD_STAT_DIR: default: - writeU32(os, *(u32 *)value); + pkt << *(u32 *) value; break; } - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8 *)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + Send(&pkt); } void Server::SendHUDSetFlags(u16 peer_id, u32 flags, u32 mask) { - std::ostringstream os(std::ios_base::binary); - - // Write command - writeU16(os, TOCLIENT_HUD_SET_FLAGS); + NetworkPacket pkt(TOCLIENT_HUD_SET_FLAGS, 4 + 4, peer_id); - //////////////////////////// compatibility code to be removed ////////////// flags &= ~(HUD_FLAG_HEALTHBAR_VISIBLE | HUD_FLAG_BREATHBAR_VISIBLE); - //////////////////////////////////////////////////////////////////////////// - writeU32(os, flags); - writeU32(os, mask); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8 *)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + pkt << flags << mask; + + Send(&pkt); } void Server::SendHUDSetParam(u16 peer_id, u16 param, const std::string &value) { - std::ostringstream os(std::ios_base::binary); - - // Write command - writeU16(os, TOCLIENT_HUD_SET_PARAM); - writeU16(os, param); - os<<serializeString(value); - - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8 *)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + NetworkPacket pkt(TOCLIENT_HUD_SET_PARAM, 0, peer_id); + pkt << param << value; + Send(&pkt); } void Server::SendSetSky(u16 peer_id, const video::SColor &bgcolor, const std::string &type, const std::vector<std::string> ¶ms) { - std::ostringstream os(std::ios_base::binary); + NetworkPacket pkt(TOCLIENT_SET_SKY, 0, peer_id); + pkt << bgcolor << type << (u16) params.size(); - // Write command - writeU16(os, TOCLIENT_SET_SKY); - writeARGB8(os, bgcolor); - os<<serializeString(type); - writeU16(os, params.size()); for(size_t i=0; i<params.size(); i++) - os<<serializeString(params[i]); + pkt << params[i]; - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8 *)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + Send(&pkt); } void Server::SendOverrideDayNightRatio(u16 peer_id, bool do_override, float ratio) { - std::ostringstream os(std::ios_base::binary); + NetworkPacket pkt(TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO, + 1 + 2, peer_id); - // Write command - writeU16(os, TOCLIENT_OVERRIDE_DAY_NIGHT_RATIO); - writeU8(os, do_override); - writeU16(os, ratio*65535); + pkt << do_override << (u16) (ratio * 65535); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8 *)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + Send(&pkt); } void Server::SendTimeOfDay(u16 peer_id, u16 time, f32 time_speed) { DSTACK(__FUNCTION_NAME); - // Make packet - SharedBuffer<u8> data(2+2+4); - writeU16(&data[0], TOCLIENT_TIME_OF_DAY); - writeU16(&data[2], time); - writeF1000(&data[4], time_speed); + NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id); + pkt << time << time_speed; if (peer_id == PEER_ID_INEXISTENT) { - m_clients.sendToAll(0,data,true); + m_clients.sendToAll(0, &pkt, true); } else { - // Send as reliable - m_clients.send(peer_id, 0, data, true); + Send(&pkt); } } @@ -3459,15 +1840,18 @@ void Server::SendPlayerHP(u16 peer_id) { DSTACK(__FUNCTION_NAME); PlayerSAO *playersao = getPlayerSAO(peer_id); - assert(playersao); - playersao->m_hp_not_sent = false; + // In some rare case, if the player is disconnected + // while Lua call l_punch, for example, this can be NULL + if (!playersao) + return; + SendHP(peer_id, playersao->getHP()); m_script->player_event(playersao,"health_changed"); // Send to other clients std::string str = gob_cmd_punched(playersao->readDamage(), playersao->getHP()); ActiveObjectMessage aom(playersao->getId(), true, str); - playersao->m_messages_out.push_back(aom); + playersao->m_messages_out.push(aom); } void Server::SendPlayerBreath(u16 peer_id) @@ -3475,8 +1859,8 @@ void Server::SendPlayerBreath(u16 peer_id) DSTACK(__FUNCTION_NAME); PlayerSAO *playersao = getPlayerSAO(peer_id); assert(playersao); - playersao->m_breath_not_sent = false; - m_script->player_event(playersao,"breath_changed"); + + m_script->player_event(playersao, "breath_changed"); SendBreath(peer_id, playersao->getBreath()); } @@ -3486,61 +1870,39 @@ void Server::SendMovePlayer(u16 peer_id) Player *player = m_env->getPlayer(peer_id); assert(player); - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_MOVE_PLAYER); - writeV3F1000(os, player->getPosition()); - writeF1000(os, player->getPitch()); - writeF1000(os, player->getYaw()); + NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, peer_id); + pkt << player->getPosition() << player->getPitch() << player->getYaw(); { v3f pos = player->getPosition(); f32 pitch = player->getPitch(); f32 yaw = player->getYaw(); - verbosestream<<"Server: Sending TOCLIENT_MOVE_PLAYER" - <<" pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")" - <<" pitch="<<pitch - <<" yaw="<<yaw - <<std::endl; + verbosestream << "Server: Sending TOCLIENT_MOVE_PLAYER" + << " pos=(" << pos.X << "," << pos.Y << "," << pos.Z << ")" + << " pitch=" << pitch + << " yaw=" << yaw + << std::endl; } - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + Send(&pkt); } void Server::SendLocalPlayerAnimations(u16 peer_id, v2s32 animation_frames[4], f32 animation_speed) { - std::ostringstream os(std::ios_base::binary); + NetworkPacket pkt(TOCLIENT_LOCAL_PLAYER_ANIMATIONS, 0, + peer_id); - writeU16(os, TOCLIENT_LOCAL_PLAYER_ANIMATIONS); - writeV2S32(os, animation_frames[0]); - writeV2S32(os, animation_frames[1]); - writeV2S32(os, animation_frames[2]); - writeV2S32(os, animation_frames[3]); - writeF1000(os, animation_speed); + pkt << animation_frames[0] << animation_frames[1] << animation_frames[2] + << animation_frames[3] << animation_speed; - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8 *)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + Send(&pkt); } void Server::SendEyeOffset(u16 peer_id, v3f first, v3f third) { - std::ostringstream os(std::ios_base::binary); - - writeU16(os, TOCLIENT_EYE_OFFSET); - writeV3F1000(os, first); - writeV3F1000(os, third); - - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8 *)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + NetworkPacket pkt(TOCLIENT_EYE_OFFSET, 0, peer_id); + pkt << first << third; + Send(&pkt); } void Server::SendPlayerPrivileges(u16 peer_id) { @@ -3552,19 +1914,15 @@ void Server::SendPlayerPrivileges(u16 peer_id) std::set<std::string> privs; m_script->getAuth(player->getName(), NULL, &privs); - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_PRIVILEGES); - writeU16(os, privs.size()); + NetworkPacket pkt(TOCLIENT_PRIVILEGES, 0, peer_id); + pkt << (u16) privs.size(); + for(std::set<std::string>::const_iterator i = privs.begin(); - i != privs.end(); i++){ - os<<serializeString(*i); + i != privs.end(); i++) { + pkt << (*i); } - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + Send(&pkt); } void Server::SendPlayerInventoryFormspec(u16 peer_id) @@ -3574,15 +1932,30 @@ void Server::SendPlayerInventoryFormspec(u16 peer_id) if(player->peer_id == PEER_ID_INEXISTENT) return; - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_INVENTORY_FORMSPEC); - os<<serializeLongString(FORMSPEC_VERSION_STRING + player->inventory_formspec); + NetworkPacket pkt(TOCLIENT_INVENTORY_FORMSPEC, 0, peer_id); + pkt.putLongString(FORMSPEC_VERSION_STRING + player->inventory_formspec); + Send(&pkt); +} + +u32 Server::SendActiveObjectRemoveAdd(u16 peer_id, const std::string &datas) +{ + NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD, datas.size(), peer_id); + pkt.putRawString(datas.c_str(), datas.size()); + Send(&pkt); + return pkt.getSize(); +} + +void Server::SendActiveObjectMessages(u16 peer_id, const std::string &datas, bool reliable) +{ + NetworkPacket pkt(TOCLIENT_ACTIVE_OBJECT_MESSAGES, + datas.size(), peer_id); + + pkt.putRawString(datas.c_str(), datas.size()); + + m_clients.send(pkt.getPeerId(), + reliable ? clientCommandFactoryTable[pkt.getCommand()].channel : 1, + &pkt, reliable); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); } s32 Server::playSound(const SimpleSoundSpec &spec, @@ -3596,7 +1969,7 @@ s32 Server::playSound(const SimpleSoundSpec &spec, return -1; // Filter destination clients - std::list<u16> dst_clients; + std::vector<u16> dst_clients; if(params.to_player != "") { Player *player = m_env->getPlayer(params.to_player.c_str()); @@ -3612,17 +1985,16 @@ s32 Server::playSound(const SimpleSoundSpec &spec, } dst_clients.push_back(player->peer_id); } - else - { - std::list<u16> clients = m_clients.getClientIDs(); + else { + std::vector<u16> clients = m_clients.getClientIDs(); - for(std::list<u16>::iterator - i = clients.begin(); i != clients.end(); ++i) - { + for(std::vector<u16>::iterator + i = clients.begin(); i != clients.end(); ++i) { Player *player = m_env->getPlayer(*i); if(!player) continue; - if(pos_exists){ + + if(pos_exists) { if(player->getPosition().getDistanceFrom(pos) > params.max_hear_distance) continue; @@ -3630,6 +2002,7 @@ s32 Server::playSound(const SimpleSoundSpec &spec, dst_clients.push_back(*i); } } + if(dst_clients.empty()) return -1; @@ -3639,27 +2012,15 @@ s32 Server::playSound(const SimpleSoundSpec &spec, m_playing_sounds[id] = ServerPlayingSound(); ServerPlayingSound &psound = m_playing_sounds[id]; psound.params = params; - for(std::list<u16>::iterator i = dst_clients.begin(); - i != dst_clients.end(); i++) + + NetworkPacket pkt(TOCLIENT_PLAY_SOUND, 0); + pkt << id << spec.name << (float) (spec.gain * params.gain) + << (u8) params.type << pos << params.object << params.loop; + + for(std::vector<u16>::iterator i = dst_clients.begin(); + i != dst_clients.end(); i++) { psound.clients.insert(*i); - // Create packet - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_PLAY_SOUND); - writeS32(os, id); - os<<serializeString(spec.name); - writeF1000(os, spec.gain * params.gain); - writeU8(os, params.type); - writeV3F1000(os, pos); - writeU16(os, params.object); - writeU8(os, params.loop); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send - for(std::list<u16>::iterator i = dst_clients.begin(); - i != dst_clients.end(); i++){ - // Send as reliable - m_clients.send(*i, 0, data, true); + m_clients.send(*i, 0, &pkt, true); } return id; } @@ -3671,52 +2032,37 @@ void Server::stopSound(s32 handle) if(i == m_playing_sounds.end()) return; ServerPlayingSound &psound = i->second; - // Create packet - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_STOP_SOUND); - writeS32(os, handle); - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send + + NetworkPacket pkt(TOCLIENT_STOP_SOUND, 4); + pkt << handle; + for(std::set<u16>::iterator i = psound.clients.begin(); - i != psound.clients.end(); i++){ + i != psound.clients.end(); i++) { // Send as reliable - m_clients.send(*i, 0, data, true); + m_clients.send(*i, 0, &pkt, true); } // Remove sound reference m_playing_sounds.erase(i); } void Server::sendRemoveNode(v3s16 p, u16 ignore_id, - std::list<u16> *far_players, float far_d_nodes) + std::vector<u16> *far_players, float far_d_nodes) { float maxd = far_d_nodes*BS; v3f p_f = intToFloat(p, BS); - // Create packet - u32 replysize = 8; - SharedBuffer<u8> reply(replysize); - writeU16(&reply[0], TOCLIENT_REMOVENODE); - writeS16(&reply[2], p.X); - writeS16(&reply[4], p.Y); - writeS16(&reply[6], p.Z); - - std::list<u16> clients = m_clients.getClientIDs(); - for(std::list<u16>::iterator - i = clients.begin(); - i != clients.end(); ++i) - { - if(far_players) - { + NetworkPacket pkt(TOCLIENT_REMOVENODE, 6); + pkt << p; + + std::vector<u16> clients = m_clients.getClientIDs(); + for(std::vector<u16>::iterator i = clients.begin(); + i != clients.end(); ++i) { + if (far_players) { // Get player - Player *player = m_env->getPlayer(*i); - if(player) - { + if(Player *player = m_env->getPlayer(*i)) { // If player is far away, only set modified blocks not sent v3f player_pos = player->getPosition(); - if(player_pos.getDistanceFrom(p_f) > maxd) - { + if(player_pos.getDistanceFrom(p_f) > maxd) { far_players->push_back(*i); continue; } @@ -3724,53 +2070,39 @@ void Server::sendRemoveNode(v3s16 p, u16 ignore_id, } // Send as reliable - m_clients.send(*i, 0, reply, true); + m_clients.send(*i, 0, &pkt, true); } } void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id, - std::list<u16> *far_players, float far_d_nodes, + std::vector<u16> *far_players, float far_d_nodes, bool remove_metadata) { float maxd = far_d_nodes*BS; v3f p_f = intToFloat(p, BS); - std::list<u16> clients = m_clients.getClientIDs(); - for(std::list<u16>::iterator - i = clients.begin(); - i != clients.end(); ++i) - { + std::vector<u16> clients = m_clients.getClientIDs(); + for(std::vector<u16>::iterator i = clients.begin(); + i != clients.end(); ++i) { - if(far_players) - { + if(far_players) { // Get player - Player *player = m_env->getPlayer(*i); - if(player) - { + if(Player *player = m_env->getPlayer(*i)) { // If player is far away, only set modified blocks not sent v3f player_pos = player->getPosition(); - if(player_pos.getDistanceFrom(p_f) > maxd) - { + if(player_pos.getDistanceFrom(p_f) > maxd) { far_players->push_back(*i); continue; } } } - SharedBuffer<u8> reply(0); + + NetworkPacket pkt(TOCLIENT_ADDNODE, 6 + 2 + 1 + 1 + 1); m_clients.Lock(); RemoteClient* client = m_clients.lockedGetClientNoEx(*i); - if (client != 0) - { - // Create packet - u32 replysize = 9 + MapNode::serializedLength(client->serialization_version); - reply = SharedBuffer<u8>(replysize); - writeU16(&reply[0], TOCLIENT_ADDNODE); - writeS16(&reply[2], p.X); - writeS16(&reply[4], p.Y); - writeS16(&reply[6], p.Z); - n.serialize(&reply[8], client->serialization_version); - u32 index = 8 + MapNode::serializedLength(client->serialization_version); - writeU8(&reply[index], remove_metadata ? 0 : 1); + if (client != 0) { + pkt << p << n.param0 << n.param1 << n.param2 + << (u8) (remove_metadata ? 0 : 1); if (!remove_metadata) { if (client->net_proto_version <= 21) { @@ -3783,19 +2115,17 @@ void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id, m_clients.Unlock(); // Send as reliable - if (reply.getSize() > 0) - m_clients.send(*i, 0, reply, true); + if (pkt.getSize() > 0) + m_clients.send(*i, 0, &pkt, true); } } void Server::setBlockNotSent(v3s16 p) { - std::list<u16> clients = m_clients.getClientIDs(); + std::vector<u16> clients = m_clients.getClientIDs(); m_clients.Lock(); - for(std::list<u16>::iterator - i = clients.begin(); - i != clients.end(); ++i) - { + for(std::vector<u16>::iterator i = clients.begin(); + i != clients.end(); ++i) { RemoteClient *client = m_clients.lockedGetClientNoEx(*i); client->SetBlockNotSent(p); } @@ -3808,27 +2138,6 @@ void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver, u16 net_proto v3s16 p = block->getPos(); -#if 0 - // Analyze it a bit - bool completely_air = true; - for(s16 z0=0; z0<MAP_BLOCKSIZE; z0++) - for(s16 x0=0; x0<MAP_BLOCKSIZE; x0++) - for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++) - { - if(block->getNodeNoEx(v3s16(x0,y0,z0)).d != CONTENT_AIR) - { - completely_air = false; - x0 = y0 = z0 = MAP_BLOCKSIZE; // Break out - } - } - - // Print result - infostream<<"Server: Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<"): "; - if(completely_air) - infostream<<"[completely air] "; - infostream<<std::endl; -#endif - /* Create a packet with the block in the right format */ @@ -3837,23 +2146,12 @@ void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver, u16 net_proto block->serialize(os, ver, false); block->serializeNetworkSpecific(os, net_proto_version); std::string s = os.str(); - SharedBuffer<u8> blockdata((u8*)s.c_str(), s.size()); - - u32 replysize = 8 + blockdata.getSize(); - SharedBuffer<u8> reply(replysize); - writeU16(&reply[0], TOCLIENT_BLOCKDATA); - writeS16(&reply[2], p.X); - writeS16(&reply[4], p.Y); - writeS16(&reply[6], p.Z); - memcpy(&reply[8], *blockdata, blockdata.getSize()); - /*infostream<<"Server: Sending block ("<<p.X<<","<<p.Y<<","<<p.Z<<")" - <<": \tpacket size: "<<replysize<<std::endl;*/ + NetworkPacket pkt(TOCLIENT_BLOCKDATA, 2 + 2 + 2 + 2 + s.size(), peer_id); - /* - Send packet - */ - m_clients.send(peer_id, 2, reply, true); + pkt << p; + pkt.putRawString(s.c_str(), s.size()); + Send(&pkt); } void Server::SendBlocks(float dtime) @@ -3872,13 +2170,11 @@ void Server::SendBlocks(float dtime) { ScopeProfiler sp(g_profiler, "Server: selecting blocks for sending"); - std::list<u16> clients = m_clients.getClientIDs(); + std::vector<u16> clients = m_clients.getClientIDs(); m_clients.Lock(); - for(std::list<u16>::iterator - i = clients.begin(); - i != clients.end(); ++i) - { + for(std::vector<u16>::iterator i = clients.begin(); + i != clients.end(); ++i) { RemoteClient *client = m_clients.lockedGetClientNoEx(*i, CS_Active); if (client == NULL) @@ -3935,9 +2231,9 @@ void Server::fillMediaCache() infostream<<"Server: Calculating media file checksums"<<std::endl; // Collect all media file paths - std::list<std::string> paths; + std::vector<std::string> paths; for(std::vector<ModSpec>::iterator i = m_mods.begin(); - i != m_mods.end(); i++){ + i != m_mods.end(); i++) { const ModSpec &mod = *i; paths.push_back(mod.path + DIR_DELIM + "textures"); paths.push_back(mod.path + DIR_DELIM + "sounds"); @@ -3947,19 +2243,18 @@ void Server::fillMediaCache() paths.push_back(porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server"); // Collect media file information from paths into cache - for(std::list<std::string>::iterator i = paths.begin(); - i != paths.end(); i++) - { + for(std::vector<std::string>::iterator i = paths.begin(); + i != paths.end(); i++) { std::string mediapath = *i; std::vector<fs::DirListNode> dirlist = fs::GetDirListing(mediapath); - for(u32 j=0; j<dirlist.size(); j++){ - if(dirlist[j].dir) // Ignode dirs + for (u32 j = 0; j < dirlist.size(); j++) { + if (dirlist[j].dir) // Ignode dirs continue; std::string filename = dirlist[j].name; // If name contains illegal characters, ignore the file - if(!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)){ + if (!string_allowed(filename, TEXTURENAME_ALLOWED_CHARS)) { infostream<<"Server: ignoring illegal file name: \"" - <<filename<<"\""<<std::endl; + << filename << "\"" << std::endl; continue; } // If name is not in a supported format, ignore it @@ -3970,42 +2265,42 @@ void Server::fillMediaCache() ".x", ".b3d", ".md2", ".obj", NULL }; - if(removeStringEnd(filename, supported_ext) == ""){ - infostream<<"Server: ignoring unsupported file extension: \"" - <<filename<<"\""<<std::endl; + if (removeStringEnd(filename, supported_ext) == ""){ + infostream << "Server: ignoring unsupported file extension: \"" + << filename << "\"" << std::endl; continue; } // Ok, attempt to load the file and add to cache std::string filepath = mediapath + DIR_DELIM + filename; // Read data std::ifstream fis(filepath.c_str(), std::ios_base::binary); - if(fis.good() == false){ - errorstream<<"Server::fillMediaCache(): Could not open \"" - <<filename<<"\" for reading"<<std::endl; + if (!fis.good()) { + errorstream << "Server::fillMediaCache(): Could not open \"" + << filename << "\" for reading" << std::endl; continue; } std::ostringstream tmp_os(std::ios_base::binary); bool bad = false; - for(;;){ + for(;;) { char buf[1024]; fis.read(buf, 1024); std::streamsize len = fis.gcount(); tmp_os.write(buf, len); - if(fis.eof()) + if (fis.eof()) break; - if(!fis.good()){ + if (!fis.good()) { bad = true; break; } } - if(bad){ + if(bad) { errorstream<<"Server::fillMediaCache(): Failed to read \"" - <<filename<<"\""<<std::endl; + << filename << "\"" << std::endl; continue; } - if(tmp_os.str().length() == 0){ - errorstream<<"Server::fillMediaCache(): Empty file \"" - <<filepath<<"\""<<std::endl; + if(tmp_os.str().length() == 0) { + errorstream << "Server::fillMediaCache(): Empty file \"" + << filepath << "\"" << std::endl; continue; } @@ -4018,71 +2313,33 @@ void Server::fillMediaCache() free(digest); // Put in list - this->m_media[filename] = MediaInfo(filepath, sha1_base64); - verbosestream<<"Server: "<<sha1_hex<<" is "<<filename<<std::endl; + m_media[filename] = MediaInfo(filepath, sha1_base64); + verbosestream << "Server: " << sha1_hex << " is " << filename + << std::endl; } } } -struct SendableMediaAnnouncement -{ - std::string name; - std::string sha1_digest; - - SendableMediaAnnouncement(const std::string &name_="", - const std::string &sha1_digest_=""): - name(name_), - sha1_digest(sha1_digest_) - {} -}; - void Server::sendMediaAnnouncement(u16 peer_id) { DSTACK(__FUNCTION_NAME); - verbosestream<<"Server: Announcing files to id("<<peer_id<<")" - <<std::endl; - - std::list<SendableMediaAnnouncement> file_announcements; - - for(std::map<std::string, MediaInfo>::iterator i = m_media.begin(); - i != m_media.end(); i++){ - // Put in list - file_announcements.push_back( - SendableMediaAnnouncement(i->first, i->second.sha1_digest)); - } + verbosestream << "Server: Announcing files to id(" << peer_id << ")" + << std::endl; // Make packet std::ostringstream os(std::ios_base::binary); - /* - u16 command - u32 number of files - for each texture { - u16 length of name - string name - u16 length of sha1_digest - string sha1_digest - } - */ + NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id); + pkt << (u16) m_media.size(); - writeU16(os, TOCLIENT_ANNOUNCE_MEDIA); - writeU16(os, file_announcements.size()); - - for(std::list<SendableMediaAnnouncement>::iterator - j = file_announcements.begin(); - j != file_announcements.end(); ++j){ - os<<serializeString(j->name); - os<<serializeString(j->sha1_digest); + for (std::map<std::string, MediaInfo>::iterator i = m_media.begin(); + i != m_media.end(); ++i) { + pkt << i->first << i->second.sha1_digest; } - os<<serializeString(g_settings->get("remote_media")); - - // Make data buffer - std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 0, data, true); + pkt << g_settings->get("remote_media"); + Send(&pkt); } struct SendableMedia @@ -4100,7 +2357,7 @@ struct SendableMedia }; void Server::sendRequestedMedia(u16 peer_id, - const std::list<std::string> &tosend) + const std::vector<std::string> &tosend) { DSTACK(__FUNCTION_NAME); @@ -4112,17 +2369,16 @@ void Server::sendRequestedMedia(u16 peer_id, // Put 5kB in one bunch (this is not accurate) u32 bytes_per_bunch = 5000; - std::vector< std::list<SendableMedia> > file_bunches; - file_bunches.push_back(std::list<SendableMedia>()); + std::vector< std::vector<SendableMedia> > file_bunches; + file_bunches.push_back(std::vector<SendableMedia>()); u32 file_size_bunch_total = 0; - for(std::list<std::string>::const_iterator i = tosend.begin(); - i != tosend.end(); ++i) - { + for(std::vector<std::string>::const_iterator i = tosend.begin(); + i != tosend.end(); ++i) { const std::string &name = *i; - if(m_media.find(name) == m_media.end()){ + if(m_media.find(name) == m_media.end()) { errorstream<<"Server::sendRequestedMedia(): Client asked for " <<"unknown file \""<<(name)<<"\""<<std::endl; continue; @@ -4140,7 +2396,7 @@ void Server::sendRequestedMedia(u16 peer_id, } std::ostringstream tmp_os(std::ios_base::binary); bool bad = false; - for(;;){ + for(;;) { char buf[1024]; fis.read(buf, 1024); std::streamsize len = fis.gcount(); @@ -4148,12 +2404,12 @@ void Server::sendRequestedMedia(u16 peer_id, file_size_bunch_total += len; if(fis.eof()) break; - if(!fis.good()){ + if(!fis.good()) { bad = true; break; } } - if(bad){ + if(bad) { errorstream<<"Server::sendRequestedMedia(): Failed to read \"" <<name<<"\""<<std::endl; continue; @@ -4165,8 +2421,8 @@ void Server::sendRequestedMedia(u16 peer_id, SendableMedia(name, tpath, tmp_os.str())); // Start next bunch if got enough data - if(file_size_bunch_total >= bytes_per_bunch){ - file_bunches.push_back(std::list<SendableMedia>()); + if(file_size_bunch_total >= bytes_per_bunch) { + file_bunches.push_back(std::vector<SendableMedia>()); file_size_bunch_total = 0; } @@ -4174,11 +2430,8 @@ void Server::sendRequestedMedia(u16 peer_id, /* Create and send packets */ - u32 num_bunches = file_bunches.size(); - for(u32 i=0; i<num_bunches; i++) - { - std::ostringstream os(std::ios_base::binary); - + u16 num_bunches = file_bunches.size(); + for(u16 i = 0; i < num_bunches; i++) { /* u16 command u16 total number of texture bunches @@ -4192,55 +2445,47 @@ void Server::sendRequestedMedia(u16 peer_id, } */ - writeU16(os, TOCLIENT_MEDIA); - writeU16(os, num_bunches); - writeU16(os, i); - writeU32(os, file_bunches[i].size()); + NetworkPacket pkt(TOCLIENT_MEDIA, 4 + 0, peer_id); + pkt << num_bunches << i << (u32) file_bunches[i].size(); - for(std::list<SendableMedia>::iterator + for(std::vector<SendableMedia>::iterator j = file_bunches[i].begin(); - j != file_bunches[i].end(); ++j){ - os<<serializeString(j->name); - os<<serializeLongString(j->data); + j != file_bunches[i].end(); ++j) { + pkt << j->name; + pkt.putLongString(j->data); } - // Make data buffer - std::string s = os.str(); - verbosestream<<"Server::sendRequestedMedia(): bunch " - <<i<<"/"<<num_bunches - <<" files="<<file_bunches[i].size() - <<" size=" <<s.size()<<std::endl; - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - // Send as reliable - m_clients.send(peer_id, 2, data, true); + verbosestream << "Server::sendRequestedMedia(): bunch " + << i << "/" << num_bunches + << " files=" << file_bunches[i].size() + << " size=" << pkt.getSize() << std::endl; + Send(&pkt); } } void Server::sendDetachedInventory(const std::string &name, u16 peer_id) { - if(m_detached_inventories.count(name) == 0){ + if(m_detached_inventories.count(name) == 0) { errorstream<<__FUNCTION_NAME<<": \""<<name<<"\" not found"<<std::endl; return; } Inventory *inv = m_detached_inventories[name]; - std::ostringstream os(std::ios_base::binary); - writeU16(os, TOCLIENT_DETACHED_INVENTORY); - os<<serializeString(name); + + os << serializeString(name); inv->serialize(os); // Make data buffer std::string s = os.str(); - SharedBuffer<u8> data((u8*)s.c_str(), s.size()); - if (peer_id != PEER_ID_INEXISTENT) - { - // Send as reliable - m_clients.send(peer_id, 0, data, true); + NetworkPacket pkt(TOCLIENT_DETACHED_INVENTORY, 0, peer_id); + pkt.putRawString(s.c_str(), s.size()); + + if (peer_id != PEER_ID_INEXISTENT) { + Send(&pkt); } - else - { - m_clients.sendToAll(0,data,true); + else { + m_clients.sendToAll(0, &pkt, true); } } @@ -4250,7 +2495,7 @@ void Server::sendDetachedInventories(u16 peer_id) for(std::map<std::string, Inventory*>::iterator i = m_detached_inventories.begin(); - i != m_detached_inventories.end(); i++){ + i != m_detached_inventories.end(); i++) { const std::string &name = i->first; //Inventory *inv = i->second; sendDetachedInventory(name, peer_id); @@ -4268,9 +2513,9 @@ void Server::DiePlayer(u16 peer_id) PlayerSAO *playersao = getPlayerSAO(peer_id); assert(playersao); - infostream<<"Server::DiePlayer(): Player " - <<playersao->getPlayer()->getName() - <<" dies"<<std::endl; + infostream << "Server::DiePlayer(): Player " + << playersao->getPlayer()->getName() + << " dies" << std::endl; playersao->setHP(0); @@ -4288,29 +2533,102 @@ void Server::RespawnPlayer(u16 peer_id) PlayerSAO *playersao = getPlayerSAO(peer_id); assert(playersao); - infostream<<"Server::RespawnPlayer(): Player " - <<playersao->getPlayer()->getName() - <<" respawns"<<std::endl; + infostream << "Server::RespawnPlayer(): Player " + << playersao->getPlayer()->getName() + << " respawns" << std::endl; playersao->setHP(PLAYER_MAX_HP); playersao->setBreath(PLAYER_MAX_BREATH); + SendPlayerHP(peer_id); + SendPlayerBreath(peer_id); + bool repositioned = m_script->on_respawnplayer(playersao); if(!repositioned){ - v3f pos = findSpawnPos(m_env->getServerMap()); + v3f pos = findSpawnPos(); + // setPos will send the new position to client playersao->setPos(pos); } } -void Server::DenyAccess(u16 peer_id, const std::wstring &reason) + +void Server::DenySudoAccess(u16 peer_id) +{ + DSTACK(__FUNCTION_NAME); + + NetworkPacket pkt(TOCLIENT_DENY_SUDO_MODE, 0, peer_id); + Send(&pkt); +} + + +void Server::DenyAccessVerCompliant(u16 peer_id, u16 proto_ver, AccessDeniedCode reason, + const std::string &str_reason, bool reconnect) +{ + if (proto_ver >= 25) { + SendAccessDenied(peer_id, reason, str_reason); + } else { + std::wstring wreason = utf8_to_wide( + reason == SERVER_ACCESSDENIED_CUSTOM_STRING ? str_reason : + accessDeniedStrings[(u8)reason]); + SendAccessDenied_Legacy(peer_id, wreason); + } + + m_clients.event(peer_id, CSE_SetDenied); + m_con.DisconnectPeer(peer_id); +} + + +void Server::DenyAccess(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason) +{ + DSTACK(__FUNCTION_NAME); + + SendAccessDenied(peer_id, reason, custom_reason); + m_clients.event(peer_id, CSE_SetDenied); + m_con.DisconnectPeer(peer_id); +} + +// 13/03/15: remove this function when protocol version 25 will become +// the minimum version for MT users, maybe in 1 year +void Server::DenyAccess_Legacy(u16 peer_id, const std::wstring &reason) { DSTACK(__FUNCTION_NAME); - SendAccessDenied(peer_id, reason); + SendAccessDenied_Legacy(peer_id, reason); m_clients.event(peer_id, CSE_SetDenied); m_con.DisconnectPeer(peer_id); } +void Server::acceptAuth(u16 peer_id, bool forSudoMode) +{ + DSTACK(__FUNCTION_NAME); + + if (!forSudoMode) { + RemoteClient* client = getClient(peer_id, CS_Invalid); + + NetworkPacket resp_pkt(TOCLIENT_AUTH_ACCEPT, 1 + 6 + 8 + 4, peer_id); + + // Right now, the auth mechs don't change between login and sudo mode. + u32 sudo_auth_mechs = client->allowed_auth_mechs; + client->allowed_sudo_mechs = sudo_auth_mechs; + + resp_pkt << v3f(0,0,0) << (u64) m_env->getServerMap().getSeed() + << g_settings->getFloat("dedicated_server_step") + << sudo_auth_mechs; + + Send(&resp_pkt); + m_clients.event(peer_id, CSE_AuthAccept); + } else { + NetworkPacket resp_pkt(TOCLIENT_ACCEPT_SUDO_MODE, 1 + 6 + 8 + 4, peer_id); + + // We only support SRP right now + u32 sudo_auth_mechs = AUTH_MECHANISM_FIRST_SRP; + + resp_pkt << sudo_auth_mechs; + Send(&resp_pkt); + m_clients.event(peer_id, CSE_SudoSuccess); + } +} + void Server::DeleteClient(u16 peer_id, ClientDeletionReason reason) { DSTACK(__FUNCTION_NAME); @@ -4363,26 +2681,24 @@ void Server::DeleteClient(u16 peer_id, ClientDeletionReason reason) Print out action */ { - if(player != NULL && reason != CDR_DENY) - { + if(player != NULL && reason != CDR_DENY) { std::ostringstream os(std::ios_base::binary); - std::list<u16> clients = m_clients.getClientIDs(); + std::vector<u16> clients = m_clients.getClientIDs(); - for(std::list<u16>::iterator - i = clients.begin(); - i != clients.end(); ++i) - { + for(std::vector<u16>::iterator i = clients.begin(); + i != clients.end(); ++i) { // Get player Player *player = m_env->getPlayer(*i); if(!player) continue; + // Get name of player - os<<player->getName()<<" "; + os << player->getName() << " "; } - actionstream<<player->getName()<<" " - <<(reason==CDR_TIMEOUT?"times out.":"leaves game.") - <<" List of players: "<<os.str()<<std::endl; + actionstream << player->getName() << " " + << (reason == CDR_TIMEOUT ? "times out." : "leaves game.") + << " List of players: " << os.str() << std::endl; } } { @@ -4396,24 +2712,22 @@ void Server::DeleteClient(u16 peer_id, ClientDeletionReason reason) SendChatMessage(PEER_ID_INEXISTENT,message); } -void Server::UpdateCrafting(u16 peer_id) +void Server::UpdateCrafting(Player* player) { DSTACK(__FUNCTION_NAME); - Player* player = m_env->getPlayer(peer_id); - assert(player); - // Get a preview for crafting ItemStack preview; InventoryLocation loc; loc.setPlayer(player->getName()); - getCraftingResult(&player->inventory, preview, false, this); + std::vector<ItemStack> output_replacements; + getCraftingResult(&player->inventory, preview, output_replacements, false, this); m_env->getScriptIface()->item_CraftPredict(preview, player->getPlayerSAO(), (&player->inventory)->getList("craft"), loc); // Put the new preview in InventoryList *plist = player->inventory.getList("craftpreview"); - assert(plist); - assert(plist->getSize() >= 1); + sanity_check(plist); + sanity_check(plist->getSize() >= 1); plist->changeItem(0, preview); } @@ -4451,7 +2765,7 @@ std::wstring Server::getStatusString() std::wostringstream os(std::ios_base::binary); os<<L"# Server: "; // Version - os<<L"version="<<narrow_to_wide(minetest_version_simple); + os<<L"version="<<narrow_to_wide(g_version_string); // Uptime os<<L", uptime="<<m_uptime.get(); // Max lag estimate @@ -4459,10 +2773,9 @@ std::wstring Server::getStatusString() // Information about clients bool first = true; os<<L", clients={"; - std::list<u16> clients = m_clients.getClientIDs(); - for(std::list<u16>::iterator i = clients.begin(); - i != clients.end(); ++i) - { + std::vector<u16> clients = m_clients.getClientIDs(); + for(std::vector<u16>::iterator i = clients.begin(); + i != clients.end(); ++i) { // Get player Player *player = m_env->getPlayer(*i); // Get name of player @@ -4471,12 +2784,12 @@ std::wstring Server::getStatusString() name = narrow_to_wide(player->getName()); // Add name to information string if(!first) - os<<L", "; + os << L", "; else first = false; - os<<name; + os << name; } - os<<L"}"; + os << L"}"; if(((ServerMap*)(&m_env->getMap()))->isSavingEnabled() == false) os<<std::endl<<L"# Server: "<<" WARNING: Map saving is disabled."; if(g_settings->get("motd") != "") @@ -4499,11 +2812,10 @@ bool Server::checkPriv(const std::string &name, const std::string &priv) void Server::reportPrivsModified(const std::string &name) { - if(name == ""){ - std::list<u16> clients = m_clients.getClientIDs(); - for(std::list<u16>::iterator - i = clients.begin(); - i != clients.end(); ++i){ + if(name == "") { + std::vector<u16> clients = m_clients.getClientIDs(); + for(std::vector<u16>::iterator i = clients.begin(); + i != clients.end(); ++i) { Player *player = m_env->getPlayer(*i); reportPrivsModified(player->getName()); } @@ -4546,8 +2858,12 @@ std::string Server::getBanDescription(const std::string &ip_or_name) void Server::notifyPlayer(const char *name, const std::wstring &msg) { + // m_env will be NULL if the server is initializing + if (!m_env) + return; + Player *player = m_env->getPlayer(name); - if(!player) + if (!player) return; if (player->peer_id == PEER_ID_INEXISTENT) @@ -4556,21 +2872,23 @@ void Server::notifyPlayer(const char *name, const std::wstring &msg) SendChatMessage(player->peer_id, msg); } -bool Server::showFormspec(const char *playername, const std::string &formspec, const std::string &formname) +bool Server::showFormspec(const char *playername, const std::string &formspec, + const std::string &formname) { - Player *player = m_env->getPlayer(playername); + // m_env will be NULL if the server is initializing + if (!m_env) + return false; - if(!player) - { - infostream<<"showFormspec: couldn't find player:"<<playername<<std::endl; + Player *player = m_env->getPlayer(playername); + if (!player) return false; - } SendShowFormspecMessage(player->peer_id, formspec, formname); return true; } -u32 Server::hudAdd(Player *player, HudElement *form) { +u32 Server::hudAdd(Player *player, HudElement *form) +{ if (!player) return -1; @@ -4596,7 +2914,8 @@ bool Server::hudRemove(Player *player, u32 id) { return true; } -bool Server::hudChange(Player *player, u32 id, HudElementStat stat, void *data) { +bool Server::hudChange(Player *player, u32 id, HudElementStat stat, void *data) +{ if (!player) return false; @@ -4604,7 +2923,8 @@ bool Server::hudChange(Player *player, u32 id, HudElementStat stat, void *data) return true; } -bool Server::hudSetFlags(Player *player, u32 flags, u32 mask) { +bool Server::hudSetFlags(Player *player, u32 flags, u32 mask) +{ if (!player) return false; @@ -4620,37 +2940,67 @@ bool Server::hudSetFlags(Player *player, u32 flags, u32 mask) { return true; } -bool Server::hudSetHotbarItemcount(Player *player, s32 hotbar_itemcount) { +bool Server::hudSetHotbarItemcount(Player *player, s32 hotbar_itemcount) +{ if (!player) return false; if (hotbar_itemcount <= 0 || hotbar_itemcount > HUD_HOTBAR_ITEMCOUNT_MAX) return false; + player->setHotbarItemcount(hotbar_itemcount); std::ostringstream os(std::ios::binary); writeS32(os, hotbar_itemcount); SendHUDSetParam(player->peer_id, HUD_PARAM_HOTBAR_ITEMCOUNT, os.str()); return true; } -void Server::hudSetHotbarImage(Player *player, std::string name) { +s32 Server::hudGetHotbarItemcount(Player *player) +{ + if (!player) + return 0; + return player->getHotbarItemcount(); +} + +void Server::hudSetHotbarImage(Player *player, std::string name) +{ if (!player) return; + player->setHotbarImage(name); SendHUDSetParam(player->peer_id, HUD_PARAM_HOTBAR_IMAGE, name); } -void Server::hudSetHotbarSelectedImage(Player *player, std::string name) { +std::string Server::hudGetHotbarImage(Player *player) +{ + if (!player) + return ""; + return player->getHotbarImage(); +} + +void Server::hudSetHotbarSelectedImage(Player *player, std::string name) +{ if (!player) return; + player->setHotbarSelectedImage(name); SendHUDSetParam(player->peer_id, HUD_PARAM_HOTBAR_SELECTED_IMAGE, name); } -bool Server::setLocalPlayerAnimations(Player *player, v2s32 animation_frames[4], f32 frame_speed) +std::string Server::hudGetHotbarSelectedImage(Player *player) +{ + if (!player) + return ""; + + return player->getHotbarSelectedImage(); +} + +bool Server::setLocalPlayerAnimations(Player *player, + v2s32 animation_frames[4], f32 frame_speed) { if (!player) return false; + player->setLocalAnimations(animation_frames, frame_speed); SendLocalPlayerAnimations(player->peer_id, animation_frames, frame_speed); return true; } @@ -4660,26 +3010,30 @@ bool Server::setPlayerEyeOffset(Player *player, v3f first, v3f third) if (!player) return false; + player->eye_offset_first = first; + player->eye_offset_third = third; SendEyeOffset(player->peer_id, first, third); return true; } bool Server::setSky(Player *player, const video::SColor &bgcolor, - const std::string &type, const std::vector<std::string> ¶ms) + const std::string &type, const std::vector<std::string> ¶ms) { if (!player) return false; + player->setSky(bgcolor, type, params); SendSetSky(player->peer_id, bgcolor, type, params); return true; } bool Server::overrideDayNightRatio(Player *player, bool do_override, - float ratio) + float ratio) { if (!player) return false; + player->overrideDayNightRatio(do_override, ratio); SendOverrideDayNightRatio(player->peer_id, do_override, ratio); return true; } @@ -4689,68 +3043,45 @@ void Server::notifyPlayers(const std::wstring &msg) SendChatMessage(PEER_ID_INEXISTENT,msg); } -void Server::spawnParticle(const char *playername, v3f pos, - v3f velocity, v3f acceleration, - float expirationtime, float size, bool - collisiondetection, bool vertical, std::string texture) +void Server::spawnParticle(const std::string &playername, v3f pos, + v3f velocity, v3f acceleration, + float expirationtime, float size, bool + collisiondetection, bool vertical, const std::string &texture) { - Player *player = m_env->getPlayer(playername); - if(!player) + // m_env will be NULL if the server is initializing + if (!m_env) return; - SendSpawnParticle(player->peer_id, pos, velocity, acceleration, - expirationtime, size, collisiondetection, vertical, texture); -} -void Server::spawnParticleAll(v3f pos, v3f velocity, v3f acceleration, - float expirationtime, float size, - bool collisiondetection, bool vertical, std::string texture) -{ - SendSpawnParticle(PEER_ID_INEXISTENT,pos, velocity, acceleration, + u16 peer_id = PEER_ID_INEXISTENT; + if (playername != "") { + Player* player = m_env->getPlayer(playername.c_str()); + if (!player) + return; + peer_id = player->peer_id; + } + + SendSpawnParticle(peer_id, pos, velocity, acceleration, expirationtime, size, collisiondetection, vertical, texture); } -u32 Server::addParticleSpawner(const char *playername, - u16 amount, float spawntime, - v3f minpos, v3f maxpos, - v3f minvel, v3f maxvel, - v3f minacc, v3f maxacc, - float minexptime, float maxexptime, - float minsize, float maxsize, - bool collisiondetection, bool vertical, std::string texture) +u32 Server::addParticleSpawner(u16 amount, float spawntime, + v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc, + float minexptime, float maxexptime, float minsize, float maxsize, + bool collisiondetection, bool vertical, const std::string &texture, + const std::string &playername) { - Player *player = m_env->getPlayer(playername); - if(!player) + // m_env will be NULL if the server is initializing + if (!m_env) return -1; - u32 id = 0; - for(;;) // look for unused particlespawner id - { - id++; - if (std::find(m_particlespawner_ids.begin(), - m_particlespawner_ids.end(), id) - == m_particlespawner_ids.end()) - { - m_particlespawner_ids.push_back(id); - break; - } + u16 peer_id = PEER_ID_INEXISTENT; + if (playername != "") { + Player* player = m_env->getPlayer(playername.c_str()); + if (!player) + return -1; + peer_id = player->peer_id; } - SendAddParticleSpawner(player->peer_id, amount, spawntime, - minpos, maxpos, minvel, maxvel, minacc, maxacc, - minexptime, maxexptime, minsize, maxsize, - collisiondetection, vertical, texture, id); - - return id; -} - -u32 Server::addParticleSpawnerAll(u16 amount, float spawntime, - v3f minpos, v3f maxpos, - v3f minvel, v3f maxvel, - v3f minacc, v3f maxacc, - float minexptime, float maxexptime, - float minsize, float maxsize, - bool collisiondetection, bool vertical, std::string texture) -{ u32 id = 0; for(;;) // look for unused particlespawner id { @@ -4764,7 +3095,7 @@ u32 Server::addParticleSpawnerAll(u16 amount, float spawntime, } } - SendAddParticleSpawner(PEER_ID_INEXISTENT, amount, spawntime, + SendAddParticleSpawner(peer_id, amount, spawntime, minpos, maxpos, minvel, maxvel, minacc, maxacc, minexptime, maxexptime, minsize, maxsize, collisiondetection, vertical, texture, id); @@ -4772,26 +3103,25 @@ u32 Server::addParticleSpawnerAll(u16 amount, float spawntime, return id; } -void Server::deleteParticleSpawner(const char *playername, u32 id) +void Server::deleteParticleSpawner(const std::string &playername, u32 id) { - Player *player = m_env->getPlayer(playername); - if(!player) - return; + // m_env will be NULL if the server is initializing + if (!m_env) + throw ServerError("Can't delete particle spawners during initialisation!"); - m_particlespawner_ids.erase( - std::remove(m_particlespawner_ids.begin(), - m_particlespawner_ids.end(), id), - m_particlespawner_ids.end()); - SendDeleteParticleSpawner(player->peer_id, id); -} + u16 peer_id = PEER_ID_INEXISTENT; + if (playername != "") { + Player* player = m_env->getPlayer(playername.c_str()); + if (!player) + return; + peer_id = player->peer_id; + } -void Server::deleteParticleSpawnerAll(u32 id) -{ m_particlespawner_ids.erase( std::remove(m_particlespawner_ids.begin(), m_particlespawner_ids.end(), id), m_particlespawner_ids.end()); - SendDeleteParticleSpawner(PEER_ID_INEXISTENT, id); + SendDeleteParticleSpawner(peer_id, id); } Inventory* Server::createDetachedInventory(const std::string &name) @@ -4803,31 +3133,13 @@ Inventory* Server::createDetachedInventory(const std::string &name) infostream<<"Server creating detached inventory \""<<name<<"\""<<std::endl; } Inventory *inv = new Inventory(m_itemdef); - assert(inv); + sanity_check(inv); m_detached_inventories[name] = inv; //TODO find a better way to do this sendDetachedInventory(name,PEER_ID_INEXISTENT); return inv; } -class BoolScopeSet -{ -public: - BoolScopeSet(bool *dst, bool val): - m_dst(dst) - { - m_orig_state = *m_dst; - *m_dst = val; - } - ~BoolScopeSet() - { - *m_dst = m_orig_state; - } -private: - bool *m_dst; - bool m_orig_state; -}; - // actions: time-reversed list // Return value: success/failure bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions, @@ -4877,27 +3189,29 @@ bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions, // IGameDef interface // Under envlock -IItemDefManager* Server::getItemDefManager() +IItemDefManager *Server::getItemDefManager() { return m_itemdef; } -INodeDefManager* Server::getNodeDefManager() + +INodeDefManager *Server::getNodeDefManager() { return m_nodedef; } -ICraftDefManager* Server::getCraftDefManager() + +ICraftDefManager *Server::getCraftDefManager() { return m_craftdef; } -ITextureSource* Server::getTextureSource() +ITextureSource *Server::getTextureSource() { return NULL; } -IShaderSource* Server::getShaderSource() +IShaderSource *Server::getShaderSource() { return NULL; } -scene::ISceneManager* Server::getSceneManager() +scene::ISceneManager *Server::getSceneManager() { return NULL; } @@ -4906,67 +3220,73 @@ u16 Server::allocateUnknownNodeId(const std::string &name) { return m_nodedef->allocateDummy(name); } -ISoundManager* Server::getSoundManager() + +ISoundManager *Server::getSoundManager() { return &dummySoundManager; } -MtEventManager* Server::getEventManager() + +MtEventManager *Server::getEventManager() { return m_event; } -IWritableItemDefManager* Server::getWritableItemDefManager() +IWritableItemDefManager *Server::getWritableItemDefManager() { return m_itemdef; } -IWritableNodeDefManager* Server::getWritableNodeDefManager() + +IWritableNodeDefManager *Server::getWritableNodeDefManager() { return m_nodedef; } -IWritableCraftDefManager* Server::getWritableCraftDefManager() + +IWritableCraftDefManager *Server::getWritableCraftDefManager() { return m_craftdef; } -const ModSpec* Server::getModSpec(const std::string &modname) +const ModSpec *Server::getModSpec(const std::string &modname) const { - for(std::vector<ModSpec>::iterator i = m_mods.begin(); - i != m_mods.end(); i++){ - const ModSpec &mod = *i; - if(mod.name == modname) + std::vector<ModSpec>::const_iterator it; + for (it = m_mods.begin(); it != m_mods.end(); ++it) { + const ModSpec &mod = *it; + if (mod.name == modname) return &mod; } return NULL; } -void Server::getModNames(std::list<std::string> &modlist) + +void Server::getModNames(std::vector<std::string> &modlist) { - for(std::vector<ModSpec>::iterator i = m_mods.begin(); i != m_mods.end(); i++) - { - modlist.push_back(i->name); - } + std::vector<ModSpec>::iterator it; + for (it = m_mods.begin(); it != m_mods.end(); ++it) + modlist.push_back(it->name); } + std::string Server::getBuiltinLuaPath() { return porting::path_share + DIR_DELIM + "builtin"; } -v3f findSpawnPos(ServerMap &map) +v3f Server::findSpawnPos() { - //return v3f(50,50,50)*BS; - - v3s16 nodepos; + ServerMap &map = m_env->getServerMap(); + v3f nodeposf; + if (g_settings->getV3FNoEx("static_spawnpoint", nodeposf)) { + return nodeposf * BS; + } -#if 0 - nodepos = v2s16(0,0); - groundheight = 20; -#endif + // Default position is static_spawnpoint + // We will return it if we don't found a good place + v3s16 nodepos(nodeposf.X, nodeposf.Y, nodeposf.Z); -#if 1 s16 water_level = map.getWaterLevel(); + bool is_good = false; + // Try to find a good place a few times - for(s32 i=0; i<1000; i++) - { + for(s32 i = 0; i < 1000 && !is_good; i++) { s32 range = 1 + i; // We're going to try to throw the player to this position v2s16 nodepos2d = v2s16( @@ -4981,7 +3301,7 @@ v3f findSpawnPos(ServerMap &map) continue; nodepos = v3s16(nodepos2d.X, groundheight, nodepos2d.Y); - bool is_good = false; + s32 air_count = 0; for (s32 i = 0; i < 10; i++) { v3s16 blockpos = getNodeBlockPos(nodepos); @@ -4996,26 +3316,19 @@ v3f findSpawnPos(ServerMap &map) } nodepos.Y++; } - if(is_good){ - // Found a good place - //infostream<<"Searched through "<<i<<" places."<<std::endl; - break; - } } -#endif return intToFloat(nodepos, BS); } -PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id) +PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id, u16 proto_version) { - RemotePlayer *player = NULL; bool newplayer = false; /* Try to get an existing player */ - player = static_cast<RemotePlayer*>(m_env->getPlayer(name)); + RemotePlayer *player = static_cast<RemotePlayer*>(m_env->getPlayer(name)); // If player is already connected, cancel if(player != NULL && player->peer_id != 0) @@ -5046,7 +3359,7 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id) // Set player position infostream<<"Server: Finding spawn place for player \"" <<name<<"\""<<std::endl; - v3f pos = findSpawnPos(m_env->getServerMap()); + v3f pos = findSpawnPos(); player->setPosition(pos); // Make sure the player is saved @@ -5061,6 +3374,8 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id) getPlayerEffectivePrivs(player->getName()), isSingleplayer()); + player->protocol_version = proto_version; + /* Clean up old HUD elements from previous sessions */ player->clearHud(); @@ -5120,5 +3435,3 @@ void dedicated_server_loop(Server &server, bool &kill) } } } - - diff --git a/src/server.h b/src/server.h index 3d6b00d99..d16230967 100644 --- a/src/server.h +++ b/src/server.h @@ -20,7 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef SERVER_HEADER #define SERVER_HEADER -#include "connection.h" +#include "network/connection.h" #include "irr_v3d.h" #include "map.h" #include "hud.h" @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/thread.h" #include "environment.h" #include "clientiface.h" +#include "network/networkpacket.h" #include <string> #include <list> #include <map> @@ -62,11 +63,6 @@ enum ClientDeletionReason { CDR_DENY }; -/* - Some random functions -*/ -v3f findSpawnPos(ServerMap &map); - class MapEditEventIgnorer { public: @@ -187,7 +183,42 @@ public: void AsyncRunStep(bool initial_step=false); void Receive(); PlayerSAO* StageTwoClientInit(u16 peer_id); - void ProcessData(u8 *data, u32 datasize, u16 peer_id); + + /* + * Command Handlers + */ + + void handleCommand(NetworkPacket* pkt); + + void handleCommand_Null(NetworkPacket* pkt) {}; + void handleCommand_Deprecated(NetworkPacket* pkt); + void handleCommand_Init(NetworkPacket* pkt); + void handleCommand_Init_Legacy(NetworkPacket* pkt); + void handleCommand_Init2(NetworkPacket* pkt); + void handleCommand_RequestMedia(NetworkPacket* pkt); + void handleCommand_ReceivedMedia(NetworkPacket* pkt); + void handleCommand_ClientReady(NetworkPacket* pkt); + void handleCommand_GotBlocks(NetworkPacket* pkt); + void handleCommand_PlayerPos(NetworkPacket* pkt); + void handleCommand_DeletedBlocks(NetworkPacket* pkt); + void handleCommand_InventoryAction(NetworkPacket* pkt); + void handleCommand_ChatMessage(NetworkPacket* pkt); + void handleCommand_Damage(NetworkPacket* pkt); + void handleCommand_Breath(NetworkPacket* pkt); + void handleCommand_Password(NetworkPacket* pkt); + void handleCommand_PlayerItem(NetworkPacket* pkt); + void handleCommand_Respawn(NetworkPacket* pkt); + void handleCommand_Interact(NetworkPacket* pkt); + void handleCommand_RemovedSounds(NetworkPacket* pkt); + void handleCommand_NodeMetaFields(NetworkPacket* pkt); + void handleCommand_InventoryFields(NetworkPacket* pkt); + void handleCommand_FirstSrp(NetworkPacket* pkt); + void handleCommand_SrpBytesA(NetworkPacket* pkt); + void handleCommand_SrpBytesM(NetworkPacket* pkt); + + void ProcessData(NetworkPacket *pkt); + + void Send(NetworkPacket* pkt); // Environment must be locked when called void setTimeOfDay(u32 time); @@ -203,7 +234,7 @@ public: Shall be called with the environment and the connection locked. */ Inventory* getInventory(const InventoryLocation &loc); - void setInventoryModified(const InventoryLocation &loc); + void setInventoryModified(const InventoryLocation &loc, bool playerSend = true); // Connection must be locked when called std::wstring getStatusString(); @@ -213,8 +244,13 @@ public: { return m_shutdown_requested; } // request server to shutdown - inline void requestShutdown(void) - { m_shutdown_requested = true; } + inline void requestShutdown() { m_shutdown_requested = true; } + void requestShutdown(const std::string &msg, bool reconnect) + { + m_shutdown_requested = true; + m_shutdown_msg = msg; + m_shutdown_ask_reconnect = reconnect; + } // Returns -1 if failed, sound handle on success // Envlock @@ -233,34 +269,21 @@ public: void notifyPlayer(const char *name, const std::wstring &msg); void notifyPlayers(const std::wstring &msg); - void spawnParticle(const char *playername, + void spawnParticle(const std::string &playername, v3f pos, v3f velocity, v3f acceleration, float expirationtime, float size, - bool collisiondetection, bool vertical, std::string texture); + bool collisiondetection, bool vertical, const std::string &texture); - void spawnParticleAll(v3f pos, v3f velocity, v3f acceleration, - float expirationtime, float size, - bool collisiondetection, bool vertical, std::string texture); - - u32 addParticleSpawner(const char *playername, - u16 amount, float spawntime, + u32 addParticleSpawner(u16 amount, float spawntime, v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc, float minexptime, float maxexptime, float minsize, float maxsize, - bool collisiondetection, bool vertical, std::string texture); + bool collisiondetection, bool vertical, const std::string &texture, + const std::string &playername); - u32 addParticleSpawnerAll(u16 amount, float spawntime, - v3f minpos, v3f maxpos, - v3f minvel, v3f maxvel, - v3f minacc, v3f maxacc, - float minexptime, float maxexptime, - float minsize, float maxsize, - bool collisiondetection, bool vertical, std::string texture); - - void deleteParticleSpawner(const char *playername, u32 id); - void deleteParticleSpawnerAll(u32 id); + void deleteParticleSpawner(const std::string &playername, u32 id); // Creates or resets inventory Inventory* createDetachedInventory(const std::string &name); @@ -268,9 +291,6 @@ public: // Envlock and conlock should be locked when using scriptapi GameScripting *getScriptIface(){ return m_script; } - //TODO: determine what (if anything) should be locked to access EmergeManager - EmergeManager *getEmergeManager(){ return m_emerge; } - // actions: time-reversed list // Return value: success/failure bool rollbackRevertActions(const std::list<RollbackAction> &actions, @@ -288,16 +308,16 @@ public: virtual MtEventManager* getEventManager(); virtual scene::ISceneManager* getSceneManager(); virtual IRollbackManager *getRollbackManager() { return m_rollback; } - + virtual EmergeManager *getEmergeManager() { return m_emerge; } IWritableItemDefManager* getWritableItemDefManager(); IWritableNodeDefManager* getWritableNodeDefManager(); IWritableCraftDefManager* getWritableCraftDefManager(); - const ModSpec* getModSpec(const std::string &modname); - void getModNames(std::list<std::string> &modlist); + const ModSpec* getModSpec(const std::string &modname) const; + void getModNames(std::vector<std::string> &modlist); std::string getBuiltinLuaPath(); - inline std::string getWorldPath() + inline std::string getWorldPath() const { return m_path_world; } inline bool isSingleplayer() @@ -309,24 +329,27 @@ public: bool showFormspec(const char *name, const std::string &formspec, const std::string &formname); Map & getMap() { return m_env->getMap(); } ServerEnvironment & getEnv() { return *m_env; } - + u32 hudAdd(Player *player, HudElement *element); bool hudRemove(Player *player, u32 id); bool hudChange(Player *player, u32 id, HudElementStat stat, void *value); bool hudSetFlags(Player *player, u32 flags, u32 mask); bool hudSetHotbarItemcount(Player *player, s32 hotbar_itemcount); + s32 hudGetHotbarItemcount(Player *player); void hudSetHotbarImage(Player *player, std::string name); + std::string hudGetHotbarImage(Player *player); void hudSetHotbarSelectedImage(Player *player, std::string name); + std::string hudGetHotbarSelectedImage(Player *player); inline Address getPeerAddress(u16 peer_id) { return m_con.GetPeerAddress(peer_id); } - + bool setLocalPlayerAnimations(Player *player, v2s32 animation_frames[4], f32 frame_speed); bool setPlayerEyeOffset(Player *player, v3f first, v3f third); bool setSky(Player *player, const video::SColor &bgcolor, const std::string &type, const std::vector<std::string> ¶ms); - + bool overrideDayNightRatio(Player *player, bool do_override, float brightness); @@ -334,12 +357,22 @@ public: void peerAdded(con::Peer *peer); void deletingPeer(con::Peer *peer, bool timeout); - void DenyAccess(u16 peer_id, const std::wstring &reason); + void DenySudoAccess(u16 peer_id); + void DenyAccessVerCompliant(u16 peer_id, u16 proto_ver, AccessDeniedCode reason, + const std::string &str_reason = "", bool reconnect = false); + void DenyAccess(u16 peer_id, AccessDeniedCode reason, const std::string &custom_reason=""); + void acceptAuth(u16 peer_id, bool forSudoMode); + void DenyAccess_Legacy(u16 peer_id, const std::wstring &reason); bool getClientConInfo(u16 peer_id, con::rtt_stat_type type,float* retval); bool getClientInfo(u16 peer_id,ClientState* state, u32* uptime, u8* ser_vers, u16* prot_vers, u8* major, u8* minor, u8* patch, std::string* vers_string); + void SendPlayerHPOrDie(PlayerSAO *player); + void SendPlayerBreath(u16 peer_id); + void SendInventory(PlayerSAO* playerSAO); + void SendMovePlayer(u16 peer_id); + // Bind address Address m_bind_addr; @@ -351,7 +384,9 @@ private: void SendMovement(u16 peer_id); void SendHP(u16 peer_id, u8 hp); void SendBreath(u16 peer_id, u16 breath); - void SendAccessDenied(u16 peer_id,const std::wstring &reason); + void SendAccessDenied(u16 peer_id, AccessDeniedCode reason, + const std::string &custom_reason, bool reconnect = false); + void SendAccessDenied_Legacy(u16 peer_id, const std::wstring &reason); void SendDeathscreen(u16 peer_id,bool set_camera_point_target, v3f camera_point_target); void SendItemDef(u16 peer_id,IItemDefManager *itemdef, u16 protocol_version); void SendNodeDef(u16 peer_id,INodeDefManager *nodedef, u16 protocol_version); @@ -359,13 +394,11 @@ private: /* mark blocks not sent for all clients */ void SetBlocksNotSent(std::map<v3s16, MapBlock *>& block); - // Envlock and conlock should be locked when calling these - void SendInventory(u16 peer_id); + void SendChatMessage(u16 peer_id, const std::wstring &message); void SendTimeOfDay(u16 peer_id, u16 time, f32 time_speed); void SendPlayerHP(u16 peer_id); - void SendPlayerBreath(u16 peer_id); - void SendMovePlayer(u16 peer_id); + void SendLocalPlayerAnimations(u16 peer_id, v2s32 animation_frames[4], f32 animation_speed); void SendEyeOffset(u16 peer_id, v3f first, v3f third); void SendPlayerPrivileges(u16 peer_id); @@ -379,7 +412,7 @@ private: void SendSetSky(u16 peer_id, const video::SColor &bgcolor, const std::string &type, const std::vector<std::string> ¶ms); void SendOverrideDayNightRatio(u16 peer_id, bool do_override, float ratio); - + /* Send a node removal/addition event to all clients except ignore_id. Additionally, if far_players!=NULL, players further away than @@ -387,9 +420,9 @@ private: */ // Envlock and conlock should be locked when calling these void sendRemoveNode(v3s16 p, u16 ignore_id=0, - std::list<u16> *far_players=NULL, float far_d_nodes=100); + std::vector<u16> *far_players=NULL, float far_d_nodes=100); void sendAddNode(v3s16 p, MapNode n, u16 ignore_id=0, - std::list<u16> *far_players=NULL, float far_d_nodes=100, + std::vector<u16> *far_players=NULL, float far_d_nodes=100, bool remove_metadata=true); void setBlockNotSent(v3s16 p); @@ -402,7 +435,7 @@ private: void fillMediaCache(); void sendMediaAnnouncement(u16 peer_id); void sendRequestedMedia(u16 peer_id, - const std::list<std::string> &tosend); + const std::vector<std::string> &tosend); void sendDetachedInventory(const std::string &name, u16 peer_id); void sendDetachedInventories(u16 peer_id); @@ -424,6 +457,8 @@ private: float expirationtime, float size, bool collisiondetection, bool vertical, std::string texture); + u32 SendActiveObjectRemoveAdd(u16 peer_id, const std::string &datas); + void SendActiveObjectMessages(u16 peer_id, const std::string &datas, bool reliable = true); /* Something random */ @@ -431,7 +466,9 @@ private: void DiePlayer(u16 peer_id); void RespawnPlayer(u16 peer_id); void DeleteClient(u16 peer_id, ClientDeletionReason reason); - void UpdateCrafting(u16 peer_id); + void UpdateCrafting(Player *player); + + v3f findSpawnPos(); // When called, connection mutex should be locked RemoteClient* getClient(u16 peer_id,ClientState state_min=CS_Active); @@ -448,7 +485,7 @@ private: Call with env and con locked. */ - PlayerSAO *emergePlayer(const char *name, u16 peer_id); + PlayerSAO *emergePlayer(const char *name, u16 peer_id, u16 proto_version); void handlePeerChanges(); @@ -547,16 +584,15 @@ private: Queues stuff from peerAdded() and deletingPeer() to handlePeerChanges() */ - Queue<con::PeerChange> m_peer_change_queue; + std::queue<con::PeerChange> m_peer_change_queue; /* Random stuff */ - // Mod parent directory paths - std::list<std::string> m_modspaths; - bool m_shutdown_requested; + std::string m_shutdown_msg; + bool m_shutdown_ask_reconnect; /* Map edit event queue. Automatically receives all map edits. @@ -571,7 +607,7 @@ private: Queue of map edits from the environment for sending to the clients This is behind m_env_mutex */ - Queue<MapEditEvent*> m_unsent_map_edit_queue; + std::queue<MapEditEvent*> m_unsent_map_edit_queue; /* Set to true when the server itself is modifying the map and does all sending of information by itself. @@ -616,9 +652,9 @@ private: /* Runs a simple dedicated server loop. - Shuts down when run is set to false. + Shuts down when kill is set to true. */ -void dedicated_server_loop(Server &server, bool &run); +void dedicated_server_loop(Server &server, bool &kill); #endif diff --git a/src/serverlist.cpp b/src/serverlist.cpp index 6732e5ac9..a33d1d6bf 100644 --- a/src/serverlist.cpp +++ b/src/serverlist.cpp @@ -17,18 +17,18 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <fstream> #include <iostream> #include <sstream> #include <algorithm> #include "version.h" -#include "main.h" // for g_settings #include "settings.h" #include "serverlist.h" #include "filesys.h" #include "porting.h" #include "log.h" -#include "clientserver.h" +#include "network/networkprotocol.h" #include "json/json.h" #include "convert_json.h" #include "httpfetch.h" @@ -212,7 +212,7 @@ void sendAnnounce(const std::string &action, bool strict_checking = g_settings->getBool("strict_protocol_version_checking"); server["name"] = g_settings->get("server_name"); server["description"] = g_settings->get("server_description"); - server["version"] = minetest_version_simple; + server["version"] = g_version_string; server["proto_min"] = strict_checking ? LATEST_PROTOCOL_VERSION : SERVER_PROTOCOL_VERSION_MIN; server["proto_max"] = strict_checking ? LATEST_PROTOCOL_VERSION : SERVER_PROTOCOL_VERSION_MAX; server["url"] = g_settings->get("server_url"); diff --git a/src/serverobject.cpp b/src/serverobject.cpp index 81307bc34..699040bf2 100644 --- a/src/serverobject.cpp +++ b/src/serverobject.cpp @@ -38,15 +38,19 @@ ServerActiveObject::~ServerActiveObject() { } -ServerActiveObject* ServerActiveObject::create(u8 type, +ServerActiveObject* ServerActiveObject::create(ActiveObjectType type, ServerEnvironment *env, u16 id, v3f pos, const std::string &data) { // Find factory function std::map<u16, Factory>::iterator n; n = m_types.find(type); - if(n == m_types.end()) - { + if(n == m_types.end()) { + // These are 0.3 entity types, return without error. + if (ACTIVEOBJECT_TYPE_ITEM <= type && type <= ACTIVEOBJECT_TYPE_MOBV2) { + return NULL; + } + // If factory is not found, just return. dstream<<"WARNING: ServerActiveObject: No factory for type=" <<type<<std::endl; @@ -86,14 +90,9 @@ ItemStack ServerActiveObject::getWieldedItem() const bool ServerActiveObject::setWieldedItem(const ItemStack &item) { - Inventory *inv = getInventory(); - if(inv) - { - InventoryList *list = inv->getList(getWieldList()); - if (list) - { + if(Inventory *inv = getInventory()) { + if (InventoryList *list = inv->getList(getWieldList())) { list->changeItem(getWieldIndex(), item); - setInventoryModified(); return true; } } diff --git a/src/serverobject.h b/src/serverobject.h index 8e80225e4..597eb63a8 100644 --- a/src/serverobject.h +++ b/src/serverobject.h @@ -58,7 +58,7 @@ public: ServerActiveObject(ServerEnvironment *env, v3f pos); virtual ~ServerActiveObject(); - virtual u8 getSendType() const + virtual ActiveObjectType getSendType() const { return getType(); } // Called after id has been set and has been inserted in environment @@ -71,7 +71,7 @@ public: { return true; } // Create a certain type of ServerActiveObject - static ServerActiveObject* create(u8 type, + static ServerActiveObject* create(ActiveObjectType type, ServerEnvironment *env, u16 id, v3f pos, const std::string &data); @@ -147,14 +147,28 @@ public: virtual void setArmorGroups(const ItemGroupList &armor_groups) {} + virtual ItemGroupList getArmorGroups() + { return ItemGroupList(); } virtual void setPhysicsOverride(float physics_override_speed, float physics_override_jump, float physics_override_gravity) {} - virtual void setAnimation(v2f frames, float frame_speed, float frame_blend) + virtual void setAnimation(v2f frames, float frame_speed, float frame_blend, bool frame_loop) {} - virtual void setBonePosition(std::string bone, v3f position, v3f rotation) + virtual void getAnimation(v2f *frames, float *frame_speed, float *frame_blend, bool *frame_loop) {} - virtual void setAttachment(int parent_id, std::string bone, v3f position, v3f rotation) + virtual void setBonePosition(const std::string &bone, v3f position, v3f rotation) {} + 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 addAttachmentChild(int child_id) + {} + virtual void removeAttachmentChild(int child_id) + {} + virtual std::set<int> getAttachmentChildIds() + { return std::set<int>(); } virtual ObjectProperties* accessObjectProperties() { return NULL; } virtual void notifyObjectPropertiesModified() @@ -218,7 +232,7 @@ public: /* Queue of messages to be sent to the client */ - Queue<ActiveObjectMessage> m_messages_out; + std::queue<ActiveObjectMessage> m_messages_out; protected: // Used for creating objects based on type diff --git a/src/settings.cpp b/src/settings.cpp index 7339af62b..e95bd436d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -33,6 +33,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cctype> #include <algorithm> +static Settings main_settings; +Settings *g_settings = &main_settings; +std::string g_settings_path; Settings::~Settings() { @@ -65,10 +68,11 @@ Settings & Settings::operator = (const Settings &other) bool Settings::checkNameValid(const std::string &name) { - size_t pos = name.find_first_of("\t\n\v\f\r\b =\"{}#"); - if (pos != std::string::npos) { - errorstream << "Invalid character '" << name[pos] - << "' found in setting name" << std::endl; + bool valid = name.find_first_of("=\"{}#") == std::string::npos; + if (valid) valid = trim(name) == name; + if (!valid) { + errorstream << "Invalid setting name \"" << name << "\"" + << std::endl; return false; } return true; @@ -80,7 +84,7 @@ bool Settings::checkValueValid(const std::string &value) if (value.substr(0, 3) == "\"\"\"" || value.find("\n\"\"\"") != std::string::npos) { errorstream << "Invalid character sequence '\"\"\"' found in" - " setting value" << std::endl; + " setting value!" << std::endl; return false; } return true; @@ -89,9 +93,9 @@ bool Settings::checkValueValid(const std::string &value) std::string Settings::sanitizeName(const std::string &name) { - std::string n(name); + std::string n = trim(name); - for (const char *s = "\t\n\v\f\r\b =\"{}#"; *s; s++) + for (const char *s = "=\"{}#"; *s; s++) n.erase(std::remove(n.begin(), n.end(), *s), n.end()); return n; @@ -264,7 +268,7 @@ bool Settings::updateConfigObject(std::istream &is, std::ostream &os, it = m_settings.find(name); if (it != m_settings.end() && it->second.is_group) { os << line << "\n"; - assert(it->second.group != NULL); + sanity_check(it->second.group != NULL); was_modified |= it->second.group->updateConfigObject(is, os, "}", tab_depth + 1); } else { @@ -887,6 +891,11 @@ void Settings::clear() clearNoLock(); } +void Settings::clearDefaults() +{ + JMutexAutoLock lock(m_mutex); + clearDefaultsNoLock(); +} void Settings::updateValue(const Settings &other, const std::string &name) { @@ -958,11 +967,18 @@ void Settings::clearNoLock() delete it->second.group; m_settings.clear(); + clearDefaultsNoLock(); +} + +void Settings::clearDefaultsNoLock() +{ + std::map<std::string, SettingsEntry>::const_iterator it; for (it = m_defaults.begin(); it != m_defaults.end(); ++it) delete it->second.group; m_defaults.clear(); } + void Settings::registerChangedCallback(std::string name, setting_changed_callback cbf, void *userdata) { diff --git a/src/settings.h b/src/settings.h index 1a7d9ab96..d41f134cd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -31,8 +31,12 @@ with this program; if not, write to the Free Software Foundation, Inc., class Settings; struct NoiseParams; +// Global objects +extern Settings *g_settings; +extern std::string g_settings_path; + /** function type to register a changed callback */ -typedef void (*setting_changed_callback)(const std::string, void*); +typedef void (*setting_changed_callback)(const std::string &name, void *data); enum ValueType { VALUETYPE_STRING, @@ -202,6 +206,7 @@ public: // remove a setting bool remove(const std::string &name); void clear(); + void clearDefaults(); void updateValue(const Settings &other, const std::string &name); void update(const Settings &other); void registerChangedCallback(std::string name, setting_changed_callback cbf, void *userdata = NULL); @@ -211,6 +216,7 @@ private: void updateNoLock(const Settings &other); void clearNoLock(); + void clearDefaultsNoLock(); void doCallbacks(std::string name); diff --git a/src/shader.cpp b/src/shader.cpp index 167045804..7e4f40810 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -18,15 +18,15 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <fstream> +#include <iterator> #include "shader.h" #include "irrlichttypes_extrabloated.h" #include "debug.h" -#include "main.h" // for g_settings #include "filesys.h" #include "util/container.h" #include "util/thread.h" #include "settings.h" -#include <iterator> #include <ICameraSceneNode.h> #include <IGPUProgrammingServices.h> #include <IMaterialRenderer.h> @@ -36,7 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "gamedef.h" #include "strfnd.h" // trim() -#include "tile.h" +#include "client/tile.h" /* A cache from shader name to shader path @@ -103,10 +103,8 @@ std::string getShaderPath(const std::string &name_of_shader, class SourceShaderCache { public: - void insert(const std::string &name_of_shader, - const std::string &filename, - const std::string &program, - bool prefer_local) + void insert(const std::string &name_of_shader, const std::string &filename, + const std::string &program, bool prefer_local) { std::string combined = name_of_shader + DIR_DELIM + filename; // Try to use local shader instead if asked to @@ -122,42 +120,43 @@ public: } m_programs[combined] = program; } + std::string get(const std::string &name_of_shader, - const std::string &filename) + const std::string &filename) { std::string combined = name_of_shader + DIR_DELIM + filename; - std::map<std::string, std::string>::iterator n; - n = m_programs.find(combined); - if(n != m_programs.end()) + StringMap::iterator n = m_programs.find(combined); + if (n != m_programs.end()) return n->second; return ""; } + // Primarily fetches from cache, secondarily tries to read from filesystem std::string getOrLoad(const std::string &name_of_shader, - const std::string &filename) + const std::string &filename) { std::string combined = name_of_shader + DIR_DELIM + filename; - std::map<std::string, std::string>::iterator n; - n = m_programs.find(combined); - if(n != m_programs.end()) + StringMap::iterator n = m_programs.find(combined); + if (n != m_programs.end()) return n->second; std::string path = getShaderPath(name_of_shader, filename); - if(path == ""){ - infostream<<"SourceShaderCache::getOrLoad(): No path found for \"" - <<combined<<"\""<<std::endl; + if (path == "") { + infostream << "SourceShaderCache::getOrLoad(): No path found for \"" + << combined << "\"" << std::endl; return ""; } - infostream<<"SourceShaderCache::getOrLoad(): Loading path \""<<path - <<"\""<<std::endl; + infostream << "SourceShaderCache::getOrLoad(): Loading path \"" + << path << "\"" << std::endl; std::string p = readFile(path); - if(p != ""){ + if (p != "") { m_programs[combined] = p; return p; } return ""; } private: - std::map<std::string, std::string> m_programs; + StringMap m_programs; + std::string readFile(const std::string &path) { std::ifstream is(path.c_str(), std::ios::binary); @@ -196,7 +195,7 @@ public: virtual void OnSetConstants(video::IMaterialRendererServices *services, s32 userData) { video::IVideoDriver *driver = services->getVideoDriver(); - assert(driver); + sanity_check(driver != NULL); bool is_highlevel = userData; @@ -219,7 +218,7 @@ public: bool is_highlevel) { video::IVideoDriver *driver = services->getVideoDriver(); - assert(driver); + sanity_check(driver); // set inverted world matrix core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD); @@ -274,23 +273,23 @@ public: The id 0 points to a null shader. Its material is EMT_SOLID. */ - u32 getShaderIdDirect(const std::string &name, + u32 getShaderIdDirect(const std::string &name, const u8 material_type, const u8 drawtype); /* If shader specified by the name pointed by the id doesn't - exist, create it, then return id. + exist, create it, then return id. Can be called from any thread. If called from some other thread and not found in cache, the call is queued to the main thread for processing. */ - + u32 getShader(const std::string &name, const u8 material_type, const u8 drawtype); - + ShaderInfo getShaderInfo(u32 id); - + // Processes queued shader requests from other threads. // Shall be called from the main thread. void processQueue(); @@ -364,7 +363,7 @@ void load_shaders(std::string name, SourceShaderCache *sourcecache, ShaderSource::ShaderSource(IrrlichtDevice *device): m_device(device) { - assert(m_device); + assert(m_device); // Pre-condition m_shader_callback = new ShaderCallback(this, "default"); @@ -391,7 +390,7 @@ ShaderSource::~ShaderSource() } } -u32 ShaderSource::getShader(const std::string &name, +u32 ShaderSource::getShader(const std::string &name, const u8 material_type, const u8 drawtype) { /* @@ -435,7 +434,7 @@ u32 ShaderSource::getShader(const std::string &name, /* This method generates all the shaders */ -u32 ShaderSource::getShaderIdDirect(const std::string &name, +u32 ShaderSource::getShaderIdDirect(const std::string &name, const u8 material_type, const u8 drawtype) { //infostream<<"getShaderIdDirect(): name=\""<<name<<"\""<<std::endl; @@ -494,7 +493,7 @@ ShaderInfo ShaderSource::getShaderInfo(u32 id) void ShaderSource::processQueue() { - + } @@ -505,7 +504,7 @@ void ShaderSource::insertSourceShader(const std::string &name_of_shader, "name_of_shader=\""<<name_of_shader<<"\", " "filename=\""<<filename<<"\""<<std::endl;*/ - assert(get_current_thread_id() == m_main_thread); + sanity_check(get_current_thread_id() == m_main_thread); m_sourcecache.insert(name_of_shader, filename, program, true); } @@ -572,13 +571,13 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, shaderinfo.base_material = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; break; } - + bool enable_shaders = g_settings->getBool("enable_shaders"); if(!enable_shaders) return shaderinfo; video::IVideoDriver* driver = device->getVideoDriver(); - assert(driver); + sanity_check(driver); video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices(); if(!gpu){ @@ -648,7 +647,7 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, "NDT_FIRELIKE", "NDT_GLASSLIKE_FRAMED_OPTIONAL" }; - + for (int i = 0; i < 14; i++){ shaders_header += "#define "; shaders_header += drawTypes[i]; @@ -681,47 +680,62 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, shaders_header += itos(drawtype); shaders_header += "\n"; - if (g_settings->getBool("generate_normalmaps")){ - shaders_header += "#define GENERATE_NORMALMAPS\n"; - shaders_header += "#define NORMALMAPS_STRENGTH "; - shaders_header += ftos(g_settings->getFloat("normalmaps_strength")); - shaders_header += "\n"; - float sample_step; - int smooth = (int)g_settings->getFloat("normalmaps_smooth"); - switch (smooth){ - case 0: - sample_step = 0.0078125; // 1.0 / 128.0 - break; - case 1: - sample_step = 0.00390625; // 1.0 / 256.0 - break; - case 2: - sample_step = 0.001953125; // 1.0 / 512.0 - break; - default: - sample_step = 0.0078125; - break; - } - shaders_header += "#define SAMPLE_STEP "; - shaders_header += ftos(sample_step); - shaders_header += "\n"; + if (g_settings->getBool("generate_normalmaps")) { + shaders_header += "#define GENERATE_NORMALMAPS 1\n"; + } else { + shaders_header += "#define GENERATE_NORMALMAPS 0\n"; + } + shaders_header += "#define NORMALMAPS_STRENGTH "; + shaders_header += ftos(g_settings->getFloat("normalmaps_strength")); + shaders_header += "\n"; + float sample_step; + int smooth = (int)g_settings->getFloat("normalmaps_smooth"); + switch (smooth){ + case 0: + sample_step = 0.0078125; // 1.0 / 128.0 + break; + case 1: + sample_step = 0.00390625; // 1.0 / 256.0 + break; + case 2: + sample_step = 0.001953125; // 1.0 / 512.0 + break; + default: + sample_step = 0.0078125; + break; } + shaders_header += "#define SAMPLE_STEP "; + shaders_header += ftos(sample_step); + shaders_header += "\n"; if (g_settings->getBool("enable_bumpmapping")) shaders_header += "#define ENABLE_BUMPMAPPING\n"; if (g_settings->getBool("enable_parallax_occlusion")){ + int mode = g_settings->getFloat("parallax_occlusion_mode"); + float scale = g_settings->getFloat("parallax_occlusion_scale"); + float bias = g_settings->getFloat("parallax_occlusion_bias"); + int iterations = g_settings->getFloat("parallax_occlusion_iterations"); shaders_header += "#define ENABLE_PARALLAX_OCCLUSION\n"; + shaders_header += "#define PARALLAX_OCCLUSION_MODE "; + shaders_header += itos(mode); + shaders_header += "\n"; shaders_header += "#define PARALLAX_OCCLUSION_SCALE "; - shaders_header += ftos(g_settings->getFloat("parallax_occlusion_scale")); + shaders_header += ftos(scale); shaders_header += "\n"; shaders_header += "#define PARALLAX_OCCLUSION_BIAS "; - shaders_header += ftos(g_settings->getFloat("parallax_occlusion_bias")); + shaders_header += ftos(bias); + shaders_header += "\n"; + shaders_header += "#define PARALLAX_OCCLUSION_ITERATIONS "; + shaders_header += itos(iterations); shaders_header += "\n"; } + shaders_header += "#define USE_NORMALMAPS "; if (g_settings->getBool("enable_bumpmapping") || g_settings->getBool("enable_parallax_occlusion")) - shaders_header += "#define USE_NORMALMAPS\n"; + shaders_header += "1\n"; + else + shaders_header += "0\n"; if (g_settings->getBool("enable_waving_water")){ shaders_header += "#define ENABLE_WAVING_WATER 1\n"; @@ -741,10 +755,10 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, shaders_header += "#define ENABLE_WAVING_LEAVES "; if (g_settings->getBool("enable_waving_leaves")) shaders_header += "1\n"; - else + else shaders_header += "0\n"; - shaders_header += "#define ENABLE_WAVING_PLANTS "; + shaders_header += "#define ENABLE_WAVING_PLANTS "; if (g_settings->getBool("enable_waving_plants")) shaders_header += "1\n"; else @@ -756,7 +770,6 @@ ShaderInfo generate_shader(std::string name, u8 material_type, u8 drawtype, vertex_program = shaders_header + vertex_program; if(geometry_program != "") geometry_program = shaders_header + geometry_program; - // Call addHighLevelShaderMaterial() or addShaderMaterial() const c8* vertex_program_ptr = 0; const c8* pixel_program_ptr = 0; diff --git a/src/sky.cpp b/src/sky.cpp index 664ed694b..01fb8ef86 100644 --- a/src/sky.cpp +++ b/src/sky.cpp @@ -3,14 +3,13 @@ #include "ISceneManager.h" #include "ICameraSceneNode.h" #include "S3DVertex.h" -#include "tile.h" -#include "noise.h" // easeCurve -#include "main.h" // g_profiler +#include "client/tile.h" +#include "noise.h" // easeCurve #include "profiler.h" -#include "util/numeric.h" // MYMIN +#include "util/numeric.h" #include <cmath> #include "settings.h" -#include "camera.h" // CameraModes +#include "camera.h" // CameraModes //! constructor Sky::Sky(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id, @@ -47,14 +46,14 @@ Sky::Sky(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id, m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; m_materials[2] = mat; - m_materials[2].setTexture(0, tsrc->getTexture("sunrisebg.png")); + m_materials[2].setTexture(0, tsrc->getTextureForMesh("sunrisebg.png")); m_materials[2].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; //m_materials[2].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR; m_sun_texture = tsrc->isKnownSourceImage("sun.png") ? - tsrc->getTexture("sun.png") : NULL; + tsrc->getTextureForMesh("sun.png") : NULL; m_moon_texture = tsrc->isKnownSourceImage("moon.png") ? - tsrc->getTexture("moon.png") : NULL; + tsrc->getTextureForMesh("moon.png") : NULL; m_sun_tonemap = tsrc->isKnownSourceImage("sun_tonemap.png") ? tsrc->getTexture("sun_tonemap.png") : NULL; m_moon_tonemap = tsrc->isKnownSourceImage("moon_tonemap.png") ? diff --git a/src/socket.cpp b/src/socket.cpp index df9c57c5f..e82052f77 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -32,7 +32,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "debug.h" #include "settings.h" #include "log.h" -#include "main.h" // for g_settings #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -45,9 +44,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> - #ifdef _MSC_VER - #pragma comment(lib, "ws2_32.lib") - #endif typedef SOCKET socket_t; typedef int socklen_t; #else @@ -155,7 +151,7 @@ void Address::Resolve(const char *name) struct addrinfo *resolved, hints; memset(&hints, 0, sizeof(hints)); - + // Setup hints hints.ai_socktype = 0; hints.ai_protocol = 0; @@ -169,7 +165,7 @@ void Address::Resolve(const char *name) { hints.ai_family = AF_INET; } - + // Do getaddrinfo() int e = getaddrinfo(name, NULL, &hints, &resolved); if(e != 0) @@ -515,7 +511,7 @@ int UDPSocket::Receive(Address & sender, void *data, int size) dstream << (int) m_handle << " <- "; sender.print(&dstream); dstream << ", size=" << received; - + // Print packet contents dstream << ", data="; for(int i = 0; i < received && i < 20; i++) { @@ -526,7 +522,7 @@ int UDPSocket::Receive(Address & sender, void *data, int size) } if(received > 20) dstream << "..."; - + dstream << std::endl; } @@ -587,7 +583,7 @@ bool UDPSocket::WaitData(int timeout_ms) // No data return false; } - + // There is data return true; } diff --git a/src/sound_openal.cpp b/src/sound_openal.cpp index b2b424a19..cb4c7b581 100644 --- a/src/sound_openal.cpp +++ b/src/sound_openal.cpp @@ -37,10 +37,10 @@ with this program; ifnot, write to the Free Software Foundation, Inc., #include <AL/alext.h> #endif #include <vorbis/vorbisfile.h> +#include <assert.h> #include "log.h" #include "filesys.h" #include "util/numeric.h" // myrand() -#include "debug.h" // assert() #include "porting.h" #include <map> #include <vector> diff --git a/src/staticobject.cpp b/src/staticobject.cpp index 16d98605b..2e7d45a47 100644 --- a/src/staticobject.cpp +++ b/src/staticobject.cpp @@ -47,10 +47,9 @@ void StaticObjectList::serialize(std::ostream &os) // count u16 count = m_stored.size() + m_active.size(); writeU16(os, count); - for(std::list<StaticObject>::iterator + for(std::vector<StaticObject>::iterator i = m_stored.begin(); - i != m_stored.end(); ++i) - { + i != m_stored.end(); ++i) { StaticObject &s_obj = *i; s_obj.serialize(os); } @@ -68,8 +67,7 @@ void StaticObjectList::deSerialize(std::istream &is) u8 version = readU8(is); // count u16 count = readU16(is); - for(u16 i=0; i<count; i++) - { + for(u16 i = 0; i < count; i++) { StaticObject s_obj; s_obj.deSerialize(is, version); m_stored.push_back(s_obj); diff --git a/src/staticobject.h b/src/staticobject.h index 575c15b18..95a1b945e 100644 --- a/src/staticobject.h +++ b/src/staticobject.h @@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include <string> #include <sstream> -#include <list> +#include <vector> #include <map> #include "debug.h" @@ -68,8 +68,7 @@ public: { dstream<<"ERROR: StaticObjectList::insert(): " <<"id already exists"<<std::endl; - assert(0); - return; + FATAL_ERROR("StaticObjectList::insert()"); } m_active[id] = obj; } @@ -77,7 +76,7 @@ public: void remove(u16 id) { - assert(id != 0); + assert(id != 0); // Pre-condition if(m_active.find(id) == m_active.end()) { dstream<<"WARNING: StaticObjectList::remove(): id="<<id @@ -95,7 +94,7 @@ public: from m_stored and inserted to m_active. The caller directly manipulates these containers. */ - std::list<StaticObject> m_stored; + std::vector<StaticObject> m_stored; std::map<u16, StaticObject> m_active; private: diff --git a/src/subgame.cpp b/src/subgame.cpp index fd2679eae..f736a78c6 100644 --- a/src/subgame.cpp +++ b/src/subgame.cpp @@ -21,13 +21,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "porting.h" #include "filesys.h" #include "settings.h" -#include "main.h" #include "log.h" #include "strfnd.h" +#include "defaultsettings.h" // for override_default_settings +#include "mapgen.h" // for MapgenParams +#include "util/string.h" + #ifndef SERVER -#include "tile.h" // getImagePath + #include "client/tile.h" // getImagePath #endif -#include "util/string.h" bool getGameMinetestConfig(const std::string &game_path, Settings &conf) { @@ -264,8 +266,18 @@ std::vector<WorldSpec> getAvailableWorlds() return worlds; } -bool initializeWorld(const std::string &path, const std::string &gameid) +bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamespec) { + // Override defaults with those provided by the game. + // We clear and reload the defaults because the defaults + // might have been overridden by other subgame config + // files that were loaded before. + g_settings->clearDefaults(); + set_default_settings(g_settings); + Settings game_defaults; + getGameMinetestConfig(gamespec.path, game_defaults); + override_default_settings(g_settings, &game_defaults); + infostream << "Initializing world at " << path << std::endl; fs::CreateAllDirs(path); @@ -274,7 +286,11 @@ bool initializeWorld(const std::string &path, const std::string &gameid) std::string worldmt_path = path + DIR_DELIM "world.mt"; if (!fs::PathExists(worldmt_path)) { std::ostringstream ss(std::ios_base::binary); - ss << "gameid = " << gameid << "\nbackend = sqlite3\n"; + ss << "gameid = " << gamespec.id + << "\nbackend = sqlite3" + << "\ncreative_mode = " << g_settings->get("creative_mode") + << "\nenable_damage = " << g_settings->get("enable_damage") + << "\n"; if (!fs::safeWriteToFile(worldmt_path, ss.str())) return false; @@ -282,21 +298,22 @@ bool initializeWorld(const std::string &path, const std::string &gameid) } // Create map_meta.txt if does not already exist - std::string mapmeta_path = path + DIR_DELIM "map_meta.txt"; - if (!fs::PathExists(mapmeta_path)) { - std::ostringstream ss(std::ios_base::binary); - ss - << "mg_name = " << g_settings->get("mg_name") - << "\nseed = " << g_settings->get("fixed_map_seed") - << "\nchunksize = " << g_settings->get("chunksize") - << "\nwater_level = " << g_settings->get("water_level") - << "\nmg_flags = " << g_settings->get("mg_flags") - << "\n[end_of_params]\n"; - if (!fs::safeWriteToFile(mapmeta_path, ss.str())) - return false; + std::string map_meta_path = path + DIR_DELIM + "map_meta.txt"; + if (!fs::PathExists(map_meta_path)){ + verbosestream << "Creating map_meta.txt (" << map_meta_path << ")" << std::endl; + fs::CreateAllDirs(path); + std::ostringstream oss(std::ios_base::binary); - infostream << "Wrote map_meta.txt (" << mapmeta_path << ")" << std::endl; - } + Settings conf; + MapgenParams params; + + params.load(*g_settings); + params.save(conf); + conf.writeLines(oss); + oss << "[end_of_params]\n"; + fs::safeWriteToFile(map_meta_path, oss.str()); + } return true; } + diff --git a/src/subgame.h b/src/subgame.h index 4b15faa8d..f3633ce2f 100644 --- a/src/subgame.h +++ b/src/subgame.h @@ -98,8 +98,9 @@ struct WorldSpec std::vector<WorldSpec> getAvailableWorlds(); -// Create world directory and world.mt if they don't exist -bool initializeWorld(const std::string &path, const std::string &gameid); +// loads the subgame's config and creates world directory +// and world.mt if they don't exist +bool loadGameConfAndInitWorld(const std::string &path, const SubgameSpec &gamespec); #endif diff --git a/src/test.cpp b/src/test.cpp deleted file mode 100644 index 80494e07a..000000000 --- a/src/test.cpp +++ /dev/null @@ -1,2217 +0,0 @@ -/* -Minetest -Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ - -#include "test.h" -#include "irrlichttypes_extrabloated.h" -#include "debug.h" -#include "map.h" -#include "player.h" -#include "main.h" -#include "socket.h" -#include "connection.h" -#include "serialization.h" -#include "voxel.h" -#include "collision.h" -#include <sstream> -#include "porting.h" -#include "content_mapnode.h" -#include "nodedef.h" -#include "mapsector.h" -#include "settings.h" -#include "log.h" -#include "util/string.h" -#include "filesys.h" -#include "voxelalgorithms.h" -#include "inventory.h" -#include "util/numeric.h" -#include "util/serialize.h" -#include "noise.h" // PseudoRandom used for random data for compression -#include "clientserver.h" // LATEST_PROTOCOL_VERSION -#include <algorithm> - -/* - Asserts that the exception occurs -*/ -#define EXCEPTION_CHECK(EType, code)\ -{\ - bool exception_thrown = false;\ - try{ code; }\ - catch(EType &e) { exception_thrown = true; }\ - UASSERT(exception_thrown);\ -} - -#define UTEST(x, fmt, ...)\ -{\ - if(!(x)){\ - dstream << "Test (" #x ") failed: " fmt << std::endl; \ - test_failed = true;\ - }\ -} - -#define UASSERT(x) UTEST(x, "UASSERT") - -/* - A few item and node definitions for those tests that need them -*/ - -static content_t CONTENT_STONE; -static content_t CONTENT_GRASS; -static content_t CONTENT_TORCH; - -void define_some_nodes(IWritableItemDefManager *idef, IWritableNodeDefManager *ndef) -{ - ItemDefinition itemdef; - ContentFeatures f; - - /* - Stone - */ - itemdef = ItemDefinition(); - itemdef.type = ITEM_NODE; - itemdef.name = "default:stone"; - itemdef.description = "Stone"; - itemdef.groups["cracky"] = 3; - itemdef.inventory_image = "[inventorycube" - "{default_stone.png" - "{default_stone.png" - "{default_stone.png"; - f = ContentFeatures(); - f.name = itemdef.name; - for(int i = 0; i < 6; i++) - f.tiledef[i].name = "default_stone.png"; - f.is_ground_content = true; - idef->registerItem(itemdef); - CONTENT_STONE = ndef->set(f.name, f); - - /* - Grass - */ - itemdef = ItemDefinition(); - itemdef.type = ITEM_NODE; - itemdef.name = "default:dirt_with_grass"; - itemdef.description = "Dirt with grass"; - itemdef.groups["crumbly"] = 3; - itemdef.inventory_image = "[inventorycube" - "{default_grass.png" - "{default_dirt.png&default_grass_side.png" - "{default_dirt.png&default_grass_side.png"; - f = ContentFeatures(); - f.name = itemdef.name; - f.tiledef[0].name = "default_grass.png"; - f.tiledef[1].name = "default_dirt.png"; - for(int i = 2; i < 6; i++) - f.tiledef[i].name = "default_dirt.png^default_grass_side.png"; - f.is_ground_content = true; - idef->registerItem(itemdef); - CONTENT_GRASS = ndef->set(f.name, f); - - /* - Torch (minimal definition for lighting tests) - */ - itemdef = ItemDefinition(); - itemdef.type = ITEM_NODE; - itemdef.name = "default:torch"; - f = ContentFeatures(); - f.name = itemdef.name; - f.param_type = CPT_LIGHT; - f.light_propagates = true; - f.sunlight_propagates = true; - f.light_source = LIGHT_MAX-1; - idef->registerItem(itemdef); - CONTENT_TORCH = ndef->set(f.name, f); -} - -struct TestBase -{ - bool test_failed; - TestBase(): - test_failed(false) - {} -}; - -struct TestUtilities: public TestBase -{ - void Run() - { - /*infostream<<"wrapDegrees(100.0) = "<<wrapDegrees(100.0)<<std::endl; - infostream<<"wrapDegrees(720.5) = "<<wrapDegrees(720.5)<<std::endl; - infostream<<"wrapDegrees(-0.5) = "<<wrapDegrees(-0.5)<<std::endl;*/ - UASSERT(fabs(wrapDegrees(100.0) - 100.0) < 0.001); - UASSERT(fabs(wrapDegrees(720.5) - 0.5) < 0.001); - UASSERT(fabs(wrapDegrees(-0.5) - (-0.5)) < 0.001); - UASSERT(fabs(wrapDegrees(-365.5) - (-5.5)) < 0.001); - UASSERT(lowercase("Foo bAR") == "foo bar"); - UASSERT(trim("\n \t\r Foo bAR \r\n\t\t ") == "Foo bAR"); - UASSERT(trim("\n \t\r \r\n\t\t ") == ""); - UASSERT(is_yes("YeS") == true); - UASSERT(is_yes("") == false); - UASSERT(is_yes("FAlse") == false); - UASSERT(is_yes("-1") == true); - UASSERT(is_yes("0") == false); - UASSERT(is_yes("1") == true); - UASSERT(is_yes("2") == true); - const char *ends[] = {"abc", "c", "bc", "", NULL}; - UASSERT(removeStringEnd("abc", ends) == ""); - UASSERT(removeStringEnd("bc", ends) == "b"); - UASSERT(removeStringEnd("12c", ends) == "12"); - UASSERT(removeStringEnd("foo", ends) == ""); - UASSERT(urlencode("\"Aardvarks lurk, OK?\"") - == "%22Aardvarks%20lurk%2C%20OK%3F%22"); - UASSERT(urldecode("%22Aardvarks%20lurk%2C%20OK%3F%22") - == "\"Aardvarks lurk, OK?\""); - UASSERT(padStringRight("hello", 8) == "hello "); - UASSERT(str_equal(narrow_to_wide("abc"), narrow_to_wide("abc"))); - UASSERT(str_equal(narrow_to_wide("ABC"), narrow_to_wide("abc"), true)); - UASSERT(trim(" a") == "a"); - UASSERT(trim(" a ") == "a"); - UASSERT(trim("a ") == "a"); - UASSERT(trim("") == ""); - UASSERT(mystoi("123", 0, 1000) == 123); - UASSERT(mystoi("123", 0, 10) == 10); - std::string test_str; - test_str = "Hello there"; - str_replace(test_str, "there", "world"); - UASSERT(test_str == "Hello world"); - test_str = "ThisAisAaAtest"; - str_replace(test_str, 'A', ' '); - UASSERT(test_str == "This is a test"); - UASSERT(string_allowed("hello", "abcdefghijklmno") == true); - UASSERT(string_allowed("123", "abcdefghijklmno") == false); - UASSERT(string_allowed_blacklist("hello", "123") == true); - UASSERT(string_allowed_blacklist("hello123", "123") == false); - UASSERT(wrap_rows("12345678",4) == "1234\n5678"); - UASSERT(is_number("123") == true); - UASSERT(is_number("") == false); - UASSERT(is_number("123a") == false); - UASSERT(is_power_of_two(0) == false); - UASSERT(is_power_of_two(1) == true); - UASSERT(is_power_of_two(2) == true); - UASSERT(is_power_of_two(3) == false); - for (int exponent = 2; exponent <= 31; ++exponent) { - UASSERT(is_power_of_two((1 << exponent) - 1) == false); - UASSERT(is_power_of_two((1 << exponent)) == true); - UASSERT(is_power_of_two((1 << exponent) + 1) == false); - } - UASSERT(is_power_of_two((u32)-1) == false); - } -}; - -struct TestPath: public TestBase -{ - // adjusts a POSIX path to system-specific conventions - // -> changes '/' to DIR_DELIM - // -> absolute paths start with "C:\\" on windows - std::string p(std::string path) - { - for(size_t i = 0; i < path.size(); ++i){ - if(path[i] == '/'){ - path.replace(i, 1, DIR_DELIM); - i += std::string(DIR_DELIM).size() - 1; // generally a no-op - } - } - - #ifdef _WIN32 - if(path[0] == '\\') - path = "C:" + path; - #endif - - return path; - } - - void Run() - { - std::string path, result, removed; - - /* - Test fs::IsDirDelimiter - */ - UASSERT(fs::IsDirDelimiter('/') == true); - UASSERT(fs::IsDirDelimiter('A') == false); - UASSERT(fs::IsDirDelimiter(0) == false); - #ifdef _WIN32 - UASSERT(fs::IsDirDelimiter('\\') == true); - #else - UASSERT(fs::IsDirDelimiter('\\') == false); - #endif - - /* - Test fs::PathStartsWith - */ - { - const int numpaths = 12; - std::string paths[numpaths] = { - "", - p("/"), - p("/home/user/minetest"), - p("/home/user/minetest/bin"), - p("/home/user/.minetest"), - p("/tmp/dir/file"), - p("/tmp/file/"), - p("/tmP/file"), - p("/tmp"), - p("/tmp/dir"), - p("/home/user2/minetest/worlds"), - p("/home/user2/minetest/world"), - }; - /* - expected fs::PathStartsWith results - 0 = returns false - 1 = returns true - 2 = returns false on windows, true elsewhere - 3 = returns true on windows, false elsewhere - 4 = returns true if and only if - FILESYS_CASE_INSENSITIVE is true - */ - int expected_results[numpaths][numpaths] = { - {1,2,0,0,0,0,0,0,0,0,0,0}, - {1,1,0,0,0,0,0,0,0,0,0,0}, - {1,1,1,0,0,0,0,0,0,0,0,0}, - {1,1,1,1,0,0,0,0,0,0,0,0}, - {1,1,0,0,1,0,0,0,0,0,0,0}, - {1,1,0,0,0,1,0,0,1,1,0,0}, - {1,1,0,0,0,0,1,4,1,0,0,0}, - {1,1,0,0,0,0,4,1,4,0,0,0}, - {1,1,0,0,0,0,0,0,1,0,0,0}, - {1,1,0,0,0,0,0,0,1,1,0,0}, - {1,1,0,0,0,0,0,0,0,0,1,0}, - {1,1,0,0,0,0,0,0,0,0,0,1}, - }; - - for (int i = 0; i < numpaths; i++) - for (int j = 0; j < numpaths; j++){ - /*verbosestream<<"testing fs::PathStartsWith(\"" - <<paths[i]<<"\", \"" - <<paths[j]<<"\")"<<std::endl;*/ - bool starts = fs::PathStartsWith(paths[i], paths[j]); - int expected = expected_results[i][j]; - if(expected == 0){ - UASSERT(starts == false); - } - else if(expected == 1){ - UASSERT(starts == true); - } - #ifdef _WIN32 - else if(expected == 2){ - UASSERT(starts == false); - } - else if(expected == 3){ - UASSERT(starts == true); - } - #else - else if(expected == 2){ - UASSERT(starts == true); - } - else if(expected == 3){ - UASSERT(starts == false); - } - #endif - else if(expected == 4){ - UASSERT(starts == (bool)FILESYS_CASE_INSENSITIVE); - } - } - } - - /* - Test fs::RemoveLastPathComponent - */ - UASSERT(fs::RemoveLastPathComponent("") == ""); - path = p("/home/user/minetest/bin/..//worlds/world1"); - result = fs::RemoveLastPathComponent(path, &removed, 0); - UASSERT(result == path); - UASSERT(removed == ""); - result = fs::RemoveLastPathComponent(path, &removed, 1); - UASSERT(result == p("/home/user/minetest/bin/..//worlds")); - UASSERT(removed == p("world1")); - result = fs::RemoveLastPathComponent(path, &removed, 2); - UASSERT(result == p("/home/user/minetest/bin/..")); - UASSERT(removed == p("worlds/world1")); - result = fs::RemoveLastPathComponent(path, &removed, 3); - UASSERT(result == p("/home/user/minetest/bin")); - UASSERT(removed == p("../worlds/world1")); - result = fs::RemoveLastPathComponent(path, &removed, 4); - UASSERT(result == p("/home/user/minetest")); - UASSERT(removed == p("bin/../worlds/world1")); - result = fs::RemoveLastPathComponent(path, &removed, 5); - UASSERT(result == p("/home/user")); - UASSERT(removed == p("minetest/bin/../worlds/world1")); - result = fs::RemoveLastPathComponent(path, &removed, 6); - UASSERT(result == p("/home")); - UASSERT(removed == p("user/minetest/bin/../worlds/world1")); - result = fs::RemoveLastPathComponent(path, &removed, 7); - #ifdef _WIN32 - UASSERT(result == "C:"); - #else - UASSERT(result == ""); - #endif - UASSERT(removed == p("home/user/minetest/bin/../worlds/world1")); - - /* - Now repeat the test with a trailing delimiter - */ - path = p("/home/user/minetest/bin/..//worlds/world1/"); - result = fs::RemoveLastPathComponent(path, &removed, 0); - UASSERT(result == path); - UASSERT(removed == ""); - result = fs::RemoveLastPathComponent(path, &removed, 1); - UASSERT(result == p("/home/user/minetest/bin/..//worlds")); - UASSERT(removed == p("world1")); - result = fs::RemoveLastPathComponent(path, &removed, 2); - UASSERT(result == p("/home/user/minetest/bin/..")); - UASSERT(removed == p("worlds/world1")); - result = fs::RemoveLastPathComponent(path, &removed, 3); - UASSERT(result == p("/home/user/minetest/bin")); - UASSERT(removed == p("../worlds/world1")); - result = fs::RemoveLastPathComponent(path, &removed, 4); - UASSERT(result == p("/home/user/minetest")); - UASSERT(removed == p("bin/../worlds/world1")); - result = fs::RemoveLastPathComponent(path, &removed, 5); - UASSERT(result == p("/home/user")); - UASSERT(removed == p("minetest/bin/../worlds/world1")); - result = fs::RemoveLastPathComponent(path, &removed, 6); - UASSERT(result == p("/home")); - UASSERT(removed == p("user/minetest/bin/../worlds/world1")); - result = fs::RemoveLastPathComponent(path, &removed, 7); - #ifdef _WIN32 - UASSERT(result == "C:"); - #else - UASSERT(result == ""); - #endif - UASSERT(removed == p("home/user/minetest/bin/../worlds/world1")); - - /* - Test fs::RemoveRelativePathComponent - */ - path = p("/home/user/minetest/bin"); - result = fs::RemoveRelativePathComponents(path); - UASSERT(result == path); - path = p("/home/user/minetest/bin/../worlds/world1"); - result = fs::RemoveRelativePathComponents(path); - UASSERT(result == p("/home/user/minetest/worlds/world1")); - path = p("/home/user/minetest/bin/../worlds/world1/"); - result = fs::RemoveRelativePathComponents(path); - UASSERT(result == p("/home/user/minetest/worlds/world1")); - path = p("."); - result = fs::RemoveRelativePathComponents(path); - UASSERT(result == ""); - path = p("./subdir/../.."); - result = fs::RemoveRelativePathComponents(path); - UASSERT(result == ""); - path = p("/a/b/c/.././../d/../e/f/g/../h/i/j/../../../.."); - result = fs::RemoveRelativePathComponents(path); - UASSERT(result == p("/a/e")); - } -}; - -#define TEST_CONFIG_TEXT_BEFORE \ - "leet = 1337\n" \ - "leetleet = 13371337\n" \ - "leetleet_neg = -13371337\n" \ - "floaty_thing = 1.1\n" \ - "stringy_thing = asd /( ¤%&(/\" BLÖÄRP\n" \ - "coord = (1, 2, 4.5)\n" \ - " # this is just a comment\n" \ - "this is an invalid line\n" \ - "asdf = {\n" \ - " a = 5\n" \ - " bb = 2.5\n" \ - " ccc = \"\"\"\n" \ - "testy\n" \ - " testa \n" \ - "\"\"\"\n" \ - "\n" \ - "}\n" \ - "blarg = \"\"\" \n" \ - "some multiline text\n" \ - " with leading whitespace!\n" \ - "\"\"\"\n" \ - "np_terrain = 5, 40, (250, 250, 250), 12341, 5, 0.7, 2.4\n" \ - "zoop = true" - -#define TEST_CONFIG_TEXT_AFTER \ - "leet = 1337\n" \ - "leetleet = 13371337\n" \ - "leetleet_neg = -13371337\n" \ - "floaty_thing = 1.1\n" \ - "stringy_thing = asd /( ¤%&(/\" BLÖÄRP\n" \ - "coord = (1, 2, 4.5)\n" \ - " # this is just a comment\n" \ - "this is an invalid line\n" \ - "asdf = {\n" \ - " a = 5\n" \ - " bb = 2.5\n" \ - " ccc = \"\"\"\n" \ - "testy\n" \ - " testa \n" \ - "\"\"\"\n" \ - "\n" \ - "}\n" \ - "blarg = \"\"\" \n" \ - "some multiline text\n" \ - " with leading whitespace!\n" \ - "\"\"\"\n" \ - "np_terrain = {\n" \ - " flags = defaults\n" \ - " lacunarity = 2.4\n" \ - " octaves = 6\n" \ - " offset = 3.5\n" \ - " persistence = 0.7\n" \ - " scale = 40\n" \ - " seed = 12341\n" \ - " spread = (250,250,250)\n" \ - "}\n" \ - "zoop = true\n" \ - "coord2 = (1,2,3.3)\n" \ - "floaty_thing_2 = 1.2\n" \ - "groupy_thing = {\n" \ - " animals = cute\n" \ - " num_apples = 4\n" \ - " num_oranges = 53\n" \ - "}\n" - -struct TestSettings: public TestBase -{ - void Run() - { - try { - Settings s; - - // Test reading of settings - std::istringstream is(TEST_CONFIG_TEXT_BEFORE); - s.parseConfigLines(is); - - UASSERT(s.getS32("leet") == 1337); - UASSERT(s.getS16("leetleet") == 32767); - UASSERT(s.getS16("leetleet_neg") == -32768); - - // Not sure if 1.1 is an exact value as a float, but doesn't matter - UASSERT(fabs(s.getFloat("floaty_thing") - 1.1) < 0.001); - UASSERT(s.get("stringy_thing") == "asd /( ¤%&(/\" BLÖÄRP"); - UASSERT(fabs(s.getV3F("coord").X - 1.0) < 0.001); - UASSERT(fabs(s.getV3F("coord").Y - 2.0) < 0.001); - UASSERT(fabs(s.getV3F("coord").Z - 4.5) < 0.001); - - // Test the setting of settings too - s.setFloat("floaty_thing_2", 1.2); - s.setV3F("coord2", v3f(1, 2, 3.3)); - UASSERT(s.get("floaty_thing_2").substr(0,3) == "1.2"); - UASSERT(fabs(s.getFloat("floaty_thing_2") - 1.2) < 0.001); - UASSERT(fabs(s.getV3F("coord2").X - 1.0) < 0.001); - UASSERT(fabs(s.getV3F("coord2").Y - 2.0) < 0.001); - UASSERT(fabs(s.getV3F("coord2").Z - 3.3) < 0.001); - - // Test settings groups - Settings *group = s.getGroup("asdf"); - UASSERT(group != NULL); - UASSERT(s.getGroupNoEx("zoop", group) == false); - UASSERT(group->getS16("a") == 5); - UASSERT(fabs(group->getFloat("bb") - 2.5) < 0.001); - - Settings *group3 = new Settings; - group3->set("cat", "meow"); - group3->set("dog", "woof"); - - Settings *group2 = new Settings; - group2->setS16("num_apples", 4); - group2->setS16("num_oranges", 53); - group2->setGroup("animals", group3); - group2->set("animals", "cute"); //destroys group 3 - s.setGroup("groupy_thing", group2); - - // Test set failure conditions - UASSERT(s.set("Zoop = Poop\nsome_other_setting", "false") == false); - UASSERT(s.set("sneaky", "\"\"\"\njabberwocky = false") == false); - UASSERT(s.set("hehe", "asdfasdf\n\"\"\"\nsomething = false") == false); - - // Test multiline settings - UASSERT(group->get("ccc") == "testy\n testa "); - - UASSERT(s.get("blarg") == - "some multiline text\n" - " with leading whitespace!"); - - // Test NoiseParams - UASSERT(s.getEntry("np_terrain").is_group == false); - - NoiseParams np; - UASSERT(s.getNoiseParams("np_terrain", np) == true); - UASSERT(fabs(np.offset - 5) < 0.001); - UASSERT(fabs(np.scale - 40) < 0.001); - UASSERT(fabs(np.spread.X - 250) < 0.001); - UASSERT(fabs(np.spread.Y - 250) < 0.001); - UASSERT(fabs(np.spread.Z - 250) < 0.001); - UASSERT(np.seed == 12341); - UASSERT(np.octaves == 5); - UASSERT(fabs(np.persist - 0.7) < 0.001); - - np.offset = 3.5; - np.octaves = 6; - s.setNoiseParams("np_terrain", np); - - UASSERT(s.getEntry("np_terrain").is_group == true); - - // Test writing - std::ostringstream os(std::ios_base::binary); - is.clear(); - is.seekg(0); - - UASSERT(s.updateConfigObject(is, os, "", 0) == true); - //printf(">>>> expected config:\n%s\n", TEST_CONFIG_TEXT_AFTER); - //printf(">>>> actual config:\n%s\n", os.str().c_str()); - UASSERT(os.str() == TEST_CONFIG_TEXT_AFTER); - } catch (SettingNotFoundException &e) { - UASSERT(!"Setting not found!"); - } - } -}; - -struct TestSerialization: public TestBase -{ - // To be used like this: - // mkstr("Some\0string\0with\0embedded\0nuls") - // since std::string("...") doesn't work as expected in that case. - template<size_t N> std::string mkstr(const char (&s)[N]) - { - return std::string(s, N - 1); - } - - void Run() - { - // Tests some serialization primitives - - UASSERT(serializeString("") == mkstr("\0\0")); - UASSERT(serializeWideString(L"") == mkstr("\0\0")); - UASSERT(serializeLongString("") == mkstr("\0\0\0\0")); - UASSERT(serializeJsonString("") == "\"\""); - - std::string teststring = "Hello world!"; - UASSERT(serializeString(teststring) == - mkstr("\0\14Hello world!")); - UASSERT(serializeWideString(narrow_to_wide(teststring)) == - mkstr("\0\14\0H\0e\0l\0l\0o\0 \0w\0o\0r\0l\0d\0!")); - UASSERT(serializeLongString(teststring) == - mkstr("\0\0\0\14Hello world!")); - UASSERT(serializeJsonString(teststring) == - "\"Hello world!\""); - - std::string teststring2; - std::wstring teststring2_w; - std::string teststring2_w_encoded; - { - std::ostringstream tmp_os; - std::wostringstream tmp_os_w; - std::ostringstream tmp_os_w_encoded; - for(int i = 0; i < 256; i++) - { - tmp_os<<(char)i; - tmp_os_w<<(wchar_t)i; - tmp_os_w_encoded<<(char)0<<(char)i; - } - teststring2 = tmp_os.str(); - teststring2_w = tmp_os_w.str(); - teststring2_w_encoded = tmp_os_w_encoded.str(); - } - UASSERT(serializeString(teststring2) == - mkstr("\1\0") + teststring2); - UASSERT(serializeWideString(teststring2_w) == - mkstr("\1\0") + teststring2_w_encoded); - UASSERT(serializeLongString(teststring2) == - mkstr("\0\0\1\0") + teststring2); - // MSVC fails when directly using "\\\\" - std::string backslash = "\\"; - UASSERT(serializeJsonString(teststring2) == - mkstr("\"") + - "\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007" + - "\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f" + - "\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017" + - "\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f" + - " !\\\"" + teststring2.substr(0x23, 0x2f-0x23) + - "\\/" + teststring2.substr(0x30, 0x5c-0x30) + - backslash + backslash + teststring2.substr(0x5d, 0x7f-0x5d) + "\\u007f" + - "\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087" + - "\\u0088\\u0089\\u008a\\u008b\\u008c\\u008d\\u008e\\u008f" + - "\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097" + - "\\u0098\\u0099\\u009a\\u009b\\u009c\\u009d\\u009e\\u009f" + - "\\u00a0\\u00a1\\u00a2\\u00a3\\u00a4\\u00a5\\u00a6\\u00a7" + - "\\u00a8\\u00a9\\u00aa\\u00ab\\u00ac\\u00ad\\u00ae\\u00af" + - "\\u00b0\\u00b1\\u00b2\\u00b3\\u00b4\\u00b5\\u00b6\\u00b7" + - "\\u00b8\\u00b9\\u00ba\\u00bb\\u00bc\\u00bd\\u00be\\u00bf" + - "\\u00c0\\u00c1\\u00c2\\u00c3\\u00c4\\u00c5\\u00c6\\u00c7" + - "\\u00c8\\u00c9\\u00ca\\u00cb\\u00cc\\u00cd\\u00ce\\u00cf" + - "\\u00d0\\u00d1\\u00d2\\u00d3\\u00d4\\u00d5\\u00d6\\u00d7" + - "\\u00d8\\u00d9\\u00da\\u00db\\u00dc\\u00dd\\u00de\\u00df" + - "\\u00e0\\u00e1\\u00e2\\u00e3\\u00e4\\u00e5\\u00e6\\u00e7" + - "\\u00e8\\u00e9\\u00ea\\u00eb\\u00ec\\u00ed\\u00ee\\u00ef" + - "\\u00f0\\u00f1\\u00f2\\u00f3\\u00f4\\u00f5\\u00f6\\u00f7" + - "\\u00f8\\u00f9\\u00fa\\u00fb\\u00fc\\u00fd\\u00fe\\u00ff" + - "\""); - - { - std::istringstream is(serializeString(teststring2), std::ios::binary); - UASSERT(deSerializeString(is) == teststring2); - UASSERT(!is.eof()); - is.get(); - UASSERT(is.eof()); - } - { - std::istringstream is(serializeWideString(teststring2_w), std::ios::binary); - UASSERT(deSerializeWideString(is) == teststring2_w); - UASSERT(!is.eof()); - is.get(); - UASSERT(is.eof()); - } - { - std::istringstream is(serializeLongString(teststring2), std::ios::binary); - UASSERT(deSerializeLongString(is) == teststring2); - UASSERT(!is.eof()); - is.get(); - UASSERT(is.eof()); - } - { - std::istringstream is(serializeJsonString(teststring2), std::ios::binary); - //dstream<<serializeJsonString(deSerializeJsonString(is)); - UASSERT(deSerializeJsonString(is) == teststring2); - UASSERT(!is.eof()); - is.get(); - UASSERT(is.eof()); - } - } -}; - -struct TestNodedefSerialization: public TestBase -{ - void Run() - { - ContentFeatures f; - f.name = "default:stone"; - for(int i = 0; i < 6; i++) - f.tiledef[i].name = "default_stone.png"; - f.is_ground_content = true; - std::ostringstream os(std::ios::binary); - f.serialize(os, LATEST_PROTOCOL_VERSION); - verbosestream<<"Test ContentFeatures size: "<<os.str().size()<<std::endl; - std::istringstream is(os.str(), std::ios::binary); - ContentFeatures f2; - f2.deSerialize(is); - UASSERT(f.walkable == f2.walkable); - UASSERT(f.node_box.type == f2.node_box.type); - } -}; - -struct TestCompress: public TestBase -{ - void Run() - { - { // ver 0 - - SharedBuffer<u8> fromdata(4); - fromdata[0]=1; - fromdata[1]=5; - fromdata[2]=5; - fromdata[3]=1; - - std::ostringstream os(std::ios_base::binary); - compress(fromdata, os, 0); - - std::string str_out = os.str(); - - infostream<<"str_out.size()="<<str_out.size()<<std::endl; - infostream<<"TestCompress: 1,5,5,1 -> "; - for(u32 i=0; i<str_out.size(); i++) - { - infostream<<(u32)str_out[i]<<","; - } - infostream<<std::endl; - - UASSERT(str_out.size() == 10); - - UASSERT(str_out[0] == 0); - UASSERT(str_out[1] == 0); - UASSERT(str_out[2] == 0); - UASSERT(str_out[3] == 4); - UASSERT(str_out[4] == 0); - UASSERT(str_out[5] == 1); - UASSERT(str_out[6] == 1); - UASSERT(str_out[7] == 5); - UASSERT(str_out[8] == 0); - UASSERT(str_out[9] == 1); - - std::istringstream is(str_out, std::ios_base::binary); - std::ostringstream os2(std::ios_base::binary); - - decompress(is, os2, 0); - std::string str_out2 = os2.str(); - - infostream<<"decompress: "; - for(u32 i=0; i<str_out2.size(); i++) - { - infostream<<(u32)str_out2[i]<<","; - } - infostream<<std::endl; - - UASSERT(str_out2.size() == fromdata.getSize()); - - for(u32 i=0; i<str_out2.size(); i++) - { - UASSERT(str_out2[i] == fromdata[i]); - } - - } - - { // ver HIGHEST - - SharedBuffer<u8> fromdata(4); - fromdata[0]=1; - fromdata[1]=5; - fromdata[2]=5; - fromdata[3]=1; - - std::ostringstream os(std::ios_base::binary); - compress(fromdata, os, SER_FMT_VER_HIGHEST_READ); - - std::string str_out = os.str(); - - infostream<<"str_out.size()="<<str_out.size()<<std::endl; - infostream<<"TestCompress: 1,5,5,1 -> "; - for(u32 i=0; i<str_out.size(); i++) - { - infostream<<(u32)str_out[i]<<","; - } - infostream<<std::endl; - - std::istringstream is(str_out, std::ios_base::binary); - std::ostringstream os2(std::ios_base::binary); - - decompress(is, os2, SER_FMT_VER_HIGHEST_READ); - std::string str_out2 = os2.str(); - - infostream<<"decompress: "; - for(u32 i=0; i<str_out2.size(); i++) - { - infostream<<(u32)str_out2[i]<<","; - } - infostream<<std::endl; - - UASSERT(str_out2.size() == fromdata.getSize()); - - for(u32 i=0; i<str_out2.size(); i++) - { - UASSERT(str_out2[i] == fromdata[i]); - } - - } - - // Test zlib wrapper with large amounts of data (larger than its - // internal buffers) - { - infostream<<"Test: Testing zlib wrappers with a large amount " - <<"of pseudorandom data"<<std::endl; - u32 size = 50000; - infostream<<"Test: Input size of large compressZlib is " - <<size<<std::endl; - std::string data_in; - data_in.resize(size); - PseudoRandom pseudorandom(9420); - for(u32 i=0; i<size; i++) - data_in[i] = pseudorandom.range(0,255); - std::ostringstream os_compressed(std::ios::binary); - compressZlib(data_in, os_compressed); - infostream<<"Test: Output size of large compressZlib is " - <<os_compressed.str().size()<<std::endl; - std::istringstream is_compressed(os_compressed.str(), std::ios::binary); - std::ostringstream os_decompressed(std::ios::binary); - decompressZlib(is_compressed, os_decompressed); - infostream<<"Test: Output size of large decompressZlib is " - <<os_decompressed.str().size()<<std::endl; - std::string str_decompressed = os_decompressed.str(); - UTEST(str_decompressed.size() == data_in.size(), "Output size not" - " equal (output: %u, input: %u)", - (unsigned int)str_decompressed.size(), (unsigned int)data_in.size()); - for(u32 i=0; i<size && i<str_decompressed.size(); i++){ - UTEST(str_decompressed[i] == data_in[i], - "index out[%i]=%i differs from in[%i]=%i", - i, str_decompressed[i], i, data_in[i]); - } - } - } -}; - -struct TestMapNode: public TestBase -{ - void Run(INodeDefManager *nodedef) - { - MapNode n(CONTENT_AIR); - - UASSERT(n.getContent() == CONTENT_AIR); - UASSERT(n.getLight(LIGHTBANK_DAY, nodedef) == 0); - UASSERT(n.getLight(LIGHTBANK_NIGHT, nodedef) == 0); - - // Transparency - n.setContent(CONTENT_AIR); - UASSERT(nodedef->get(n).light_propagates == true); - n.setContent(LEGN(nodedef, "CONTENT_STONE")); - UASSERT(nodedef->get(n).light_propagates == false); - } -}; - -struct TestVoxelManipulator: public TestBase -{ - void Run(INodeDefManager *nodedef) - { - /* - VoxelArea - */ - - VoxelArea a(v3s16(-1,-1,-1), v3s16(1,1,1)); - UASSERT(a.index(0,0,0) == 1*3*3 + 1*3 + 1); - UASSERT(a.index(-1,-1,-1) == 0); - - VoxelArea c(v3s16(-2,-2,-2), v3s16(2,2,2)); - // An area that is 1 bigger in x+ and z- - VoxelArea d(v3s16(-2,-2,-3), v3s16(3,2,2)); - - std::list<VoxelArea> aa; - d.diff(c, aa); - - // Correct results - std::vector<VoxelArea> results; - results.push_back(VoxelArea(v3s16(-2,-2,-3),v3s16(3,2,-3))); - results.push_back(VoxelArea(v3s16(3,-2,-2),v3s16(3,2,2))); - - UASSERT(aa.size() == results.size()); - - infostream<<"Result of diff:"<<std::endl; - for(std::list<VoxelArea>::const_iterator - i = aa.begin(); i != aa.end(); ++i) - { - i->print(infostream); - infostream<<std::endl; - - std::vector<VoxelArea>::iterator j = std::find(results.begin(), results.end(), *i); - UASSERT(j != results.end()); - results.erase(j); - } - - - /* - VoxelManipulator - */ - - VoxelManipulator v; - - v.print(infostream, nodedef); - - infostream<<"*** Setting (-1,0,-1)=2 ***"<<std::endl; - - v.setNodeNoRef(v3s16(-1,0,-1), MapNode(CONTENT_GRASS)); - - v.print(infostream, nodedef); - - UASSERT(v.getNode(v3s16(-1,0,-1)).getContent() == CONTENT_GRASS); - - infostream<<"*** Reading from inexistent (0,0,-1) ***"<<std::endl; - - EXCEPTION_CHECK(InvalidPositionException, v.getNode(v3s16(0,0,-1))); - - v.print(infostream, nodedef); - - infostream<<"*** Adding area ***"<<std::endl; - - v.addArea(a); - - v.print(infostream, nodedef); - - UASSERT(v.getNode(v3s16(-1,0,-1)).getContent() == CONTENT_GRASS); - EXCEPTION_CHECK(InvalidPositionException, v.getNode(v3s16(0,1,1))); - } -}; - -struct TestVoxelAlgorithms: public TestBase -{ - void Run(INodeDefManager *ndef) - { - /* - voxalgo::propagateSunlight - */ - { - VoxelManipulator v; - for(u16 z=0; z<3; z++) - for(u16 y=0; y<3; y++) - for(u16 x=0; x<3; x++) - { - v3s16 p(x,y,z); - v.setNodeNoRef(p, MapNode(CONTENT_AIR)); - } - VoxelArea a(v3s16(0,0,0), v3s16(2,2,2)); - { - std::set<v3s16> light_sources; - voxalgo::setLight(v, a, 0, ndef); - voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( - v, a, true, light_sources, ndef); - //v.print(dstream, ndef, VOXELPRINT_LIGHT_DAY); - UASSERT(res.bottom_sunlight_valid == true); - UASSERT(v.getNode(v3s16(1,1,1)).getLight(LIGHTBANK_DAY, ndef) - == LIGHT_SUN); - } - v.setNodeNoRef(v3s16(0,0,0), MapNode(CONTENT_STONE)); - { - std::set<v3s16> light_sources; - voxalgo::setLight(v, a, 0, ndef); - voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( - v, a, true, light_sources, ndef); - UASSERT(res.bottom_sunlight_valid == true); - UASSERT(v.getNode(v3s16(1,1,1)).getLight(LIGHTBANK_DAY, ndef) - == LIGHT_SUN); - } - { - std::set<v3s16> light_sources; - voxalgo::setLight(v, a, 0, ndef); - voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( - v, a, false, light_sources, ndef); - UASSERT(res.bottom_sunlight_valid == true); - UASSERT(v.getNode(v3s16(2,0,2)).getLight(LIGHTBANK_DAY, ndef) - == 0); - } - v.setNodeNoRef(v3s16(1,3,2), MapNode(CONTENT_STONE)); - { - std::set<v3s16> light_sources; - voxalgo::setLight(v, a, 0, ndef); - voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( - v, a, true, light_sources, ndef); - UASSERT(res.bottom_sunlight_valid == true); - UASSERT(v.getNode(v3s16(1,1,2)).getLight(LIGHTBANK_DAY, ndef) - == 0); - } - { - std::set<v3s16> light_sources; - voxalgo::setLight(v, a, 0, ndef); - voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( - v, a, false, light_sources, ndef); - UASSERT(res.bottom_sunlight_valid == true); - UASSERT(v.getNode(v3s16(1,0,2)).getLight(LIGHTBANK_DAY, ndef) - == 0); - } - { - MapNode n(CONTENT_AIR); - n.setLight(LIGHTBANK_DAY, 10, ndef); - v.setNodeNoRef(v3s16(1,-1,2), n); - } - { - std::set<v3s16> light_sources; - voxalgo::setLight(v, a, 0, ndef); - voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( - v, a, true, light_sources, ndef); - UASSERT(res.bottom_sunlight_valid == true); - } - { - std::set<v3s16> light_sources; - voxalgo::setLight(v, a, 0, ndef); - voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( - v, a, false, light_sources, ndef); - UASSERT(res.bottom_sunlight_valid == true); - } - { - MapNode n(CONTENT_AIR); - n.setLight(LIGHTBANK_DAY, LIGHT_SUN, ndef); - v.setNodeNoRef(v3s16(1,-1,2), n); - } - { - std::set<v3s16> light_sources; - voxalgo::setLight(v, a, 0, ndef); - voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( - v, a, true, light_sources, ndef); - UASSERT(res.bottom_sunlight_valid == false); - } - { - std::set<v3s16> light_sources; - voxalgo::setLight(v, a, 0, ndef); - voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( - v, a, false, light_sources, ndef); - UASSERT(res.bottom_sunlight_valid == false); - } - v.setNodeNoRef(v3s16(1,3,2), MapNode(CONTENT_IGNORE)); - { - std::set<v3s16> light_sources; - voxalgo::setLight(v, a, 0, ndef); - voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( - v, a, true, light_sources, ndef); - UASSERT(res.bottom_sunlight_valid == true); - } - } - /* - voxalgo::clearLightAndCollectSources - */ - { - VoxelManipulator v; - for(u16 z=0; z<3; z++) - for(u16 y=0; y<3; y++) - for(u16 x=0; x<3; x++) - { - v3s16 p(x,y,z); - v.setNode(p, MapNode(CONTENT_AIR)); - } - VoxelArea a(v3s16(0,0,0), v3s16(2,2,2)); - v.setNodeNoRef(v3s16(0,0,0), MapNode(CONTENT_STONE)); - v.setNodeNoRef(v3s16(1,1,1), MapNode(CONTENT_TORCH)); - { - MapNode n(CONTENT_AIR); - n.setLight(LIGHTBANK_DAY, 1, ndef); - v.setNode(v3s16(1,1,2), n); - } - { - std::set<v3s16> light_sources; - std::map<v3s16, u8> unlight_from; - voxalgo::clearLightAndCollectSources(v, a, LIGHTBANK_DAY, - ndef, light_sources, unlight_from); - //v.print(dstream, ndef, VOXELPRINT_LIGHT_DAY); - UASSERT(v.getNode(v3s16(0,1,1)).getLight(LIGHTBANK_DAY, ndef) - == 0); - UASSERT(light_sources.find(v3s16(1,1,1)) != light_sources.end()); - UASSERT(light_sources.size() == 1); - UASSERT(unlight_from.find(v3s16(1,1,2)) != unlight_from.end()); - UASSERT(unlight_from.size() == 1); - } - } - } -}; - -struct TestInventory: public TestBase -{ - void Run(IItemDefManager *idef) - { - std::string serialized_inventory = - "List 0 32\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" - "EndInventory\n"; - - std::string serialized_inventory_2 = - "List main 32\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" - "EndInventory\n"; - - Inventory inv(idef); - std::istringstream is(serialized_inventory, std::ios::binary); - inv.deSerialize(is); - UASSERT(inv.getList("0")); - UASSERT(!inv.getList("main")); - inv.getList("0")->setName("main"); - UASSERT(!inv.getList("0")); - UASSERT(inv.getList("main")); - UASSERT(inv.getList("main")->getWidth() == 3); - inv.getList("main")->setWidth(5); - std::ostringstream inv_os(std::ios::binary); - inv.serialize(inv_os); - UASSERT(inv_os.str() == serialized_inventory_2); - } -}; - -/* - NOTE: These tests became non-working then NodeContainer was removed. - These should be redone, utilizing some kind of a virtual - interface for Map (IMap would be fine). -*/ -#if 0 -struct TestMapBlock: public TestBase -{ - class TC : public NodeContainer - { - public: - - MapNode node; - bool position_valid; - core::list<v3s16> validity_exceptions; - - TC() - { - position_valid = true; - } - - virtual bool isValidPosition(v3s16 p) - { - //return position_valid ^ (p==position_valid_exception); - bool exception = false; - for(core::list<v3s16>::Iterator i=validity_exceptions.begin(); - i != validity_exceptions.end(); i++) - { - if(p == *i) - { - exception = true; - break; - } - } - return exception ? !position_valid : position_valid; - } - - virtual MapNode getNode(v3s16 p) - { - if(isValidPosition(p) == false) - throw InvalidPositionException(); - return node; - } - - virtual void setNode(v3s16 p, MapNode & n) - { - if(isValidPosition(p) == false) - throw InvalidPositionException(); - }; - - virtual u16 nodeContainerId() const - { - return 666; - } - }; - - void Run() - { - TC parent; - - MapBlock b(&parent, v3s16(1,1,1)); - v3s16 relpos(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); - - UASSERT(b.getPosRelative() == relpos); - - UASSERT(b.getBox().MinEdge.X == MAP_BLOCKSIZE); - UASSERT(b.getBox().MaxEdge.X == MAP_BLOCKSIZE*2-1); - UASSERT(b.getBox().MinEdge.Y == MAP_BLOCKSIZE); - UASSERT(b.getBox().MaxEdge.Y == MAP_BLOCKSIZE*2-1); - UASSERT(b.getBox().MinEdge.Z == MAP_BLOCKSIZE); - UASSERT(b.getBox().MaxEdge.Z == MAP_BLOCKSIZE*2-1); - - UASSERT(b.isValidPosition(v3s16(0,0,0)) == true); - UASSERT(b.isValidPosition(v3s16(-1,0,0)) == false); - UASSERT(b.isValidPosition(v3s16(-1,-142,-2341)) == false); - UASSERT(b.isValidPosition(v3s16(-124,142,2341)) == false); - UASSERT(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true); - UASSERT(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE,MAP_BLOCKSIZE-1)) == false); - - /* - TODO: this method should probably be removed - if the block size isn't going to be set variable - */ - /*UASSERT(b.getSizeNodes() == v3s16(MAP_BLOCKSIZE, - MAP_BLOCKSIZE, MAP_BLOCKSIZE));*/ - - // Changed flag should be initially set - UASSERT(b.getModified() == MOD_STATE_WRITE_NEEDED); - b.resetModified(); - UASSERT(b.getModified() == MOD_STATE_CLEAN); - - // All nodes should have been set to - // .d=CONTENT_IGNORE and .getLight() = 0 - for(u16 z=0; z<MAP_BLOCKSIZE; z++) - for(u16 y=0; y<MAP_BLOCKSIZE; y++) - for(u16 x=0; x<MAP_BLOCKSIZE; x++) - { - //UASSERT(b.getNode(v3s16(x,y,z)).getContent() == CONTENT_AIR); - UASSERT(b.getNode(v3s16(x,y,z)).getContent() == CONTENT_IGNORE); - UASSERT(b.getNode(v3s16(x,y,z)).getLight(LIGHTBANK_DAY) == 0); - UASSERT(b.getNode(v3s16(x,y,z)).getLight(LIGHTBANK_NIGHT) == 0); - } - - { - MapNode n(CONTENT_AIR); - for(u16 z=0; z<MAP_BLOCKSIZE; z++) - for(u16 y=0; y<MAP_BLOCKSIZE; y++) - for(u16 x=0; x<MAP_BLOCKSIZE; x++) - { - b.setNode(v3s16(x,y,z), n); - } - } - - /* - Parent fetch functions - */ - parent.position_valid = false; - parent.node.setContent(5); - - MapNode n; - - // Positions in the block should still be valid - UASSERT(b.isValidPositionParent(v3s16(0,0,0)) == true); - UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true); - n = b.getNodeParent(v3s16(0,MAP_BLOCKSIZE-1,0)); - UASSERT(n.getContent() == CONTENT_AIR); - - // ...but outside the block they should be invalid - UASSERT(b.isValidPositionParent(v3s16(-121,2341,0)) == false); - UASSERT(b.isValidPositionParent(v3s16(-1,0,0)) == false); - UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == false); - - { - bool exception_thrown = false; - try{ - // This should throw an exception - MapNode n = b.getNodeParent(v3s16(0,0,-1)); - } - catch(InvalidPositionException &e) - { - exception_thrown = true; - } - UASSERT(exception_thrown); - } - - parent.position_valid = true; - // Now the positions outside should be valid - UASSERT(b.isValidPositionParent(v3s16(-121,2341,0)) == true); - UASSERT(b.isValidPositionParent(v3s16(-1,0,0)) == true); - UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == true); - n = b.getNodeParent(v3s16(0,0,MAP_BLOCKSIZE)); - UASSERT(n.getContent() == 5); - - /* - Set a node - */ - v3s16 p(1,2,0); - n.setContent(4); - b.setNode(p, n); - UASSERT(b.getNode(p).getContent() == 4); - //TODO: Update to new system - /*UASSERT(b.getNodeTile(p) == 4); - UASSERT(b.getNodeTile(v3s16(-1,-1,0)) == 5);*/ - - /* - propagateSunlight() - */ - // Set lighting of all nodes to 0 - for(u16 z=0; z<MAP_BLOCKSIZE; z++){ - for(u16 y=0; y<MAP_BLOCKSIZE; y++){ - for(u16 x=0; x<MAP_BLOCKSIZE; x++){ - MapNode n = b.getNode(v3s16(x,y,z)); - n.setLight(LIGHTBANK_DAY, 0); - n.setLight(LIGHTBANK_NIGHT, 0); - b.setNode(v3s16(x,y,z), n); - } - } - } - { - /* - Check how the block handles being a lonely sky block - */ - parent.position_valid = true; - b.setIsUnderground(false); - parent.node.setContent(CONTENT_AIR); - parent.node.setLight(LIGHTBANK_DAY, LIGHT_SUN); - parent.node.setLight(LIGHTBANK_NIGHT, 0); - core::map<v3s16, bool> light_sources; - // The bottom block is invalid, because we have a shadowing node - UASSERT(b.propagateSunlight(light_sources) == false); - UASSERT(b.getNode(v3s16(1,4,0)).getLight(LIGHTBANK_DAY) == LIGHT_SUN); - UASSERT(b.getNode(v3s16(1,3,0)).getLight(LIGHTBANK_DAY) == LIGHT_SUN); - UASSERT(b.getNode(v3s16(1,2,0)).getLight(LIGHTBANK_DAY) == 0); - UASSERT(b.getNode(v3s16(1,1,0)).getLight(LIGHTBANK_DAY) == 0); - UASSERT(b.getNode(v3s16(1,0,0)).getLight(LIGHTBANK_DAY) == 0); - UASSERT(b.getNode(v3s16(1,2,3)).getLight(LIGHTBANK_DAY) == LIGHT_SUN); - UASSERT(b.getFaceLight2(1000, p, v3s16(0,1,0)) == LIGHT_SUN); - UASSERT(b.getFaceLight2(1000, p, v3s16(0,-1,0)) == 0); - UASSERT(b.getFaceLight2(0, p, v3s16(0,-1,0)) == 0); - // According to MapBlock::getFaceLight, - // The face on the z+ side should have double-diminished light - //UASSERT(b.getFaceLight(p, v3s16(0,0,1)) == diminish_light(diminish_light(LIGHT_MAX))); - // The face on the z+ side should have diminished light - UASSERT(b.getFaceLight2(1000, p, v3s16(0,0,1)) == diminish_light(LIGHT_MAX)); - } - /* - Check how the block handles being in between blocks with some non-sunlight - while being underground - */ - { - // Make neighbours to exist and set some non-sunlight to them - parent.position_valid = true; - b.setIsUnderground(true); - parent.node.setLight(LIGHTBANK_DAY, LIGHT_MAX/2); - core::map<v3s16, bool> light_sources; - // The block below should be valid because there shouldn't be - // sunlight in there either - UASSERT(b.propagateSunlight(light_sources, true) == true); - // Should not touch nodes that are not affected (that is, all of them) - //UASSERT(b.getNode(v3s16(1,2,3)).getLight() == LIGHT_SUN); - // Should set light of non-sunlighted blocks to 0. - UASSERT(b.getNode(v3s16(1,2,3)).getLight(LIGHTBANK_DAY) == 0); - } - /* - Set up a situation where: - - There is only air in this block - - There is a valid non-sunlighted block at the bottom, and - - Invalid blocks elsewhere. - - the block is not underground. - - This should result in bottom block invalidity - */ - { - b.setIsUnderground(false); - // Clear block - for(u16 z=0; z<MAP_BLOCKSIZE; z++){ - for(u16 y=0; y<MAP_BLOCKSIZE; y++){ - for(u16 x=0; x<MAP_BLOCKSIZE; x++){ - MapNode n; - n.setContent(CONTENT_AIR); - n.setLight(LIGHTBANK_DAY, 0); - b.setNode(v3s16(x,y,z), n); - } - } - } - // Make neighbours invalid - parent.position_valid = false; - // Add exceptions to the top of the bottom block - for(u16 x=0; x<MAP_BLOCKSIZE; x++) - for(u16 z=0; z<MAP_BLOCKSIZE; z++) - { - parent.validity_exceptions.push_back(v3s16(MAP_BLOCKSIZE+x, MAP_BLOCKSIZE-1, MAP_BLOCKSIZE+z)); - } - // Lighting value for the valid nodes - parent.node.setLight(LIGHTBANK_DAY, LIGHT_MAX/2); - core::map<v3s16, bool> light_sources; - // Bottom block is not valid - UASSERT(b.propagateSunlight(light_sources) == false); - } - } -}; - -struct TestMapSector: public TestBase -{ - class TC : public NodeContainer - { - public: - - MapNode node; - bool position_valid; - - TC() - { - position_valid = true; - } - - virtual bool isValidPosition(v3s16 p) - { - return position_valid; - } - - virtual MapNode getNode(v3s16 p) - { - if(position_valid == false) - throw InvalidPositionException(); - return node; - } - - virtual void setNode(v3s16 p, MapNode & n) - { - if(position_valid == false) - throw InvalidPositionException(); - }; - - virtual u16 nodeContainerId() const - { - return 666; - } - }; - - void Run() - { - TC parent; - parent.position_valid = false; - - // Create one with no heightmaps - ServerMapSector sector(&parent, v2s16(1,1)); - - UASSERT(sector.getBlockNoCreateNoEx(0) == 0); - UASSERT(sector.getBlockNoCreateNoEx(1) == 0); - - MapBlock * bref = sector.createBlankBlock(-2); - - UASSERT(sector.getBlockNoCreateNoEx(0) == 0); - UASSERT(sector.getBlockNoCreateNoEx(-2) == bref); - - //TODO: Check for AlreadyExistsException - - /*bool exception_thrown = false; - try{ - sector.getBlock(0); - } - catch(InvalidPositionException &e){ - exception_thrown = true; - } - UASSERT(exception_thrown);*/ - - } -}; -#endif - -struct TestCollision: public TestBase -{ - void Run() - { - /* - axisAlignedCollision - */ - - for(s16 bx = -3; bx <= 3; bx++) - for(s16 by = -3; by <= 3; by++) - for(s16 bz = -3; bz <= 3; bz++) - { - // X- - { - aabb3f s(bx, by, bz, bx+1, by+1, bz+1); - aabb3f m(bx-2, by, bz, bx-1, by+1, bz+1); - v3f v(1, 0, 0); - f32 dtime = 0; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); - UASSERT(fabs(dtime - 1.000) < 0.001); - } - { - aabb3f s(bx, by, bz, bx+1, by+1, bz+1); - aabb3f m(bx-2, by, bz, bx-1, by+1, bz+1); - v3f v(-1, 0, 0); - f32 dtime = 0; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == -1); - } - { - aabb3f s(bx, by, bz, bx+1, by+1, bz+1); - aabb3f m(bx-2, by+1.5, bz, bx-1, by+2.5, bz-1); - v3f v(1, 0, 0); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == -1); - } - { - aabb3f s(bx, by, bz, bx+1, by+1, bz+1); - aabb3f m(bx-2, by-1.5, bz, bx-1.5, by+0.5, bz+1); - v3f v(0.5, 0.1, 0); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); - UASSERT(fabs(dtime - 3.000) < 0.001); - } - { - aabb3f s(bx, by, bz, bx+1, by+1, bz+1); - aabb3f m(bx-2, by-1.5, bz, bx-1.5, by+0.5, bz+1); - v3f v(0.5, 0.1, 0); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); - UASSERT(fabs(dtime - 3.000) < 0.001); - } - - // X+ - { - aabb3f s(bx, by, bz, bx+1, by+1, bz+1); - aabb3f m(bx+2, by, bz, bx+3, by+1, bz+1); - v3f v(-1, 0, 0); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); - UASSERT(fabs(dtime - 1.000) < 0.001); - } - { - aabb3f s(bx, by, bz, bx+1, by+1, bz+1); - aabb3f m(bx+2, by, bz, bx+3, by+1, bz+1); - v3f v(1, 0, 0); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == -1); - } - { - aabb3f s(bx, by, bz, bx+1, by+1, bz+1); - aabb3f m(bx+2, by, bz+1.5, bx+3, by+1, bz+3.5); - v3f v(-1, 0, 0); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == -1); - } - { - aabb3f s(bx, by, bz, bx+1, by+1, bz+1); - aabb3f m(bx+2, by-1.5, bz, bx+2.5, by-0.5, bz+1); - v3f v(-0.5, 0.2, 0); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 1); // Y, not X! - UASSERT(fabs(dtime - 2.500) < 0.001); - } - { - aabb3f s(bx, by, bz, bx+1, by+1, bz+1); - aabb3f m(bx+2, by-1.5, bz, bx+2.5, by-0.5, bz+1); - v3f v(-0.5, 0.3, 0); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); - UASSERT(fabs(dtime - 2.000) < 0.001); - } - - // TODO: Y-, Y+, Z-, Z+ - - // misc - { - aabb3f s(bx, by, bz, bx+2, by+2, bz+2); - aabb3f m(bx+2.3, by+2.29, bz+2.29, bx+4.2, by+4.2, bz+4.2); - v3f v(-1./3, -1./3, -1./3); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); - UASSERT(fabs(dtime - 0.9) < 0.001); - } - { - aabb3f s(bx, by, bz, bx+2, by+2, bz+2); - aabb3f m(bx+2.29, by+2.3, bz+2.29, bx+4.2, by+4.2, bz+4.2); - v3f v(-1./3, -1./3, -1./3); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 1); - UASSERT(fabs(dtime - 0.9) < 0.001); - } - { - aabb3f s(bx, by, bz, bx+2, by+2, bz+2); - aabb3f m(bx+2.29, by+2.29, bz+2.3, bx+4.2, by+4.2, bz+4.2); - v3f v(-1./3, -1./3, -1./3); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 2); - UASSERT(fabs(dtime - 0.9) < 0.001); - } - { - aabb3f s(bx, by, bz, bx+2, by+2, bz+2); - aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.3, by-2.29, bz-2.29); - v3f v(1./7, 1./7, 1./7); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); - UASSERT(fabs(dtime - 16.1) < 0.001); - } - { - aabb3f s(bx, by, bz, bx+2, by+2, bz+2); - aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.29, by-2.3, bz-2.29); - v3f v(1./7, 1./7, 1./7); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 1); - UASSERT(fabs(dtime - 16.1) < 0.001); - } - { - aabb3f s(bx, by, bz, bx+2, by+2, bz+2); - aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.29, by-2.29, bz-2.3); - v3f v(1./7, 1./7, 1./7); - f32 dtime; - UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 2); - UASSERT(fabs(dtime - 16.1) < 0.001); - } - } - } -}; - -struct TestSocket: public TestBase -{ - void Run() - { - const int port = 30003; - Address address(0,0,0,0, port); - Address address6((IPv6AddressBytes*) NULL, port); - - // IPv6 socket test - { - UDPSocket socket6; - - if (!socket6.init(true, true)) { - /* Note: Failing to create an IPv6 socket is not technically an - error because the OS may not support IPv6 or it may - have been disabled. IPv6 is not /required/ by - minetest and therefore this should not cause the unit - test to fail - */ - dstream << "WARNING: IPv6 socket creation failed (unit test)" - << std::endl; - } else { - const char sendbuffer[] = "hello world!"; - IPv6AddressBytes bytes; - bytes.bytes[15] = 1; - - socket6.Bind(address6); - - try { - socket6.Send(Address(&bytes, port), sendbuffer, sizeof(sendbuffer)); - - sleep_ms(50); - - char rcvbuffer[256] = { 0 }; - Address sender; - - for(;;) { - if (socket6.Receive(sender, rcvbuffer, sizeof(rcvbuffer )) < 0) - break; - } - //FIXME: This fails on some systems - UASSERT(strncmp(sendbuffer, rcvbuffer, sizeof(sendbuffer)) == 0); - UASSERT(memcmp(sender.getAddress6().sin6_addr.s6_addr, - Address(&bytes, 0).getAddress6().sin6_addr.s6_addr, 16) == 0); - } - catch (SendFailedException e) { - errorstream << "IPv6 support enabled but not available!" - << std::endl; - } - } - } - - // IPv4 socket test - { - UDPSocket socket(false); - socket.Bind(address); - - const char sendbuffer[] = "hello world!"; - socket.Send(Address(127, 0, 0 ,1, port), sendbuffer, sizeof(sendbuffer)); - - sleep_ms(50); - - char rcvbuffer[256] = { 0 }; - Address sender; - for(;;) { - if (socket.Receive(sender, rcvbuffer, sizeof(rcvbuffer)) < 0) - break; - } - //FIXME: This fails on some systems - UASSERT(strncmp(sendbuffer, rcvbuffer, sizeof(sendbuffer)) == 0); - UASSERT(sender.getAddress().sin_addr.s_addr == - Address(127, 0, 0, 1, 0).getAddress().sin_addr.s_addr); - } - } -}; - -struct TestConnection: public TestBase -{ - void TestHelpers() - { - /* - Test helper functions - */ - - // Some constants for testing - u32 proto_id = 0x12345678; - u16 peer_id = 123; - u8 channel = 2; - SharedBuffer<u8> data1(1); - data1[0] = 100; - Address a(127,0,0,1, 10); - const u16 seqnum = 34352; - - con::BufferedPacket p1 = con::makePacket(a, data1, - proto_id, peer_id, channel); - /* - We should now have a packet with this data: - Header: - [0] u32 protocol_id - [4] u16 sender_peer_id - [6] u8 channel - Data: - [7] u8 data1[0] - */ - UASSERT(readU32(&p1.data[0]) == proto_id); - UASSERT(readU16(&p1.data[4]) == peer_id); - UASSERT(readU8(&p1.data[6]) == channel); - UASSERT(readU8(&p1.data[7]) == data1[0]); - - //infostream<<"initial data1[0]="<<((u32)data1[0]&0xff)<<std::endl; - - SharedBuffer<u8> p2 = con::makeReliablePacket(data1, seqnum); - - /*infostream<<"p2.getSize()="<<p2.getSize()<<", data1.getSize()=" - <<data1.getSize()<<std::endl; - infostream<<"readU8(&p2[3])="<<readU8(&p2[3]) - <<" p2[3]="<<((u32)p2[3]&0xff)<<std::endl; - infostream<<"data1[0]="<<((u32)data1[0]&0xff)<<std::endl;*/ - - UASSERT(p2.getSize() == 3 + data1.getSize()); - UASSERT(readU8(&p2[0]) == TYPE_RELIABLE); - UASSERT(readU16(&p2[1]) == seqnum); - UASSERT(readU8(&p2[3]) == data1[0]); - } - - struct Handler : public con::PeerHandler - { - Handler(const char *a_name) - { - count = 0; - last_id = 0; - name = a_name; - } - void peerAdded(con::Peer *peer) - { - infostream<<"Handler("<<name<<")::peerAdded(): " - "id="<<peer->id<<std::endl; - last_id = peer->id; - count++; - } - void deletingPeer(con::Peer *peer, bool timeout) - { - infostream<<"Handler("<<name<<")::deletingPeer(): " - "id="<<peer->id - <<", timeout="<<timeout<<std::endl; - last_id = peer->id; - count--; - } - - s32 count; - u16 last_id; - const char *name; - }; - - void Run() - { - DSTACK("TestConnection::Run"); - - TestHelpers(); - - /* - Test some real connections - - NOTE: This mostly tests the legacy interface. - */ - - u32 proto_id = 0xad26846a; - - Handler hand_server("server"); - Handler hand_client("client"); - - infostream<<"** Creating server Connection"<<std::endl; - con::Connection server(proto_id, 512, 5.0, false, &hand_server); - Address address(0,0,0,0, 30001); - server.Serve(address); - - infostream<<"** Creating client Connection"<<std::endl; - con::Connection client(proto_id, 512, 5.0, false, &hand_client); - - UASSERT(hand_server.count == 0); - UASSERT(hand_client.count == 0); - - sleep_ms(50); - - Address server_address(127,0,0,1, 30001); - infostream<<"** running client.Connect()"<<std::endl; - client.Connect(server_address); - - sleep_ms(50); - - // Client should not have added client yet - UASSERT(hand_client.count == 0); - - try - { - u16 peer_id; - SharedBuffer<u8> data; - infostream<<"** running client.Receive()"<<std::endl; - u32 size = client.Receive(peer_id, data); - infostream<<"** Client received: peer_id="<<peer_id - <<", size="<<size - <<std::endl; - } - catch(con::NoIncomingDataException &e) - { - } - - // Client should have added server now - UASSERT(hand_client.count == 1); - UASSERT(hand_client.last_id == 1); - // Server should not have added client yet - UASSERT(hand_server.count == 0); - - sleep_ms(100); - - try - { - u16 peer_id; - SharedBuffer<u8> data; - infostream<<"** running server.Receive()"<<std::endl; - u32 size = server.Receive(peer_id, data); - infostream<<"** Server received: peer_id="<<peer_id - <<", size="<<size - <<std::endl; - } - catch(con::NoIncomingDataException &e) - { - // No actual data received, but the client has - // probably been connected - } - - // Client should be the same - UASSERT(hand_client.count == 1); - UASSERT(hand_client.last_id == 1); - // Server should have the client - UASSERT(hand_server.count == 1); - UASSERT(hand_server.last_id == 2); - - //sleep_ms(50); - - while(client.Connected() == false) - { - try - { - u16 peer_id; - SharedBuffer<u8> data; - infostream<<"** running client.Receive()"<<std::endl; - u32 size = client.Receive(peer_id, data); - infostream<<"** Client received: peer_id="<<peer_id - <<", size="<<size - <<std::endl; - } - catch(con::NoIncomingDataException &e) - { - } - sleep_ms(50); - } - - sleep_ms(50); - - try - { - u16 peer_id; - SharedBuffer<u8> data; - infostream<<"** running server.Receive()"<<std::endl; - u32 size = server.Receive(peer_id, data); - infostream<<"** Server received: peer_id="<<peer_id - <<", size="<<size - <<std::endl; - } - catch(con::NoIncomingDataException &e) - { - } -#if 1 - /* - Simple send-receive test - */ - { - /*u8 data[] = "Hello World!"; - u32 datasize = sizeof(data);*/ - SharedBuffer<u8> data = SharedBufferFromString("Hello World!"); - - infostream<<"** running client.Send()"<<std::endl; - client.Send(PEER_ID_SERVER, 0, data, true); - - sleep_ms(50); - - u16 peer_id; - SharedBuffer<u8> recvdata; - infostream<<"** running server.Receive()"<<std::endl; - u32 size = server.Receive(peer_id, recvdata); - infostream<<"** Server received: peer_id="<<peer_id - <<", size="<<size - <<", data="<<*data - <<std::endl; - UASSERT(memcmp(*data, *recvdata, data.getSize()) == 0); - } -#endif - u16 peer_id_client = 2; -#if 0 - /* - Send consequent packets in different order - Not compatible with new Connection, thus commented out. - */ - { - //u8 data1[] = "hello1"; - //u8 data2[] = "hello2"; - SharedBuffer<u8> data1 = SharedBufferFromString("hello1"); - SharedBuffer<u8> data2 = SharedBufferFromString("Hello2"); - - Address client_address = - server.GetPeerAddress(peer_id_client); - - infostream<<"*** Sending packets in wrong order (2,1,2)" - <<std::endl; - - u8 chn = 0; - con::Channel *ch = &server.getPeer(peer_id_client)->channels[chn]; - u16 sn = ch->next_outgoing_seqnum; - ch->next_outgoing_seqnum = sn+1; - server.Send(peer_id_client, chn, data2, true); - ch->next_outgoing_seqnum = sn; - server.Send(peer_id_client, chn, data1, true); - ch->next_outgoing_seqnum = sn+1; - server.Send(peer_id_client, chn, data2, true); - - sleep_ms(50); - - infostream<<"*** Receiving the packets"<<std::endl; - - u16 peer_id; - SharedBuffer<u8> recvdata; - u32 size; - - infostream<<"** running client.Receive()"<<std::endl; - peer_id = 132; - size = client.Receive(peer_id, recvdata); - infostream<<"** Client received: peer_id="<<peer_id - <<", size="<<size - <<", data="<<*recvdata - <<std::endl; - UASSERT(size == data1.getSize()); - UASSERT(memcmp(*data1, *recvdata, data1.getSize()) == 0); - UASSERT(peer_id == PEER_ID_SERVER); - - infostream<<"** running client.Receive()"<<std::endl; - peer_id = 132; - size = client.Receive(peer_id, recvdata); - infostream<<"** Client received: peer_id="<<peer_id - <<", size="<<size - <<", data="<<*recvdata - <<std::endl; - UASSERT(size == data2.getSize()); - UASSERT(memcmp(*data2, *recvdata, data2.getSize()) == 0); - UASSERT(peer_id == PEER_ID_SERVER); - - bool got_exception = false; - try - { - infostream<<"** running client.Receive()"<<std::endl; - peer_id = 132; - size = client.Receive(peer_id, recvdata); - infostream<<"** Client received: peer_id="<<peer_id - <<", size="<<size - <<", data="<<*recvdata - <<std::endl; - } - catch(con::NoIncomingDataException &e) - { - infostream<<"** No incoming data for client"<<std::endl; - got_exception = true; - } - UASSERT(got_exception); - } -#endif -#if 0 - /* - Send large amounts of packets (infinite test) - Commented out because of infinity. - */ - { - infostream<<"Sending large amounts of packets (infinite test)"<<std::endl; - int sendcount = 0; - for(;;){ - int datasize = myrand_range(0,5)==0?myrand_range(100,10000):myrand_range(0,100); - infostream<<"datasize="<<datasize<<std::endl; - SharedBuffer<u8> data1(datasize); - for(u16 i=0; i<datasize; i++) - data1[i] = i/4; - - int sendtimes = myrand_range(1,10); - for(int i=0; i<sendtimes; i++){ - server.Send(peer_id_client, 0, data1, true); - sendcount++; - } - infostream<<"sendcount="<<sendcount<<std::endl; - - //int receivetimes = myrand_range(1,20); - int receivetimes = 20; - for(int i=0; i<receivetimes; i++){ - SharedBuffer<u8> recvdata; - u16 peer_id = 132; - u16 size = 0; - bool received = false; - try{ - size = client.Receive(peer_id, recvdata); - received = true; - }catch(con::NoIncomingDataException &e){ - } - } - } - } -#endif - /* - Send a large packet - */ - { - const int datasize = 30000; - SharedBuffer<u8> data1(datasize); - for(u16 i=0; i<datasize; i++){ - data1[i] = i/4; - } - - infostream<<"Sending data (size="<<datasize<<"):"; - for(int i=0; i<datasize && i<20; i++){ - if(i%2==0) infostream<<" "; - char buf[10]; - snprintf(buf, 10, "%.2X", ((int)((const char*)*data1)[i])&0xff); - infostream<<buf; - } - if(datasize>20) - infostream<<"..."; - infostream<<std::endl; - - server.Send(peer_id_client, 0, data1, true); - - //sleep_ms(3000); - - SharedBuffer<u8> recvdata; - infostream<<"** running client.Receive()"<<std::endl; - u16 peer_id = 132; - u16 size = 0; - bool received = false; - u32 timems0 = porting::getTimeMs(); - for(;;){ - if(porting::getTimeMs() - timems0 > 5000 || received) - break; - try{ - size = client.Receive(peer_id, recvdata); - received = true; - }catch(con::NoIncomingDataException &e){ - } - sleep_ms(10); - } - UASSERT(received); - infostream<<"** Client received: peer_id="<<peer_id - <<", size="<<size - <<std::endl; - - infostream<<"Received data (size="<<size<<"): "; - for(int i=0; i<size && i<20; i++){ - if(i%2==0) infostream<<" "; - char buf[10]; - snprintf(buf, 10, "%.2X", ((int)(recvdata[i]))&0xff); - infostream<<buf; - } - if(size>20) - infostream<<"..."; - infostream<<std::endl; - - UASSERT(memcmp(*data1, *recvdata, data1.getSize()) == 0); - UASSERT(peer_id == PEER_ID_SERVER); - } - - // Check peer handlers - UASSERT(hand_client.count == 1); - UASSERT(hand_client.last_id == 1); - UASSERT(hand_server.count == 1); - UASSERT(hand_server.last_id == 2); - - //assert(0); - } -}; - -#define TEST(X) do {\ - X x;\ - infostream<<"Running " #X <<std::endl;\ - x.Run();\ - tests_run++;\ - tests_failed += x.test_failed ? 1 : 0;\ -} while (0) - -#define TESTPARAMS(X, ...) do {\ - X x;\ - infostream<<"Running " #X <<std::endl;\ - x.Run(__VA_ARGS__);\ - tests_run++;\ - tests_failed += x.test_failed ? 1 : 0;\ -} while (0) - -void run_tests() -{ - DSTACK(__FUNCTION_NAME); - - int tests_run = 0; - int tests_failed = 0; - - // Create item and node definitions - IWritableItemDefManager *idef = createItemDefManager(); - IWritableNodeDefManager *ndef = createNodeDefManager(); - define_some_nodes(idef, ndef); - - log_set_lev_silence(LMT_ERROR, true); - - infostream<<"run_tests() started"<<std::endl; - TEST(TestUtilities); - TEST(TestPath); - TEST(TestSettings); - TEST(TestCompress); - TEST(TestSerialization); - TEST(TestNodedefSerialization); - TESTPARAMS(TestMapNode, ndef); - TESTPARAMS(TestVoxelManipulator, ndef); - TESTPARAMS(TestVoxelAlgorithms, ndef); - TESTPARAMS(TestInventory, idef); - //TEST(TestMapBlock); - //TEST(TestMapSector); - TEST(TestCollision); - if(INTERNET_SIMULATOR == false){ - TEST(TestSocket); - dout_con<<"=== BEGIN RUNNING UNIT TESTS FOR CONNECTION ==="<<std::endl; - TEST(TestConnection); - dout_con<<"=== END RUNNING UNIT TESTS FOR CONNECTION ==="<<std::endl; - } - - log_set_lev_silence(LMT_ERROR, false); - - delete idef; - delete ndef; - - if(tests_failed == 0){ - infostream<<"run_tests(): "<<tests_failed<<" / "<<tests_run<<" tests failed."<<std::endl; - infostream<<"run_tests() passed."<<std::endl; - return; - } else { - errorstream<<"run_tests(): "<<tests_failed<<" / "<<tests_run<<" tests failed."<<std::endl; - errorstream<<"run_tests() aborting."<<std::endl; - abort(); - } -} - diff --git a/src/touchscreengui.cpp b/src/touchscreengui.cpp index a2c981cff..f5868133f 100644 --- a/src/touchscreengui.cpp +++ b/src/touchscreengui.cpp @@ -25,12 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gettime.h" #include "util/numeric.h" #include "porting.h" +#include "guiscalingfilter.h" #include <iostream> #include <algorithm> #include <ISceneCollisionManager.h> +// Very slow button repeat frequency (in seconds) +#define SLOW_BUTTON_REPEAT (1.0f) + using namespace irr::core; extern Settings *g_settings; @@ -121,38 +125,48 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver) for (unsigned int i=0; i < after_last_element_id; i++) { m_buttons[i].guibutton = 0; m_buttons[i].repeatcounter = -1; + m_buttons[i].repeatdelay = BUTTON_REPEAT_DELAY; } m_screensize = m_device->getVideoDriver()->getScreenSize(); } -void TouchScreenGUI::loadButtonTexture(button_info* btn, const char* path) +void TouchScreenGUI::loadButtonTexture(button_info* btn, const char* path, rect<s32> button_rect) { unsigned int tid; - video::ITexture *texture = m_texturesource->getTexture(path,&tid); + video::ITexture *texture = guiScalingImageButton(m_device->getVideoDriver(), + m_texturesource->getTexture(path, &tid), button_rect.getWidth(), button_rect.getHeight()); if (texture) { btn->guibutton->setUseAlphaChannel(true); - btn->guibutton->setImage(texture); - btn->guibutton->setPressedImage(texture); - btn->guibutton->setScaleImage(true); + if (g_settings->getBool("gui_scaling_filter")) { + rect<s32> txr_rect = rect<s32>(0, 0, button_rect.getWidth(), button_rect.getHeight()); + btn->guibutton->setImage(texture, txr_rect); + btn->guibutton->setPressedImage(texture, txr_rect); + btn->guibutton->setScaleImage(false); + } else { + btn->guibutton->setImage(texture); + btn->guibutton->setPressedImage(texture); + btn->guibutton->setScaleImage(true); + } btn->guibutton->setDrawBorder(false); btn->guibutton->setText(L""); } } void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect, - std::wstring caption, bool immediate_release ) + std::wstring caption, bool immediate_release, float repeat_delay) { button_info* btn = &m_buttons[id]; btn->guibutton = m_guienv->addButton(button_rect, 0, id, caption.c_str()); btn->guibutton->grab(); btn->repeatcounter = -1; + btn->repeatdelay = repeat_delay; btn->keycode = id2keycode(id); btn->immediate_release = immediate_release; btn->ids.clear(); - loadButtonTexture(btn,touchgui_button_imagenames[id]); + loadButtonTexture(btn,touchgui_button_imagenames[id], button_rect); } static int getMaxControlPadSize(float density) { @@ -240,25 +254,25 @@ void TouchScreenGUI::init(ISimpleTextureSource* tsrc, float density) rect<s32>(m_screensize.X - (0.75*button_size), m_screensize.Y - (2.25*button_size), m_screensize.X, m_screensize.Y - (button_size*1.5)), - L"fly", true); + L"fly", false, SLOW_BUTTON_REPEAT); /* init noclip button */ initButton(noclip_id, rect<s32>(m_screensize.X - (0.75*button_size), 2.25*button_size, m_screensize.X, 3*button_size), - L"clip", true); + L"clip", false, SLOW_BUTTON_REPEAT); /* init fast button */ initButton(fast_id, rect<s32>(m_screensize.X - (0.75*button_size), 1.5*button_size, m_screensize.X, 2.25*button_size), - L"fast", true); + L"fast", false, SLOW_BUTTON_REPEAT); /* init debug button */ initButton(debug_id, rect<s32>(m_screensize.X - (0.75*button_size), 0.75*button_size, m_screensize.X, 1.5*button_size), - L"dbg", true); + L"dbg", false, SLOW_BUTTON_REPEAT); /* init chat button */ initButton(chat_id, @@ -270,13 +284,13 @@ void TouchScreenGUI::init(ISimpleTextureSource* tsrc, float density) initButton(camera_id, rect<s32>(m_screensize.X - (1.5*button_size), 0, m_screensize.X - (0.75*button_size), 0.75*button_size), - L"cam", true); + L"cam", false, SLOW_BUTTON_REPEAT); /* init rangeselect button */ initButton(range_id, rect<s32>(m_screensize.X - (2.25*button_size), 0, m_screensize.X - (1.5*button_size), 0.75*button_size), - L"far", true); + L"far", false, SLOW_BUTTON_REPEAT); } touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y) @@ -687,7 +701,7 @@ void TouchScreenGUI::step(float dtime) if (m_move_id != -1) m_move_has_really_moved = true; - if (btn->repeatcounter < 0.2) continue; + if (btn->repeatcounter < btn->repeatdelay) continue; btn->repeatcounter = 0; SEvent translated; diff --git a/src/touchscreengui.h b/src/touchscreengui.h index 4fe731513..bb3231793 100644 --- a/src/touchscreengui.h +++ b/src/touchscreengui.h @@ -27,7 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <map> #include "game.h" -#include "tile.h" +#include "client/tile.h" using namespace irr; using namespace irr::core; @@ -54,6 +54,7 @@ typedef enum { #define MIN_DIG_TIME_MS 500 #define MAX_TOUCH_COUNT 64 +#define BUTTON_REPEAT_DELAY 0.2f extern const char** touchgui_button_imagenames; @@ -105,6 +106,7 @@ private: struct button_info { float repeatcounter; + float repeatdelay; irr::EKEY_CODE keycode; std::vector<int> ids; IGUIButton* guibutton; @@ -124,10 +126,11 @@ private: /* initialize a button */ void initButton(touch_gui_button_id id, rect<s32> button_rect, - std::wstring caption, bool immediate_release ); + std::wstring caption, bool immediate_release, + float repeat_delay = BUTTON_REPEAT_DELAY); /* load texture */ - void loadButtonTexture(button_info* btn, const char* path); + void loadButtonTexture(button_info* btn, const char* path, rect<s32> button_rect); struct id_status{ int id; diff --git a/src/treegen.cpp b/src/treegen.cpp index 5c95b250e..208f34552 100644 --- a/src/treegen.cpp +++ b/src/treegen.cpp @@ -45,80 +45,75 @@ void make_tree(MMVManip &vmanip, v3s16 p0, PseudoRandom pr(seed); s16 trunk_h = pr.range(4, 5); v3s16 p1 = p0; - for(s16 ii=0; ii<trunk_h; ii++) - { - if(vmanip.m_area.contains(p1)) - if(ii == 0 || vmanip.getNodeNoExNoEmerge(p1).getContent() == CONTENT_AIR) - vmanip.m_data[vmanip.m_area.index(p1)] = treenode; + for (s16 ii = 0; ii < trunk_h; ii++) { + if (vmanip.m_area.contains(p1)) { + u32 vi = vmanip.m_area.index(p1); + vmanip.m_data[vi] = treenode; + } p1.Y++; } // p1 is now the last piece of the trunk p1.Y -= 1; - VoxelArea leaves_a(v3s16(-2,-1,-2), v3s16(2,2,2)); + VoxelArea leaves_a(v3s16(-2, -1, -2), v3s16(2, 2, 2)); //SharedPtr<u8> leaves_d(new u8[leaves_a.getVolume()]); Buffer<u8> leaves_d(leaves_a.getVolume()); - for(s32 i=0; i<leaves_a.getVolume(); i++) + for (s32 i = 0; i < leaves_a.getVolume(); i++) leaves_d[i] = 0; // Force leaves at near the end of the trunk - { - s16 d = 1; - for(s16 z=-d; z<=d; z++) - for(s16 y=-d; y<=d; y++) - for(s16 x=-d; x<=d; x++) - { - leaves_d[leaves_a.index(v3s16(x,y,z))] = 1; - } + s16 d = 1; + for (s16 z = -d; z <= d; z++) + for (s16 y = -d; y <= d; y++) + for (s16 x = -d; x <= d; x++) { + leaves_d[leaves_a.index(v3s16(x, y, z))] = 1; } // Add leaves randomly - for(u32 iii=0; iii<7; iii++) - { - s16 d = 1; - + for (u32 iii = 0; iii < 7; iii++) { v3s16 p( - pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X-d), - pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y-d), - pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z-d) + pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d), + pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d), + pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d) ); - for(s16 z=0; z<=d; z++) - for(s16 y=0; y<=d; y++) - for(s16 x=0; x<=d; x++) - { - leaves_d[leaves_a.index(p+v3s16(x,y,z))] = 1; + for (s16 z = 0; z <= d; z++) + for (s16 y = 0; y <= d; y++) + for (s16 x = 0; x <= d; x++) { + leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1; } } // Blit leaves to vmanip - for(s16 z=leaves_a.MinEdge.Z; z<=leaves_a.MaxEdge.Z; z++) - for(s16 y=leaves_a.MinEdge.Y; y<=leaves_a.MaxEdge.Y; y++) - for(s16 x=leaves_a.MinEdge.X; x<=leaves_a.MaxEdge.X; x++) - { - v3s16 p(x,y,z); - p += p1; - if(vmanip.m_area.contains(p) == false) - continue; - u32 vi = vmanip.m_area.index(p); - if(vmanip.m_data[vi].getContent() != CONTENT_AIR - && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) - continue; - u32 i = leaves_a.index(x,y,z); - if(leaves_d[i] == 1) { - bool is_apple = pr.range(0,99) < 10; - if(is_apple_tree && is_apple) { - vmanip.m_data[vi] = applenode; - } else { - vmanip.m_data[vi] = leavesnode; + for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++) + for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { + v3s16 pmin(leaves_a.MinEdge.X, y, z); + u32 i = leaves_a.index(pmin); + u32 vi = vmanip.m_area.index(pmin + p1); + for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { + v3s16 p(x, y, z); + if (vmanip.m_area.contains(p + p1) == true && + (vmanip.m_data[vi].getContent() == CONTENT_AIR || + vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) { + if (leaves_d[i] == 1) { + bool is_apple = pr.range(0, 99) < 10; + if (is_apple_tree && is_apple) + vmanip.m_data[vi] = applenode; + else + vmanip.m_data[vi] = leavesnode; + } } + vi++; + i++; } } } + // L-System tree LUA spawner -treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0, INodeDefManager *ndef, TreeDef tree_definition) +treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0, + INodeDefManager *ndef, TreeDef tree_definition) { ServerMap *map = &env->getServerMap(); std::map<v3s16, MapBlock*> modified_blocks; @@ -126,8 +121,8 @@ treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0, INodeDefManager *nd v3s16 tree_blockp = getNodeBlockPos(p0); treegen::error e; - vmanip.initialEmerge(tree_blockp - v3s16(1,1,1), tree_blockp + v3s16(1,3,1)); - e = make_ltree (vmanip, p0, ndef, tree_definition); + vmanip.initialEmerge(tree_blockp - v3s16(1, 1, 1), tree_blockp + v3s16(1, 3, 1)); + e = make_ltree(vmanip, p0, ndef, tree_definition); if (e != SUCCESS) return e; @@ -140,30 +135,25 @@ treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0, INodeDefManager *nd // Send a MEET_OTHER event MapEditEvent event; event.type = MEET_OTHER; - for(std::map<v3s16, MapBlock*>::iterator - i = modified_blocks.begin(); - i != modified_blocks.end(); ++i) - { + for (std::map<v3s16, MapBlock*>::iterator + i = modified_blocks.begin(); + i != modified_blocks.end(); ++i) event.modified_blocks.insert(i->first); - } map->dispatchEvent(&event); return SUCCESS; } + //L-System tree generator -treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, - TreeDef tree_definition) +treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, + INodeDefManager *ndef, TreeDef tree_definition) { MapNode dirtnode(ndef->getId("mapgen_dirt")); int seed; if (tree_definition.explicit_seed) - { - seed = tree_definition.seed+14002; - } + seed = tree_definition.seed + 14002; else - { - seed = p0.X*2 + p0.Y*4 + p0.Z; // use the tree position to seed PRNG - } + seed = p0.X * 2 + p0.Y * 4 + p0.Z; // use the tree position to seed PRNG PseudoRandom ps(seed); // chance of inserting abcd rules @@ -174,18 +164,18 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, //randomize tree growth level, minimum=2 s16 iterations = tree_definition.iterations; - if (tree_definition.iterations_random_level>0) - iterations -= ps.range(0,tree_definition.iterations_random_level); - if (iterations<2) - iterations=2; + if (tree_definition.iterations_random_level > 0) + iterations -= ps.range(0, tree_definition.iterations_random_level); + if (iterations < 2) + iterations = 2; s16 MAX_ANGLE_OFFSET = 5; - double angle_in_radians = (double)tree_definition.angle*M_PI/180; - double angleOffset_in_radians = (s16)(ps.range(0,1)%MAX_ANGLE_OFFSET)*M_PI/180; + double angle_in_radians = (double)tree_definition.angle * M_PI / 180; + double angleOffset_in_radians = (s16)(ps.range(0, 1) % MAX_ANGLE_OFFSET) * M_PI / 180; //initialize rotation matrix, position and stacks for branches core::matrix4 rotation; - rotation = setRotationAxisRadians(rotation, M_PI/2,v3f(0,0,1)); + rotation = setRotationAxisRadians(rotation, M_PI / 2, v3f(0, 0, 1)); v3f position; position.X = p0.X; position.Y = p0.Y; @@ -195,63 +185,85 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, //generate axiom std::string axiom = tree_definition.initial_axiom; - for(s16 i=0; i<iterations; i++) - { + for (s16 i = 0; i < iterations; i++) { std::string temp = ""; - for(s16 j=0; j<(s16)axiom.size(); j++) - { + for (s16 j = 0; j < (s16)axiom.size(); j++) { char axiom_char = axiom.at(j); - switch (axiom_char) - { + switch (axiom_char) { case 'A': - temp+=tree_definition.rules_a; + temp += tree_definition.rules_a; break; case 'B': - temp+=tree_definition.rules_b; + temp += tree_definition.rules_b; break; case 'C': - temp+=tree_definition.rules_c; + temp += tree_definition.rules_c; break; case 'D': - temp+=tree_definition.rules_d; + temp += tree_definition.rules_d; break; case 'a': - if (prop_a >= ps.range(1,10)) - temp+=tree_definition.rules_a; + if (prop_a >= ps.range(1, 10)) + temp += tree_definition.rules_a; break; case 'b': - if (prop_b >= ps.range(1,10)) - temp+=tree_definition.rules_b; + if (prop_b >= ps.range(1, 10)) + temp += tree_definition.rules_b; break; case 'c': - if (prop_c >= ps.range(1,10)) - temp+=tree_definition.rules_c; + if (prop_c >= ps.range(1, 10)) + temp += tree_definition.rules_c; break; case 'd': - if (prop_d >= ps.range(1,10)) - temp+=tree_definition.rules_d; + if (prop_d >= ps.range(1, 10)) + temp += tree_definition.rules_d; break; default: - temp+=axiom_char; + temp += axiom_char; break; } } - axiom=temp; + axiom = temp; } //make sure tree is not floating in the air - if (tree_definition.trunk_type == "double") - { - tree_node_placement(vmanip,v3f(position.X+1,position.Y-1,position.Z),dirtnode); - tree_node_placement(vmanip,v3f(position.X,position.Y-1,position.Z+1),dirtnode); - tree_node_placement(vmanip,v3f(position.X+1,position.Y-1,position.Z+1),dirtnode); - } - else if (tree_definition.trunk_type == "crossed") - { - tree_node_placement(vmanip,v3f(position.X+1,position.Y-1,position.Z),dirtnode); - tree_node_placement(vmanip,v3f(position.X-1,position.Y-1,position.Z),dirtnode); - tree_node_placement(vmanip,v3f(position.X,position.Y-1,position.Z+1),dirtnode); - tree_node_placement(vmanip,v3f(position.X,position.Y-1,position.Z-1),dirtnode); + if (tree_definition.trunk_type == "double") { + tree_node_placement( + vmanip, + v3f(position.X + 1, position.Y - 1, position.Z), + dirtnode + ); + tree_node_placement( + vmanip, + v3f(position.X, position.Y - 1, position.Z + 1), + dirtnode + ); + tree_node_placement( + vmanip, + v3f(position.X + 1, position.Y - 1, position.Z + 1), + dirtnode + ); + } else if (tree_definition.trunk_type == "crossed") { + tree_node_placement( + vmanip, + v3f(position.X + 1, position.Y - 1, position.Z), + dirtnode + ); + tree_node_placement( + vmanip, + v3f(position.X - 1, position.Y - 1, position.Z), + dirtnode + ); + tree_node_placement( + vmanip, + v3f(position.X, position.Y - 1, position.Z + 1), + dirtnode + ); + tree_node_placement( + vmanip, + v3f(position.X, position.Y - 1, position.Z - 1), + dirtnode + ); } /* build tree out of generated axiom @@ -283,84 +295,179 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, */ s16 x,y,z; - for(s16 i=0; i<(s16)axiom.size(); i++) - { + for (s16 i = 0; i < (s16)axiom.size(); i++) { char axiom_char = axiom.at(i); core::matrix4 temp_rotation; temp_rotation.makeIdentity(); v3f dir; - switch (axiom_char) - { + switch (axiom_char) { case 'G': - dir = v3f(1,0,0); - dir = transposeMatrix(rotation,dir); - position+=dir; + dir = v3f(1, 0, 0); + dir = transposeMatrix(rotation, dir); + position += dir; break; case 'T': - tree_trunk_placement(vmanip,v3f(position.X,position.Y,position.Z),tree_definition); - if (tree_definition.trunk_type == "double" && !tree_definition.thin_branches) - { - tree_trunk_placement(vmanip,v3f(position.X+1,position.Y,position.Z),tree_definition); - tree_trunk_placement(vmanip,v3f(position.X,position.Y,position.Z+1),tree_definition); - tree_trunk_placement(vmanip,v3f(position.X+1,position.Y,position.Z+1),tree_definition); - } - else if (tree_definition.trunk_type == "crossed" && !tree_definition.thin_branches) - { - tree_trunk_placement(vmanip,v3f(position.X+1,position.Y,position.Z),tree_definition); - tree_trunk_placement(vmanip,v3f(position.X-1,position.Y,position.Z),tree_definition); - tree_trunk_placement(vmanip,v3f(position.X,position.Y,position.Z+1),tree_definition); - tree_trunk_placement(vmanip,v3f(position.X,position.Y,position.Z-1),tree_definition); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z), + tree_definition + ); + if (tree_definition.trunk_type == "double" && + !tree_definition.thin_branches) { + tree_trunk_placement( + vmanip, + v3f(position.X + 1, position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z + 1), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X + 1, position.Y, position.Z + 1), + tree_definition + ); + } else if (tree_definition.trunk_type == "crossed" && + !tree_definition.thin_branches) { + tree_trunk_placement( + vmanip, + v3f(position.X + 1, position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X - 1, position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z + 1), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z - 1), + tree_definition + ); } - dir = v3f(1,0,0); - dir = transposeMatrix(rotation,dir); - position+=dir; + dir = v3f(1, 0, 0); + dir = transposeMatrix(rotation, dir); + position += dir; break; case 'F': - tree_trunk_placement(vmanip,v3f(position.X,position.Y,position.Z),tree_definition); - if ((stack_orientation.empty() && tree_definition.trunk_type == "double") || - (!stack_orientation.empty() && tree_definition.trunk_type == "double" && !tree_definition.thin_branches)) - { - tree_trunk_placement(vmanip,v3f(position.X+1,position.Y,position.Z),tree_definition); - tree_trunk_placement(vmanip,v3f(position.X,position.Y,position.Z+1),tree_definition); - tree_trunk_placement(vmanip,v3f(position.X+1,position.Y,position.Z+1),tree_definition); - } - else if ((stack_orientation.empty() && tree_definition.trunk_type == "crossed") || - (!stack_orientation.empty() && tree_definition.trunk_type == "crossed" && !tree_definition.thin_branches)) - { - tree_trunk_placement(vmanip,v3f(position.X+1,position.Y,position.Z),tree_definition); - tree_trunk_placement(vmanip,v3f(position.X-1,position.Y,position.Z),tree_definition); - tree_trunk_placement(vmanip,v3f(position.X,position.Y,position.Z+1),tree_definition); - tree_trunk_placement(vmanip,v3f(position.X,position.Y,position.Z-1),tree_definition); - } - if (stack_orientation.empty() == false) - { + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z), + tree_definition + ); + if ((stack_orientation.empty() && + tree_definition.trunk_type == "double") || + (!stack_orientation.empty() && + tree_definition.trunk_type == "double" && + !tree_definition.thin_branches)) { + tree_trunk_placement( + vmanip, + v3f(position.X +1 , position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z + 1), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X + 1, position.Y, position.Z + 1), + tree_definition + ); + } else if ((stack_orientation.empty() && + tree_definition.trunk_type == "crossed") || + (!stack_orientation.empty() && + tree_definition.trunk_type == "crossed" && + !tree_definition.thin_branches)) { + tree_trunk_placement( + vmanip, + v3f(position.X + 1, position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X - 1, position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z + 1), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z - 1), + tree_definition + ); + } if (stack_orientation.empty() == false) { s16 size = 1; - for(x=-size; x<=size; x++) - for(y=-size; y<=size; y++) - for(z=-size; z<=size; z++) - if (abs(x) == size && abs(y) == size && abs(z) == size) - { - tree_leaves_placement(vmanip,v3f(position.X+x+1,position.Y+y,position.Z+z),ps.next(), tree_definition); - tree_leaves_placement(vmanip,v3f(position.X+x-1,position.Y+y,position.Z+z),ps.next(), tree_definition); - tree_leaves_placement(vmanip,v3f(position.X+x,position.Y+y,position.Z+z+1),ps.next(), tree_definition); - tree_leaves_placement(vmanip,v3f(position.X+x,position.Y+y,position.Z+z-1),ps.next(), tree_definition); - } + for (x = -size; x <= size; x++) + for (y = -size; y <= size; y++) + for (z = -size; z <= size; z++) { + if (abs(x) == size && + abs(y) == size && + abs(z) == size) { + tree_leaves_placement( + vmanip, + v3f(position.X + x + 1, position.Y + y, + position.Z + z), + ps.next(), + tree_definition + ); + tree_leaves_placement( + vmanip, + v3f(position.X + x - 1, position.Y + y, + position.Z + z), + ps.next(), + tree_definition + ); + tree_leaves_placement( + vmanip,v3f(position.X + x, position.Y + y, + position.Z + z + 1), + ps.next(), + tree_definition + ); + tree_leaves_placement( + vmanip,v3f(position.X + x, position.Y + y, + position.Z + z - 1), + ps.next(), + tree_definition + ); + } + } } - dir = v3f(1,0,0); - dir = transposeMatrix(rotation,dir); - position+=dir; + dir = v3f(1, 0, 0); + dir = transposeMatrix(rotation, dir); + position += dir; break; case 'f': - tree_single_leaves_placement(vmanip,v3f(position.X,position.Y,position.Z),ps.next() ,tree_definition); - dir = v3f(1,0,0); - dir = transposeMatrix(rotation,dir); - position+=dir; + tree_single_leaves_placement( + vmanip, + v3f(position.X, position.Y, position.Z), + ps.next(), + tree_definition + ); + dir = v3f(1, 0, 0); + dir = transposeMatrix(rotation, dir); + position += dir; break; case 'R': - tree_fruit_placement(vmanip,v3f(position.X,position.Y,position.Z),tree_definition); - dir = v3f(1,0,0); - dir = transposeMatrix(rotation,dir); - position+=dir; + tree_fruit_placement( + vmanip, + v3f(position.X, position.Y, position.Z), + tree_definition + ); + dir = v3f(1, 0, 0); + dir = transposeMatrix(rotation, dir); + position += dir; break; // turtle orientation commands @@ -371,40 +478,46 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, case ']': if (stack_orientation.empty()) return UNBALANCED_BRACKETS; - rotation=stack_orientation.top(); + rotation = stack_orientation.top(); stack_orientation.pop(); - position=stack_position.top(); + position = stack_position.top(); stack_position.pop(); break; case '+': temp_rotation.makeIdentity(); - temp_rotation=setRotationAxisRadians(temp_rotation, angle_in_radians+angleOffset_in_radians,v3f(0,0,1)); - rotation*=temp_rotation; + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians + angleOffset_in_radians, v3f(0, 0, 1)); + rotation *= temp_rotation; break; case '-': temp_rotation.makeIdentity(); - temp_rotation=setRotationAxisRadians(temp_rotation, angle_in_radians+angleOffset_in_radians,v3f(0,0,-1)); - rotation*=temp_rotation; + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians + angleOffset_in_radians, v3f(0, 0, -1)); + rotation *= temp_rotation; break; case '&': temp_rotation.makeIdentity(); - temp_rotation=setRotationAxisRadians(temp_rotation, angle_in_radians+angleOffset_in_radians,v3f(0,1,0)); - rotation*=temp_rotation; + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians + angleOffset_in_radians, v3f(0, 1, 0)); + rotation *= temp_rotation; break; case '^': temp_rotation.makeIdentity(); - temp_rotation=setRotationAxisRadians(temp_rotation, angle_in_radians+angleOffset_in_radians,v3f(0,-1,0)); - rotation*=temp_rotation; + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians + angleOffset_in_radians, v3f(0, -1, 0)); + rotation *= temp_rotation; break; case '*': temp_rotation.makeIdentity(); - temp_rotation=setRotationAxisRadians(temp_rotation, angle_in_radians,v3f(1,0,0)); - rotation*=temp_rotation; + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians, v3f(1, 0, 0)); + rotation *= temp_rotation; break; case '/': temp_rotation.makeIdentity(); - temp_rotation=setRotationAxisRadians(temp_rotation, angle_in_radians,v3f(-1,0,0)); - rotation*=temp_rotation; + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians, v3f(-1, 0, 0)); + rotation *= temp_rotation; break; default: break; @@ -414,85 +527,87 @@ treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, return SUCCESS; } -void tree_node_placement(MMVManip &vmanip, v3f p0, - MapNode node) + +void tree_node_placement(MMVManip &vmanip, v3f p0, MapNode node) { - v3s16 p1 = v3s16(myround(p0.X),myround(p0.Y),myround(p0.Z)); - if(vmanip.m_area.contains(p1) == false) + v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); + if (vmanip.m_area.contains(p1) == false) return; u32 vi = vmanip.m_area.index(p1); - if(vmanip.m_data[vi].getContent() != CONTENT_AIR + if (vmanip.m_data[vi].getContent() != CONTENT_AIR && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) return; vmanip.m_data[vmanip.m_area.index(p1)] = node; } -void tree_trunk_placement(MMVManip &vmanip, v3f p0, - TreeDef &tree_definition) + +void tree_trunk_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition) { - v3s16 p1 = v3s16(myround(p0.X),myround(p0.Y),myround(p0.Z)); - if(vmanip.m_area.contains(p1) == false) + v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); + if (vmanip.m_area.contains(p1) == false) return; u32 vi = vmanip.m_area.index(p1); - if(vmanip.m_data[vi].getContent() != CONTENT_AIR + if (vmanip.m_data[vi].getContent() != CONTENT_AIR && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) return; vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.trunknode; } + void tree_leaves_placement(MMVManip &vmanip, v3f p0, - PseudoRandom ps ,TreeDef &tree_definition) + PseudoRandom ps, TreeDef &tree_definition) { - MapNode leavesnode=tree_definition.leavesnode; - if (ps.range(1,100) > 100-tree_definition.leaves2_chance) - leavesnode=tree_definition.leaves2node; - v3s16 p1 = v3s16(myround(p0.X),myround(p0.Y),myround(p0.Z)); - if(vmanip.m_area.contains(p1) == false) + MapNode leavesnode = tree_definition.leavesnode; + if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance) + leavesnode = tree_definition.leaves2node; + v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); + if (vmanip.m_area.contains(p1) == false) return; u32 vi = vmanip.m_area.index(p1); - if(vmanip.m_data[vi].getContent() != CONTENT_AIR + if (vmanip.m_data[vi].getContent() != CONTENT_AIR && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) return; - if (tree_definition.fruit_chance>0) - { - if (ps.range(1,100) > 100-tree_definition.fruit_chance) + if (tree_definition.fruit_chance > 0) { + if (ps.range(1, 100) > 100 - tree_definition.fruit_chance) vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode; else vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode; - } - else if (ps.range(1,100) > 20) + } else if (ps.range(1, 100) > 20) { vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode; + } } + void tree_single_leaves_placement(MMVManip &vmanip, v3f p0, PseudoRandom ps, TreeDef &tree_definition) { - MapNode leavesnode=tree_definition.leavesnode; - if (ps.range(1,100) > 100-tree_definition.leaves2_chance) - leavesnode=tree_definition.leaves2node; - v3s16 p1 = v3s16(myround(p0.X),myround(p0.Y),myround(p0.Z)); - if(vmanip.m_area.contains(p1) == false) + MapNode leavesnode = tree_definition.leavesnode; + if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance) + leavesnode = tree_definition.leaves2node; + v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); + if (vmanip.m_area.contains(p1) == false) return; u32 vi = vmanip.m_area.index(p1); - if(vmanip.m_data[vi].getContent() != CONTENT_AIR - && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) + if (vmanip.m_data[vi].getContent() != CONTENT_AIR + && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) return; vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode; } -void tree_fruit_placement(MMVManip &vmanip, v3f p0, - TreeDef &tree_definition) + +void tree_fruit_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition) { - v3s16 p1 = v3s16(myround(p0.X),myround(p0.Y),myround(p0.Z)); - if(vmanip.m_area.contains(p1) == false) + v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); + if (vmanip.m_area.contains(p1) == false) return; u32 vi = vmanip.m_area.index(p1); - if(vmanip.m_data[vi].getContent() != CONTENT_AIR - && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) + if (vmanip.m_data[vi].getContent() != CONTENT_AIR + && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) return; vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode; } + irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis) { double c = cos(angle); @@ -520,20 +635,21 @@ irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3 return M; } + v3f transposeMatrix(irr::core::matrix4 M, v3f v) { v3f translated; double x = M[0] * v.X + M[4] * v.Y + M[8] * v.Z +M[12]; double y = M[1] * v.X + M[5] * v.Y + M[9] * v.Z +M[13]; double z = M[2] * v.X + M[6] * v.Y + M[10] * v.Z +M[14]; - translated.X=x; - translated.Y=y; - translated.Z=z; + translated.X = x; + translated.Y = y; + translated.Z = z; return translated; } -void make_jungletree(VoxelManipulator &vmanip, v3s16 p0, - INodeDefManager *ndef, int seed) + +void make_jungletree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, int seed) { /* NOTE: Tree-placing code is currently duplicated in the engine @@ -551,18 +667,17 @@ void make_jungletree(VoxelManipulator &vmanip, v3s16 p0, MapNode leavesnode(c_leaves); PseudoRandom pr(seed); - for(s16 x=-1; x<=1; x++) - for(s16 z=-1; z<=1; z++) - { - if(pr.range(0, 2) == 0) + for (s16 x= -1; x <= 1; x++) + for (s16 z= -1; z <= 1; z++) { + if (pr.range(0, 2) == 0) continue; - v3s16 p1 = p0 + v3s16(x,0,z); - v3s16 p2 = p0 + v3s16(x,-1,z); + v3s16 p1 = p0 + v3s16(x, 0, z); + v3s16 p2 = p0 + v3s16(x, -1, z); u32 vi1 = vmanip.m_area.index(p1); u32 vi2 = vmanip.m_area.index(p2); if (vmanip.m_area.contains(p2) && - vmanip.m_data[vi2].getContent() == CONTENT_AIR) + vmanip.m_data[vi2].getContent() == CONTENT_AIR) vmanip.m_data[vi2] = treenode; else if (vmanip.m_area.contains(p1) && vmanip.m_data[vi1].getContent() == CONTENT_AIR) @@ -572,12 +687,10 @@ void make_jungletree(VoxelManipulator &vmanip, v3s16 p0, s16 trunk_h = pr.range(8, 12); v3s16 p1 = p0; - for (s16 ii=0; ii<trunk_h; ii++) - { + for (s16 ii = 0; ii < trunk_h; ii++) { if (vmanip.m_area.contains(p1)) { u32 vi = vmanip.m_area.index(p1); - if (vmanip.m_data[vi].getContent() == CONTENT_AIR) - vmanip.m_data[vi] = treenode; + vmanip.m_data[vi] = treenode; } p1.Y++; } @@ -585,58 +698,181 @@ void make_jungletree(VoxelManipulator &vmanip, v3s16 p0, // p1 is now the last piece of the trunk p1.Y -= 1; - VoxelArea leaves_a(v3s16(-3,-2,-3), v3s16(3,2,3)); + VoxelArea leaves_a(v3s16(-3, -2, -3), v3s16(3, 2, 3)); //SharedPtr<u8> leaves_d(new u8[leaves_a.getVolume()]); Buffer<u8> leaves_d(leaves_a.getVolume()); - for(s32 i=0; i<leaves_a.getVolume(); i++) + for (s32 i = 0; i < leaves_a.getVolume(); i++) leaves_d[i] = 0; // Force leaves at near the end of the trunk - { - s16 d = 1; - for(s16 z=-d; z<=d; z++) - for(s16 y=-d; y<=d; y++) - for(s16 x=-d; x<=d; x++) - { - leaves_d[leaves_a.index(v3s16(x,y,z))] = 1; - } + s16 d = 1; + for (s16 z = -d; z <= d; z++) + for (s16 y = -d; y <= d; y++) + for (s16 x = -d; x <= d; x++) { + leaves_d[leaves_a.index(v3s16(x,y,z))] = 1; } // Add leaves randomly - for(u32 iii=0; iii<30; iii++) - { - s16 d = 1; - + for (u32 iii = 0; iii < 30; iii++) { v3s16 p( - pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X-d), - pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y-d), - pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z-d) + pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d), + pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d), + pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d) ); - for(s16 z=0; z<=d; z++) - for(s16 y=0; y<=d; y++) - for(s16 x=0; x<=d; x++) - { - leaves_d[leaves_a.index(p+v3s16(x,y,z))] = 1; + for (s16 z = 0; z <= d; z++) + for (s16 y = 0; y <= d; y++) + for (s16 x = 0; x <= d; x++) { + leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1; } } // Blit leaves to vmanip - for(s16 z=leaves_a.MinEdge.Z; z<=leaves_a.MaxEdge.Z; z++) - for(s16 y=leaves_a.MinEdge.Y; y<=leaves_a.MaxEdge.Y; y++) - for(s16 x=leaves_a.MinEdge.X; x<=leaves_a.MaxEdge.X; x++) - { - v3s16 p(x,y,z); - p += p1; - if(vmanip.m_area.contains(p) == false) - continue; - u32 vi = vmanip.m_area.index(p); - if (vmanip.m_data[vi].getContent() != CONTENT_AIR && - vmanip.m_data[vi].getContent() != CONTENT_IGNORE) - continue; - u32 i = leaves_a.index(x,y,z); - if(leaves_d[i] == 1) - vmanip.m_data[vi] = leavesnode; + for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++) + for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { + v3s16 pmin(leaves_a.MinEdge.X, y, z); + u32 i = leaves_a.index(pmin); + u32 vi = vmanip.m_area.index(pmin + p1); + for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { + v3s16 p(x, y, z); + if (vmanip.m_area.contains(p + p1) == true && + (vmanip.m_data[vi].getContent() == CONTENT_AIR || + vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) { + if (leaves_d[i] == 1) + vmanip.m_data[vi] = leavesnode; + } + vi++; + i++; + } + } +} + + +void make_pine_tree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, int seed) +{ + /* + NOTE: Tree-placing code is currently duplicated in the engine + and in games that have saplings; both are deprecated but not + replaced yet + */ + content_t c_tree = ndef->getId("mapgen_pine_tree"); + content_t c_leaves = ndef->getId("mapgen_pine_needles"); + content_t c_snow = ndef->getId("mapgen_snow"); + if (c_tree == CONTENT_IGNORE) + c_tree = ndef->getId("mapgen_tree"); + if (c_leaves == CONTENT_IGNORE) + c_leaves = ndef->getId("mapgen_leaves"); + if (c_snow == CONTENT_IGNORE) + c_snow = CONTENT_AIR; + + MapNode treenode(c_tree); + MapNode leavesnode(c_leaves); + MapNode snownode(c_snow); + + PseudoRandom pr(seed); + s16 trunk_h = pr.range(9, 13); + v3s16 p1 = p0; + for (s16 ii = 0; ii < trunk_h; ii++) { + if (vmanip.m_area.contains(p1)) { + u32 vi = vmanip.m_area.index(p1); + vmanip.m_data[vi] = treenode; + } + p1.Y++; + } + + // Make p1 the top node of the trunk + p1.Y -= 1; + + VoxelArea leaves_a(v3s16(-3, -6, -3), v3s16(3, 3, 3)); + //SharedPtr<u8> leaves_d(new u8[leaves_a.getVolume()]); + Buffer<u8> leaves_d(leaves_a.getVolume()); + for (s32 i = 0; i < leaves_a.getVolume(); i++) + leaves_d[i] = 0; + + // Upper branches + s16 dev = 3; + for (s16 yy = -1; yy <= 1; yy++) { + for (s16 zz = -dev; zz <= dev; zz++) { + u32 i = leaves_a.index(v3s16(-dev, yy, zz)); + u32 ia = leaves_a.index(v3s16(-dev, yy+1, zz)); + for (s16 xx = -dev; xx <= dev; xx++) { + if (pr.range(0, 20) <= 19 - dev) { + leaves_d[i] = 1; + leaves_d[ia] = 2; + } + i++; + ia++; + } + } + dev--; + } + + // Centre top nodes + u32 i = leaves_a.index(v3s16(0, 1, 0)); + leaves_d[i] = 1; + i = leaves_a.index(v3s16(0, 2, 0)); + leaves_d[i] = 1; + i = leaves_a.index(v3s16(0, 3, 0)); + leaves_d[i] = 2; + + // Lower branches + s16 my = -6; + for (u32 iii = 0; iii < 20; iii++) { + s16 xi = pr.range(-3, 2); + s16 yy = pr.range(-6, -5); + s16 zi = pr.range(-3, 2); + if (yy > my) + my = yy; + for (s16 zz = zi; zz <= zi + 1; zz++) { + u32 i = leaves_a.index(v3s16(xi, yy, zz)); + u32 ia = leaves_a.index(v3s16(xi, yy + 1, zz)); + for (s16 xx = xi; xx <= xi + 1; xx++) { + leaves_d[i] = 1; + if (leaves_d[ia] == 0) + leaves_d[ia] = 2; + i++; + ia++; + } + } + } + + dev = 2; + for (s16 yy = my + 1; yy <= my + 2; yy++) { + for (s16 zz = -dev; zz <= dev; zz++) { + u32 i = leaves_a.index(v3s16(-dev, yy, zz)); + u32 ia = leaves_a.index(v3s16(-dev, yy + 1, zz)); + for (s16 xx = -dev; xx <= dev; xx++) { + if (pr.range(0, 20) <= 19 - dev) { + leaves_d[i] = 1; + leaves_d[ia] = 2; + } + i++; + ia++; + } + } + dev--; + } + + // Blit leaves to vmanip + for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++) + for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { + v3s16 pmin(leaves_a.MinEdge.X, y, z); + u32 i = leaves_a.index(pmin); + u32 vi = vmanip.m_area.index(pmin + p1); + for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { + v3s16 p(x, y, z); + if (vmanip.m_area.contains(p + p1) == true && + (vmanip.m_data[vi].getContent() == CONTENT_AIR || + vmanip.m_data[vi].getContent() == CONTENT_IGNORE || + vmanip.m_data[vi] == snownode)) { + if (leaves_d[i] == 1) + vmanip.m_data[vi] = leavesnode; + else if (leaves_d[i] == 2) + vmanip.m_data[vi] = snownode; + } + vi++; + i++; + } } } diff --git a/src/treegen.h b/src/treegen.h index febbc29ee..4b0089d1e 100644 --- a/src/treegen.h +++ b/src/treegen.h @@ -62,7 +62,10 @@ namespace treegen { void make_tree(MMVManip &vmanip, v3s16 p0, bool is_apple_tree, INodeDefManager *ndef, int seed); // Add jungle tree - void make_jungletree(VoxelManipulator &vmanip, v3s16 p0, + void make_jungletree(MMVManip &vmanip, v3s16 p0, + INodeDefManager *ndef, int seed); + // Add pine tree + void make_pine_tree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, int seed); // Add L-Systems tree (used by engine) diff --git a/src/unittest/CMakeLists.txt b/src/unittest/CMakeLists.txt new file mode 100644 index 000000000..bdff14f05 --- /dev/null +++ b/src/unittest/CMakeLists.txt @@ -0,0 +1,23 @@ +set (UNITTEST_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_areastore.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_collision.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_compression.cpp + ${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_mapnode.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_nodedef.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_noderesolver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_noise.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_objdef.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_profiler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_serialization.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_settings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_socket.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_utilities.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_voxelalgorithms.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test_voxelmanipulator.cpp + PARENT_SCOPE) diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp new file mode 100644 index 000000000..d0ffb423f --- /dev/null +++ b/src/unittest/test.cpp @@ -0,0 +1,638 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "debug.h" +#include "log.h" +#include "nodedef.h" +#include "itemdef.h" +#include "gamedef.h" + +content_t t_CONTENT_STONE; +content_t t_CONTENT_GRASS; +content_t t_CONTENT_TORCH; +content_t t_CONTENT_WATER; +content_t t_CONTENT_LAVA; +content_t t_CONTENT_BRICK; + +//////////////////////////////////////////////////////////////////////////////// + +//// +//// TestGameDef +//// + +class TestGameDef : public IGameDef { +public: + TestGameDef(); + ~TestGameDef(); + + IItemDefManager *getItemDefManager() { return m_itemdef; } + INodeDefManager *getNodeDefManager() { return m_nodedef; } + ICraftDefManager *getCraftDefManager() { return m_craftdef; } + ITextureSource *getTextureSource() { return m_texturesrc; } + IShaderSource *getShaderSource() { return m_shadersrc; } + ISoundManager *getSoundManager() { return m_soundmgr; } + MtEventManager *getEventManager() { return m_eventmgr; } + scene::ISceneManager *getSceneManager() { return m_scenemgr; } + IRollbackManager *getRollbackManager() { return m_rollbackmgr; } + EmergeManager *getEmergeManager() { return m_emergemgr; } + + scene::IAnimatedMesh *getMesh(const std::string &filename) { return NULL; } + bool checkLocalPrivilege(const std::string &priv) { return false; } + u16 allocateUnknownNodeId(const std::string &name) { return 0; } + + void defineSomeNodes(); + +private: + IItemDefManager *m_itemdef; + INodeDefManager *m_nodedef; + ICraftDefManager *m_craftdef; + ITextureSource *m_texturesrc; + IShaderSource *m_shadersrc; + ISoundManager *m_soundmgr; + MtEventManager *m_eventmgr; + scene::ISceneManager *m_scenemgr; + IRollbackManager *m_rollbackmgr; + EmergeManager *m_emergemgr; +}; + + +TestGameDef::TestGameDef() +{ + m_itemdef = createItemDefManager(); + m_nodedef = createNodeDefManager(); + + defineSomeNodes(); +} + + +TestGameDef::~TestGameDef() +{ + delete m_itemdef; + delete m_nodedef; +} + + +void TestGameDef::defineSomeNodes() +{ + IWritableItemDefManager *idef = (IWritableItemDefManager *)m_itemdef; + IWritableNodeDefManager *ndef = (IWritableNodeDefManager *)m_nodedef; + + ItemDefinition itemdef; + ContentFeatures f; + + //// Stone + itemdef = ItemDefinition(); + itemdef.type = ITEM_NODE; + itemdef.name = "default:stone"; + itemdef.description = "Stone"; + itemdef.groups["cracky"] = 3; + itemdef.inventory_image = "[inventorycube" + "{default_stone.png" + "{default_stone.png" + "{default_stone.png"; + f = ContentFeatures(); + f.name = itemdef.name; + for(int i = 0; i < 6; i++) + f.tiledef[i].name = "default_stone.png"; + f.is_ground_content = true; + idef->registerItem(itemdef); + t_CONTENT_STONE = ndef->set(f.name, f); + + //// Grass + itemdef = ItemDefinition(); + itemdef.type = ITEM_NODE; + itemdef.name = "default:dirt_with_grass"; + itemdef.description = "Dirt with grass"; + itemdef.groups["crumbly"] = 3; + itemdef.inventory_image = "[inventorycube" + "{default_grass.png" + "{default_dirt.png&default_grass_side.png" + "{default_dirt.png&default_grass_side.png"; + f = ContentFeatures(); + f.name = itemdef.name; + f.tiledef[0].name = "default_grass.png"; + f.tiledef[1].name = "default_dirt.png"; + for(int i = 2; i < 6; i++) + f.tiledef[i].name = "default_dirt.png^default_grass_side.png"; + f.is_ground_content = true; + idef->registerItem(itemdef); + t_CONTENT_GRASS = ndef->set(f.name, f); + + //// Torch (minimal definition for lighting tests) + itemdef = ItemDefinition(); + itemdef.type = ITEM_NODE; + itemdef.name = "default:torch"; + f = ContentFeatures(); + f.name = itemdef.name; + f.param_type = CPT_LIGHT; + f.light_propagates = true; + f.sunlight_propagates = true; + f.light_source = LIGHT_MAX-1; + idef->registerItem(itemdef); + t_CONTENT_TORCH = ndef->set(f.name, f); + + //// Water + itemdef = ItemDefinition(); + itemdef.type = ITEM_NODE; + itemdef.name = "default:water"; + itemdef.description = "Water"; + itemdef.inventory_image = "[inventorycube" + "{default_water.png" + "{default_water.png" + "{default_water.png"; + f = ContentFeatures(); + f.name = itemdef.name; + f.alpha = 128; + f.liquid_type = LIQUID_SOURCE; + f.liquid_viscosity = 4; + f.is_ground_content = true; + f.groups["liquids"] = 3; + for(int i = 0; i < 6; i++) + f.tiledef[i].name = "default_water.png"; + idef->registerItem(itemdef); + t_CONTENT_WATER = ndef->set(f.name, f); + + //// Lava + itemdef = ItemDefinition(); + itemdef.type = ITEM_NODE; + itemdef.name = "default:lava"; + itemdef.description = "Lava"; + itemdef.inventory_image = "[inventorycube" + "{default_lava.png" + "{default_lava.png" + "{default_lava.png"; + f = ContentFeatures(); + f.name = itemdef.name; + f.alpha = 128; + f.liquid_type = LIQUID_SOURCE; + f.liquid_viscosity = 7; + f.light_source = LIGHT_MAX-1; + f.is_ground_content = true; + f.groups["liquids"] = 3; + for(int i = 0; i < 6; i++) + f.tiledef[i].name = "default_lava.png"; + idef->registerItem(itemdef); + t_CONTENT_LAVA = ndef->set(f.name, f); + + + //// Brick + itemdef = ItemDefinition(); + itemdef.type = ITEM_NODE; + itemdef.name = "default:brick"; + itemdef.description = "Brick"; + itemdef.groups["cracky"] = 3; + itemdef.inventory_image = "[inventorycube" + "{default_brick.png" + "{default_brick.png" + "{default_brick.png"; + f = ContentFeatures(); + f.name = itemdef.name; + for(int i = 0; i < 6; i++) + f.tiledef[i].name = "default_brick.png"; + f.is_ground_content = true; + idef->registerItem(itemdef); + t_CONTENT_BRICK = ndef->set(f.name, f); +} + +//// +//// run_tests +//// + +void run_tests() +{ + DSTACK(__FUNCTION_NAME); + + u32 t1 = porting::getTime(PRECISION_MILLI); + TestGameDef gamedef; + + log_set_lev_silence(LMT_ERROR, true); + + u32 num_modules_failed = 0; + u32 num_total_tests_failed = 0; + u32 num_total_tests_run = 0; + std::vector<TestBase *> &testmods = TestManager::getTestModules(); + for (size_t i = 0; i != testmods.size(); i++) { + if (!testmods[i]->testModule(&gamedef)) + num_modules_failed++; + + num_total_tests_failed += testmods[i]->num_tests_failed; + num_total_tests_run += testmods[i]->num_tests_run; + } + + u32 tdiff = porting::getTime(PRECISION_MILLI) - t1; + + log_set_lev_silence(LMT_ERROR, false); + + const char *overall_status = (num_modules_failed == 0) ? "PASSED" : "FAILED"; + + dstream + << "++++++++++++++++++++++++++++++++++++++++" + << "++++++++++++++++++++++++++++++++++++++++" << std::endl + << "Unit Test Results: " << overall_status << std::endl + << " " << num_modules_failed << " / " << testmods.size() + << " failed modules (" << num_total_tests_failed << " / " + << num_total_tests_run << " failed individual tests)." << std::endl + << " Testing took " << tdiff << "ms total." << std::endl + << "++++++++++++++++++++++++++++++++++++++++" + << "++++++++++++++++++++++++++++++++++++++++" << std::endl; + + if (num_modules_failed) + abort(); +} + +//// +//// TestBase +//// + +bool TestBase::testModule(IGameDef *gamedef) +{ + dstream << "======== Testing module " << getName() << std::endl; + u32 t1 = porting::getTime(PRECISION_MILLI); + + + runTests(gamedef); + + u32 tdiff = porting::getTime(PRECISION_MILLI) - t1; + dstream << "======== Module " << getName() << " " + << (num_tests_failed ? "failed" : "passed") << " (" << num_tests_failed + << " failures / " << num_tests_run << " tests) - " << tdiff + << "ms" << std::endl; + + if (!m_test_dir.empty()) + fs::RecursiveDelete(m_test_dir); + + return num_tests_failed == 0; +} + +std::string TestBase::getTestTempDirectory() +{ + if (!m_test_dir.empty()) + return m_test_dir; + + char buf[32]; + snprintf(buf, sizeof(buf), "%08X", myrand()); + + m_test_dir = fs::TempPath() + DIR_DELIM "mttest_" + buf; + if (!fs::CreateDir(m_test_dir)) + throw TestFailedException(); + + return m_test_dir; +} + +std::string TestBase::getTestTempFile() +{ + char buf[32]; + snprintf(buf, sizeof(buf), "%08X", myrand()); + + return getTestTempDirectory() + DIR_DELIM + buf + ".tmp"; +} + + +/* + NOTE: These tests became non-working then NodeContainer was removed. + These should be redone, utilizing some kind of a virtual + interface for Map (IMap would be fine). +*/ +#if 0 +struct TestMapBlock: public TestBase +{ + class TC : public NodeContainer + { + public: + + MapNode node; + bool position_valid; + core::list<v3s16> validity_exceptions; + + TC() + { + position_valid = true; + } + + virtual bool isValidPosition(v3s16 p) + { + //return position_valid ^ (p==position_valid_exception); + bool exception = false; + for(core::list<v3s16>::Iterator i=validity_exceptions.begin(); + i != validity_exceptions.end(); i++) + { + if(p == *i) + { + exception = true; + break; + } + } + return exception ? !position_valid : position_valid; + } + + virtual MapNode getNode(v3s16 p) + { + if(isValidPosition(p) == false) + throw InvalidPositionException(); + return node; + } + + virtual void setNode(v3s16 p, MapNode & n) + { + if(isValidPosition(p) == false) + throw InvalidPositionException(); + }; + + virtual u16 nodeContainerId() const + { + return 666; + } + }; + + void Run() + { + TC parent; + + MapBlock b(&parent, v3s16(1,1,1)); + v3s16 relpos(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE); + + UASSERT(b.getPosRelative() == relpos); + + UASSERT(b.getBox().MinEdge.X == MAP_BLOCKSIZE); + UASSERT(b.getBox().MaxEdge.X == MAP_BLOCKSIZE*2-1); + UASSERT(b.getBox().MinEdge.Y == MAP_BLOCKSIZE); + UASSERT(b.getBox().MaxEdge.Y == MAP_BLOCKSIZE*2-1); + UASSERT(b.getBox().MinEdge.Z == MAP_BLOCKSIZE); + UASSERT(b.getBox().MaxEdge.Z == MAP_BLOCKSIZE*2-1); + + UASSERT(b.isValidPosition(v3s16(0,0,0)) == true); + UASSERT(b.isValidPosition(v3s16(-1,0,0)) == false); + UASSERT(b.isValidPosition(v3s16(-1,-142,-2341)) == false); + UASSERT(b.isValidPosition(v3s16(-124,142,2341)) == false); + UASSERT(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true); + UASSERT(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE,MAP_BLOCKSIZE-1)) == false); + + /* + TODO: this method should probably be removed + if the block size isn't going to be set variable + */ + /*UASSERT(b.getSizeNodes() == v3s16(MAP_BLOCKSIZE, + MAP_BLOCKSIZE, MAP_BLOCKSIZE));*/ + + // Changed flag should be initially set + UASSERT(b.getModified() == MOD_STATE_WRITE_NEEDED); + b.resetModified(); + UASSERT(b.getModified() == MOD_STATE_CLEAN); + + // All nodes should have been set to + // .d=CONTENT_IGNORE and .getLight() = 0 + for(u16 z=0; z<MAP_BLOCKSIZE; z++) + for(u16 y=0; y<MAP_BLOCKSIZE; y++) + for(u16 x=0; x<MAP_BLOCKSIZE; x++) + { + //UASSERT(b.getNode(v3s16(x,y,z)).getContent() == CONTENT_AIR); + UASSERT(b.getNode(v3s16(x,y,z)).getContent() == CONTENT_IGNORE); + UASSERT(b.getNode(v3s16(x,y,z)).getLight(LIGHTBANK_DAY) == 0); + UASSERT(b.getNode(v3s16(x,y,z)).getLight(LIGHTBANK_NIGHT) == 0); + } + + { + MapNode n(CONTENT_AIR); + for(u16 z=0; z<MAP_BLOCKSIZE; z++) + for(u16 y=0; y<MAP_BLOCKSIZE; y++) + for(u16 x=0; x<MAP_BLOCKSIZE; x++) + { + b.setNode(v3s16(x,y,z), n); + } + } + + /* + Parent fetch functions + */ + parent.position_valid = false; + parent.node.setContent(5); + + MapNode n; + + // Positions in the block should still be valid + UASSERT(b.isValidPositionParent(v3s16(0,0,0)) == true); + UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true); + n = b.getNodeParent(v3s16(0,MAP_BLOCKSIZE-1,0)); + UASSERT(n.getContent() == CONTENT_AIR); + + // ...but outside the block they should be invalid + UASSERT(b.isValidPositionParent(v3s16(-121,2341,0)) == false); + UASSERT(b.isValidPositionParent(v3s16(-1,0,0)) == false); + UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == false); + + { + bool exception_thrown = false; + try{ + // This should throw an exception + MapNode n = b.getNodeParent(v3s16(0,0,-1)); + } + catch(InvalidPositionException &e) + { + exception_thrown = true; + } + UASSERT(exception_thrown); + } + + parent.position_valid = true; + // Now the positions outside should be valid + UASSERT(b.isValidPositionParent(v3s16(-121,2341,0)) == true); + UASSERT(b.isValidPositionParent(v3s16(-1,0,0)) == true); + UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == true); + n = b.getNodeParent(v3s16(0,0,MAP_BLOCKSIZE)); + UASSERT(n.getContent() == 5); + + /* + Set a node + */ + v3s16 p(1,2,0); + n.setContent(4); + b.setNode(p, n); + UASSERT(b.getNode(p).getContent() == 4); + //TODO: Update to new system + /*UASSERT(b.getNodeTile(p) == 4); + UASSERT(b.getNodeTile(v3s16(-1,-1,0)) == 5);*/ + + /* + propagateSunlight() + */ + // Set lighting of all nodes to 0 + for(u16 z=0; z<MAP_BLOCKSIZE; z++){ + for(u16 y=0; y<MAP_BLOCKSIZE; y++){ + for(u16 x=0; x<MAP_BLOCKSIZE; x++){ + MapNode n = b.getNode(v3s16(x,y,z)); + n.setLight(LIGHTBANK_DAY, 0); + n.setLight(LIGHTBANK_NIGHT, 0); + b.setNode(v3s16(x,y,z), n); + } + } + } + { + /* + Check how the block handles being a lonely sky block + */ + parent.position_valid = true; + b.setIsUnderground(false); + parent.node.setContent(CONTENT_AIR); + parent.node.setLight(LIGHTBANK_DAY, LIGHT_SUN); + parent.node.setLight(LIGHTBANK_NIGHT, 0); + core::map<v3s16, bool> light_sources; + // The bottom block is invalid, because we have a shadowing node + UASSERT(b.propagateSunlight(light_sources) == false); + UASSERT(b.getNode(v3s16(1,4,0)).getLight(LIGHTBANK_DAY) == LIGHT_SUN); + UASSERT(b.getNode(v3s16(1,3,0)).getLight(LIGHTBANK_DAY) == LIGHT_SUN); + UASSERT(b.getNode(v3s16(1,2,0)).getLight(LIGHTBANK_DAY) == 0); + UASSERT(b.getNode(v3s16(1,1,0)).getLight(LIGHTBANK_DAY) == 0); + UASSERT(b.getNode(v3s16(1,0,0)).getLight(LIGHTBANK_DAY) == 0); + UASSERT(b.getNode(v3s16(1,2,3)).getLight(LIGHTBANK_DAY) == LIGHT_SUN); + UASSERT(b.getFaceLight2(1000, p, v3s16(0,1,0)) == LIGHT_SUN); + UASSERT(b.getFaceLight2(1000, p, v3s16(0,-1,0)) == 0); + UASSERT(b.getFaceLight2(0, p, v3s16(0,-1,0)) == 0); + // According to MapBlock::getFaceLight, + // The face on the z+ side should have double-diminished light + //UASSERT(b.getFaceLight(p, v3s16(0,0,1)) == diminish_light(diminish_light(LIGHT_MAX))); + // The face on the z+ side should have diminished light + UASSERT(b.getFaceLight2(1000, p, v3s16(0,0,1)) == diminish_light(LIGHT_MAX)); + } + /* + Check how the block handles being in between blocks with some non-sunlight + while being underground + */ + { + // Make neighbours to exist and set some non-sunlight to them + parent.position_valid = true; + b.setIsUnderground(true); + parent.node.setLight(LIGHTBANK_DAY, LIGHT_MAX/2); + core::map<v3s16, bool> light_sources; + // The block below should be valid because there shouldn't be + // sunlight in there either + UASSERT(b.propagateSunlight(light_sources, true) == true); + // Should not touch nodes that are not affected (that is, all of them) + //UASSERT(b.getNode(v3s16(1,2,3)).getLight() == LIGHT_SUN); + // Should set light of non-sunlighted blocks to 0. + UASSERT(b.getNode(v3s16(1,2,3)).getLight(LIGHTBANK_DAY) == 0); + } + /* + Set up a situation where: + - There is only air in this block + - There is a valid non-sunlighted block at the bottom, and + - Invalid blocks elsewhere. + - the block is not underground. + + This should result in bottom block invalidity + */ + { + b.setIsUnderground(false); + // Clear block + for(u16 z=0; z<MAP_BLOCKSIZE; z++){ + for(u16 y=0; y<MAP_BLOCKSIZE; y++){ + for(u16 x=0; x<MAP_BLOCKSIZE; x++){ + MapNode n; + n.setContent(CONTENT_AIR); + n.setLight(LIGHTBANK_DAY, 0); + b.setNode(v3s16(x,y,z), n); + } + } + } + // Make neighbours invalid + parent.position_valid = false; + // Add exceptions to the top of the bottom block + for(u16 x=0; x<MAP_BLOCKSIZE; x++) + for(u16 z=0; z<MAP_BLOCKSIZE; z++) + { + parent.validity_exceptions.push_back(v3s16(MAP_BLOCKSIZE+x, MAP_BLOCKSIZE-1, MAP_BLOCKSIZE+z)); + } + // Lighting value for the valid nodes + parent.node.setLight(LIGHTBANK_DAY, LIGHT_MAX/2); + core::map<v3s16, bool> light_sources; + // Bottom block is not valid + UASSERT(b.propagateSunlight(light_sources) == false); + } + } +}; + +struct TestMapSector: public TestBase +{ + class TC : public NodeContainer + { + public: + + MapNode node; + bool position_valid; + + TC() + { + position_valid = true; + } + + virtual bool isValidPosition(v3s16 p) + { + return position_valid; + } + + virtual MapNode getNode(v3s16 p) + { + if(position_valid == false) + throw InvalidPositionException(); + return node; + } + + virtual void setNode(v3s16 p, MapNode & n) + { + if(position_valid == false) + throw InvalidPositionException(); + }; + + virtual u16 nodeContainerId() const + { + return 666; + } + }; + + void Run() + { + TC parent; + parent.position_valid = false; + + // Create one with no heightmaps + ServerMapSector sector(&parent, v2s16(1,1)); + + UASSERT(sector.getBlockNoCreateNoEx(0) == 0); + UASSERT(sector.getBlockNoCreateNoEx(1) == 0); + + MapBlock * bref = sector.createBlankBlock(-2); + + UASSERT(sector.getBlockNoCreateNoEx(0) == 0); + UASSERT(sector.getBlockNoCreateNoEx(-2) == bref); + + //TODO: Check for AlreadyExistsException + + /*bool exception_thrown = false; + try{ + sector.getBlock(0); + } + catch(InvalidPositionException &e){ + exception_thrown = true; + } + UASSERT(exception_thrown);*/ + + } +}; +#endif diff --git a/src/unittest/test.h b/src/unittest/test.h new file mode 100644 index 000000000..47a441e02 --- /dev/null +++ b/src/unittest/test.h @@ -0,0 +1,146 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef TEST_HEADER +#define TEST_HEADER + +#include <exception> +#include <vector> + +#include "irrlichttypes_extrabloated.h" +#include "porting.h" +#include "filesys.h" +#include "mapnode.h" + +class TestFailedException : public std::exception { +}; + +// Runs a unit test and reports results +#define TEST(fxn, ...) do { \ + u32 t1 = porting::getTime(PRECISION_MILLI); \ + try { \ + fxn(__VA_ARGS__); \ + dstream << "[PASS] "; \ + } catch (TestFailedException &e) { \ + dstream << "[FAIL] "; \ + num_tests_failed++; \ + } catch (std::exception &e) { \ + dstream << "Caught unhandled exception: " << e.what() << std::endl; \ + dstream << "[FAIL] "; \ + num_tests_failed++; \ + } \ + num_tests_run++; \ + u32 tdiff = porting::getTime(PRECISION_MILLI) - t1; \ + dstream << #fxn << " - " << tdiff << "ms" << std::endl; \ +} while (0) + +// Asserts the specified condition is true, or fails the current unit test +#define UASSERT(x) do { \ + if (!(x)) { \ + dstream << "Test assertion failed: " #x << std::endl \ + << " at " << fs::GetFilenameFromPath(__FILE__) \ + << ":" << __LINE__ << std::endl; \ + throw TestFailedException(); \ + } \ +} while (0) + +// Asserts the specified condition is true, or fails the current unit test +// and prints the format specifier fmt +#define UTEST(x, fmt, ...) do { \ + if (!(x)) { \ + char utest_buf[1024]; \ + snprintf(utest_buf, sizeof(utest_buf), fmt, __VA_ARGS__); \ + dstream << "Test assertion failed: " << utest_buf << std::endl \ + << " at " << fs::GetFilenameFromPath(__FILE__) \ + << ":" << __LINE__ << std::endl; \ + throw TestFailedException(); \ + } \ +} while (0) + +// Asserts the comparison specified by CMP is true, or fails the current unit test +#define UASSERTCMP(T, CMP, actual, expected) do { \ + T a = (actual); \ + T e = (expected); \ + if (!(a CMP e)) { \ + dstream << "Test assertion failed: " << #actual << " " << #CMP << " " \ + << #expected << std::endl \ + << " at " << fs::GetFilenameFromPath(__FILE__) << ":" \ + << __LINE__ << std::endl \ + << " actual: " << a << std::endl << " expected: " \ + << e << std::endl; \ + throw TestFailedException(); \ + } \ +} while (0) + +#define UASSERTEQ(T, actual, expected) UASSERTCMP(T, ==, actual, expected) + +// UASSERTs that the specified exception occurs +#define EXCEPTION_CHECK(EType, code) do { \ + bool exception_thrown = false; \ + try { \ + code; \ + } catch (EType &e) { \ + exception_thrown = true; \ + } \ + UASSERT(exception_thrown); \ +} while (0) + +class IGameDef; + +class TestBase { +public: + bool testModule(IGameDef *gamedef); + std::string getTestTempDirectory(); + std::string getTestTempFile(); + + virtual void runTests(IGameDef *gamedef) = 0; + virtual const char *getName() = 0; + + u32 num_tests_failed; + u32 num_tests_run; + +private: + std::string m_test_dir; +}; + +class TestManager { +public: + static std::vector<TestBase *> &getTestModules() + { + static std::vector<TestBase *> m_modules_to_test; + return m_modules_to_test; + } + + static void registerTestModule(TestBase *module) + { + getTestModules().push_back(module); + } +}; + +// A few item and node definitions for those tests that need them +extern content_t t_CONTENT_STONE; +extern content_t t_CONTENT_GRASS; +extern content_t t_CONTENT_TORCH; +extern content_t t_CONTENT_WATER; +extern content_t t_CONTENT_LAVA; +extern content_t t_CONTENT_BRICK; + +void run_tests(); + +#endif diff --git a/src/unittest/test_areastore.cpp b/src/unittest/test_areastore.cpp new file mode 100644 index 000000000..a0dcada94 --- /dev/null +++ b/src/unittest/test_areastore.cpp @@ -0,0 +1,129 @@ +/* +Minetest +Copyright (C) 2015 est31, <MTest31@outlook.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "areastore.h" + +class TestAreaStore : public TestBase { +public: + TestAreaStore() { TestManager::registerTestModule(this); } + const char *getName() { return "TestAreaStore"; } + + void runTests(IGameDef *gamedef); + + void genericStoreTest(AreaStore *store); + void testVectorStore(); + void testSpatialStore(); +}; + +static TestAreaStore g_test_instance; + +void TestAreaStore::runTests(IGameDef *gamedef) +{ + TEST(testVectorStore); +#if USE_SPATIAL + TEST(testSpatialStore); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestAreaStore::testVectorStore() +{ + VectorAreaStore store; + genericStoreTest(&store); +} + +void TestAreaStore::testSpatialStore() +{ +#if USE_SPATIAL + SpatialAreaStore store; + genericStoreTest(&store); +#endif +} + +void TestAreaStore::genericStoreTest(AreaStore *store) +{ + Area a(v3s16(-10, -3, 5), v3s16(0, 29, 7)); + a.id = 1; + Area b(v3s16(-5, -2, 5), v3s16(0, 28, 6)); + b.id = 2; + Area c(v3s16(-7, -3, 6), v3s16(-1, 27, 7)); + c.id = 3; + std::vector<Area *> res; + + UASSERTEQ(size_t, store->size(), 0); + store->reserve(2); // sic + store->insertArea(a); + store->insertArea(b); + store->insertArea(c); + UASSERTEQ(size_t, store->size(), 3); + + store->getAreasForPos(&res, v3s16(-1, 0, 6)); + UASSERTEQ(size_t, res.size(), 3); + res.clear(); + store->getAreasForPos(&res, v3s16(0, 0, 7)); + UASSERTEQ(size_t, res.size(), 1); + UASSERTEQ(u32, res[0]->id, 1); + res.clear(); + + store->removeArea(1); + + store->getAreasForPos(&res, v3s16(0, 0, 7)); + UASSERTEQ(size_t, res.size(), 0); + res.clear(); + + store->insertArea(a); + + store->getAreasForPos(&res, v3s16(0, 0, 7)); + UASSERTEQ(size_t, res.size(), 1); + UASSERTEQ(u32, res[0]->id, 1); + res.clear(); + + store->getAreasInArea(&res, v3s16(-10, -3, 5), v3s16(0, 29, 7), false); + UASSERTEQ(size_t, res.size(), 3); + res.clear(); + + store->getAreasInArea(&res, v3s16(-100, 0, 6), v3s16(200, 0, 6), false); + UASSERTEQ(size_t, res.size(), 0); + res.clear(); + + store->getAreasInArea(&res, v3s16(-100, 0, 6), v3s16(200, 0, 6), true); + UASSERTEQ(size_t, res.size(), 3); + res.clear(); + + store->removeArea(1); + store->removeArea(2); + store->removeArea(3); + + Area d(v3s16(-100, -300, -200), v3s16(-50, -200, -100)); + d.id = 4; + d.data = "Hi!"; + store->insertArea(d); + + store->getAreasForPos(&res, v3s16(-75, -250, -150)); + UASSERTEQ(size_t, res.size(), 1); + UASSERTEQ(u32, res[0]->id, 4); + UASSERTEQ(u16, res[0]->data.size(), 3); + UASSERT(strncmp(res[0]->data.c_str(), "Hi!", 3) == 0); + res.clear(); + + store->removeArea(4); +} diff --git a/src/unittest/test_collision.cpp b/src/unittest/test_collision.cpp new file mode 100644 index 000000000..e505de450 --- /dev/null +++ b/src/unittest/test_collision.cpp @@ -0,0 +1,180 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "collision.h" + +class TestCollision : public TestBase { +public: + TestCollision() { TestManager::registerTestModule(this); } + const char *getName() { return "TestCollision"; } + + void runTests(IGameDef *gamedef); + + void testAxisAlignedCollision(); +}; + +static TestCollision g_test_instance; + +void TestCollision::runTests(IGameDef *gamedef) +{ + TEST(testAxisAlignedCollision); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestCollision::testAxisAlignedCollision() +{ + for (s16 bx = -3; bx <= 3; bx++) + for (s16 by = -3; by <= 3; by++) + for (s16 bz = -3; bz <= 3; bz++) { + // X- + { + aabb3f s(bx, by, bz, bx+1, by+1, bz+1); + aabb3f m(bx-2, by, bz, bx-1, by+1, bz+1); + v3f v(1, 0, 0); + f32 dtime = 0; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); + UASSERT(fabs(dtime - 1.000) < 0.001); + } + { + aabb3f s(bx, by, bz, bx+1, by+1, bz+1); + aabb3f m(bx-2, by, bz, bx-1, by+1, bz+1); + v3f v(-1, 0, 0); + f32 dtime = 0; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == -1); + } + { + aabb3f s(bx, by, bz, bx+1, by+1, bz+1); + aabb3f m(bx-2, by+1.5, bz, bx-1, by+2.5, bz-1); + v3f v(1, 0, 0); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == -1); + } + { + aabb3f s(bx, by, bz, bx+1, by+1, bz+1); + aabb3f m(bx-2, by-1.5, bz, bx-1.5, by+0.5, bz+1); + v3f v(0.5, 0.1, 0); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); + UASSERT(fabs(dtime - 3.000) < 0.001); + } + { + aabb3f s(bx, by, bz, bx+1, by+1, bz+1); + aabb3f m(bx-2, by-1.5, bz, bx-1.5, by+0.5, bz+1); + v3f v(0.5, 0.1, 0); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); + UASSERT(fabs(dtime - 3.000) < 0.001); + } + + // X+ + { + aabb3f s(bx, by, bz, bx+1, by+1, bz+1); + aabb3f m(bx+2, by, bz, bx+3, by+1, bz+1); + v3f v(-1, 0, 0); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); + UASSERT(fabs(dtime - 1.000) < 0.001); + } + { + aabb3f s(bx, by, bz, bx+1, by+1, bz+1); + aabb3f m(bx+2, by, bz, bx+3, by+1, bz+1); + v3f v(1, 0, 0); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == -1); + } + { + aabb3f s(bx, by, bz, bx+1, by+1, bz+1); + aabb3f m(bx+2, by, bz+1.5, bx+3, by+1, bz+3.5); + v3f v(-1, 0, 0); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == -1); + } + { + aabb3f s(bx, by, bz, bx+1, by+1, bz+1); + aabb3f m(bx+2, by-1.5, bz, bx+2.5, by-0.5, bz+1); + v3f v(-0.5, 0.2, 0); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 1); // Y, not X! + UASSERT(fabs(dtime - 2.500) < 0.001); + } + { + aabb3f s(bx, by, bz, bx+1, by+1, bz+1); + aabb3f m(bx+2, by-1.5, bz, bx+2.5, by-0.5, bz+1); + v3f v(-0.5, 0.3, 0); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); + UASSERT(fabs(dtime - 2.000) < 0.001); + } + + // TODO: Y-, Y+, Z-, Z+ + + // misc + { + aabb3f s(bx, by, bz, bx+2, by+2, bz+2); + aabb3f m(bx+2.3, by+2.29, bz+2.29, bx+4.2, by+4.2, bz+4.2); + v3f v(-1./3, -1./3, -1./3); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); + UASSERT(fabs(dtime - 0.9) < 0.001); + } + { + aabb3f s(bx, by, bz, bx+2, by+2, bz+2); + aabb3f m(bx+2.29, by+2.3, bz+2.29, bx+4.2, by+4.2, bz+4.2); + v3f v(-1./3, -1./3, -1./3); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 1); + UASSERT(fabs(dtime - 0.9) < 0.001); + } + { + aabb3f s(bx, by, bz, bx+2, by+2, bz+2); + aabb3f m(bx+2.29, by+2.29, bz+2.3, bx+4.2, by+4.2, bz+4.2); + v3f v(-1./3, -1./3, -1./3); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 2); + UASSERT(fabs(dtime - 0.9) < 0.001); + } + { + aabb3f s(bx, by, bz, bx+2, by+2, bz+2); + aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.3, by-2.29, bz-2.29); + v3f v(1./7, 1./7, 1./7); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 0); + UASSERT(fabs(dtime - 16.1) < 0.001); + } + { + aabb3f s(bx, by, bz, bx+2, by+2, bz+2); + aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.29, by-2.3, bz-2.29); + v3f v(1./7, 1./7, 1./7); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 1); + UASSERT(fabs(dtime - 16.1) < 0.001); + } + { + aabb3f s(bx, by, bz, bx+2, by+2, bz+2); + aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.29, by-2.29, bz-2.3); + v3f v(1./7, 1./7, 1./7); + f32 dtime; + UASSERT(axisAlignedCollision(s, m, v, 0, dtime) == 2); + UASSERT(fabs(dtime - 16.1) < 0.001); + } + } +} diff --git a/src/unittest/test_compression.cpp b/src/unittest/test_compression.cpp new file mode 100644 index 000000000..a3132aa17 --- /dev/null +++ b/src/unittest/test_compression.cpp @@ -0,0 +1,172 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include <sstream> + +#include "irrlichttypes_extrabloated.h" +#include "log.h" +#include "serialization.h" +#include "nodedef.h" +#include "noise.h" + +class TestCompression : public TestBase { +public: + TestCompression() { TestManager::registerTestModule(this); } + const char *getName() { return "TestCompression"; } + + void runTests(IGameDef *gamedef); + + void testRLECompression(); + void testZlibCompression(); + void testZlibLargeData(); +}; + +static TestCompression g_test_instance; + +void TestCompression::runTests(IGameDef *gamedef) +{ + TEST(testRLECompression); + TEST(testZlibCompression); + TEST(testZlibLargeData); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestCompression::testRLECompression() +{ + SharedBuffer<u8> fromdata(4); + fromdata[0]=1; + fromdata[1]=5; + fromdata[2]=5; + fromdata[3]=1; + + std::ostringstream os(std::ios_base::binary); + compress(fromdata, os, 0); + + std::string str_out = os.str(); + + infostream << "str_out.size()="<<str_out.size()<<std::endl; + infostream << "TestCompress: 1,5,5,1 -> "; + for (u32 i = 0; i < str_out.size(); i++) + infostream << (u32)str_out[i] << ","; + infostream << std::endl; + + UASSERT(str_out.size() == 10); + + UASSERT(str_out[0] == 0); + UASSERT(str_out[1] == 0); + UASSERT(str_out[2] == 0); + UASSERT(str_out[3] == 4); + UASSERT(str_out[4] == 0); + UASSERT(str_out[5] == 1); + UASSERT(str_out[6] == 1); + UASSERT(str_out[7] == 5); + UASSERT(str_out[8] == 0); + UASSERT(str_out[9] == 1); + + std::istringstream is(str_out, std::ios_base::binary); + std::ostringstream os2(std::ios_base::binary); + + decompress(is, os2, 0); + std::string str_out2 = os2.str(); + + infostream << "decompress: "; + for (u32 i = 0; i < str_out2.size(); i++) + infostream << (u32)str_out2[i] << ","; + infostream << std::endl; + + UASSERTEQ(size_t, str_out2.size(), fromdata.getSize()); + + for (u32 i = 0; i < str_out2.size(); i++) + UASSERT(str_out2[i] == fromdata[i]); +} + +void TestCompression::testZlibCompression() +{ + SharedBuffer<u8> fromdata(4); + fromdata[0]=1; + fromdata[1]=5; + fromdata[2]=5; + fromdata[3]=1; + + std::ostringstream os(std::ios_base::binary); + compress(fromdata, os, SER_FMT_VER_HIGHEST_READ); + + std::string str_out = os.str(); + + infostream << "str_out.size()=" << str_out.size() <<std::endl; + infostream << "TestCompress: 1,5,5,1 -> "; + for (u32 i = 0; i < str_out.size(); i++) + infostream << (u32)str_out[i] << ","; + infostream << std::endl; + + std::istringstream is(str_out, std::ios_base::binary); + std::ostringstream os2(std::ios_base::binary); + + decompress(is, os2, SER_FMT_VER_HIGHEST_READ); + std::string str_out2 = os2.str(); + + infostream << "decompress: "; + for (u32 i = 0; i < str_out2.size(); i++) + infostream << (u32)str_out2[i] << ","; + infostream << std::endl; + + UASSERTEQ(size_t, str_out2.size(), fromdata.getSize()); + + for (u32 i = 0; i < str_out2.size(); i++) + UASSERT(str_out2[i] == fromdata[i]); +} + +void TestCompression::testZlibLargeData() +{ + infostream << "Test: Testing zlib wrappers with a large amount " + "of pseudorandom data" << std::endl; + + u32 size = 50000; + infostream << "Test: Input size of large compressZlib is " + << size << std::endl; + + std::string data_in; + data_in.resize(size); + PseudoRandom pseudorandom(9420); + for (u32 i = 0; i < size; i++) + data_in[i] = pseudorandom.range(0, 255); + + std::ostringstream os_compressed(std::ios::binary); + compressZlib(data_in, os_compressed); + infostream << "Test: Output size of large compressZlib is " + << os_compressed.str().size()<<std::endl; + + std::istringstream is_compressed(os_compressed.str(), std::ios::binary); + std::ostringstream os_decompressed(std::ios::binary); + decompressZlib(is_compressed, os_decompressed); + infostream << "Test: Output size of large decompressZlib is " + << os_decompressed.str().size() << std::endl; + + std::string str_decompressed = os_decompressed.str(); + UASSERTEQ(size_t, str_decompressed.size(), data_in.size()); + + for (u32 i = 0; i < size && i < str_decompressed.size(); i++) { + UTEST(str_decompressed[i] == data_in[i], + "index out[%i]=%i differs from in[%i]=%i", + i, str_decompressed[i], i, data_in[i]); + } +} diff --git a/src/unittest/test_connection.cpp b/src/unittest/test_connection.cpp new file mode 100644 index 000000000..49e412fc8 --- /dev/null +++ b/src/unittest/test_connection.cpp @@ -0,0 +1,348 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "log.h" +#include "socket.h" +#include "settings.h" +#include "util/serialize.h" +#include "network/connection.h" + +class TestConnection : public TestBase { +public: + TestConnection() + { + if (INTERNET_SIMULATOR == false) + TestManager::registerTestModule(this); + } + + const char *getName() { return "TestConnection"; } + + void runTests(IGameDef *gamedef); + + void testHelpers(); + void testConnectSendReceive(); +}; + +static TestConnection g_test_instance; + +void TestConnection::runTests(IGameDef *gamedef) +{ + TEST(testHelpers); + TEST(testConnectSendReceive); +} + +//////////////////////////////////////////////////////////////////////////////// + +struct Handler : public con::PeerHandler +{ + Handler(const char *a_name) + { + count = 0; + last_id = 0; + name = a_name; + } + + void peerAdded(con::Peer *peer) + { + infostream << "Handler(" << name << ")::peerAdded(): " + "id=" << peer->id << std::endl; + last_id = peer->id; + count++; + } + + void deletingPeer(con::Peer *peer, bool timeout) + { + infostream << "Handler(" << name << ")::deletingPeer(): " + "id=" << peer->id << ", timeout=" << timeout << std::endl; + last_id = peer->id; + count--; + } + + s32 count; + u16 last_id; + const char *name; +}; + +void TestConnection::testHelpers() +{ + // Some constants for testing + u32 proto_id = 0x12345678; + u16 peer_id = 123; + u8 channel = 2; + SharedBuffer<u8> data1(1); + data1[0] = 100; + Address a(127,0,0,1, 10); + const u16 seqnum = 34352; + + con::BufferedPacket p1 = con::makePacket(a, data1, + proto_id, peer_id, channel); + /* + We should now have a packet with this data: + Header: + [0] u32 protocol_id + [4] u16 sender_peer_id + [6] u8 channel + Data: + [7] u8 data1[0] + */ + UASSERT(readU32(&p1.data[0]) == proto_id); + UASSERT(readU16(&p1.data[4]) == peer_id); + UASSERT(readU8(&p1.data[6]) == channel); + UASSERT(readU8(&p1.data[7]) == data1[0]); + + //infostream<<"initial data1[0]="<<((u32)data1[0]&0xff)<<std::endl; + + SharedBuffer<u8> p2 = con::makeReliablePacket(data1, seqnum); + + /*infostream<<"p2.getSize()="<<p2.getSize()<<", data1.getSize()=" + <<data1.getSize()<<std::endl; + infostream<<"readU8(&p2[3])="<<readU8(&p2[3]) + <<" p2[3]="<<((u32)p2[3]&0xff)<<std::endl; + infostream<<"data1[0]="<<((u32)data1[0]&0xff)<<std::endl;*/ + + UASSERT(p2.getSize() == 3 + data1.getSize()); + UASSERT(readU8(&p2[0]) == TYPE_RELIABLE); + UASSERT(readU16(&p2[1]) == seqnum); + UASSERT(readU8(&p2[3]) == data1[0]); +} + + +void TestConnection::testConnectSendReceive() +{ + DSTACK("TestConnection::Run"); + + /* + Test some real connections + + NOTE: This mostly tests the legacy interface. + */ + + u32 proto_id = 0xad26846a; + + Handler hand_server("server"); + Handler hand_client("client"); + + Address address(0, 0, 0, 0, 30001); + Address bind_addr(0, 0, 0, 0, 30001); + /* + * Try to use the bind_address for servers with no localhost address + * For example: FreeBSD jails + */ + std::string bind_str = g_settings->get("bind_address"); + try { + bind_addr.Resolve(bind_str.c_str()); + + if (!bind_addr.isIPv6()) { + address = bind_addr; + } + } catch (ResolveError &e) { + } + + infostream << "** Creating server Connection" << std::endl; + con::Connection server(proto_id, 512, 5.0, false, &hand_server); + server.Serve(address); + + infostream << "** Creating client Connection" << std::endl; + con::Connection client(proto_id, 512, 5.0, false, &hand_client); + + UASSERT(hand_server.count == 0); + UASSERT(hand_client.count == 0); + + sleep_ms(50); + + Address server_address(127, 0, 0, 1, 30001); + if (address != Address(0, 0, 0, 0, 30001)) { + server_address = bind_addr; + } + + infostream << "** running client.Connect()" << std::endl; + client.Connect(server_address); + + sleep_ms(50); + + // Client should not have added client yet + UASSERT(hand_client.count == 0); + + try { + NetworkPacket pkt; + infostream << "** running client.Receive()" << std::endl; + client.Receive(&pkt); + infostream << "** Client received: peer_id=" << pkt.getPeerId() + << ", size=" << pkt.getSize() << std::endl; + } catch (con::NoIncomingDataException &e) { + } + + // Client should have added server now + UASSERT(hand_client.count == 1); + UASSERT(hand_client.last_id == 1); + // Server should not have added client yet + UASSERT(hand_server.count == 0); + + sleep_ms(100); + + try { + NetworkPacket pkt; + infostream << "** running server.Receive()" << std::endl; + server.Receive(&pkt); + infostream << "** Server received: peer_id=" << pkt.getPeerId() + << ", size=" << pkt.getSize() + << std::endl; + } catch (con::NoIncomingDataException &e) { + // No actual data received, but the client has + // probably been connected + } + + // Client should be the same + UASSERT(hand_client.count == 1); + UASSERT(hand_client.last_id == 1); + // Server should have the client + UASSERT(hand_server.count == 1); + UASSERT(hand_server.last_id == 2); + + //sleep_ms(50); + + while (client.Connected() == false) { + try { + NetworkPacket pkt; + infostream << "** running client.Receive()" << std::endl; + client.Receive(&pkt); + infostream << "** Client received: peer_id=" << pkt.getPeerId() + << ", size=" << pkt.getSize() << std::endl; + } catch (con::NoIncomingDataException &e) { + } + sleep_ms(50); + } + + sleep_ms(50); + + try { + NetworkPacket pkt; + infostream << "** running server.Receive()" << std::endl; + server.Receive(&pkt); + infostream << "** Server received: peer_id=" << pkt.getPeerId() + << ", size=" << pkt.getSize() + << std::endl; + } catch (con::NoIncomingDataException &e) { + } + + /* + Simple send-receive test + */ + { + NetworkPacket pkt; + pkt.putRawPacket((u8*) "Hello World !", 14, 0); + + Buffer<u8> sentdata = pkt.oldForgePacket(); + + infostream<<"** running client.Send()"<<std::endl; + client.Send(PEER_ID_SERVER, 0, &pkt, true); + + sleep_ms(50); + + NetworkPacket recvpacket; + infostream << "** running server.Receive()" << std::endl; + server.Receive(&recvpacket); + infostream << "** Server received: peer_id=" << pkt.getPeerId() + << ", size=" << pkt.getSize() + << ", data=" << (const char*)pkt.getU8Ptr(0) + << std::endl; + + Buffer<u8> recvdata = pkt.oldForgePacket(); + + UASSERT(memcmp(*sentdata, *recvdata, recvdata.getSize()) == 0); + } + + u16 peer_id_client = 2; + /* + Send a large packet + */ + { + const int datasize = 30000; + NetworkPacket pkt(0, datasize); + for (u16 i=0; i<datasize; i++) { + pkt << (u8) i/4; + } + + infostream << "Sending data (size=" << datasize << "):"; + for (int i = 0; i < datasize && i < 20; i++) { + if (i % 2 == 0) + infostream << " "; + char buf[10]; + snprintf(buf, 10, "%.2X", + ((int)((const char *)pkt.getU8Ptr(0))[i]) & 0xff); + infostream<<buf; + } + if (datasize > 20) + infostream << "..."; + infostream << std::endl; + + Buffer<u8> sentdata = pkt.oldForgePacket(); + + server.Send(peer_id_client, 0, &pkt, true); + + //sleep_ms(3000); + + Buffer<u8> recvdata; + infostream << "** running client.Receive()" << std::endl; + u16 peer_id = 132; + u16 size = 0; + bool received = false; + u32 timems0 = porting::getTimeMs(); + for (;;) { + if (porting::getTimeMs() - timems0 > 5000 || received) + break; + try { + NetworkPacket pkt; + client.Receive(&pkt); + size = pkt.getSize(); + peer_id = pkt.getPeerId(); + recvdata = pkt.oldForgePacket(); + received = true; + } catch (con::NoIncomingDataException &e) { + } + sleep_ms(10); + } + UASSERT(received); + infostream << "** Client received: peer_id=" << peer_id + << ", size=" << size << std::endl; + + infostream << "Received data (size=" << size << "): "; + for (int i = 0; i < size && i < 20; i++) { + if (i % 2 == 0) + infostream << " "; + char buf[10]; + snprintf(buf, 10, "%.2X", ((int)(recvdata[i])) & 0xff); + infostream << buf; + } + if (size > 20) + infostream << "..."; + infostream << std::endl; + + UASSERT(memcmp(*sentdata, *recvdata, recvdata.getSize()) == 0); + UASSERT(peer_id == PEER_ID_SERVER); + } + + // Check peer handlers + UASSERT(hand_client.count == 1); + UASSERT(hand_client.last_id == 1); + UASSERT(hand_server.count == 1); + UASSERT(hand_server.last_id == 2); +} diff --git a/src/unittest/test_filepath.cpp b/src/unittest/test_filepath.cpp new file mode 100644 index 000000000..6ea7ac076 --- /dev/null +++ b/src/unittest/test_filepath.cpp @@ -0,0 +1,261 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include <sstream> + +#include "log.h" +#include "serialization.h" +#include "nodedef.h" +#include "noise.h" + +class TestFilePath : public TestBase { +public: + TestFilePath() { TestManager::registerTestModule(this); } + const char *getName() { return "TestFilePath"; } + + void runTests(IGameDef *gamedef); + + void testIsDirDelimiter(); + void testPathStartsWith(); + void testRemoveLastPathComponent(); + void testRemoveLastPathComponentWithTrailingDelimiter(); + void testRemoveRelativePathComponent(); +}; + +static TestFilePath g_test_instance; + +void TestFilePath::runTests(IGameDef *gamedef) +{ + TEST(testIsDirDelimiter); + TEST(testPathStartsWith); + TEST(testRemoveLastPathComponent); + TEST(testRemoveLastPathComponentWithTrailingDelimiter); + TEST(testRemoveRelativePathComponent); +} + +//////////////////////////////////////////////////////////////////////////////// + +// adjusts a POSIX path to system-specific conventions +// -> changes '/' to DIR_DELIM +// -> absolute paths start with "C:\\" on windows +std::string p(std::string path) +{ + for (size_t i = 0; i < path.size(); ++i) { + if (path[i] == '/') { + path.replace(i, 1, DIR_DELIM); + i += std::string(DIR_DELIM).size() - 1; // generally a no-op + } + } + + #ifdef _WIN32 + if (path[0] == '\\') + path = "C:" + path; + #endif + + return path; +} + + +void TestFilePath::testIsDirDelimiter() +{ + UASSERT(fs::IsDirDelimiter('/') == true); + UASSERT(fs::IsDirDelimiter('A') == false); + UASSERT(fs::IsDirDelimiter(0) == false); +#ifdef _WIN32 + UASSERT(fs::IsDirDelimiter('\\') == true); +#else + UASSERT(fs::IsDirDelimiter('\\') == false); +#endif +} + + +void TestFilePath::testPathStartsWith() +{ + const int numpaths = 12; + std::string paths[numpaths] = { + "", + p("/"), + p("/home/user/minetest"), + p("/home/user/minetest/bin"), + p("/home/user/.minetest"), + p("/tmp/dir/file"), + p("/tmp/file/"), + p("/tmP/file"), + p("/tmp"), + p("/tmp/dir"), + p("/home/user2/minetest/worlds"), + p("/home/user2/minetest/world"), + }; + /* + expected fs::PathStartsWith results + 0 = returns false + 1 = returns true + 2 = returns false on windows, true elsewhere + 3 = returns true on windows, false elsewhere + 4 = returns true if and only if + FILESYS_CASE_INSENSITIVE is true + */ + int expected_results[numpaths][numpaths] = { + {1,2,0,0,0,0,0,0,0,0,0,0}, + {1,1,0,0,0,0,0,0,0,0,0,0}, + {1,1,1,0,0,0,0,0,0,0,0,0}, + {1,1,1,1,0,0,0,0,0,0,0,0}, + {1,1,0,0,1,0,0,0,0,0,0,0}, + {1,1,0,0,0,1,0,0,1,1,0,0}, + {1,1,0,0,0,0,1,4,1,0,0,0}, + {1,1,0,0,0,0,4,1,4,0,0,0}, + {1,1,0,0,0,0,0,0,1,0,0,0}, + {1,1,0,0,0,0,0,0,1,1,0,0}, + {1,1,0,0,0,0,0,0,0,0,1,0}, + {1,1,0,0,0,0,0,0,0,0,0,1}, + }; + + for (int i = 0; i < numpaths; i++) + for (int j = 0; j < numpaths; j++){ + /*verbosestream<<"testing fs::PathStartsWith(\"" + <<paths[i]<<"\", \"" + <<paths[j]<<"\")"<<std::endl;*/ + bool starts = fs::PathStartsWith(paths[i], paths[j]); + int expected = expected_results[i][j]; + if(expected == 0){ + UASSERT(starts == false); + } + else if(expected == 1){ + UASSERT(starts == true); + } + #ifdef _WIN32 + else if(expected == 2){ + UASSERT(starts == false); + } + else if(expected == 3){ + UASSERT(starts == true); + } + #else + else if(expected == 2){ + UASSERT(starts == true); + } + else if(expected == 3){ + UASSERT(starts == false); + } + #endif + else if(expected == 4){ + UASSERT(starts == (bool)FILESYS_CASE_INSENSITIVE); + } + } +} + + +void TestFilePath::testRemoveLastPathComponent() +{ + std::string path, result, removed; + + UASSERT(fs::RemoveLastPathComponent("") == ""); + path = p("/home/user/minetest/bin/..//worlds/world1"); + result = fs::RemoveLastPathComponent(path, &removed, 0); + UASSERT(result == path); + UASSERT(removed == ""); + result = fs::RemoveLastPathComponent(path, &removed, 1); + UASSERT(result == p("/home/user/minetest/bin/..//worlds")); + UASSERT(removed == p("world1")); + result = fs::RemoveLastPathComponent(path, &removed, 2); + UASSERT(result == p("/home/user/minetest/bin/..")); + UASSERT(removed == p("worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 3); + UASSERT(result == p("/home/user/minetest/bin")); + UASSERT(removed == p("../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 4); + UASSERT(result == p("/home/user/minetest")); + UASSERT(removed == p("bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 5); + UASSERT(result == p("/home/user")); + UASSERT(removed == p("minetest/bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 6); + UASSERT(result == p("/home")); + UASSERT(removed == p("user/minetest/bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 7); +#ifdef _WIN32 + UASSERT(result == "C:"); +#else + UASSERT(result == ""); +#endif + UASSERT(removed == p("home/user/minetest/bin/../worlds/world1")); +} + + +void TestFilePath::testRemoveLastPathComponentWithTrailingDelimiter() +{ + std::string path, result, removed; + + path = p("/home/user/minetest/bin/..//worlds/world1/"); + result = fs::RemoveLastPathComponent(path, &removed, 0); + UASSERT(result == path); + UASSERT(removed == ""); + result = fs::RemoveLastPathComponent(path, &removed, 1); + UASSERT(result == p("/home/user/minetest/bin/..//worlds")); + UASSERT(removed == p("world1")); + result = fs::RemoveLastPathComponent(path, &removed, 2); + UASSERT(result == p("/home/user/minetest/bin/..")); + UASSERT(removed == p("worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 3); + UASSERT(result == p("/home/user/minetest/bin")); + UASSERT(removed == p("../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 4); + UASSERT(result == p("/home/user/minetest")); + UASSERT(removed == p("bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 5); + UASSERT(result == p("/home/user")); + UASSERT(removed == p("minetest/bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 6); + UASSERT(result == p("/home")); + UASSERT(removed == p("user/minetest/bin/../worlds/world1")); + result = fs::RemoveLastPathComponent(path, &removed, 7); +#ifdef _WIN32 + UASSERT(result == "C:"); +#else + UASSERT(result == ""); +#endif + UASSERT(removed == p("home/user/minetest/bin/../worlds/world1")); +} + + +void TestFilePath::testRemoveRelativePathComponent() +{ + std::string path, result, removed; + + path = p("/home/user/minetest/bin"); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == path); + path = p("/home/user/minetest/bin/../worlds/world1"); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == p("/home/user/minetest/worlds/world1")); + path = p("/home/user/minetest/bin/../worlds/world1/"); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == p("/home/user/minetest/worlds/world1")); + path = p("."); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == ""); + path = p("./subdir/../.."); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == ""); + path = p("/a/b/c/.././../d/../e/f/g/../h/i/j/../../../.."); + result = fs::RemoveRelativePathComponents(path); + UASSERT(result == p("/a/e")); +} diff --git a/src/unittest/test_inventory.cpp b/src/unittest/test_inventory.cpp new file mode 100644 index 000000000..1a783afae --- /dev/null +++ b/src/unittest/test_inventory.cpp @@ -0,0 +1,143 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include <sstream> + +#include "gamedef.h" +#include "inventory.h" + +class TestInventory : public TestBase { +public: + TestInventory() { TestManager::registerTestModule(this); } + const char *getName() { return "TestInventory"; } + + void runTests(IGameDef *gamedef); + + void testSerializeDeserialize(IItemDefManager *idef); + + static const char *serialized_inventory; + static const char *serialized_inventory_2; +}; + +static TestInventory g_test_instance; + +void TestInventory::runTests(IGameDef *gamedef) +{ + TEST(testSerializeDeserialize, gamedef->getItemDefManager()); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestInventory::testSerializeDeserialize(IItemDefManager *idef) +{ + Inventory inv(idef); + std::istringstream is(serialized_inventory, std::ios::binary); + + inv.deSerialize(is); + UASSERT(inv.getList("0")); + UASSERT(!inv.getList("main")); + + inv.getList("0")->setName("main"); + UASSERT(!inv.getList("0")); + UASSERT(inv.getList("main")); + UASSERTEQ(u32, inv.getList("main")->getWidth(), 3); + + 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); +} + +const char *TestInventory::serialized_inventory = + "List 0 32\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" + "EndInventory\n"; + +const char *TestInventory::serialized_inventory_2 = + "List main 32\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" + "EndInventory\n"; diff --git a/src/unittest/test_mapnode.cpp b/src/unittest/test_mapnode.cpp new file mode 100644 index 000000000..9ecc2f82d --- /dev/null +++ b/src/unittest/test_mapnode.cpp @@ -0,0 +1,56 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "gamedef.h" +#include "nodedef.h" +#include "content_mapnode.h" + +class TestMapNode : public TestBase { +public: + TestMapNode() { TestManager::registerTestModule(this); } + const char *getName() { return "TestMapNode"; } + + void runTests(IGameDef *gamedef); + + void testNodeProperties(INodeDefManager *nodedef); +}; + +static TestMapNode g_test_instance; + +void TestMapNode::runTests(IGameDef *gamedef) +{ + TEST(testNodeProperties, gamedef->getNodeDefManager()); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestMapNode::testNodeProperties(INodeDefManager *nodedef) +{ + MapNode n(CONTENT_AIR); + + UASSERT(n.getContent() == CONTENT_AIR); + UASSERT(n.getLight(LIGHTBANK_DAY, nodedef) == 0); + UASSERT(n.getLight(LIGHTBANK_NIGHT, nodedef) == 0); + + // Transparency + n.setContent(CONTENT_AIR); + UASSERT(nodedef->get(n).light_propagates == true); +} diff --git a/src/unittest/test_nodedef.cpp b/src/unittest/test_nodedef.cpp new file mode 100644 index 000000000..85093f52f --- /dev/null +++ b/src/unittest/test_nodedef.cpp @@ -0,0 +1,66 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include <sstream> + +#include "gamedef.h" +#include "nodedef.h" +#include "network/networkprotocol.h" + +class TestNodeDef : public TestBase { +public: + TestNodeDef() { TestManager::registerTestModule(this); } + const char *getName() { return "TestNodeDef"; } + + void runTests(IGameDef *gamedef); + + void testContentFeaturesSerialization(); +}; + +static TestNodeDef g_test_instance; + +void TestNodeDef::runTests(IGameDef *gamedef) +{ + TEST(testContentFeaturesSerialization); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestNodeDef::testContentFeaturesSerialization() +{ + ContentFeatures f; + + f.name = "default:stone"; + for (int i = 0; i < 6; i++) + f.tiledef[i].name = "default_stone.png"; + f.is_ground_content = true; + + std::ostringstream os(std::ios::binary); + f.serialize(os, LATEST_PROTOCOL_VERSION); + //verbosestream<<"Test ContentFeatures size: "<<os.str().size()<<std::endl; + + std::istringstream is(os.str(), std::ios::binary); + ContentFeatures f2; + f2.deSerialize(is); + + UASSERT(f.walkable == f2.walkable); + UASSERT(f.node_box.type == f2.node_box.type); +} diff --git a/src/unittest/test_noderesolver.cpp b/src/unittest/test_noderesolver.cpp new file mode 100644 index 000000000..55acece6a --- /dev/null +++ b/src/unittest/test_noderesolver.cpp @@ -0,0 +1,208 @@ +/* +Minetest +Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +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 "util/numeric.h" +#include "exceptions.h" +#include "gamedef.h" +#include "nodedef.h" + + +class TestNodeResolver : public TestBase { +public: + TestNodeResolver() { TestManager::registerTestModule(this); } + const char *getName() { return "TestNodeResolver"; } + + void runTests(IGameDef *gamedef); + + void testNodeResolving(IWritableNodeDefManager *ndef); + void testPendingResolveCancellation(IWritableNodeDefManager *ndef); + void testDirectResolveMethod(IWritableNodeDefManager *ndef); + void testNoneResolveMethod(IWritableNodeDefManager *ndef); +}; + +static TestNodeResolver g_test_instance; + +void TestNodeResolver::runTests(IGameDef *gamedef) +{ + IWritableNodeDefManager *ndef = + (IWritableNodeDefManager *)gamedef->getNodeDefManager(); + + ndef->resetNodeResolveState(); + TEST(testNodeResolving, ndef); + + ndef->resetNodeResolveState(); + TEST(testPendingResolveCancellation, ndef); +} + +class Foobar : public NodeResolver { +public: + void resolveNodeNames(); + + content_t test_nr_node1; + content_t test_nr_node2; + content_t test_nr_node3; + content_t test_nr_node4; + content_t test_nr_node5; + std::vector<content_t> test_nr_list; + std::vector<content_t> test_nr_list_group; + std::vector<content_t> test_nr_list_required; + std::vector<content_t> test_nr_list_empty; +}; + +class Foobaz : public NodeResolver { +public: + void resolveNodeNames(); + + content_t test_content1; + content_t test_content2; +}; + +//////////////////////////////////////////////////////////////////////////////// + +void Foobar::resolveNodeNames() +{ + UASSERT(getIdFromNrBacklog(&test_nr_node1, "", CONTENT_IGNORE) == true); + UASSERT(getIdsFromNrBacklog(&test_nr_list) == true); + UASSERT(getIdsFromNrBacklog(&test_nr_list_group) == true); + UASSERT(getIdsFromNrBacklog(&test_nr_list_required, + true, CONTENT_AIR) == false); + UASSERT(getIdsFromNrBacklog(&test_nr_list_empty) == true); + + UASSERT(getIdFromNrBacklog(&test_nr_node2, "", CONTENT_IGNORE) == true); + UASSERT(getIdFromNrBacklog(&test_nr_node3, + "default:brick", CONTENT_IGNORE) == true); + UASSERT(getIdFromNrBacklog(&test_nr_node4, + "default:gobbledygook", CONTENT_AIR) == false); + UASSERT(getIdFromNrBacklog(&test_nr_node5, "", CONTENT_IGNORE) == false); +} + + +void Foobaz::resolveNodeNames() +{ + UASSERT(getIdFromNrBacklog(&test_content1, "", CONTENT_IGNORE) == true); + UASSERT(getIdFromNrBacklog(&test_content2, "", CONTENT_IGNORE) == false); +} + + +void TestNodeResolver::testNodeResolving(IWritableNodeDefManager *ndef) +{ + Foobar foobar; + size_t i; + + foobar.m_nodenames.push_back("default:torch"); + + foobar.m_nodenames.push_back("default:dirt_with_grass"); + foobar.m_nodenames.push_back("default:water"); + foobar.m_nodenames.push_back("default:abloobloobloo"); + foobar.m_nodenames.push_back("default:stone"); + foobar.m_nodenames.push_back("default:shmegoldorf"); + foobar.m_nnlistsizes.push_back(5); + + foobar.m_nodenames.push_back("group:liquids"); + foobar.m_nnlistsizes.push_back(1); + + foobar.m_nodenames.push_back("default:warf"); + foobar.m_nodenames.push_back("default:stone"); + foobar.m_nodenames.push_back("default:bloop"); + foobar.m_nnlistsizes.push_back(3); + + foobar.m_nnlistsizes.push_back(0); + + foobar.m_nodenames.push_back("default:brick"); + foobar.m_nodenames.push_back("default:desert_stone"); + foobar.m_nodenames.push_back("default:shnitzle"); + + ndef->pendNodeResolve(&foobar); + UASSERT(foobar.m_ndef == ndef); + + ndef->setNodeRegistrationStatus(true); + ndef->runNodeResolveCallbacks(); + + // Check that we read single nodes successfully + UASSERTEQ(content_t, foobar.test_nr_node1, t_CONTENT_TORCH); + UASSERTEQ(content_t, foobar.test_nr_node2, t_CONTENT_BRICK); + UASSERTEQ(content_t, foobar.test_nr_node3, t_CONTENT_BRICK); + UASSERTEQ(content_t, foobar.test_nr_node4, CONTENT_AIR); + UASSERTEQ(content_t, foobar.test_nr_node5, CONTENT_IGNORE); + + // Check that we read all the regular list items + static const content_t expected_test_nr_list[] = { + t_CONTENT_GRASS, + t_CONTENT_WATER, + t_CONTENT_STONE, + }; + UASSERTEQ(size_t, foobar.test_nr_list.size(), 3); + for (i = 0; i != foobar.test_nr_list.size(); i++) + UASSERTEQ(content_t, foobar.test_nr_list[i], expected_test_nr_list[i]); + + // Check that we read all the list items that were from a group entry + static const content_t expected_test_nr_list_group[] = { + t_CONTENT_WATER, + t_CONTENT_LAVA, + }; + UASSERTEQ(size_t, foobar.test_nr_list_group.size(), 2); + for (i = 0; i != foobar.test_nr_list_group.size(); i++) { + UASSERT(CONTAINS(foobar.test_nr_list_group, + expected_test_nr_list_group[i])); + } + + // Check that we read all the items we're able to in a required list + static const content_t expected_test_nr_list_required[] = { + CONTENT_AIR, + t_CONTENT_STONE, + CONTENT_AIR, + }; + UASSERTEQ(size_t, foobar.test_nr_list_required.size(), 3); + for (i = 0; i != foobar.test_nr_list_required.size(); i++) + UASSERTEQ(content_t, foobar.test_nr_list_required[i], + expected_test_nr_list_required[i]); + + // Check that the edge case of 0 is successful + UASSERTEQ(size_t, foobar.test_nr_list_empty.size(), 0); +} + + +void TestNodeResolver::testPendingResolveCancellation(IWritableNodeDefManager *ndef) +{ + Foobaz foobaz1; + foobaz1.test_content1 = 1234; + foobaz1.test_content2 = 5678; + foobaz1.m_nodenames.push_back("default:dirt_with_grass"); + foobaz1.m_nodenames.push_back("default:abloobloobloo"); + ndef->pendNodeResolve(&foobaz1); + + Foobaz foobaz2; + foobaz2.test_content1 = 1234; + foobaz2.test_content2 = 5678; + foobaz2.m_nodenames.push_back("default:dirt_with_grass"); + foobaz2.m_nodenames.push_back("default:abloobloobloo"); + ndef->pendNodeResolve(&foobaz2); + + ndef->cancelNodeResolveCallback(&foobaz1); + + ndef->setNodeRegistrationStatus(true); + ndef->runNodeResolveCallbacks(); + + UASSERT(foobaz1.test_content1 == 1234); + UASSERT(foobaz1.test_content2 == 5678); + UASSERT(foobaz2.test_content1 == t_CONTENT_GRASS); + UASSERT(foobaz2.test_content2 == CONTENT_IGNORE); +} diff --git a/src/unittest/test_noise.cpp b/src/unittest/test_noise.cpp new file mode 100644 index 000000000..d1821c950 --- /dev/null +++ b/src/unittest/test_noise.cpp @@ -0,0 +1,285 @@ +/* +Minetest +Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +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 "noise.h" + +class TestNoise : public TestBase { +public: + TestNoise() { TestManager::registerTestModule(this); } + const char *getName() { return "TestNoise"; } + + void runTests(IGameDef *gamedef); + + void testNoise2dPoint(); + void testNoise2dBulk(); + void testNoise3dPoint(); + void testNoise3dBulk(); + void testNoiseInvalidParams(); + + static const float expected_2d_results[10 * 10]; + static const float expected_3d_results[10 * 10 * 10]; +}; + +static TestNoise g_test_instance; + +void TestNoise::runTests(IGameDef *gamedef) +{ + TEST(testNoise2dPoint); + TEST(testNoise2dBulk); + TEST(testNoise3dPoint); + TEST(testNoise3dBulk); + TEST(testNoiseInvalidParams); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestNoise::testNoise2dPoint() +{ + NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0); + + u32 i = 0; + for (u32 y = 0; y != 10; y++) + for (u32 x = 0; x != 10; x++, i++) { + float actual = NoisePerlin2D(&np_normal, x, y, 1337); + float expected = expected_2d_results[i]; + UASSERT(fabs(actual - expected) <= 0.00001); + } +} + +void TestNoise::testNoise2dBulk() +{ + NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0); + Noise noise_normal_2d(&np_normal, 1337, 10, 10); + float *noisevals = noise_normal_2d.perlinMap2D(0, 0, NULL); + + for (u32 i = 0; i != 10 * 10; i++) { + float actual = noisevals[i]; + float expected = expected_2d_results[i]; + UASSERT(fabs(actual - expected) <= 0.00001); + } +} + +void TestNoise::testNoise3dPoint() +{ + NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0); + + u32 i = 0; + for (u32 z = 0; z != 10; z++) + for (u32 y = 0; y != 10; y++) + for (u32 x = 0; x != 10; x++, i++) { + float actual = NoisePerlin3D(&np_normal, x, y, z, 1337); + float expected = expected_3d_results[i]; + UASSERT(fabs(actual - expected) <= 0.00001); + } +} + +void TestNoise::testNoise3dBulk() +{ + NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0); + Noise noise_normal_3d(&np_normal, 1337, 10, 10, 10); + float *noisevals = noise_normal_3d.perlinMap3D(0, 0, 0, NULL); + + for (u32 i = 0; i != 10 * 10 * 10; i++) { + float actual = noisevals[i]; + float expected = expected_3d_results[i]; + UASSERT(fabs(actual - expected) <= 0.00001); + } +} + +void TestNoise::testNoiseInvalidParams() +{ + bool exception_thrown = false; + + try { + NoiseParams np_highmem(4, 70, v3f(1, 1, 1), 5, 60, 0.7, 10.0); + Noise noise_highmem_3d(&np_highmem, 1337, 200, 200, 200); + noise_highmem_3d.perlinMap3D(0, 0, 0, NULL); + } catch (InvalidNoiseParamsException) { + exception_thrown = true; + } + + UASSERT(exception_thrown); +} + +const float TestNoise::expected_2d_results[10 * 10] = { + 19.11726, 18.49626, 16.48476, 15.02135, 14.75713, 16.26008, 17.54822, + 18.06860, 18.57016, 18.48407, 18.49649, 17.89160, 15.94162, 14.54901, + 14.31298, 15.72643, 16.94669, 17.55494, 18.58796, 18.87925, 16.08101, + 15.53764, 13.83844, 12.77139, 12.73648, 13.95632, 14.97904, 15.81829, + 18.37694, 19.73759, 13.19182, 12.71924, 11.34560, 10.78025, 11.18980, + 12.52303, 13.45012, 14.30001, 17.43298, 19.15244, 10.93217, 10.48625, + 9.30923, 9.18632, 10.16251, 12.11264, 13.19697, 13.80801, 16.39567, + 17.66203, 10.40222, 9.86070, 8.47223, 8.45471, 10.04780, 13.54730, + 15.33709, 15.48503, 16.46177, 16.52508, 10.80333, 10.19045, 8.59420, + 8.47646, 10.22676, 14.43173, 16.48353, 16.24859, 16.20863, 15.52847, + 11.01179, 10.45209, 8.98678, 8.83986, 10.43004, 14.46054, 16.29387, + 15.73521, 15.01744, 13.85542, 10.55201, 10.33375, 9.85102, 10.07821, + 11.58235, 15.62046, 17.35505, 16.13181, 12.66011, 9.51853, 11.50994, + 11.54074, 11.77989, 12.29790, 13.76139, 17.81982, 19.49008, 17.79470, + 12.34344, 7.78363, +}; + +const float TestNoise::expected_3d_results[10 * 10 * 10] = { + 19.11726, 18.01059, 16.90392, 15.79725, 16.37154, 17.18597, 18.00040, + 18.33467, 18.50889, 18.68311, 17.85386, 16.90585, 15.95785, 15.00985, + 15.61132, 16.43415, 17.25697, 17.95415, 18.60942, 19.26471, 16.59046, + 15.80112, 15.01178, 14.22244, 14.85110, 15.68232, 16.51355, 17.57361, + 18.70996, 19.84631, 15.32705, 14.69638, 14.06571, 13.43504, 14.09087, + 14.93050, 15.77012, 17.19309, 18.81050, 20.42790, 15.06729, 14.45855, + 13.84981, 13.24107, 14.39364, 15.79782, 17.20201, 18.42640, 19.59085, + 20.75530, 14.95090, 14.34456, 13.73821, 13.13187, 14.84825, 16.89645, + 18.94465, 19.89025, 20.46832, 21.04639, 14.83452, 14.23057, 13.62662, + 13.02267, 15.30287, 17.99508, 20.68730, 21.35411, 21.34580, 21.33748, + 15.39817, 15.03590, 14.67364, 14.31137, 16.78242, 19.65824, 22.53405, + 22.54626, 21.60395, 20.66164, 16.18850, 16.14768, 16.10686, 16.06603, + 18.60362, 21.50956, 24.41549, 23.64784, 21.65566, 19.66349, 16.97884, + 17.25946, 17.54008, 17.82069, 20.42482, 23.36088, 26.29694, 24.74942, + 21.70738, 18.66534, 18.78506, 17.51834, 16.25162, 14.98489, 15.14217, + 15.50287, 15.86357, 16.40597, 17.00895, 17.61193, 18.20160, 16.98795, + 15.77430, 14.56065, 14.85059, 15.35533, 15.86007, 16.63399, 17.49763, + 18.36128, 17.61814, 16.45757, 15.29699, 14.13641, 14.55902, 15.20779, + 15.85657, 16.86200, 17.98632, 19.11064, 17.03468, 15.92718, 14.81968, + 13.71218, 14.26744, 15.06026, 15.85306, 17.09001, 18.47501, 19.86000, + 16.67870, 15.86256, 15.04641, 14.23026, 15.31397, 16.66909, 18.02420, + 18.89042, 19.59369, 20.29695, 16.35522, 15.86447, 15.37372, 14.88297, + 16.55165, 18.52883, 20.50600, 20.91547, 20.80237, 20.68927, 16.03174, + 15.86639, 15.70103, 15.53568, 17.78933, 20.38857, 22.98780, 22.94051, + 22.01105, 21.08159, 16.42434, 16.61407, 16.80381, 16.99355, 19.16133, + 21.61169, 24.06204, 23.65252, 22.28970, 20.92689, 17.05562, 17.61035, + 18.16508, 18.71981, 20.57809, 22.62260, 24.66711, 23.92686, 22.25835, + 20.58984, 17.68691, 18.60663, 19.52635, 20.44607, 21.99486, 23.63352, + 25.27217, 24.20119, 22.22699, 20.25279, 18.45285, 17.02608, 15.59931, + 14.17254, 13.91279, 13.81976, 13.72674, 14.47727, 15.50900, 16.54073, + 18.54934, 17.07005, 15.59076, 14.11146, 14.08987, 14.27651, 14.46316, + 15.31383, 16.38584, 17.45785, 18.64582, 17.11401, 15.58220, 14.05039, + 14.26694, 14.73326, 15.19958, 16.15038, 17.26268, 18.37498, 18.74231, + 17.15798, 15.57364, 13.98932, 14.44402, 15.19001, 15.93600, 16.98694, + 18.13952, 19.29210, 18.29012, 17.26656, 16.24301, 15.21946, 16.23430, + 17.54035, 18.84639, 19.35445, 19.59653, 19.83860, 17.75954, 17.38438, + 17.00923, 16.63407, 18.25505, 20.16120, 22.06734, 21.94068, 21.13642, + 20.33215, 17.22896, 17.50220, 17.77544, 18.04868, 20.27580, 22.78205, + 25.28829, 24.52691, 22.67631, 20.82571, 17.45050, 18.19224, 18.93398, + 19.67573, 21.54024, 23.56514, 25.59004, 24.75878, 22.97546, 21.19213, + 17.92274, 19.07302, 20.22330, 21.37358, 22.55256, 23.73565, 24.91873, + 24.20587, 22.86103, 21.51619, 18.39499, 19.95381, 21.51263, 23.07145, + 23.56490, 23.90615, 24.24741, 23.65296, 22.74660, 21.84024, 18.12065, + 16.53382, 14.94700, 13.36018, 12.68341, 12.13666, 11.58990, 12.54858, + 14.00906, 15.46955, 18.89708, 17.15214, 15.40721, 13.66227, 13.32914, + 13.19769, 13.06625, 13.99367, 15.27405, 16.55443, 19.67351, 17.77046, + 15.86741, 13.96436, 13.97486, 14.25873, 14.54260, 15.43877, 16.53904, + 17.63931, 20.44994, 18.38877, 16.32761, 14.26645, 14.62059, 15.31977, + 16.01895, 16.88387, 17.80403, 18.72419, 19.90153, 18.67057, 17.43962, + 16.20866, 17.15464, 18.41161, 19.66858, 19.81848, 19.59936, 19.38024, + 19.16386, 18.90429, 18.64473, 18.38517, 19.95845, 21.79357, 23.62868, + 22.96589, 21.47046, 19.97503, 18.42618, 19.13802, 19.84985, 20.56168, + 22.76226, 25.17553, 27.58879, 26.11330, 23.34156, 20.56982, 18.47667, + 19.77041, 21.06416, 22.35790, 23.91914, 25.51859, 27.11804, 25.86504, + 23.66121, 21.45738, 18.78986, 20.53570, 22.28153, 24.02736, 24.52704, + 24.84869, 25.17035, 24.48488, 23.46371, 22.44254, 19.10306, 21.30098, + 23.49890, 25.69682, 25.13494, 24.17879, 23.22265, 23.10473, 23.26621, + 23.42769, 17.93453, 16.72707, 15.51962, 14.31216, 12.96039, 11.58800, + 10.21561, 11.29675, 13.19573, 15.09471, 18.05853, 16.85308, 15.64762, + 14.44216, 13.72634, 13.08047, 12.43459, 13.48912, 15.11045, 16.73179, + 18.18253, 16.97908, 15.77562, 14.57217, 14.49229, 14.57293, 14.65357, + 15.68150, 17.02518, 18.36887, 18.30654, 17.10508, 15.90363, 14.70217, + 15.25825, 16.06540, 16.87255, 17.87387, 18.93991, 20.00595, 17.54117, + 17.32369, 17.10622, 16.88875, 18.07494, 19.46166, 20.84837, 21.12988, + 21.04298, 20.95609, 16.64874, 17.55554, 18.46234, 19.36913, 21.18461, + 23.12989, 25.07517, 24.53784, 23.17297, 21.80810, 15.75632, 17.78738, + 19.81845, 21.84951, 24.29427, 26.79812, 29.30198, 27.94580, 25.30295, + 22.66010, 15.98046, 18.43027, 20.88008, 23.32989, 25.21976, 27.02964, + 28.83951, 27.75863, 25.71416, 23.66970, 16.57679, 19.21017, 21.84355, + 24.47693, 25.41719, 26.11557, 26.81396, 26.37308, 25.55245, 24.73182, + 17.17313, 19.99008, 22.80702, 25.62397, 25.61462, 25.20151, 24.78840, + 24.98753, 25.39074, 25.79395, 17.76927, 17.01824, 16.26722, 15.51620, + 13.45256, 11.20141, 8.95025, 10.14162, 12.48049, 14.81936, 17.05051, + 16.49955, 15.94860, 15.39764, 14.28896, 13.10061, 11.91225, 13.10109, + 15.08232, 17.06355, 16.33175, 15.98086, 15.62998, 15.27909, 15.12537, + 14.99981, 14.87425, 16.06056, 17.68415, 19.30775, 15.61299, 15.46217, + 15.31136, 15.16054, 15.96177, 16.89901, 17.83625, 19.02003, 20.28599, + 21.55194, 14.61341, 15.58383, 16.55426, 17.52469, 18.99524, 20.53725, + 22.07925, 22.56233, 22.69243, 22.82254, 13.57371, 15.79697, 18.02024, + 20.24351, 22.34258, 24.42392, 26.50526, 26.18790, 25.07097, 23.95404, + 12.53401, 16.01011, 19.48622, 22.96232, 25.68993, 28.31060, 30.93126, + 29.81347, 27.44951, 25.08555, 12.98106, 16.67323, 20.36540, 24.05756, + 26.36633, 28.47748, 30.58862, 29.76471, 27.96244, 26.16016, 13.92370, + 17.48634, 21.04897, 24.61161, 26.15244, 27.40443, 28.65643, 28.49117, + 27.85349, 27.21581, 14.86633, 18.29944, 21.73255, 25.16566, 25.93854, + 26.33138, 26.72423, 27.21763, 27.74455, 28.27147, 17.60401, 17.30942, + 17.01482, 16.72023, 13.94473, 10.81481, 7.68490, 8.98648, 11.76524, + 14.54400, 16.04249, 16.14603, 16.24958, 16.35312, 14.85158, 13.12075, + 11.38991, 12.71305, 15.05418, 17.39531, 14.48097, 14.98265, 15.48433, + 15.98602, 15.75844, 15.42668, 15.09493, 16.43962, 18.34312, 20.24663, + 12.91945, 13.81927, 14.71909, 15.61891, 16.66530, 17.73262, 18.79995, + 20.16619, 21.63206, 23.09794, 11.68565, 13.84398, 16.00230, 18.16062, + 19.91554, 21.61284, 23.31013, 23.99478, 24.34188, 24.68898, 10.49868, + 14.03841, 17.57814, 21.11788, 23.50056, 25.71795, 27.93534, 27.83796, + 26.96897, 26.09999, 9.31170, 14.23284, 19.15399, 24.07513, 27.08558, + 29.82307, 32.56055, 31.68113, 29.59606, 27.51099, 9.98166, 14.91619, + 19.85071, 24.78524, 27.51291, 29.92532, 32.33773, 31.77077, 30.21070, + 28.65063, 11.27060, 15.76250, 20.25440, 24.74629, 26.88768, 28.69329, + 30.49889, 30.60925, 30.15453, 29.69981, 12.55955, 16.60881, 20.65808, + 24.70735, 26.26245, 27.46126, 28.66005, 29.44773, 30.09835, 30.74898, + 15.20134, 15.53016, 15.85898, 16.18780, 13.53087, 10.44740, 7.36393, + 8.95806, 12.11139, 15.26472, 13.87432, 14.52378, 15.17325, 15.82272, + 14.49093, 12.87611, 11.26130, 12.73342, 15.23453, 17.73563, 12.54730, + 13.51741, 14.48752, 15.45763, 15.45100, 15.30483, 15.15867, 16.50878, + 18.35766, 20.20654, 11.22027, 12.51103, 13.80179, 15.09254, 16.41106, + 17.73355, 19.05603, 20.28415, 21.48080, 22.67745, 10.27070, 12.53633, + 14.80195, 17.06758, 19.04654, 20.98454, 22.92254, 23.63840, 23.94687, + 24.25534, 9.37505, 12.70901, 16.04297, 19.37693, 21.92136, 24.35300, + 26.78465, 26.93249, 26.31907, 25.70565, 8.47939, 12.88168, 17.28398, + 21.68627, 24.79618, 27.72146, 30.64674, 30.22658, 28.69127, 27.15597, + 9.77979, 13.97583, 18.17186, 22.36790, 25.18828, 27.81215, 30.43601, + 30.34293, 29.34420, 28.34548, 11.81220, 15.37712, 18.94204, 22.50695, + 24.75282, 26.81024, 28.86766, 29.40003, 29.42404, 29.44806, 13.84461, + 16.77841, 19.71221, 22.64601, 24.31735, 25.80833, 27.29932, 28.45713, + 29.50388, 30.55064, 12.05287, 13.06077, 14.06866, 15.07656, 12.81500, + 10.08638, 7.35776, 9.30520, 12.81134, 16.31747, 11.31943, 12.47863, + 13.63782, 14.79702, 13.82253, 12.54323, 11.26392, 12.88993, 15.48436, + 18.07880, 10.58600, 11.89649, 13.20698, 14.51747, 14.83005, 15.00007, + 15.17009, 16.47465, 18.15739, 19.84013, 9.85256, 11.31435, 12.77614, + 14.23793, 15.83757, 17.45691, 19.07625, 20.05937, 20.83042, 21.60147, + 9.36002, 11.37275, 13.38548, 15.39822, 17.58109, 19.78828, 21.99546, + 22.68573, 22.87036, 23.05500, 8.90189, 11.52266, 14.14343, 16.76420, + 19.42976, 22.10172, 24.77368, 25.17519, 24.81987, 24.46455, 8.44375, + 11.67256, 14.90137, 18.13018, 21.27843, 24.41516, 27.55190, 27.66464, + 26.76937, 25.87411, 10.51042, 13.30769, 16.10496, 18.90222, 21.70659, + 24.51197, 27.31734, 27.77045, 27.43945, 27.10846, 13.41869, 15.43789, + 17.45709, 19.47628, 21.66124, 23.86989, 26.07853, 27.08170, 27.68305, + 28.28440, 16.32697, 17.56809, 18.80922, 20.05033, 21.61590, 23.22781, + 24.83972, 26.39296, 27.92665, 29.46033, 8.90439, 10.59137, 12.27835, + 13.96532, 12.09914, 9.72536, 7.35159, 9.65235, 13.51128, 17.37022, + 8.76455, 10.43347, 12.10239, 13.77132, 13.15412, 12.21033, 11.26655, + 13.04643, 15.73420, 18.42198, 8.62470, 10.27557, 11.92644, 13.57731, + 14.20910, 14.69531, 15.18151, 16.44051, 17.95712, 19.47373, 8.48485, + 10.11767, 11.75049, 13.38331, 15.26408, 17.18027, 19.09647, 19.83460, + 20.18004, 20.52548, 8.44933, 10.20917, 11.96901, 13.72885, 16.11565, + 18.59202, 21.06838, 21.73307, 21.79386, 21.85465, 8.42872, 10.33631, + 12.24389, 14.15147, 16.93816, 19.85044, 22.76272, 23.41788, 23.32067, + 23.22346, 8.40812, 10.46344, 12.51877, 14.57409, 17.76068, 21.10886, + 24.45705, 25.10269, 24.84748, 24.59226, 11.24106, 12.63955, 14.03805, + 15.43654, 18.22489, 21.21178, 24.19868, 25.19796, 25.53469, 25.87143, + 15.02519, 15.49866, 15.97213, 16.44560, 18.56967, 20.92953, 23.28940, + 24.76337, 25.94205, 27.12073, 18.80933, 18.35777, 17.90622, 17.45466, + 18.91445, 20.64729, 22.38013, 24.32880, 26.34941, 28.37003, +}; diff --git a/src/unittest/test_objdef.cpp b/src/unittest/test_objdef.cpp new file mode 100644 index 000000000..df2633b38 --- /dev/null +++ b/src/unittest/test_objdef.cpp @@ -0,0 +1,106 @@ +/* +Minetest +Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +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 "objdef.h" + +class TestObjDef : public TestBase { +public: + TestObjDef() { TestManager::registerTestModule(this); } + const char *getName() { return "TestObjDef"; } + + void runTests(IGameDef *gamedef); + + void testHandles(); + void testAddGetSetClear(); +}; + +static TestObjDef g_test_instance; + +void TestObjDef::runTests(IGameDef *gamedef) +{ + TEST(testHandles); + TEST(testAddGetSetClear); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestObjDef::testHandles() +{ + u32 uid = 0; + u32 index = 0; + ObjDefType type = OBJDEF_GENERIC; + + ObjDefHandle handle = ObjDefManager::createHandle(9530, OBJDEF_ORE, 47); + + UASSERTEQ(ObjDefHandle, 0xAF507B55, handle); + + UASSERT(ObjDefManager::decodeHandle(handle, &index, &type, &uid)); + + UASSERTEQ(u32, 9530, index); + UASSERTEQ(u32, 47, uid); + UASSERTEQ(ObjDefHandle, OBJDEF_ORE, type); +} + + +void TestObjDef::testAddGetSetClear() +{ + ObjDefManager testmgr(NULL, OBJDEF_GENERIC); + ObjDefHandle hObj0, hObj1, hObj2, hObj3; + ObjDef *obj0, *obj1, *obj2, *obj3; + + UASSERTEQ(ObjDefType, testmgr.getType(), OBJDEF_GENERIC); + + obj0 = new ObjDef; + obj0->name = "foobar"; + hObj0 = testmgr.add(obj0); + UASSERT(hObj0 != OBJDEF_INVALID_HANDLE); + UASSERTEQ(u32, obj0->index, 0); + + obj1 = new ObjDef; + obj1->name = "FooBaz"; + hObj1 = testmgr.add(obj1); + UASSERT(hObj1 != OBJDEF_INVALID_HANDLE); + UASSERTEQ(u32, obj1->index, 1); + + obj2 = new ObjDef; + obj2->name = "asdf"; + hObj2 = testmgr.add(obj2); + UASSERT(hObj2 != OBJDEF_INVALID_HANDLE); + UASSERTEQ(u32, obj2->index, 2); + + obj3 = new ObjDef; + obj3->name = "foobaz"; + hObj3 = testmgr.add(obj3); + UASSERT(hObj3 == OBJDEF_INVALID_HANDLE); + + UASSERTEQ(size_t, testmgr.getNumObjects(), 3); + + UASSERT(testmgr.get(hObj0) == obj0); + UASSERT(testmgr.getByName("FOOBAZ") == obj1); + + UASSERT(testmgr.set(hObj0, obj3) == obj0); + UASSERT(testmgr.get(hObj0) == obj3); + delete obj0; + + testmgr.clear(); + UASSERTEQ(size_t, testmgr.getNumObjects(), 0); +} diff --git a/src/unittest/test_profiler.cpp b/src/unittest/test_profiler.cpp new file mode 100644 index 000000000..fbc03f232 --- /dev/null +++ b/src/unittest/test_profiler.cpp @@ -0,0 +1,72 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "profiler.h" + +class TestProfiler : public TestBase { +public: + TestProfiler() { TestManager::registerTestModule(this); } + const char *getName() { return "TestProfiler"; } + + void runTests(IGameDef *gamedef); + + void testProfilerAverage(); +}; + +static TestProfiler g_test_instance; + +void TestProfiler::runTests(IGameDef *gamedef) +{ + TEST(testProfilerAverage); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestProfiler::testProfilerAverage() +{ + Profiler p; + + p.avg("Test1", 1.f); + UASSERT(p.getValue("Test1") == 1.f); + + p.avg("Test1", 2.f); + UASSERT(p.getValue("Test1") == 1.5f); + + p.avg("Test1", 3.f); + UASSERT(p.getValue("Test1") == 2.f); + + p.avg("Test1", 486.f); + UASSERT(p.getValue("Test1") == 123.f); + + p.avg("Test1", 8); + UASSERT(p.getValue("Test1") == 100.f); + + p.avg("Test1", 700); + UASSERT(p.getValue("Test1") == 200.f); + + p.avg("Test1", 10000); + UASSERT(p.getValue("Test1") == 1600.f); + + p.avg("Test2", 123.56); + p.avg("Test2", 123.58); + + UASSERT(p.getValue("Test2") == 123.57f); +} diff --git a/src/unittest/test_random.cpp b/src/unittest/test_random.cpp new file mode 100644 index 000000000..bbee57719 --- /dev/null +++ b/src/unittest/test_random.cpp @@ -0,0 +1,274 @@ + /* +Minetest +Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +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 "util/numeric.h" +#include "exceptions.h" +#include "noise.h" + +class TestRandom : public TestBase { +public: + TestRandom() { TestManager::registerTestModule(this); } + const char *getName() { return "TestRandom"; } + + void runTests(IGameDef *gamedef); + + void testPseudoRandom(); + void testPseudoRandomRange(); + void testPcgRandom(); + void testPcgRandomRange(); + void testPcgRandomBytes(); + void testPcgRandomNormalDist(); + + static const int expected_pseudorandom_results[256]; + static const u32 expected_pcgrandom_results[256]; + static const u8 expected_pcgrandom_bytes_result[24]; + static const u8 expected_pcgrandom_bytes_result2[24]; +}; + +static TestRandom g_test_instance; + +void TestRandom::runTests(IGameDef *gamedef) +{ + TEST(testPseudoRandom); + TEST(testPseudoRandomRange); + TEST(testPcgRandom); + TEST(testPcgRandomRange); + TEST(testPcgRandomBytes); + TEST(testPcgRandomNormalDist); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestRandom::testPseudoRandom() +{ + PseudoRandom pr(814538); + + for (u32 i = 0; i != 256; i++) + UASSERTEQ(int, pr.next(), expected_pseudorandom_results[i]); +} + + +void TestRandom::testPseudoRandomRange() +{ + PseudoRandom pr((int)time(NULL)); + + EXCEPTION_CHECK(PrngException, pr.range(2000, 6000)); + EXCEPTION_CHECK(PrngException, pr.range(5, 1)); + + for (u32 i = 0; i != 32768; i++) { + int min = (pr.next() % 3000) - 500; + int max = (pr.next() % 3000) - 500; + if (min > max) + SWAP(int, min, max); + + int randval = pr.range(min, max); + UASSERT(randval >= min); + UASSERT(randval <= max); + } +} + + +void TestRandom::testPcgRandom() +{ + PcgRandom pr(814538, 998877); + + for (u32 i = 0; i != 256; i++) + UASSERTEQ(u32, pr.next(), expected_pcgrandom_results[i]); +} + + +void TestRandom::testPcgRandomRange() +{ + PcgRandom pr((int)time(NULL)); + + EXCEPTION_CHECK(PrngException, pr.range(5, 1)); + + // Regression test for bug 3027 + pr.range(pr.RANDOM_MIN, pr.RANDOM_MAX); + + for (u32 i = 0; i != 32768; i++) { + int min = (pr.next() % 3000) - 500; + int max = (pr.next() % 3000) - 500; + if (min > max) + SWAP(int, min, max); + + int randval = pr.range(min, max); + UASSERT(randval >= min); + UASSERT(randval <= max); + } +} + + +void TestRandom::testPcgRandomBytes() +{ + char buf[32]; + PcgRandom r(1538, 877); + + memset(buf, 0, sizeof(buf)); + r.bytes(buf + 5, 23); + UASSERT(memcmp(buf + 5, expected_pcgrandom_bytes_result, + sizeof(expected_pcgrandom_bytes_result)) == 0); + + memset(buf, 0, sizeof(buf)); + r.bytes(buf, 17); + UASSERT(memcmp(buf, expected_pcgrandom_bytes_result2, + sizeof(expected_pcgrandom_bytes_result2)) == 0); +} + + +void TestRandom::testPcgRandomNormalDist() +{ + static const int max = 120; + static const int min = -120; + static const int num_trials = 20; + static const u32 num_samples = 61000; + s32 bins[max - min + 1]; + memset(bins, 0, sizeof(bins)); + + PcgRandom r(486179 + (int)time(NULL)); + + for (u32 i = 0; i != num_samples; i++) { + s32 randval = r.randNormalDist(min, max, num_trials); + UASSERT(randval <= max); + UASSERT(randval >= min); + bins[randval - min]++; + } + + // Note that here we divide variance by the number of trials; + // this is because variance is a biased estimator. + int range = (max - min + 1); + float mean = (max + min) / 2; + float variance = ((range * range - 1) / 12) / num_trials; + float stddev = sqrt(variance); + + static const float prediction_intervals[] = { + 0.68269f, // 1.0 + 0.86639f, // 1.5 + 0.95450f, // 2.0 + 0.98758f, // 2.5 + 0.99730f, // 3.0 + }; + + //// Simple normality test using the 68-95-99.7% rule + for (u32 i = 0; i != ARRLEN(prediction_intervals); i++) { + float deviations = i / 2.f + 1.f; + int lbound = myround(mean - deviations * stddev); + int ubound = myround(mean + deviations * stddev); + UASSERT(lbound >= min); + UASSERT(ubound <= max); + + int accum = 0; + for (int j = lbound; j != ubound; j++) + accum += bins[j - min]; + + float actual = (float)accum / num_samples; + UASSERT(fabs(actual - prediction_intervals[i]) < 0.02); + } +} + + +const int TestRandom::expected_pseudorandom_results[256] = { + 0x02fa, 0x60d5, 0x6c10, 0x606b, 0x098b, 0x5f1e, 0x4f56, 0x3fbd, 0x77af, + 0x4fe9, 0x419a, 0x6fe1, 0x177b, 0x6858, 0x36f8, 0x6d83, 0x14fc, 0x2d62, + 0x1077, 0x23e2, 0x041b, 0x7a7e, 0x5b52, 0x215d, 0x682b, 0x4716, 0x47e3, + 0x08c0, 0x1952, 0x56ae, 0x146d, 0x4b4f, 0x239f, 0x3fd0, 0x6794, 0x7796, + 0x7be2, 0x75b7, 0x5691, 0x28ee, 0x2656, 0x40c0, 0x133c, 0x63cd, 0x2aeb, + 0x518f, 0x7dbc, 0x6ad8, 0x736e, 0x5b05, 0x160b, 0x589f, 0x6f64, 0x5edc, + 0x092c, 0x0a39, 0x199e, 0x1927, 0x562b, 0x2689, 0x3ba3, 0x366f, 0x46da, + 0x4e49, 0x0abb, 0x40a1, 0x3846, 0x40db, 0x7adb, 0x6ec1, 0x6efa, 0x01cc, + 0x6335, 0x4352, 0x72fb, 0x4b2d, 0x509a, 0x257e, 0x2f7d, 0x5891, 0x2195, + 0x6107, 0x5269, 0x56e3, 0x4849, 0x38f7, 0x2791, 0x04f2, 0x4e05, 0x78ff, + 0x6bae, 0x50b3, 0x74ad, 0x31af, 0x531e, 0x7d56, 0x11c9, 0x0b5e, 0x405e, + 0x1e15, 0x7f6a, 0x5bd3, 0x6649, 0x71b4, 0x3ec2, 0x6ab4, 0x520e, 0x6ad6, + 0x287e, 0x10b8, 0x18f2, 0x7107, 0x46ea, 0x1d85, 0x25cc, 0x2689, 0x35c1, + 0x3065, 0x6237, 0x3edd, 0x23d9, 0x6fb5, 0x37a1, 0x3211, 0x526a, 0x4b09, + 0x23f1, 0x58cc, 0x2e42, 0x341f, 0x5e16, 0x3d1a, 0x5e8c, 0x7a82, 0x4635, + 0x2bf8, 0x6577, 0x3603, 0x1daf, 0x539f, 0x2e91, 0x6bd8, 0x42d3, 0x7a93, + 0x26e3, 0x5a91, 0x6c67, 0x1b66, 0x3ac7, 0x18bf, 0x20d8, 0x7153, 0x558d, + 0x7262, 0x653d, 0x417d, 0x3ed3, 0x3117, 0x600d, 0x6d04, 0x719c, 0x3afd, + 0x6ba5, 0x17c5, 0x4935, 0x346c, 0x5479, 0x6ff6, 0x1fcc, 0x1054, 0x3f14, + 0x6266, 0x3acc, 0x3b77, 0x71d8, 0x478b, 0x20fa, 0x4e46, 0x7e77, 0x5554, + 0x3652, 0x719c, 0x072b, 0x61ad, 0x399f, 0x621d, 0x1bba, 0x41d0, 0x7fdc, + 0x3e6c, 0x6a2a, 0x5253, 0x094e, 0x0c10, 0x3f43, 0x73eb, 0x4c5f, 0x1f23, + 0x12c9, 0x0902, 0x5238, 0x50c0, 0x1b77, 0x3ffd, 0x0124, 0x302a, 0x26b9, + 0x3648, 0x30a6, 0x1abc, 0x3031, 0x4029, 0x6358, 0x6696, 0x74e8, 0x6142, + 0x4284, 0x0c00, 0x7e50, 0x41e3, 0x3782, 0x79a5, 0x60fe, 0x2d15, 0x3ed2, + 0x7f70, 0x2b27, 0x6366, 0x5100, 0x7c44, 0x3ee0, 0x4e76, 0x7d34, 0x3a60, + 0x140e, 0x613d, 0x1193, 0x268d, 0x1e2f, 0x3123, 0x6d61, 0x4e0b, 0x51ce, + 0x13bf, 0x58d4, 0x4f43, 0x05c6, 0x4d6a, 0x7eb5, 0x2921, 0x2c36, 0x1c89, + 0x63b9, 0x1555, 0x1f41, 0x2d9f, +}; + +const u32 TestRandom::expected_pcgrandom_results[256] = { + 0x48c593f8, 0x054f59f5, 0x0d062dc1, 0x23852a23, 0x7fbbc97b, 0x1f9f141e, + 0x364e6ed8, 0x995bba58, 0xc9307dc0, 0x73fb34c4, 0xcd8de88d, 0x52e8ce08, + 0x1c4a78e4, 0x25c0882e, 0x8a82e2e0, 0xe3bc3311, 0xb8068d42, 0x73186110, + 0x19988df4, 0x69bd970b, 0x7214728c, 0x0aee320c, 0x2a5a536c, 0xaf48d715, + 0x00bce504, 0xd2b8f548, 0x520df366, 0x96d8fff5, 0xa1bb510b, 0x63477049, + 0xb85990b7, 0x7e090689, 0x275fb468, 0x50206257, 0x8bab4f8a, 0x0d6823db, + 0x63faeaac, 0x2d92deeb, 0x2ba78024, 0x0d30f631, 0x338923a0, 0xd07248d8, + 0xa5db62d3, 0xddba8af6, 0x0ad454e9, 0x6f0fd13a, 0xbbfde2bf, 0x91188009, + 0x966b394d, 0xbb9d2012, 0x7e6926cb, 0x95183860, 0x5ff4c59b, 0x035f628a, + 0xb67085ef, 0x33867e23, 0x68d1b887, 0x2e3298d7, 0x84fd0650, 0x8bc91141, + 0x6fcb0452, 0x2836fee9, 0x2e83c0a3, 0xf1bafdc5, 0x9ff77777, 0xfdfbba87, + 0x527aebeb, 0x423e5248, 0xd1756490, 0xe41148fa, 0x3361f7b4, 0xa2824f23, + 0xf4e08072, 0xc50442be, 0x35adcc21, 0x36be153c, 0xc7709012, 0xf0eeb9f2, + 0x3d73114e, 0x1c1574ee, 0x92095b9c, 0x1503d01c, 0xd6ce0677, 0x026a8ec1, + 0x76d0084d, 0x86c23633, 0x36f75ce6, 0x08fa7bbe, 0x35f6ff2a, 0x31cc9525, + 0x2c1a35e6, 0x8effcd62, 0xc782fa07, 0x8a86e248, 0x8fdb7a9b, 0x77246626, + 0x5767723f, 0x3a78b699, 0xe548ce1c, 0x5820f37d, 0x148ed9b8, 0xf6796254, + 0x32232c20, 0x392bf3a2, 0xe9af6625, 0xd40b0d88, 0x636cfa23, 0x6a5de514, + 0xc4a69183, 0xc785c853, 0xab0de901, 0x16ae7e44, 0x376f13b5, 0x070f7f31, + 0x34cbc93b, 0xe6184345, 0x1b7f911f, 0x631fbe4b, 0x86d6e023, 0xc689b518, + 0x88ef4f7c, 0xddf06b45, 0xc97f18d4, 0x2aaee94b, 0x45694723, 0x6db111d2, + 0x91974fce, 0xe33e29e2, 0xc5e99494, 0x8017e02b, 0x3ebd8143, 0x471ffb80, + 0xc0d7ca1b, 0x4954c860, 0x48935d6a, 0xf2d27999, 0xb93d608d, 0x40696e90, + 0x60b18162, 0x1a156998, 0x09b8bbab, 0xc80a79b6, 0x8adbcfbc, 0xc375248c, + 0xa584e2ea, 0x5b46fe11, 0x58e84680, 0x8a8bc456, 0xd668b94f, 0x8b9035be, + 0x278509d4, 0x6663a140, 0x81a9817a, 0xd4f9d3cf, 0x6dc5f607, 0x6ae04450, + 0x694f22a4, 0x1d061788, 0x2e39ad8b, 0x748f4db2, 0xee569b52, 0xd157166d, + 0xdabc161e, 0xc8d50176, 0x7e3110e5, 0x9f7d033b, 0x128df67f, 0xb0078583, + 0xa3a75d26, 0xc1ad8011, 0x07dd89ec, 0xef04f456, 0x91bf866c, 0x6aac5306, + 0xdd5a1573, 0xf73ff97a, 0x4e1186ad, 0xb9680680, 0xc8894515, 0xdc95a08e, + 0xc894fd8e, 0xf84ade15, 0xd787f8c1, 0x40dcecca, 0x1b24743e, 0x1ce6ab23, + 0x72321653, 0xb80fbaf7, 0x1bcf099b, 0x1ff26805, 0x78f66c8e, 0xf93bf51a, + 0xfb0c06fe, 0xe50d48cf, 0x310947e0, 0x1b78804a, 0xe73e2c14, 0x8deb8381, + 0xe576122a, 0xe5a8df39, 0x42397c5e, 0xf5503f3c, 0xbe3dbf8d, 0x1b360e5c, + 0x9254caaf, 0x7a9f6744, 0x6d4144fa, 0xd77c65fe, 0x44ca7b12, 0xf58a4c00, + 0x159500d0, 0x92769857, 0x7134fdd4, 0xa3fea693, 0xbd044831, 0xeded39a1, + 0xe4570204, 0xaea37f2f, 0x9a302971, 0x620f8402, 0x1d2f3e5e, 0xf9c2f49c, + 0x738e813a, 0xb3c92251, 0x7ecba63b, 0xbe7eebc7, 0xf800267c, 0x3fdeb760, + 0xf12d5e7d, 0x5a18dce1, 0xb35a539c, 0xe565f057, 0x2babf38c, 0xae5800ad, + 0x421004dd, 0x6715acb6, 0xff529b64, 0xd520d207, 0x7cb193e7, 0xe9b18e4c, + 0xfd2a8a59, 0x47826ae3, 0x56ba43f8, 0x453b3d99, 0x8ae1675f, 0xf66f5c34, + 0x057a6ac1, 0x010769e4, 0xa8324158, 0x410379a5, 0x5dfc8c97, 0x72848afe, + 0x59f169e5, 0xe32acb78, 0x5dfaa9c4, 0x51bb956a, +}; + +const u8 TestRandom::expected_pcgrandom_bytes_result[24] = { + 0xf3, 0x79, 0x8f, 0x31, 0xac, 0xd9, 0x34, 0xf8, 0x3c, 0x6e, 0x82, 0x37, + 0x6b, 0x4b, 0x77, 0xe3, 0xbd, 0x0a, 0xee, 0x22, 0x79, 0x6e, 0x40, 0x00, +}; + +const u8 TestRandom::expected_pcgrandom_bytes_result2[24] = { + 0x47, 0x9e, 0x08, 0x3e, 0xd4, 0x21, 0x2d, 0xf6, 0xb4, 0xb1, 0x9d, 0x7a, + 0x60, 0x02, 0x5a, 0xb2, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; diff --git a/src/unittest/test_schematic.cpp b/src/unittest/test_schematic.cpp new file mode 100644 index 000000000..480124428 --- /dev/null +++ b/src/unittest/test_schematic.cpp @@ -0,0 +1,280 @@ + /* +Minetest +Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +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 "mg_schematic.h" +#include "gamedef.h" +#include "nodedef.h" + +class TestSchematic : public TestBase { +public: + TestSchematic() { TestManager::registerTestModule(this); } + const char *getName() { return "TestSchematic"; } + + void runTests(IGameDef *gamedef); + + void testMtsSerializeDeserialize(INodeDefManager *ndef); + void testLuaTableSerialize(INodeDefManager *ndef); + void testFileSerializeDeserialize(INodeDefManager *ndef); + + static const content_t test_schem1_data[7 * 6 * 4]; + static const content_t test_schem2_data[3 * 3 * 3]; + static const u8 test_schem2_prob[3 * 3 * 3]; + static const char *expected_lua_output; +}; + +static TestSchematic g_test_instance; + +void TestSchematic::runTests(IGameDef *gamedef) +{ + IWritableNodeDefManager *ndef = + (IWritableNodeDefManager *)gamedef->getNodeDefManager(); + + ndef->setNodeRegistrationStatus(true); + + TEST(testMtsSerializeDeserialize, ndef); + TEST(testLuaTableSerialize, ndef); + TEST(testFileSerializeDeserialize, ndef); + + ndef->resetNodeResolveState(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestSchematic::testMtsSerializeDeserialize(INodeDefManager *ndef) +{ + static const v3s16 size(7, 6, 4); + static const u32 volume = size.X * size.Y * size.Z; + + std::stringstream ss(std::ios_base::binary | + std::ios_base::in | std::ios_base::out); + + std::vector<std::string> names; + names.push_back("foo"); + names.push_back("bar"); + names.push_back("baz"); + names.push_back("qux"); + + Schematic schem, schem2; + + schem.flags = 0; + schem.size = size; + schem.schemdata = new MapNode[volume]; + schem.slice_probs = new u8[size.Y]; + for (size_t i = 0; i != volume; i++) + schem.schemdata[i] = MapNode(test_schem1_data[i], MTSCHEM_PROB_ALWAYS, 0); + for (s16 y = 0; y != size.Y; y++) + schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS; + + UASSERT(schem.serializeToMts(&ss, names)); + + ss.seekg(0); + names.clear(); + + UASSERT(schem2.deserializeFromMts(&ss, &names)); + + UASSERTEQ(size_t, names.size(), 4); + UASSERTEQ(std::string, names[0], "foo"); + UASSERTEQ(std::string, names[1], "bar"); + UASSERTEQ(std::string, names[2], "baz"); + UASSERTEQ(std::string, names[3], "qux"); + + UASSERT(schem2.size == size); + for (size_t i = 0; i != volume; i++) + UASSERT(schem2.schemdata[i] == schem.schemdata[i]); + for (s16 y = 0; y != size.Y; y++) + UASSERTEQ(u8, schem2.slice_probs[y], schem.slice_probs[y]); +} + + +void TestSchematic::testLuaTableSerialize(INodeDefManager *ndef) +{ + static const v3s16 size(3, 3, 3); + static const u32 volume = size.X * size.Y * size.Z; + + Schematic schem; + + schem.flags = 0; + schem.size = size; + schem.schemdata = new MapNode[volume]; + schem.slice_probs = new u8[size.Y]; + for (size_t i = 0; i != volume; i++) + schem.schemdata[i] = MapNode(test_schem2_data[i], test_schem2_prob[i], 0); + for (s16 y = 0; y != size.Y; y++) + schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS; + + std::vector<std::string> names; + names.push_back("air"); + names.push_back("default:lava_source"); + names.push_back("default:glass"); + + std::ostringstream ss(std::ios_base::binary); + + UASSERT(schem.serializeToLua(&ss, names, false, 0)); + UASSERTEQ(std::string, ss.str(), expected_lua_output); +} + + +void TestSchematic::testFileSerializeDeserialize(INodeDefManager *ndef) +{ + static const v3s16 size(3, 3, 3); + static const u32 volume = size.X * size.Y * size.Z; + static const content_t content_map[] = { + CONTENT_AIR, + t_CONTENT_STONE, + t_CONTENT_LAVA, + }; + static const content_t content_map2[] = { + CONTENT_AIR, + t_CONTENT_STONE, + t_CONTENT_WATER, + }; + StringMap replace_names; + replace_names["default:lava"] = "default:water"; + + Schematic schem1, schem2; + + //// Construct the schematic to save + schem1.flags = 0; + schem1.size = size; + schem1.schemdata = new MapNode[volume]; + schem1.slice_probs = new u8[size.Y]; + schem1.slice_probs[0] = 80; + schem1.slice_probs[1] = 160; + schem1.slice_probs[2] = 240; + + for (size_t i = 0; i != volume; i++) { + content_t c = content_map[test_schem2_data[i]]; + schem1.schemdata[i] = MapNode(c, test_schem2_prob[i], 0); + } + + std::string temp_file = getTestTempFile(); + UASSERT(schem1.saveSchematicToFile(temp_file, ndef)); + UASSERT(schem2.loadSchematicFromFile(temp_file, ndef, &replace_names)); + + UASSERT(schem2.size == size); + UASSERT(schem2.slice_probs[0] == 80); + UASSERT(schem2.slice_probs[1] == 160); + UASSERT(schem2.slice_probs[2] == 240); + + for (size_t i = 0; i != volume; i++) { + content_t c = content_map2[test_schem2_data[i]]; + UASSERT(schem2.schemdata[i] == MapNode(c, test_schem2_prob[i], 0)); + } +} + + +// Should form a cross-shaped-thing...? +const content_t TestSchematic::test_schem1_data[7 * 6 * 4] = { + 3, 3, 1, 1, 1, 3, 3, // Y=0, Z=0 + 3, 0, 1, 2, 1, 0, 3, // Y=1, Z=0 + 3, 0, 1, 2, 1, 0, 3, // Y=2, Z=0 + 3, 1, 1, 2, 1, 1, 3, // Y=3, Z=0 + 3, 2, 2, 2, 2, 2, 3, // Y=4, Z=0 + 3, 1, 1, 2, 1, 1, 3, // Y=5, Z=0 + + 0, 0, 1, 1, 1, 0, 0, // Y=0, Z=1 + 0, 0, 1, 2, 1, 0, 0, // Y=1, Z=1 + 0, 0, 1, 2, 1, 0, 0, // Y=2, Z=1 + 1, 1, 1, 2, 1, 1, 1, // Y=3, Z=1 + 1, 2, 2, 2, 2, 2, 1, // Y=4, Z=1 + 1, 1, 1, 2, 1, 1, 1, // Y=5, Z=1 + + 0, 0, 1, 1, 1, 0, 0, // Y=0, Z=2 + 0, 0, 1, 2, 1, 0, 0, // Y=1, Z=2 + 0, 0, 1, 2, 1, 0, 0, // Y=2, Z=2 + 1, 1, 1, 2, 1, 1, 1, // Y=3, Z=2 + 1, 2, 2, 2, 2, 2, 1, // Y=4, Z=2 + 1, 1, 1, 2, 1, 1, 1, // Y=5, Z=2 + + 3, 3, 1, 1, 1, 3, 3, // Y=0, Z=3 + 3, 0, 1, 2, 1, 0, 3, // Y=1, Z=3 + 3, 0, 1, 2, 1, 0, 3, // Y=2, Z=3 + 3, 1, 1, 2, 1, 1, 3, // Y=3, Z=3 + 3, 2, 2, 2, 2, 2, 3, // Y=4, Z=3 + 3, 1, 1, 2, 1, 1, 3, // Y=5, Z=3 +}; + +const content_t TestSchematic::test_schem2_data[3 * 3 * 3] = { + 0, 0, 0, + 0, 2, 0, + 0, 0, 0, + + 0, 2, 0, + 2, 1, 2, + 0, 2, 0, + + 0, 0, 0, + 0, 2, 0, + 0, 0, 0, +}; + +const u8 TestSchematic::test_schem2_prob[3 * 3 * 3] = { + 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, + + 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, + + 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, +}; + +const char *TestSchematic::expected_lua_output = + "schematic = {\n" + "\tsize = {x=3, y=3, z=3},\n" + "\tyslice_prob = {\n" + "\t\t{ypos=0, prob=254},\n" + "\t\t{ypos=1, prob=254},\n" + "\t\t{ypos=2, prob=254},\n" + "\t},\n" + "\tdata = {\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"default:lava_source\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t},\n" + "}\n"; diff --git a/src/unittest/test_serialization.cpp b/src/unittest/test_serialization.cpp new file mode 100644 index 000000000..49f348e9c --- /dev/null +++ b/src/unittest/test_serialization.cpp @@ -0,0 +1,386 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "util/string.h" +#include "util/serialize.h" + +class TestSerialization : public TestBase { +public: + TestSerialization() { TestManager::registerTestModule(this); } + const char *getName() { return "TestSerialization"; } + + void runTests(IGameDef *gamedef); + void buildTestStrings(); + + void testSerializeString(); + void testSerializeWideString(); + void testSerializeLongString(); + void testSerializeJsonString(); + void testSerializeHex(); + void testDeSerializeString(); + void testDeSerializeWideString(); + void testDeSerializeLongString(); + void testStreamRead(); + void testStreamWrite(); + + std::string teststring2; + std::wstring teststring2_w; + std::string teststring2_w_encoded; + + static const u8 test_serialized_data[12 * 13]; +}; + +static TestSerialization g_test_instance; + +void TestSerialization::runTests(IGameDef *gamedef) +{ + buildTestStrings(); + + TEST(testSerializeString); + TEST(testDeSerializeString); + TEST(testSerializeWideString); + TEST(testDeSerializeWideString); + TEST(testSerializeLongString); + TEST(testDeSerializeLongString); + TEST(testSerializeJsonString); + TEST(testSerializeHex); + TEST(testStreamRead); + TEST(testStreamWrite); +} + +//////////////////////////////////////////////////////////////////////////////// + +// To be used like this: +// mkstr("Some\0string\0with\0embedded\0nuls") +// since std::string("...") doesn't work as expected in that case. +template<size_t N> std::string mkstr(const char (&s)[N]) +{ + return std::string(s, N - 1); +} + +void TestSerialization::buildTestStrings() +{ + std::ostringstream tmp_os; + std::wostringstream tmp_os_w; + std::ostringstream tmp_os_w_encoded; + for (int i = 0; i < 256; i++) { + tmp_os << (char)i; + tmp_os_w << (wchar_t)i; + tmp_os_w_encoded << (char)0 << (char)i; + } + teststring2 = tmp_os.str(); + teststring2_w = tmp_os_w.str(); + teststring2_w_encoded = tmp_os_w_encoded.str(); +} + +void TestSerialization::testSerializeString() +{ + // Test blank string + UASSERT(serializeString("") == mkstr("\0\0")); + + // Test basic string + UASSERT(serializeString("Hello world!") == mkstr("\0\14Hello world!")); + + // Test character range + UASSERT(serializeString(teststring2) == mkstr("\1\0") + teststring2); +} + +void TestSerialization::testDeSerializeString() +{ + // Test deserialize + { + std::istringstream is(serializeString(teststring2), std::ios::binary); + UASSERT(deSerializeString(is) == teststring2); + UASSERT(!is.eof()); + is.get(); + UASSERT(is.eof()); + } + + // Test deserialize an incomplete length specifier + { + std::istringstream is(mkstr("\x53"), std::ios::binary); + EXCEPTION_CHECK(SerializationError, deSerializeString(is)); + } + + // Test deserialize a string with incomplete data + { + std::istringstream is(mkstr("\x00\x55 abcdefg"), std::ios::binary); + EXCEPTION_CHECK(SerializationError, deSerializeString(is)); + } +} + +void TestSerialization::testSerializeWideString() +{ + // Test blank string + UASSERT(serializeWideString(L"") == mkstr("\0\0")); + + // Test basic string + UASSERT(serializeWideString(utf8_to_wide("Hello world!")) == + mkstr("\0\14\0H\0e\0l\0l\0o\0 \0w\0o\0r\0l\0d\0!")); + + // Test character range + UASSERT(serializeWideString(teststring2_w) == + mkstr("\1\0") + teststring2_w_encoded); +} + +void TestSerialization::testDeSerializeWideString() +{ + // Test deserialize + { + std::istringstream is(serializeWideString(teststring2_w), std::ios::binary); + UASSERT(deSerializeWideString(is) == teststring2_w); + UASSERT(!is.eof()); + is.get(); + UASSERT(is.eof()); + } + + // Test deserialize an incomplete length specifier + { + std::istringstream is(mkstr("\x53"), std::ios::binary); + EXCEPTION_CHECK(SerializationError, deSerializeWideString(is)); + } + + // Test deserialize a string with an incomplete character + { + std::istringstream is(mkstr("\x00\x07\0a\0b\0c\0d\0e\0f\0"), std::ios::binary); + EXCEPTION_CHECK(SerializationError, deSerializeWideString(is)); + } + + // Test deserialize a string with incomplete data + { + std::istringstream is(mkstr("\x00\x08\0a\0b\0c\0d\0e\0f"), std::ios::binary); + EXCEPTION_CHECK(SerializationError, deSerializeWideString(is)); + } +} + +void TestSerialization::testSerializeLongString() +{ + // Test blank string + UASSERT(serializeLongString("") == mkstr("\0\0\0\0")); + + // Test basic string + UASSERT(serializeLongString("Hello world!") == mkstr("\0\0\0\14Hello world!")); + + // Test character range + UASSERT(serializeLongString(teststring2) == mkstr("\0\0\1\0") + teststring2); +} + +void TestSerialization::testDeSerializeLongString() +{ + // Test deserialize + { + std::istringstream is(serializeLongString(teststring2), std::ios::binary); + UASSERT(deSerializeLongString(is) == teststring2); + UASSERT(!is.eof()); + is.get(); + UASSERT(is.eof()); + } + + // Test deserialize an incomplete length specifier + { + std::istringstream is(mkstr("\x53"), std::ios::binary); + EXCEPTION_CHECK(SerializationError, deSerializeLongString(is)); + } + + // Test deserialize a string with incomplete data + { + std::istringstream is(mkstr("\x00\x00\x00\x05 abc"), std::ios::binary); + EXCEPTION_CHECK(SerializationError, deSerializeLongString(is)); + } + + // Test deserialize a string with a length too large + { + std::istringstream is(mkstr("\xFF\xFF\xFF\xFF blah"), std::ios::binary); + EXCEPTION_CHECK(SerializationError, deSerializeLongString(is)); + } +} + + +void TestSerialization::testSerializeJsonString() +{ + // Test blank string + UASSERT(serializeJsonString("") == "\"\""); + + // Test basic string + UASSERT(serializeJsonString("Hello world!") == "\"Hello world!\""); + + // MSVC fails when directly using "\\\\" + std::string backslash = "\\"; + UASSERT(serializeJsonString(teststring2) == + mkstr("\"") + + "\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007" + + "\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f" + + "\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017" + + "\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f" + + " !\\\"" + teststring2.substr(0x23, 0x2f-0x23) + + "\\/" + teststring2.substr(0x30, 0x5c-0x30) + + backslash + backslash + teststring2.substr(0x5d, 0x7f-0x5d) + "\\u007f" + + "\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087" + + "\\u0088\\u0089\\u008a\\u008b\\u008c\\u008d\\u008e\\u008f" + + "\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097" + + "\\u0098\\u0099\\u009a\\u009b\\u009c\\u009d\\u009e\\u009f" + + "\\u00a0\\u00a1\\u00a2\\u00a3\\u00a4\\u00a5\\u00a6\\u00a7" + + "\\u00a8\\u00a9\\u00aa\\u00ab\\u00ac\\u00ad\\u00ae\\u00af" + + "\\u00b0\\u00b1\\u00b2\\u00b3\\u00b4\\u00b5\\u00b6\\u00b7" + + "\\u00b8\\u00b9\\u00ba\\u00bb\\u00bc\\u00bd\\u00be\\u00bf" + + "\\u00c0\\u00c1\\u00c2\\u00c3\\u00c4\\u00c5\\u00c6\\u00c7" + + "\\u00c8\\u00c9\\u00ca\\u00cb\\u00cc\\u00cd\\u00ce\\u00cf" + + "\\u00d0\\u00d1\\u00d2\\u00d3\\u00d4\\u00d5\\u00d6\\u00d7" + + "\\u00d8\\u00d9\\u00da\\u00db\\u00dc\\u00dd\\u00de\\u00df" + + "\\u00e0\\u00e1\\u00e2\\u00e3\\u00e4\\u00e5\\u00e6\\u00e7" + + "\\u00e8\\u00e9\\u00ea\\u00eb\\u00ec\\u00ed\\u00ee\\u00ef" + + "\\u00f0\\u00f1\\u00f2\\u00f3\\u00f4\\u00f5\\u00f6\\u00f7" + + "\\u00f8\\u00f9\\u00fa\\u00fb\\u00fc\\u00fd\\u00fe\\u00ff" + + "\""); + + // Test deserialize + std::istringstream is(serializeJsonString(teststring2), std::ios::binary); + UASSERT(deSerializeJsonString(is) == teststring2); + UASSERT(!is.eof()); + is.get(); + UASSERT(is.eof()); +} + +void TestSerialization::testSerializeHex() +{ + // Test blank string + UASSERT(serializeHexString("") == ""); + UASSERT(serializeHexString("", true) == ""); + + // Test basic string + UASSERT(serializeHexString("Hello world!") == + "48656c6c6f20776f726c6421"); + UASSERT(serializeHexString("Hello world!", true) == + "48 65 6c 6c 6f 20 77 6f 72 6c 64 21"); + + // Test binary string + UASSERT(serializeHexString(mkstr("\x00\x0a\xb0\x63\x1f\x00\xff")) == + "000ab0631f00ff"); + UASSERT(serializeHexString(mkstr("\x00\x0a\xb0\x63\x1f\x00\xff"), true) == + "00 0a b0 63 1f 00 ff"); +} + + +void TestSerialization::testStreamRead() +{ + std::string datastr( + (const char *)test_serialized_data, + sizeof(test_serialized_data)); + std::istringstream is(datastr, std::ios_base::binary); + + UASSERT(readU8(is) == 0x11); + UASSERT(readU16(is) == 0x2233); + UASSERT(readU32(is) == 0x44556677); + UASSERT(readU64(is) == 0x8899AABBCCDDEEFF); + + UASSERT(readS8(is) == -128); + UASSERT(readS16(is) == 30000); + UASSERT(readS32(is) == -6); + UASSERT(readS64(is) == -43); + + UASSERT(readF1000(is) == 53.534f); + UASSERT(readF1000(is) == -300000.32f); + UASSERT(readF1000(is) == F1000_MIN); + UASSERT(readF1000(is) == F1000_MAX); + + UASSERT(deSerializeString(is) == "foobar!"); + + UASSERT(readV2S16(is) == v2s16(500, 500)); + UASSERT(readV3S16(is) == v3s16(4207, 604, -30)); + UASSERT(readV2S32(is) == v2s32(1920, 1080)); + UASSERT(readV3S32(is) == v3s32(-400, 6400054, 290549855)); + UASSERT(readV2F1000(is) == v2f(500.656f, 350.345f)); + + UASSERT(deSerializeWideString(is) == L"\x02~woof~\x5455"); + + UASSERT(readV3F1000(is) == v3f(500, 10024.2f, -192.54f)); + UASSERT(readARGB8(is) == video::SColor(255, 128, 50, 128)); + + UASSERT(deSerializeLongString(is) == "some longer string here"); + + UASSERT(is.rdbuf()->in_avail() == 2); + UASSERT(readU16(is) == 0xF00D); + UASSERT(is.rdbuf()->in_avail() == 0); +} + + +void TestSerialization::testStreamWrite() +{ + std::ostringstream os(std::ios_base::binary); + std::string data; + + writeU8(os, 0x11); + writeU16(os, 0x2233); + writeU32(os, 0x44556677); + writeU64(os, 0x8899AABBCCDDEEFF); + + writeS8(os, -128); + writeS16(os, 30000); + writeS32(os, -6); + writeS64(os, -43); + + writeF1000(os, 53.53467f); + writeF1000(os, -300000.32f); + writeF1000(os, F1000_MIN); + writeF1000(os, F1000_MAX); + + os << serializeString("foobar!"); + + data = os.str(); + UASSERT(data.size() < sizeof(test_serialized_data)); + UASSERT(!memcmp(&data[0], test_serialized_data, data.size())); + + writeV2S16(os, v2s16(500, 500)); + writeV3S16(os, v3s16(4207, 604, -30)); + writeV2S32(os, v2s32(1920, 1080)); + writeV3S32(os, v3s32(-400, 6400054, 290549855)); + writeV2F1000(os, v2f(500.65661f, 350.34567f)); + + os << serializeWideString(L"\x02~woof~\x5455"); + + writeV3F1000(os, v3f(500, 10024.2f, -192.54f)); + writeARGB8(os, video::SColor(255, 128, 50, 128)); + + os << serializeLongString("some longer string here"); + + writeU16(os, 0xF00D); + + data = os.str(); + UASSERT(data.size() == sizeof(test_serialized_data)); + UASSERT(!memcmp(&data[0], test_serialized_data, sizeof(test_serialized_data))); +} + + +const u8 TestSerialization::test_serialized_data[12 * 13] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, + 0xdd, 0xee, 0xff, 0x80, 0x75, 0x30, 0xff, 0xff, 0xff, 0xfa, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xd5, 0x00, 0x00, 0xd1, 0x1e, 0xee, 0x1e, + 0x5b, 0xc0, 0x80, 0x00, 0x02, 0x80, 0x7F, 0xFF, 0xFD, 0x80, 0x00, 0x07, + 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x21, 0x01, 0xf4, 0x01, 0xf4, 0x10, + 0x6f, 0x02, 0x5c, 0xff, 0xe2, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x04, + 0x38, 0xff, 0xff, 0xfe, 0x70, 0x00, 0x61, 0xa8, 0x36, 0x11, 0x51, 0x70, + 0x5f, 0x00, 0x07, 0xa3, 0xb0, 0x00, 0x05, 0x58, 0x89, 0x00, 0x08, 0x00, + 0x02, 0x00, 0x7e, 0x00, 0x77, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x66, 0x00, + 0x7e, 0x54, 0x55, 0x00, 0x07, 0xa1, 0x20, 0x00, 0x98, 0xf5, 0x08, 0xff, + 0xfd, 0x0f, 0xe4, 0xff, 0x80, 0x32, 0x80, 0x00, 0x00, 0x00, 0x17, 0x73, + 0x6f, 0x6d, 0x65, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x20, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x68, 0x65, 0x72, 0x65, 0xF0, 0x0D, +}; diff --git a/src/unittest/test_settings.cpp b/src/unittest/test_settings.cpp new file mode 100644 index 000000000..a82d734f0 --- /dev/null +++ b/src/unittest/test_settings.cpp @@ -0,0 +1,204 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "settings.h" +#include "noise.h" + +class TestSettings : public TestBase { +public: + TestSettings() { TestManager::registerTestModule(this); } + const char *getName() { return "TestSettings"; } + + void runTests(IGameDef *gamedef); + + void testAllSettings(); + + static const char *config_text_before; + static const char *config_text_after; +}; + +static TestSettings g_test_instance; + +void TestSettings::runTests(IGameDef *gamedef) +{ + TEST(testAllSettings); +} + +//////////////////////////////////////////////////////////////////////////////// + +const char *TestSettings::config_text_before = + "leet = 1337\n" + "leetleet = 13371337\n" + "leetleet_neg = -13371337\n" + "floaty_thing = 1.1\n" + "stringy_thing = asd /( ¤%&(/\" BLÖÄRP\n" + "coord = (1, 2, 4.5)\n" + " # this is just a comment\n" + "this is an invalid line\n" + "asdf = {\n" + " a = 5\n" + " bb = 2.5\n" + " ccc = \"\"\"\n" + "testy\n" + " testa \n" + "\"\"\"\n" + "\n" + "}\n" + "blarg = \"\"\" \n" + "some multiline text\n" + " with leading whitespace!\n" + "\"\"\"\n" + "np_terrain = 5, 40, (250, 250, 250), 12341, 5, 0.7, 2.4\n" + "zoop = true"; + +const char *TestSettings::config_text_after = + "leet = 1337\n" + "leetleet = 13371337\n" + "leetleet_neg = -13371337\n" + "floaty_thing = 1.1\n" + "stringy_thing = asd /( ¤%&(/\" BLÖÄRP\n" + "coord = (1, 2, 4.5)\n" + " # this is just a comment\n" + "this is an invalid line\n" + "asdf = {\n" + " a = 5\n" + " bb = 2.5\n" + " ccc = \"\"\"\n" + "testy\n" + " testa \n" + "\"\"\"\n" + "\n" + "}\n" + "blarg = \"\"\" \n" + "some multiline text\n" + " with leading whitespace!\n" + "\"\"\"\n" + "np_terrain = {\n" + " flags = defaults\n" + " lacunarity = 2.4\n" + " octaves = 6\n" + " offset = 3.5\n" + " persistence = 0.7\n" + " scale = 40\n" + " seed = 12341\n" + " spread = (250,250,250)\n" + "}\n" + "zoop = true\n" + "coord2 = (1,2,3.3)\n" + "floaty_thing_2 = 1.2\n" + "groupy_thing = {\n" + " animals = cute\n" + " num_apples = 4\n" + " num_oranges = 53\n" + "}\n"; + +void TestSettings::testAllSettings() +{ + try { + Settings s; + + // Test reading of settings + std::istringstream is(config_text_before); + s.parseConfigLines(is); + + UASSERT(s.getS32("leet") == 1337); + UASSERT(s.getS16("leetleet") == 32767); + UASSERT(s.getS16("leetleet_neg") == -32768); + + // Not sure if 1.1 is an exact value as a float, but doesn't matter + UASSERT(fabs(s.getFloat("floaty_thing") - 1.1) < 0.001); + UASSERT(s.get("stringy_thing") == "asd /( ¤%&(/\" BLÖÄRP"); + UASSERT(fabs(s.getV3F("coord").X - 1.0) < 0.001); + UASSERT(fabs(s.getV3F("coord").Y - 2.0) < 0.001); + UASSERT(fabs(s.getV3F("coord").Z - 4.5) < 0.001); + + // Test the setting of settings too + s.setFloat("floaty_thing_2", 1.2); + s.setV3F("coord2", v3f(1, 2, 3.3)); + UASSERT(s.get("floaty_thing_2").substr(0,3) == "1.2"); + UASSERT(fabs(s.getFloat("floaty_thing_2") - 1.2) < 0.001); + UASSERT(fabs(s.getV3F("coord2").X - 1.0) < 0.001); + UASSERT(fabs(s.getV3F("coord2").Y - 2.0) < 0.001); + UASSERT(fabs(s.getV3F("coord2").Z - 3.3) < 0.001); + + // Test settings groups + Settings *group = s.getGroup("asdf"); + UASSERT(group != NULL); + UASSERT(s.getGroupNoEx("zoop", group) == false); + UASSERT(group->getS16("a") == 5); + UASSERT(fabs(group->getFloat("bb") - 2.5) < 0.001); + + Settings *group3 = new Settings; + group3->set("cat", "meow"); + group3->set("dog", "woof"); + + Settings *group2 = new Settings; + group2->setS16("num_apples", 4); + group2->setS16("num_oranges", 53); + group2->setGroup("animals", group3); + group2->set("animals", "cute"); //destroys group 3 + s.setGroup("groupy_thing", group2); + + // Test set failure conditions + UASSERT(s.set("Zoop = Poop\nsome_other_setting", "false") == false); + UASSERT(s.set("sneaky", "\"\"\"\njabberwocky = false") == false); + UASSERT(s.set("hehe", "asdfasdf\n\"\"\"\nsomething = false") == false); + + // Test multiline settings + UASSERT(group->get("ccc") == "testy\n testa "); + + UASSERT(s.get("blarg") == + "some multiline text\n" + " with leading whitespace!"); + + // Test NoiseParams + UASSERT(s.getEntry("np_terrain").is_group == false); + + NoiseParams np; + UASSERT(s.getNoiseParams("np_terrain", np) == true); + UASSERT(fabs(np.offset - 5) < 0.001); + UASSERT(fabs(np.scale - 40) < 0.001); + UASSERT(fabs(np.spread.X - 250) < 0.001); + UASSERT(fabs(np.spread.Y - 250) < 0.001); + UASSERT(fabs(np.spread.Z - 250) < 0.001); + UASSERT(np.seed == 12341); + UASSERT(np.octaves == 5); + UASSERT(fabs(np.persist - 0.7) < 0.001); + + np.offset = 3.5; + np.octaves = 6; + s.setNoiseParams("np_terrain", np); + + UASSERT(s.getEntry("np_terrain").is_group == true); + + // Test writing + std::ostringstream os(std::ios_base::binary); + is.clear(); + is.seekg(0); + + UASSERT(s.updateConfigObject(is, os, "", 0) == true); + //printf(">>>> expected config:\n%s\n", TEST_CONFIG_TEXT_AFTER); + //printf(">>>> actual config:\n%s\n", os.str().c_str()); + UASSERT(os.str() == config_text_after); + } catch (SettingNotFoundException &e) { + UASSERT(!"Setting not found!"); + } +} diff --git a/src/unittest/test_socket.cpp b/src/unittest/test_socket.cpp new file mode 100644 index 000000000..33e568e79 --- /dev/null +++ b/src/unittest/test_socket.cpp @@ -0,0 +1,151 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "log.h" +#include "socket.h" +#include "settings.h" + +class TestSocket : public TestBase { +public: + TestSocket() + { + if (INTERNET_SIMULATOR == false) + TestManager::registerTestModule(this); + } + + const char *getName() { return "TestSocket"; } + + void runTests(IGameDef *gamedef); + + void testIPv4Socket(); + void testIPv6Socket(); + + static const int port = 30003; +}; + +static TestSocket g_test_instance; + +void TestSocket::runTests(IGameDef *gamedef) +{ + TEST(testIPv4Socket); + + if (g_settings->getBool("enable_ipv6")) + TEST(testIPv6Socket); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestSocket::testIPv4Socket() +{ + Address address(0, 0, 0, 0, port); + Address bind_addr(0, 0, 0, 0, port); + + /* + * Try to use the bind_address for servers with no localhost address + * For example: FreeBSD jails + */ + std::string bind_str = g_settings->get("bind_address"); + try { + bind_addr.Resolve(bind_str.c_str()); + + if (!bind_addr.isIPv6()) { + address = bind_addr; + } + } catch (ResolveError &e) { + } + + UDPSocket socket(false); + socket.Bind(address); + + const char sendbuffer[] = "hello world!"; + /* + * If there is a bind address, use it. + * It's useful in container environments + */ + if (address != Address(0, 0, 0, 0, port)) + socket.Send(address, sendbuffer, sizeof(sendbuffer)); + else + socket.Send(Address(127, 0, 0, 1, port), sendbuffer, sizeof(sendbuffer)); + + sleep_ms(50); + + char rcvbuffer[256] = { 0 }; + Address sender; + for (;;) { + if (socket.Receive(sender, rcvbuffer, sizeof(rcvbuffer)) < 0) + break; + } + //FIXME: This fails on some systems + UASSERT(strncmp(sendbuffer, rcvbuffer, sizeof(sendbuffer)) == 0); + + if (address != Address(0, 0, 0, 0, port)) { + UASSERT(sender.getAddress().sin_addr.s_addr == + address.getAddress().sin_addr.s_addr); + } else { + UASSERT(sender.getAddress().sin_addr.s_addr == + Address(127, 0, 0, 1, 0).getAddress().sin_addr.s_addr); + } +} + +void TestSocket::testIPv6Socket() +{ + Address address6((IPv6AddressBytes *)NULL, port); + UDPSocket socket6; + + if (!socket6.init(true, true)) { + /* Note: Failing to create an IPv6 socket is not technically an + error because the OS may not support IPv6 or it may + have been disabled. IPv6 is not /required/ by + minetest and therefore this should not cause the unit + test to fail + */ + dstream << "WARNING: IPv6 socket creation failed (unit test)" + << std::endl; + return; + } + + const char sendbuffer[] = "hello world!"; + IPv6AddressBytes bytes; + bytes.bytes[15] = 1; + + socket6.Bind(address6); + + try { + socket6.Send(Address(&bytes, port), sendbuffer, sizeof(sendbuffer)); + + sleep_ms(50); + + char rcvbuffer[256] = { 0 }; + Address sender; + + for(;;) { + if (socket6.Receive(sender, rcvbuffer, sizeof(rcvbuffer)) < 0) + break; + } + //FIXME: This fails on some systems + UASSERT(strncmp(sendbuffer, rcvbuffer, sizeof(sendbuffer)) == 0); + UASSERT(memcmp(sender.getAddress6().sin6_addr.s6_addr, + Address(&bytes, 0).getAddress6().sin6_addr.s6_addr, 16) == 0); + } catch (SendFailedException &e) { + errorstream << "IPv6 support enabled but not available!" + << std::endl; + } +} diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp new file mode 100644 index 000000000..df90d37bd --- /dev/null +++ b/src/unittest/test_utilities.cpp @@ -0,0 +1,295 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "util/numeric.h" +#include "util/string.h" + +class TestUtilities : public TestBase { +public: + TestUtilities() { TestManager::registerTestModule(this); } + const char *getName() { return "TestUtilities"; } + + void runTests(IGameDef *gamedef); + + void testAngleWrapAround(); + void testLowercase(); + void testTrim(); + void testIsYes(); + void testRemoveStringEnd(); + void testUrlEncode(); + void testUrlDecode(); + void testPadString(); + void testStartsWith(); + void testStrEqual(); + void testStringTrim(); + void testStrToIntConversion(); + void testStringReplace(); + void testStringAllowed(); + void testUTF8(); + void testWrapRows(); + void testIsNumber(); + void testIsPowerOfTwo(); + void testMyround(); +}; + +static TestUtilities g_test_instance; + +void TestUtilities::runTests(IGameDef *gamedef) +{ + TEST(testAngleWrapAround); + TEST(testLowercase); + TEST(testTrim); + TEST(testIsYes); + TEST(testRemoveStringEnd); + TEST(testUrlEncode); + TEST(testUrlDecode); + TEST(testPadString); + TEST(testStartsWith); + TEST(testStrEqual); + TEST(testStringTrim); + TEST(testStrToIntConversion); + TEST(testStringReplace); + TEST(testStringAllowed); + TEST(testUTF8); + TEST(testWrapRows); + TEST(testIsNumber); + TEST(testIsPowerOfTwo); + TEST(testMyround); +} + +//////////////////////////////////////////////////////////////////////////////// + +inline float ref_WrapDegrees180(float f) +{ + // This is a slower alternative to the wrapDegrees_180() function; + // used as a reference for testing + float value = fmodf(f + 180, 360); + if (value < 0) + value += 360; + return value - 180; +} + + +inline float ref_WrapDegrees_0_360(float f) +{ + // This is a slower alternative to the wrapDegrees_0_360() function; + // used as a reference for testing + float value = fmodf(f, 360); + if (value < 0) + value += 360; + return value < 0 ? value + 360 : value; +} + + +void TestUtilities::testAngleWrapAround() +{ + UASSERT(fabs(modulo360f(100.0) - 100.0) < 0.001); + UASSERT(fabs(modulo360f(720.5) - 0.5) < 0.001); + UASSERT(fabs(modulo360f(-0.5) - (-0.5)) < 0.001); + UASSERT(fabs(modulo360f(-365.5) - (-5.5)) < 0.001); + + for (float f = -720; f <= -360; f += 0.25) { + UASSERT(fabs(modulo360f(f) - modulo360f(f + 360)) < 0.001); + } + + for (float f = -1440; f <= 1440; f += 0.25) { + UASSERT(fabs(modulo360f(f) - fmodf(f, 360)) < 0.001); + UASSERT(fabs(wrapDegrees_180(f) - ref_WrapDegrees180(f)) < 0.001); + UASSERT(fabs(wrapDegrees_0_360(f) - ref_WrapDegrees_0_360(f)) < 0.001); + UASSERT(wrapDegrees_0_360(fabs(wrapDegrees_180(f) - wrapDegrees_0_360(f))) < 0.001); + } +} + + +void TestUtilities::testLowercase() +{ + UASSERT(lowercase("Foo bAR") == "foo bar"); +} + + +void TestUtilities::testTrim() +{ + UASSERT(trim("") == ""); + UASSERT(trim("dirt_with_grass") == "dirt_with_grass"); + UASSERT(trim("\n \t\r Foo bAR \r\n\t\t ") == "Foo bAR"); + UASSERT(trim("\n \t\r \r\n\t\t ") == ""); +} + + +void TestUtilities::testIsYes() +{ + UASSERT(is_yes("YeS") == true); + UASSERT(is_yes("") == false); + UASSERT(is_yes("FAlse") == false); + UASSERT(is_yes("-1") == true); + UASSERT(is_yes("0") == false); + UASSERT(is_yes("1") == true); + UASSERT(is_yes("2") == true); +} + + +void TestUtilities::testRemoveStringEnd() +{ + const char *ends[] = {"abc", "c", "bc", "", NULL}; + UASSERT(removeStringEnd("abc", ends) == ""); + UASSERT(removeStringEnd("bc", ends) == "b"); + UASSERT(removeStringEnd("12c", ends) == "12"); + UASSERT(removeStringEnd("foo", ends) == ""); +} + + +void TestUtilities::testUrlEncode() +{ + UASSERT(urlencode("\"Aardvarks lurk, OK?\"") + == "%22Aardvarks%20lurk%2C%20OK%3F%22"); +} + + +void TestUtilities::testUrlDecode() +{ + UASSERT(urldecode("%22Aardvarks%20lurk%2C%20OK%3F%22") + == "\"Aardvarks lurk, OK?\""); +} + + +void TestUtilities::testPadString() +{ + UASSERT(padStringRight("hello", 8) == "hello "); +} + +void TestUtilities::testStartsWith() +{ + UASSERT(str_starts_with(std::string(), std::string()) == true); + UASSERT(str_starts_with(std::string("the sharp pickaxe"), + std::string()) == true); + UASSERT(str_starts_with(std::string("the sharp pickaxe"), + std::string("the")) == true); + UASSERT(str_starts_with(std::string("the sharp pickaxe"), + std::string("The")) == false); + UASSERT(str_starts_with(std::string("the sharp pickaxe"), + std::string("The"), true) == true); + UASSERT(str_starts_with(std::string("T"), std::string("The")) == false); +} + +void TestUtilities::testStrEqual() +{ + UASSERT(str_equal(narrow_to_wide("abc"), narrow_to_wide("abc"))); + UASSERT(str_equal(narrow_to_wide("ABC"), narrow_to_wide("abc"), true)); +} + + +void TestUtilities::testStringTrim() +{ + UASSERT(trim(" a") == "a"); + UASSERT(trim(" a ") == "a"); + UASSERT(trim("a ") == "a"); + UASSERT(trim("") == ""); +} + + +void TestUtilities::testStrToIntConversion() +{ + UASSERT(mystoi("123", 0, 1000) == 123); + UASSERT(mystoi("123", 0, 10) == 10); +} + + +void TestUtilities::testStringReplace() +{ + std::string test_str; + test_str = "Hello there"; + str_replace(test_str, "there", "world"); + UASSERT(test_str == "Hello world"); + test_str = "ThisAisAaAtest"; + str_replace(test_str, 'A', ' '); + UASSERT(test_str == "This is a test"); +} + + +void TestUtilities::testStringAllowed() +{ + UASSERT(string_allowed("hello", "abcdefghijklmno") == true); + UASSERT(string_allowed("123", "abcdefghijklmno") == false); + UASSERT(string_allowed_blacklist("hello", "123") == true); + UASSERT(string_allowed_blacklist("hello123", "123") == false); +} + +void TestUtilities::testUTF8() +{ + UASSERT(wide_to_utf8(utf8_to_wide("")) == ""); + UASSERT(wide_to_utf8(utf8_to_wide("the shovel dug a crumbly node!")) + == "the shovel dug a crumbly node!"); +} + +void TestUtilities::testWrapRows() +{ + UASSERT(wrap_rows("12345678",4) == "1234\n5678"); + // test that wrap_rows doesn't wrap inside multibyte sequences + { + const unsigned char s[] = { + 0x2f, 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x72, 0x61, 0x70, 0x74, 0x6f, + 0x72, 0x2f, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0x2f, + 0x6d, 0x69, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x62, 0x69, + 0x6e, 0x2f, 0x2e, 0x2e, 0}; + std::string str((char *)s); + UASSERT(utf8_to_wide(wrap_rows(str, 20)) != L"<invalid UTF-8 string>"); + }; + { + const unsigned char s[] = { + 0x74, 0x65, 0x73, 0x74, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, + 0xd1, 0x82, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, + 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0}; + std::string str((char *)s); + UASSERT(utf8_to_wide(wrap_rows(str, 8)) != L"<invalid UTF-8 string>"); + } +} + + +void TestUtilities::testIsNumber() +{ + UASSERT(is_number("123") == true); + UASSERT(is_number("") == false); + UASSERT(is_number("123a") == false); +} + + +void TestUtilities::testIsPowerOfTwo() +{ + UASSERT(is_power_of_two(0) == false); + UASSERT(is_power_of_two(1) == true); + UASSERT(is_power_of_two(2) == true); + UASSERT(is_power_of_two(3) == false); + for (int exponent = 2; exponent <= 31; ++exponent) { + UASSERT(is_power_of_two((1 << exponent) - 1) == false); + UASSERT(is_power_of_two((1 << exponent)) == true); + UASSERT(is_power_of_two((1 << exponent) + 1) == false); + } + UASSERT(is_power_of_two((u32)-1) == false); +} + +void TestUtilities::testMyround() +{ + UASSERT(myround(4.6f) == 5); + UASSERT(myround(1.2f) == 1); + UASSERT(myround(-3.1f) == -3); + UASSERT(myround(-6.5f) == -7); +} + diff --git a/src/unittest/test_voxelalgorithms.cpp b/src/unittest/test_voxelalgorithms.cpp new file mode 100644 index 000000000..31b9cadd5 --- /dev/null +++ b/src/unittest/test_voxelalgorithms.cpp @@ -0,0 +1,204 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include "gamedef.h" +#include "voxelalgorithms.h" + +class TestVoxelAlgorithms : public TestBase { +public: + TestVoxelAlgorithms() { TestManager::registerTestModule(this); } + const char *getName() { return "TestVoxelAlgorithms"; } + + void runTests(IGameDef *gamedef); + + void testPropogateSunlight(INodeDefManager *ndef); + void testClearLightAndCollectSources(INodeDefManager *ndef); +}; + +static TestVoxelAlgorithms g_test_instance; + +void TestVoxelAlgorithms::runTests(IGameDef *gamedef) +{ + INodeDefManager *ndef = gamedef->getNodeDefManager(); + + TEST(testPropogateSunlight, ndef); + TEST(testClearLightAndCollectSources, ndef); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestVoxelAlgorithms::testPropogateSunlight(INodeDefManager *ndef) +{ + VoxelManipulator v; + + for (u16 z = 0; z < 3; z++) + for (u16 y = 0; y < 3; y++) + for (u16 x = 0; x < 3; x++) { + v3s16 p(x,y,z); + v.setNodeNoRef(p, MapNode(CONTENT_AIR)); + } + + VoxelArea a(v3s16(0,0,0), v3s16(2,2,2)); + + { + std::set<v3s16> light_sources; + voxalgo::setLight(v, a, 0, ndef); + voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( + v, a, true, light_sources, ndef); + //v.print(dstream, ndef, VOXELPRINT_LIGHT_DAY); + UASSERT(res.bottom_sunlight_valid == true); + UASSERT(v.getNode(v3s16(1,1,1)).getLight(LIGHTBANK_DAY, ndef) + == LIGHT_SUN); + } + + v.setNodeNoRef(v3s16(0,0,0), MapNode(t_CONTENT_STONE)); + + { + std::set<v3s16> light_sources; + voxalgo::setLight(v, a, 0, ndef); + voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( + v, a, true, light_sources, ndef); + UASSERT(res.bottom_sunlight_valid == true); + UASSERT(v.getNode(v3s16(1,1,1)).getLight(LIGHTBANK_DAY, ndef) + == LIGHT_SUN); + } + + { + std::set<v3s16> light_sources; + voxalgo::setLight(v, a, 0, ndef); + voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( + v, a, false, light_sources, ndef); + UASSERT(res.bottom_sunlight_valid == true); + UASSERT(v.getNode(v3s16(2,0,2)).getLight(LIGHTBANK_DAY, ndef) + == 0); + } + + v.setNodeNoRef(v3s16(1,3,2), MapNode(t_CONTENT_STONE)); + + { + std::set<v3s16> light_sources; + voxalgo::setLight(v, a, 0, ndef); + voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( + v, a, true, light_sources, ndef); + UASSERT(res.bottom_sunlight_valid == true); + UASSERT(v.getNode(v3s16(1,1,2)).getLight(LIGHTBANK_DAY, ndef) + == 0); + } + + { + std::set<v3s16> light_sources; + voxalgo::setLight(v, a, 0, ndef); + voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( + v, a, false, light_sources, ndef); + UASSERT(res.bottom_sunlight_valid == true); + UASSERT(v.getNode(v3s16(1,0,2)).getLight(LIGHTBANK_DAY, ndef) + == 0); + } + + { + MapNode n(CONTENT_AIR); + n.setLight(LIGHTBANK_DAY, 10, ndef); + v.setNodeNoRef(v3s16(1,-1,2), n); + } + + { + std::set<v3s16> light_sources; + voxalgo::setLight(v, a, 0, ndef); + voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( + v, a, true, light_sources, ndef); + UASSERT(res.bottom_sunlight_valid == true); + } + + { + std::set<v3s16> light_sources; + voxalgo::setLight(v, a, 0, ndef); + voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( + v, a, false, light_sources, ndef); + UASSERT(res.bottom_sunlight_valid == true); + } + + { + MapNode n(CONTENT_AIR); + n.setLight(LIGHTBANK_DAY, LIGHT_SUN, ndef); + v.setNodeNoRef(v3s16(1,-1,2), n); + } + + { + std::set<v3s16> light_sources; + voxalgo::setLight(v, a, 0, ndef); + voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( + v, a, true, light_sources, ndef); + UASSERT(res.bottom_sunlight_valid == false); + } + + { + std::set<v3s16> light_sources; + voxalgo::setLight(v, a, 0, ndef); + voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( + v, a, false, light_sources, ndef); + UASSERT(res.bottom_sunlight_valid == false); + } + + v.setNodeNoRef(v3s16(1,3,2), MapNode(CONTENT_IGNORE)); + + { + std::set<v3s16> light_sources; + voxalgo::setLight(v, a, 0, ndef); + voxalgo::SunlightPropagateResult res = voxalgo::propagateSunlight( + v, a, true, light_sources, ndef); + UASSERT(res.bottom_sunlight_valid == true); + } +} + +void TestVoxelAlgorithms::testClearLightAndCollectSources(INodeDefManager *ndef) +{ + VoxelManipulator v; + + for (u16 z = 0; z < 3; z++) + for (u16 y = 0; y < 3; y++) + for (u16 x = 0; x < 3; x++) { + v3s16 p(x,y,z); + v.setNode(p, MapNode(CONTENT_AIR)); + } + + VoxelArea a(v3s16(0,0,0), v3s16(2,2,2)); + v.setNodeNoRef(v3s16(0,0,0), MapNode(t_CONTENT_STONE)); + v.setNodeNoRef(v3s16(1,1,1), MapNode(t_CONTENT_TORCH)); + + { + MapNode n(CONTENT_AIR); + n.setLight(LIGHTBANK_DAY, 1, ndef); + v.setNode(v3s16(1,1,2), n); + } + + { + std::set<v3s16> light_sources; + std::map<v3s16, u8> unlight_from; + voxalgo::clearLightAndCollectSources(v, a, LIGHTBANK_DAY, + ndef, light_sources, unlight_from); + //v.print(dstream, ndef, VOXELPRINT_LIGHT_DAY); + UASSERT(v.getNode(v3s16(0,1,1)).getLight(LIGHTBANK_DAY, ndef) == 0); + UASSERT(light_sources.find(v3s16(1,1,1)) != light_sources.end()); + UASSERT(light_sources.size() == 1); + UASSERT(unlight_from.find(v3s16(1,1,2)) != unlight_from.end()); + UASSERT(unlight_from.size() == 1); + } +} diff --git a/src/unittest/test_voxelmanipulator.cpp b/src/unittest/test_voxelmanipulator.cpp new file mode 100644 index 000000000..7f8ba3849 --- /dev/null +++ b/src/unittest/test_voxelmanipulator.cpp @@ -0,0 +1,108 @@ +/* +Minetest +Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "test.h" + +#include <algorithm> + +#include "gamedef.h" +#include "log.h" +#include "voxel.h" + +class TestVoxelManipulator : public TestBase { +public: + TestVoxelManipulator() { TestManager::registerTestModule(this); } + const char *getName() { return "TestVoxelManipulator"; } + + void runTests(IGameDef *gamedef); + + void testVoxelArea(); + void testVoxelManipulator(INodeDefManager *nodedef); +}; + +static TestVoxelManipulator g_test_instance; + +void TestVoxelManipulator::runTests(IGameDef *gamedef) +{ + TEST(testVoxelArea); + TEST(testVoxelManipulator, gamedef->getNodeDefManager()); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TestVoxelManipulator::testVoxelArea() +{ + VoxelArea a(v3s16(-1,-1,-1), v3s16(1,1,1)); + UASSERT(a.index(0,0,0) == 1*3*3 + 1*3 + 1); + UASSERT(a.index(-1,-1,-1) == 0); + + VoxelArea c(v3s16(-2,-2,-2), v3s16(2,2,2)); + // An area that is 1 bigger in x+ and z- + VoxelArea d(v3s16(-2,-2,-3), v3s16(3,2,2)); + + std::list<VoxelArea> aa; + d.diff(c, aa); + + // Correct results + std::vector<VoxelArea> results; + results.push_back(VoxelArea(v3s16(-2,-2,-3), v3s16(3,2,-3))); + results.push_back(VoxelArea(v3s16(3,-2,-2), v3s16(3,2,2))); + + UASSERT(aa.size() == results.size()); + + infostream<<"Result of diff:"<<std::endl; + for (std::list<VoxelArea>::const_iterator + it = aa.begin(); it != aa.end(); ++it) { + it->print(infostream); + infostream << std::endl; + + std::vector<VoxelArea>::iterator j; + j = std::find(results.begin(), results.end(), *it); + UASSERT(j != results.end()); + results.erase(j); + } +} + + +void TestVoxelManipulator::testVoxelManipulator(INodeDefManager *nodedef) +{ + VoxelManipulator v; + + v.print(infostream, nodedef); + + infostream << "*** Setting (-1,0,-1)=2 ***" << std::endl; + v.setNodeNoRef(v3s16(-1,0,-1), MapNode(t_CONTENT_GRASS)); + + v.print(infostream, nodedef); + UASSERT(v.getNode(v3s16(-1,0,-1)).getContent() == t_CONTENT_GRASS); + + infostream << "*** Reading from inexistent (0,0,-1) ***" << std::endl; + + EXCEPTION_CHECK(InvalidPositionException, v.getNode(v3s16(0,0,-1))); + v.print(infostream, nodedef); + + infostream << "*** Adding area ***" << std::endl; + + VoxelArea a(v3s16(-1,-1,-1), v3s16(1,1,1)); + v.addArea(a); + v.print(infostream, nodedef); + + UASSERT(v.getNode(v3s16(-1,0,-1)).getContent() == t_CONTENT_GRASS); + EXCEPTION_CHECK(InvalidPositionException, v.getNode(v3s16(0,1,1))); +} diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 9cb8a19b6..33900a43a 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -1,8 +1,14 @@ set(UTIL_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/auth.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp ${CMAKE_CURRENT_SOURCE_DIR}/directiontables.cpp ${CMAKE_CURRENT_SOURCE_DIR}/numeric.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pointedthing.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serialize.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sha1.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sha256.c ${CMAKE_CURRENT_SOURCE_DIR}/string.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/srp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/timetaker.cpp PARENT_SCOPE) + diff --git a/src/util/auth.cpp b/src/util/auth.cpp new file mode 100644 index 000000000..df8940e87 --- /dev/null +++ b/src/util/auth.cpp @@ -0,0 +1,126 @@ +/* +Minetest +Copyright (C) 2015 est31 <MTest31@outlook.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include <algorithm> +#include <string> +#include "auth.h" +#include "base64.h" +#include "sha1.h" +#include "srp.h" +#include "string.h" + +// Get an sha-1 hash of the player's name combined with +// the password entered. That's what the server uses as +// their password. (Exception : if the password field is +// blank, we send a blank password - this is for backwards +// compatibility with password-less players). +std::string translatePassword(const std::string &name, + const std::string &password) +{ + if (password.length() == 0) + return ""; + + std::string slt = name + password; + SHA1 sha1; + sha1.addBytes(slt.c_str(), slt.length()); + unsigned char *digest = sha1.getDigest(); + std::string pwd = base64_encode(digest, 20); + free(digest); + return pwd; +} + +void getSRPVerifier(const std::string &name, + const std::string &password, char **salt, size_t *salt_len, + char **bytes_v, size_t *len_v) +{ + std::string n_name = lowercase(name); + srp_create_salted_verification_key(SRP_SHA256, SRP_NG_2048, + n_name.c_str(), (const unsigned char *)password.c_str(), + password.size(), (unsigned char **)salt, salt_len, + (unsigned char **)bytes_v, len_v, NULL, NULL); +} + +// Get a db-ready SRP verifier +// If the salt param is NULL, one is automatically generated. +// Please free() it afterwards. You shouldn't use it for other purposes, +// as you will need the contents of salt_len too. +inline static std::string getSRPVerifier(const std::string &name, + const std::string &password, char ** salt, size_t salt_len) +{ + char * bytes_v = NULL; + size_t len_v; + getSRPVerifier(name, password, salt, &salt_len, + &bytes_v, &len_v); + std::string ret_val = encodeSRPVerifier(std::string(bytes_v, len_v), + std::string(*salt, salt_len)); + free(bytes_v); + return ret_val; +} + +// Get a db-ready SRP verifier +std::string getSRPVerifier(const std::string &name, + const std::string &password) +{ + char * salt = NULL; + std::string ret_val = getSRPVerifier(name, + password, &salt, 0); + free(salt); + return ret_val; +} + +// Get a db-ready SRP verifier +std::string getSRPVerifier(const std::string &name, + const std::string &password, const std::string &salt) +{ + // The implementation won't change the salt if its set, + // therefore we can cast. + char *salt_cstr = (char *)salt.c_str(); + return getSRPVerifier(name, password, + &salt_cstr, salt.size()); +} + +// Make a SRP verifier db-ready +std::string encodeSRPVerifier(const std::string &verifier, + const std::string &salt) +{ + std::ostringstream ret_str; + ret_str << "#1#" + << base64_encode((unsigned char*) salt.c_str(), salt.size()) << "#" + << base64_encode((unsigned char*) verifier.c_str(), verifier.size()); + return ret_str.str(); +} + +bool decodeSRPVerifier(const std::string &enc_pwd, + std::string *salt, std::string *bytes_v) +{ + std::vector<std::string> pwd_components = str_split(enc_pwd, '#'); + + if ((pwd_components.size() != 4) + || (pwd_components[1] != "1") // 1 means srp + || !base64_is_valid(pwd_components[2]) + || !base64_is_valid(pwd_components[3])) + return false; + + std::string salt_str = base64_decode(pwd_components[2]); + std::string bytes_v_str = base64_decode(pwd_components[3]); + *salt = salt_str; + *bytes_v = bytes_v_str; + return true; + +} diff --git a/src/util/auth.h b/src/util/auth.h new file mode 100644 index 000000000..36d8c20a4 --- /dev/null +++ b/src/util/auth.h @@ -0,0 +1,37 @@ +/* +Minetest +Copyright (C) 2015 est31 <MTest31@outlook.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef AUTH_H +#define AUTH_H + +std::string translatePassword(const std::string &name, + const std::string &password); +void getSRPVerifier(const std::string &name, + const std::string &password, char **salt, size_t *salt_len, + char **bytes_v, size_t *len_v); +std::string getSRPVerifier(const std::string &name, + const std::string &password); +std::string getSRPVerifier(const std::string &name, + const std::string &password, const std::string &salt); +std::string encodeSRPVerifier(const std::string &verifier, + const std::string &salt); +bool decodeSRPVerifier(const std::string &enc_pwd, + std::string *salt, std::string *bytes_v); + +#endif
\ No newline at end of file diff --git a/src/base64.cpp b/src/util/base64.cpp index e14de7de2..e14de7de2 100644 --- a/src/base64.cpp +++ b/src/util/base64.cpp diff --git a/src/base64.h b/src/util/base64.h index 1cb175518..1cb175518 100644 --- a/src/base64.h +++ b/src/util/base64.h diff --git a/src/util/container.h b/src/util/container.h index 5e9f13d88..267d54c16 100644 --- a/src/util/container.h +++ b/src/util/container.h @@ -77,7 +77,6 @@ private: std::queue<Value> m_queue; }; -#if 1 template<typename Key, typename Value> class MutexedMap { @@ -109,9 +108,9 @@ public: return true; } - std::list<Value> getValues() + std::vector<Value> getValues() { - std::list<Value> result; + std::vector<Value> result; for(typename std::map<Key, Value>::iterator i = m_values.begin(); i != m_values.end(); ++i){ @@ -129,7 +128,6 @@ private: std::map<Key, Value> m_values; JMutex m_mutex; }; -#endif /* Generates ids for comparable values. @@ -186,67 +184,6 @@ private: }; /* -FIFO queue (well, actually a FILO also) -*/ -template<typename T> -class Queue -{ -public: - Queue(): - m_list_size(0) - {} - - void push_back(T t) - { - m_list.push_back(t); - ++m_list_size; - } - - void push_front(T t) - { - m_list.push_front(t); - ++m_list_size; - } - - T pop_front() - { - if(m_list.empty()) - throw ItemNotFoundException("Queue: queue is empty"); - - typename std::list<T>::iterator begin = m_list.begin(); - T t = *begin; - m_list.erase(begin); - --m_list_size; - return t; - } - T pop_back() - { - if(m_list.empty()) - throw ItemNotFoundException("Queue: queue is empty"); - - typename std::list<T>::iterator last = m_list.back(); - T t = *last; - m_list.erase(last); - --m_list_size; - return t; - } - - u32 size() - { - return m_list_size; - } - - bool empty() - { - return m_list.empty(); - } - -protected: - std::list<T> m_list; - u32 m_list_size; -}; - -/* Thread-safe FIFO queue (well, actually a FILO also) */ @@ -263,12 +200,12 @@ public: bool empty() { JMutexAutoLock lock(m_mutex); - return (m_size.GetValue() == 0); + return (m_queue.size() == 0); } void push_back(T t) { JMutexAutoLock lock(m_mutex); - m_list.push_back(t); + m_queue.push_back(t); m_size.Post(); } @@ -277,34 +214,28 @@ public: */ T pop_frontNoEx(u32 wait_time_max_ms) { - if (m_size.Wait(wait_time_max_ms)) - { + if (m_size.Wait(wait_time_max_ms)) { JMutexAutoLock lock(m_mutex); - typename std::list<T>::iterator begin = m_list.begin(); - T t = *begin; - m_list.erase(begin); + T t = m_queue.front(); + m_queue.pop_front(); return t; } - else - { + else { return T(); } } T pop_front(u32 wait_time_max_ms) { - if (m_size.Wait(wait_time_max_ms)) - { + if (m_size.Wait(wait_time_max_ms)) { JMutexAutoLock lock(m_mutex); - typename std::list<T>::iterator begin = m_list.begin(); - T t = *begin; - m_list.erase(begin); + T t = m_queue.front(); + m_queue.pop_front(); return t; } - else - { + else { throw ItemNotFoundException("MutexedQueue: queue is empty"); } } @@ -315,26 +246,21 @@ public: JMutexAutoLock lock(m_mutex); - typename std::list<T>::iterator begin = m_list.begin(); - T t = *begin; - m_list.erase(begin); + T t = m_queue.front(); + m_queue.pop_front(); return t; } T pop_back(u32 wait_time_max_ms=0) { - if (m_size.Wait(wait_time_max_ms)) - { + if (m_size.Wait(wait_time_max_ms)) { JMutexAutoLock lock(m_mutex); - typename std::list<T>::iterator last = m_list.end(); - last--; - T t = *last; - m_list.erase(last); + T t = m_queue.back(); + m_queue.pop_back(); return t; } - else - { + else { throw ItemNotFoundException("MutexedQueue: queue is empty"); } } @@ -344,18 +270,14 @@ public: */ T pop_backNoEx(u32 wait_time_max_ms=0) { - if (m_size.Wait(wait_time_max_ms)) - { + if (m_size.Wait(wait_time_max_ms)) { JMutexAutoLock lock(m_mutex); - typename std::list<T>::iterator last = m_list.end(); - last--; - T t = *last; - m_list.erase(last); + T t = m_queue.back(); + m_queue.pop_back(); return t; } - else - { + else { return T(); } } @@ -366,10 +288,8 @@ public: JMutexAutoLock lock(m_mutex); - typename std::list<T>::iterator last = m_list.end(); - last--; - T t = *last; - m_list.erase(last); + T t = m_queue.back(); + m_queue.pop_back(); return t; } @@ -379,17 +299,84 @@ protected: return m_mutex; } - // NEVER EVER modify the >>list<< you got by using this function! - // You may only modify it's content - std::list<T> & getList() + std::deque<T> & getQueue() { - return m_list; + return m_queue; } + std::deque<T> m_queue; JMutex m_mutex; - std::list<T> m_list; JSemaphore m_size; }; +template<typename K, typename V> +class LRUCache +{ +public: + LRUCache(size_t limit, void (*cache_miss)(void *data, const K &key, V *dest), + void *data) + { + m_limit = limit; + m_cache_miss = cache_miss; + m_cache_miss_data = data; + } + + void setLimit(size_t limit) + { + m_limit = limit; + invalidate(); + } + + void invalidate() + { + m_map.clear(); + m_queue.clear(); + } + + const V *lookupCache(K key) + { + typename cache_type::iterator it = m_map.find(key); + V *ret; + if (it != m_map.end()) { + // found! + + cache_entry_t &entry = it->second; + + ret = &entry.second; + + // update the usage information + m_queue.erase(entry.first); + m_queue.push_front(key); + entry.first = m_queue.begin(); + } else { + // cache miss -- enter into cache + cache_entry_t &entry = + m_map[key]; + ret = &entry.second; + m_cache_miss(m_cache_miss_data, key, &entry.second); + + // delete old entries + if (m_queue.size() == m_limit) { + const K &id = m_queue.back(); + m_map.erase(id); + m_queue.pop_back(); + } + + m_queue.push_front(key); + entry.first = m_queue.begin(); + } + return ret; + } +private: + void (*m_cache_miss)(void *data, const K &key, V *dest); + void *m_cache_miss_data; + size_t m_limit; + typedef typename std::template pair<typename std::template list<K>::iterator, V> cache_entry_t; + typedef std::template map<K, cache_entry_t> cache_type; + cache_type m_map; + // we can't use std::deque here, because its iterators get invalidated + std::list<K> m_queue; +}; + #endif diff --git a/src/hex.h b/src/util/hex.h index 6f00a79bf..6f00a79bf 100644 --- a/src/hex.h +++ b/src/util/hex.h diff --git a/src/util/md32_common.h b/src/util/md32_common.h new file mode 100644 index 000000000..085d1d7a5 --- /dev/null +++ b/src/util/md32_common.h @@ -0,0 +1,428 @@ +/* md32_common.h file used by sha256 implementation */ +/* ==================================================================== + * Copyright (c) 1999-2007 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * licensing@OpenSSL.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + */ + +/*- + * This is a generic 32 bit "collector" for message digest algorithms. + * Whenever needed it collects input character stream into chunks of + * 32 bit values and invokes a block function that performs actual hash + * calculations. + * + * Porting guide. + * + * Obligatory macros: + * + * DATA_ORDER_IS_BIG_ENDIAN or DATA_ORDER_IS_LITTLE_ENDIAN + * this macro defines byte order of input stream. + * HASH_CBLOCK + * size of a unit chunk HASH_BLOCK operates on. + * HASH_LONG + * has to be at lest 32 bit wide, if it's wider, then + * HASH_LONG_LOG2 *has to* be defined along + * HASH_CTX + * context structure that at least contains following + * members: + * typedef struct { + * ... + * HASH_LONG Nl,Nh; + * either { + * HASH_LONG data[HASH_LBLOCK]; + * unsigned char data[HASH_CBLOCK]; + * }; + * unsigned int num; + * ... + * } HASH_CTX; + * data[] vector is expected to be zeroed upon first call to + * HASH_UPDATE. + * HASH_UPDATE + * name of "Update" function, implemented here. + * HASH_TRANSFORM + * name of "Transform" function, implemented here. + * HASH_FINAL + * name of "Final" function, implemented here. + * HASH_BLOCK_DATA_ORDER + * name of "block" function capable of treating *unaligned* input + * message in original (data) byte order, implemented externally. + * HASH_MAKE_STRING + * macro convering context variables to an ASCII hash string. + * + * MD5 example: + * + * #define DATA_ORDER_IS_LITTLE_ENDIAN + * + * #define HASH_LONG MD5_LONG + * #define HASH_LONG_LOG2 MD5_LONG_LOG2 + * #define HASH_CTX MD5_CTX + * #define HASH_CBLOCK MD5_CBLOCK + * #define HASH_UPDATE MD5_Update + * #define HASH_TRANSFORM MD5_Transform + * #define HASH_FINAL MD5_Final + * #define HASH_BLOCK_DATA_ORDER md5_block_data_order + * + * <appro@fy.chalmers.se> + */ + +#if !defined(DATA_ORDER_IS_BIG_ENDIAN) && !defined(DATA_ORDER_IS_LITTLE_ENDIAN) +# error "DATA_ORDER must be defined!" +#endif + +#ifndef HASH_CBLOCK +# error "HASH_CBLOCK must be defined!" +#endif +#ifndef HASH_LONG +# error "HASH_LONG must be defined!" +#endif +#ifndef HASH_CTX +# error "HASH_CTX must be defined!" +#endif + +#ifndef HASH_UPDATE +# error "HASH_UPDATE must be defined!" +#endif +#ifndef HASH_TRANSFORM +# error "HASH_TRANSFORM must be defined!" +#endif +#ifndef HASH_FINAL +# error "HASH_FINAL must be defined!" +#endif + +#ifndef HASH_BLOCK_DATA_ORDER +# error "HASH_BLOCK_DATA_ORDER must be defined!" +#endif + +/* + * Engage compiler specific rotate intrinsic function if available. + */ +#undef ROTATE +#ifndef PEDANTIC +# if defined(_MSC_VER) +# define ROTATE(a,n) _lrotl(a,n) +# elif defined(__ICC) +# define ROTATE(a,n) _rotl(a,n) +# elif defined(__MWERKS__) +# if defined(__POWERPC__) +# define ROTATE(a,n) __rlwinm(a,n,0,31) +# elif defined(__MC68K__) + /* Motorola specific tweak. <appro@fy.chalmers.se> */ +# define ROTATE(a,n) ( n<24 ? __rol(a,n) : __ror(a,32-n) ) +# else +# define ROTATE(a,n) __rol(a,n) +# endif +# elif defined(__GNUC__) && __GNUC__>=2 && !defined(OPENSSL_NO_ASM) && !defined(OPENSSL_NO_INLINE_ASM) + /* + * Some GNU C inline assembler templates. Note that these are + * rotates by *constant* number of bits! But that's exactly + * what we need here... + * <appro@fy.chalmers.se> + */ +# if defined(__i386) || defined(__i386__) || defined(__x86_64) || defined(__x86_64__) +# define ROTATE(a,n) ({ register unsigned int ret; \ + asm ( \ + "roll %1,%0" \ + : "=r"(ret) \ + : "I"(n), "0"((unsigned int)(a)) \ + : "cc"); \ + ret; \ + }) +# elif defined(_ARCH_PPC) || defined(_ARCH_PPC64) || \ + defined(__powerpc) || defined(__ppc__) || defined(__powerpc64__) +# define ROTATE(a,n) ({ register unsigned int ret; \ + asm ( \ + "rlwinm %0,%1,%2,0,31" \ + : "=r"(ret) \ + : "r"(a), "I"(n)); \ + ret; \ + }) +# elif defined(__s390x__) +# define ROTATE(a,n) ({ register unsigned int ret; \ + asm ("rll %0,%1,%2" \ + : "=r"(ret) \ + : "r"(a), "I"(n)); \ + ret; \ + }) +# endif +# endif +#endif /* PEDANTIC */ + +#ifndef ROTATE +# define ROTATE(a,n) (((a)<<(n))|(((a)&0xffffffff)>>(32-(n)))) +#endif + +#if defined(DATA_ORDER_IS_BIG_ENDIAN) + +# ifndef PEDANTIC +# if defined(__GNUC__) && __GNUC__>=2 && !defined(OPENSSL_NO_ASM) && !defined(OPENSSL_NO_INLINE_ASM) +# if ((defined(__i386) || defined(__i386__)) && !defined(I386_ONLY)) || \ + (defined(__x86_64) || defined(__x86_64__)) +# if !defined(B_ENDIAN) + /* + * This gives ~30-40% performance improvement in SHA-256 compiled + * with gcc [on P4]. Well, first macro to be frank. We can pull + * this trick on x86* platforms only, because these CPUs can fetch + * unaligned data without raising an exception. + */ +# define HOST_c2l(c,l) ({ unsigned int r=*((const unsigned int *)(c)); \ + asm ("bswapl %0":"=r"(r):"0"(r)); \ + (c)+=4; (l)=r; }) +# define HOST_l2c(l,c) ({ unsigned int r=(l); \ + asm ("bswapl %0":"=r"(r):"0"(r)); \ + *((unsigned int *)(c))=r; (c)+=4; r; }) +# endif +# elif defined(__aarch64__) +# if defined(__BYTE_ORDER__) +# if defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ +# define HOST_c2l(c,l) ({ unsigned int r; \ + asm ("rev %w0,%w1" \ + :"=r"(r) \ + :"r"(*((const unsigned int *)(c))));\ + (c)+=4; (l)=r; }) +# define HOST_l2c(l,c) ({ unsigned int r; \ + asm ("rev %w0,%w1" \ + :"=r"(r) \ + :"r"((unsigned int)(l)));\ + *((unsigned int *)(c))=r; (c)+=4; r; }) +# elif defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__ +# define HOST_c2l(c,l) ((l)=*((const unsigned int *)(c)), (c)+=4, (l)) +# define HOST_l2c(l,c) (*((unsigned int *)(c))=(l), (c)+=4, (l)) +# endif +# endif +# endif +# endif +# if defined(__s390__) || defined(__s390x__) +# define HOST_c2l(c,l) ((l)=*((const unsigned int *)(c)), (c)+=4, (l)) +# define HOST_l2c(l,c) (*((unsigned int *)(c))=(l), (c)+=4, (l)) +# endif +# endif + +# ifndef HOST_c2l +# define HOST_c2l(c,l) (l =(((unsigned long)(*((c)++)))<<24), \ + l|=(((unsigned long)(*((c)++)))<<16), \ + l|=(((unsigned long)(*((c)++)))<< 8), \ + l|=(((unsigned long)(*((c)++))) ) ) +# endif +# ifndef HOST_l2c +# define HOST_l2c(l,c) (*((c)++)=(unsigned char)(((l)>>24)&0xff), \ + *((c)++)=(unsigned char)(((l)>>16)&0xff), \ + *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ + *((c)++)=(unsigned char)(((l) )&0xff), \ + l) +# endif + +#elif defined(DATA_ORDER_IS_LITTLE_ENDIAN) + +# ifndef PEDANTIC +# if defined(__GNUC__) && __GNUC__>=2 && !defined(OPENSSL_NO_ASM) && !defined(OPENSSL_NO_INLINE_ASM) +# if defined(__s390x__) +# define HOST_c2l(c,l) ({ asm ("lrv %0,%1" \ + :"=d"(l) :"m"(*(const unsigned int *)(c)));\ + (c)+=4; (l); }) +# define HOST_l2c(l,c) ({ asm ("strv %1,%0" \ + :"=m"(*(unsigned int *)(c)) :"d"(l));\ + (c)+=4; (l); }) +# endif +# endif +# if defined(__i386) || defined(__i386__) || defined(__x86_64) || defined(__x86_64__) +# ifndef B_ENDIAN + /* See comment in DATA_ORDER_IS_BIG_ENDIAN section. */ +# define HOST_c2l(c,l) ((l)=*((const unsigned int *)(c)), (c)+=4, l) +# define HOST_l2c(l,c) (*((unsigned int *)(c))=(l), (c)+=4, l) +# endif +# endif +# endif + +# ifndef HOST_c2l +# define HOST_c2l(c,l) (l =(((unsigned long)(*((c)++))) ), \ + l|=(((unsigned long)(*((c)++)))<< 8), \ + l|=(((unsigned long)(*((c)++)))<<16), \ + l|=(((unsigned long)(*((c)++)))<<24) ) +# endif +# ifndef HOST_l2c +# define HOST_l2c(l,c) (*((c)++)=(unsigned char)(((l) )&0xff), \ + *((c)++)=(unsigned char)(((l)>> 8)&0xff), \ + *((c)++)=(unsigned char)(((l)>>16)&0xff), \ + *((c)++)=(unsigned char)(((l)>>24)&0xff), \ + l) +# endif + +#endif + +/* + * Time for some action:-) + */ + +int HASH_UPDATE(HASH_CTX *c, const void *data_, size_t len) +{ + const unsigned char *data = data_; + unsigned char *p; + HASH_LONG l; + size_t n; + + if (len == 0) + return 1; + + l = (c->Nl + (((HASH_LONG) len) << 3)) & 0xffffffffUL; + /* + * 95-05-24 eay Fixed a bug with the overflow handling, thanks to Wei Dai + * <weidai@eskimo.com> for pointing it out. + */ + if (l < c->Nl) /* overflow */ + c->Nh++; + c->Nh += (HASH_LONG) (len >> 29); /* might cause compiler warning on + * 16-bit */ + c->Nl = l; + + n = c->num; + if (n != 0) { + p = (unsigned char *)c->data; + + if (len >= HASH_CBLOCK || len + n >= HASH_CBLOCK) { + memcpy(p + n, data, HASH_CBLOCK - n); + HASH_BLOCK_DATA_ORDER(c, p, 1); + n = HASH_CBLOCK - n; + data += n; + len -= n; + c->num = 0; + memset(p, 0, HASH_CBLOCK); /* keep it zeroed */ + } else { + memcpy(p + n, data, len); + c->num += (unsigned int)len; + return 1; + } + } + + n = len / HASH_CBLOCK; + if (n > 0) { + HASH_BLOCK_DATA_ORDER(c, data, n); + n *= HASH_CBLOCK; + data += n; + len -= n; + } + + if (len != 0) { + p = (unsigned char *)c->data; + c->num = (unsigned int)len; + memcpy(p, data, len); + } + return 1; +} + +void HASH_TRANSFORM(HASH_CTX *c, const unsigned char *data) +{ + HASH_BLOCK_DATA_ORDER(c, data, 1); +} + +int HASH_FINAL(unsigned char *md, HASH_CTX *c) +{ + unsigned char *p = (unsigned char *)c->data; + size_t n = c->num; + + p[n] = 0x80; /* there is always room for one */ + n++; + + if (n > (HASH_CBLOCK - 8)) { + memset(p + n, 0, HASH_CBLOCK - n); + n = 0; + HASH_BLOCK_DATA_ORDER(c, p, 1); + } + memset(p + n, 0, HASH_CBLOCK - 8 - n); + + p += HASH_CBLOCK - 8; +#if defined(DATA_ORDER_IS_BIG_ENDIAN) + (void)HOST_l2c(c->Nh, p); + (void)HOST_l2c(c->Nl, p); +#elif defined(DATA_ORDER_IS_LITTLE_ENDIAN) + (void)HOST_l2c(c->Nl, p); + (void)HOST_l2c(c->Nh, p); +#endif + p -= HASH_CBLOCK; + HASH_BLOCK_DATA_ORDER(c, p, 1); + c->num = 0; + memset(p, 0, HASH_CBLOCK); + +#ifndef HASH_MAKE_STRING +# error "HASH_MAKE_STRING must be defined!" +#else + HASH_MAKE_STRING(c, md); +#endif + + return 1; +} + +#ifndef MD32_REG_T +# if defined(__alpha) || defined(__sparcv9) || defined(__mips) +# define MD32_REG_T long +/* + * This comment was originaly written for MD5, which is why it + * discusses A-D. But it basically applies to all 32-bit digests, + * which is why it was moved to common header file. + * + * In case you wonder why A-D are declared as long and not + * as MD5_LONG. Doing so results in slight performance + * boost on LP64 architectures. The catch is we don't + * really care if 32 MSBs of a 64-bit register get polluted + * with eventual overflows as we *save* only 32 LSBs in + * *either* case. Now declaring 'em long excuses the compiler + * from keeping 32 MSBs zeroed resulting in 13% performance + * improvement under SPARC Solaris7/64 and 5% under AlphaLinux. + * Well, to be honest it should say that this *prevents* + * performance degradation. + * <appro@fy.chalmers.se> + */ +# else +/* + * Above is not absolute and there are LP64 compilers that + * generate better code if MD32_REG_T is defined int. The above + * pre-processor condition reflects the circumstances under which + * the conclusion was made and is subject to further extension. + * <appro@fy.chalmers.se> + */ +# define MD32_REG_T int +# endif +#endif diff --git a/src/util/numeric.cpp b/src/util/numeric.cpp index 6173515ee..3fd1c9cf9 100644 --- a/src/util/numeric.cpp +++ b/src/util/numeric.cpp @@ -20,79 +20,89 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "numeric.h" #include "mathconstants.h" -#include "../log.h" +#include "log.h" #include "../constants.h" // BS, MAP_BLOCKSIZE +#include "../noise.h" // PseudoRandom, PcgRandom +#include "../jthread/jmutexautolock.h" #include <string.h> #include <iostream> +std::map<u16, std::vector<v3s16> > FacePositionCache::m_cache; +JMutex FacePositionCache::m_cache_mutex; // Calculate the borders of a "d-radius" cube -void getFacePositions(std::list<v3s16> &list, u16 d) +// TODO: Make it work without mutex and data races, probably thread-local +std::vector<v3s16> FacePositionCache::getFacePositions(u16 d) { - if(d == 0) - { - list.push_back(v3s16(0,0,0)); + JMutexAutoLock cachelock(m_cache_mutex); + if (m_cache.find(d) != m_cache.end()) + return m_cache[d]; + + generateFacePosition(d); + return m_cache[d]; + +} + +void FacePositionCache::generateFacePosition(u16 d) +{ + m_cache[d] = std::vector<v3s16>(); + if(d == 0) { + m_cache[d].push_back(v3s16(0,0,0)); return; } - if(d == 1) - { + if(d == 1) { /* This is an optimized sequence of coordinates. */ - list.push_back(v3s16( 0, 1, 0)); // top - list.push_back(v3s16( 0, 0, 1)); // back - list.push_back(v3s16(-1, 0, 0)); // left - list.push_back(v3s16( 1, 0, 0)); // right - list.push_back(v3s16( 0, 0,-1)); // front - list.push_back(v3s16( 0,-1, 0)); // bottom + m_cache[d].push_back(v3s16( 0, 1, 0)); // top + m_cache[d].push_back(v3s16( 0, 0, 1)); // back + m_cache[d].push_back(v3s16(-1, 0, 0)); // left + m_cache[d].push_back(v3s16( 1, 0, 0)); // right + m_cache[d].push_back(v3s16( 0, 0,-1)); // front + m_cache[d].push_back(v3s16( 0,-1, 0)); // bottom // 6 - list.push_back(v3s16(-1, 0, 1)); // back left - list.push_back(v3s16( 1, 0, 1)); // back right - list.push_back(v3s16(-1, 0,-1)); // front left - list.push_back(v3s16( 1, 0,-1)); // front right - list.push_back(v3s16(-1,-1, 0)); // bottom left - list.push_back(v3s16( 1,-1, 0)); // bottom right - list.push_back(v3s16( 0,-1, 1)); // bottom back - list.push_back(v3s16( 0,-1,-1)); // bottom front - list.push_back(v3s16(-1, 1, 0)); // top left - list.push_back(v3s16( 1, 1, 0)); // top right - list.push_back(v3s16( 0, 1, 1)); // top back - list.push_back(v3s16( 0, 1,-1)); // top front + m_cache[d].push_back(v3s16(-1, 0, 1)); // back left + m_cache[d].push_back(v3s16( 1, 0, 1)); // back right + m_cache[d].push_back(v3s16(-1, 0,-1)); // front left + m_cache[d].push_back(v3s16( 1, 0,-1)); // front right + m_cache[d].push_back(v3s16(-1,-1, 0)); // bottom left + m_cache[d].push_back(v3s16( 1,-1, 0)); // bottom right + m_cache[d].push_back(v3s16( 0,-1, 1)); // bottom back + m_cache[d].push_back(v3s16( 0,-1,-1)); // bottom front + m_cache[d].push_back(v3s16(-1, 1, 0)); // top left + m_cache[d].push_back(v3s16( 1, 1, 0)); // top right + m_cache[d].push_back(v3s16( 0, 1, 1)); // top back + m_cache[d].push_back(v3s16( 0, 1,-1)); // top front // 18 - list.push_back(v3s16(-1, 1, 1)); // top back-left - list.push_back(v3s16( 1, 1, 1)); // top back-right - list.push_back(v3s16(-1, 1,-1)); // top front-left - list.push_back(v3s16( 1, 1,-1)); // top front-right - list.push_back(v3s16(-1,-1, 1)); // bottom back-left - list.push_back(v3s16( 1,-1, 1)); // bottom back-right - list.push_back(v3s16(-1,-1,-1)); // bottom front-left - list.push_back(v3s16( 1,-1,-1)); // bottom front-right + m_cache[d].push_back(v3s16(-1, 1, 1)); // top back-left + m_cache[d].push_back(v3s16( 1, 1, 1)); // top back-right + m_cache[d].push_back(v3s16(-1, 1,-1)); // top front-left + m_cache[d].push_back(v3s16( 1, 1,-1)); // top front-right + m_cache[d].push_back(v3s16(-1,-1, 1)); // bottom back-left + m_cache[d].push_back(v3s16( 1,-1, 1)); // bottom back-right + m_cache[d].push_back(v3s16(-1,-1,-1)); // bottom front-left + m_cache[d].push_back(v3s16( 1,-1,-1)); // bottom front-right // 26 return; } // Take blocks in all sides, starting from y=0 and going +-y - for(s16 y=0; y<=d-1; y++) - { + for(s16 y=0; y<=d-1; y++) { // Left and right side, including borders - for(s16 z=-d; z<=d; z++) - { - list.push_back(v3s16(d,y,z)); - list.push_back(v3s16(-d,y,z)); - if(y != 0) - { - list.push_back(v3s16(d,-y,z)); - list.push_back(v3s16(-d,-y,z)); + for(s16 z=-d; z<=d; z++) { + m_cache[d].push_back(v3s16(d,y,z)); + m_cache[d].push_back(v3s16(-d,y,z)); + if(y != 0) { + m_cache[d].push_back(v3s16(d,-y,z)); + m_cache[d].push_back(v3s16(-d,-y,z)); } } // Back and front side, excluding borders - for(s16 x=-d+1; x<=d-1; x++) - { - list.push_back(v3s16(x,y,d)); - list.push_back(v3s16(x,y,-d)); - if(y != 0) - { - list.push_back(v3s16(x,-y,d)); - list.push_back(v3s16(x,-y,-d)); + for(s16 x=-d+1; x<=d-1; x++) { + m_cache[d].push_back(v3s16(x,y,d)); + m_cache[d].push_back(v3s16(x,y,-d)); + if(y != 0) { + m_cache[d].push_back(v3s16(x,-y,d)); + m_cache[d].push_back(v3s16(x,-y,-d)); } } } @@ -100,10 +110,9 @@ void getFacePositions(std::list<v3s16> &list, u16 d) // Take the bottom and top face with borders // -d<x<d, y=+-d, -d<z<d for(s16 x=-d; x<=d; x++) - for(s16 z=-d; z<=d; z++) - { - list.push_back(v3s16(x,-d,z)); - list.push_back(v3s16(x,d,z)); + for(s16 z=-d; z<=d; z++) { + m_cache[d].push_back(v3s16(x,-d,z)); + m_cache[d].push_back(v3s16(x,d,z)); } } @@ -111,36 +120,32 @@ void getFacePositions(std::list<v3s16> &list, u16 d) myrand */ -static unsigned long next = 1; +PcgRandom g_pcgrand; + +u32 myrand() +{ + return g_pcgrand.next(); +} -/* RAND_MAX assumed to be 32767 */ -int myrand(void) +void mysrand(unsigned int seed) { - next = next * 1103515245 + 12345; - return((unsigned)(next/65536) % 32768); + g_pcgrand.seed(seed); } -void mysrand(unsigned seed) +void myrand_bytes(void *out, size_t len) { - next = seed; + g_pcgrand.bytes(out, len); } int myrand_range(int min, int max) { - if(max-min > MYRAND_MAX) - { - errorstream<<"WARNING: myrand_range: max-min > MYRAND_MAX"<<std::endl; - max = min + MYRAND_MAX; - } - if(min > max) - { - errorstream<<"WARNING: myrand_range: min > max"<<std::endl; - return max; - } - return (myrand()%(max-min+1))+min; + return g_pcgrand.range(min, max); } -// 64-bit unaligned version of MurmurHash + +/* + 64-bit unaligned version of MurmurHash +*/ u64 murmur_hash_64_ua(const void *key, int len, unsigned int seed) { const u64 m = 0xc6a4a7935bd1e995ULL; @@ -155,12 +160,12 @@ u64 murmur_hash_64_ua(const void *key, int len, unsigned int seed) memcpy(&k, data, sizeof(u64)); data++; - k *= m; - k ^= k >> r; - k *= m; - + k *= m; + k ^= k >> r; + k *= m; + h ^= k; - h *= m; + h *= m; } const unsigned char *data2 = (const unsigned char *)data; @@ -174,14 +179,13 @@ u64 murmur_hash_64_ua(const void *key, int len, unsigned int seed) case 1: h ^= (u64)data2[0]; h *= m; } - + h ^= h >> r; h *= m; h ^= h >> r; - - return h; -} + return h; +} /* blockpos: position of block in block coordinates @@ -193,7 +197,7 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, f32 camera_fov, f32 range, f32 *distance_ptr) { v3s16 blockpos_nodes = blockpos_b * MAP_BLOCKSIZE; - + // Block center position v3f blockpos( ((float)blockpos_nodes.X + MAP_BLOCKSIZE/2) * BS, @@ -209,7 +213,7 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, if(distance_ptr) *distance_ptr = d; - + // If block is far away, it's not in sight if(d > range) return false; @@ -217,7 +221,7 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, // Maximum radius of a block. The magic number is // sqrt(3.0) / 2.0 in literal form. f32 block_max_radius = 0.866025403784 * MAP_BLOCKSIZE * BS; - + // If block is (nearly) touching the camera, don't // bother validating further (that is, render it anyway) if(d < block_max_radius) @@ -238,11 +242,10 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, // Cosine of the angle between the camera direction // and the block direction (camera_dir is an unit vector) f32 cosangle = dforward / blockpos_adj.getLength(); - + // If block is not in the field of view, skip it if(cosangle < cos(camera_fov / 2)) return false; return true; } - diff --git a/src/util/numeric.h b/src/util/numeric.h index db1eb003e..9fe08434f 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -24,11 +24,26 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "../irr_v2d.h" #include "../irr_v3d.h" #include "../irr_aabb3d.h" +#include "../jthread/jmutex.h" #include <list> +#include <map> +#include <vector> #include <algorithm> -// Calculate the borders of a "d-radius" cube -void getFacePositions(std::list<v3s16> &list, u16 d); + +/* + * This class permits to cache getFacePosition call results + * This reduces CPU usage and vector calls + */ +class FacePositionCache +{ +public: + static std::vector<v3s16> getFacePositions(u16 d); +private: + static void generateFacePosition(u16 d); + static std::map<u16, std::vector<v3s16> > m_cache; + static JMutex m_cache_mutex; +}; class IndentationRaiser { @@ -171,72 +186,93 @@ inline void sortBoxVerticies(v3s16 &p1, v3s16 &p2) { } -/* - See test.cpp for example cases. - wraps degrees to the range of -360...360 - NOTE: Wrapping to 0...360 is not used because pitch needs negative values. -*/ -inline float wrapDegrees(float f) +/** Returns \p f wrapped to the range [-360, 360] + * + * See test.cpp for example cases. + * + * \note This is also used in cases where degrees wrapped to the range [0, 360] + * is innapropriate (e.g. pitch needs negative values) + * + * \internal functionally equivalent -- although precision may vary slightly -- + * to fmodf((f), 360.0f) however empirical tests indicate that this approach is + * faster. + */ +inline float modulo360f(float f) { - // Take examples of f=10, f=720.5, f=-0.5, f=-360.5 - // This results in - // 10, 720, -1, -361 - int i = floor(f); - // 0, 2, 0, -1 - int l = i / 360; - // NOTE: This would be used for wrapping to 0...360 - // 0, 2, -1, -2 - /*if(i < 0) - l -= 1;*/ - // 0, 720, 0, -360 - int k = l * 360; - // 10, 0.5, -0.5, -0.5 - f -= float(k); - return f; + int sign; + int whole; + float fraction; + + if (f < 0) { + f = -f; + sign = -1; + } else { + sign = 1; + } + + whole = f; + + fraction = f - whole; + whole %= 360; + + return sign * (whole + fraction); } -/* Wrap to 0...360 */ + +/** Returns \p f wrapped to the range [0, 360] + */ inline float wrapDegrees_0_360(float f) { - // Take examples of f=10, f=720.5, f=-0.5, f=-360.5 - // This results in - // 10, 720, -1, -361 - int i = floor(f); - // 0, 2, 0, -1 - int l = i / 360; - // Wrap to 0...360 - // 0, 2, -1, -2 - if(i < 0) - l -= 1; - // 0, 720, 0, -360 - int k = l * 360; - // 10, 0.5, -0.5, -0.5 - f -= float(k); - return f; + float value = modulo360f(f); + return value < 0 ? value + 360 : value; } -/* Wrap to -180...180 */ + +/** Returns \p f wrapped to the range [-180, 180] + */ inline float wrapDegrees_180(float f) { - f += 180; - f = wrapDegrees_0_360(f); - f -= 180; - return f; + float value = modulo360f(f + 180); + if (value < 0) + value += 360; + return value - 180; } /* Pseudo-random (VC++ rand() sucks) */ -int myrand(void); -void mysrand(unsigned seed); -#define MYRAND_MAX 32767 - +#define MYRAND_RANGE 0xffffffff +u32 myrand(); +void mysrand(unsigned int seed); +void myrand_bytes(void *out, size_t len); int myrand_range(int min, int max); /* Miscellaneous functions */ +inline u32 get_bits(u32 x, u32 pos, u32 len) +{ + u32 mask = (1 << len) - 1; + return (x >> pos) & mask; +} + +inline void set_bits(u32 *x, u32 pos, u32 len, u32 val) +{ + u32 mask = (1 << len) - 1; + *x &= ~(mask << pos); + *x |= (val & mask) << pos; +} + +inline u32 calc_parity(u32 v) +{ + v ^= v >> 16; + v ^= v >> 8; + v ^= v >> 4; + v &= 0xf; + return (0x6996 >> v) & 1; +} + u64 murmur_hash_64_ua(const void *key, int len, unsigned int seed); bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, @@ -254,7 +290,7 @@ bool isBlockInSight(v3s16 blockpos_b, v3f camera_pos, v3f camera_dir, */ inline s32 myround(f32 f) { - return floor(f + 0.5); + return (s32)(f < 0.f ? (f - 0.5f) : (f + 0.5f)); } /* @@ -377,5 +413,16 @@ inline bool is_power_of_two(u32 n) return n != 0 && (n & (n-1)) == 0; } -#endif +// Compute next-higher power of 2 efficiently, e.g. for power-of-2 texture sizes. +// Public Domain: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +inline u32 npot2(u32 orig) { + orig--; + orig |= orig >> 1; + orig |= orig >> 2; + orig |= orig >> 4; + orig |= orig >> 8; + orig |= orig >> 16; + return orig + 1; +} +#endif diff --git a/src/util/pointer.h b/src/util/pointer.h index 7922a9b39..7f6654787 100644 --- a/src/util/pointer.h +++ b/src/util/pointer.h @@ -178,6 +178,14 @@ private: unsigned int m_size; }; +/************************************************ + * !!! W A R N I N G !!! * + * !!! A C H T U N G !!! * + * * + * This smart pointer class is NOT thread safe. * + * ONLY use in a single-threaded context! * + * * + ************************************************/ template <typename T> class SharedBuffer { diff --git a/src/util/serialize.cpp b/src/util/serialize.cpp index 8a108a0ff..c0168776e 100644 --- a/src/util/serialize.cpp +++ b/src/util/serialize.cpp @@ -28,81 +28,108 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <iomanip> #include <vector> -// Creates a string with the length as the first two bytes +//// +//// String +//// + std::string serializeString(const std::string &plain) { - //assert(plain.size() <= 65535); - if(plain.size() > 65535) - throw SerializationError("String too long for serializeString"); - char buf[2]; - writeU16((u8*)&buf[0], plain.size()); std::string s; - s.append(buf, 2); - s.append(plain); - return s; -} + char buf[2]; -// Creates a string with the length as the first two bytes from wide string -std::string serializeWideString(const std::wstring &plain) -{ - //assert(plain.size() <= 65535); - if(plain.size() > 65535) + if (plain.size() > STRING_MAX_LEN) throw SerializationError("String too long for serializeString"); - char buf[2]; - writeU16((u8*)buf, plain.size()); - std::string s; + + writeU16((u8 *)&buf[0], plain.size()); s.append(buf, 2); - for(u32 i=0; i<plain.size(); i++) - { - writeU16((u8*)buf, plain[i]); - s.append(buf, 2); - } + + s.append(plain); return s; } -// Reads a string with the length as the first two bytes std::string deSerializeString(std::istream &is) { + std::string s; char buf[2]; + is.read(buf, 2); - if(is.gcount() != 2) + if (is.gcount() != 2) throw SerializationError("deSerializeString: size not read"); - u16 s_size = readU16((u8*)buf); - std::string s; - if(s_size == 0) + + u16 s_size = readU16((u8 *)buf); + if (s_size == 0) return s; + Buffer<char> buf2(s_size); is.read(&buf2[0], s_size); + if (is.gcount() != s_size) + throw SerializationError("deSerializeString: couldn't read all chars"); + s.reserve(s_size); s.append(&buf2[0], s_size); return s; } -// Reads a wide string with the length as the first two bytes +//// +//// Wide String +//// + +std::string serializeWideString(const std::wstring &plain) +{ + std::string s; + char buf[2]; + + if (plain.size() > WIDE_STRING_MAX_LEN) + throw SerializationError("String too long for serializeWideString"); + + writeU16((u8 *)buf, plain.size()); + s.append(buf, 2); + + for (u32 i = 0; i < plain.size(); i++) { + writeU16((u8 *)buf, plain[i]); + s.append(buf, 2); + } + return s; +} + std::wstring deSerializeWideString(std::istream &is) { + std::wstring s; char buf[2]; + is.read(buf, 2); - if(is.gcount() != 2) - throw SerializationError("deSerializeString: size not read"); - u16 s_size = readU16((u8*)buf); - std::wstring s; - if(s_size == 0) + if (is.gcount() != 2) + throw SerializationError("deSerializeWideString: size not read"); + + u16 s_size = readU16((u8 *)buf); + if (s_size == 0) return s; + s.reserve(s_size); - for(u32 i=0; i<s_size; i++) - { + for (u32 i = 0; i < s_size; i++) { is.read(&buf[0], 2); - wchar_t c16 = readU16((u8*)buf); + if (is.gcount() != 2) { + throw SerializationError( + "deSerializeWideString: couldn't read all chars"); + } + + wchar_t c16 = readU16((u8 *)buf); s.append(&c16, 1); } return s; } -// Creates a string with the length as the first four bytes +//// +//// Long String +//// + std::string serializeLongString(const std::string &plain) { char buf[4]; + + if (plain.size() > LONG_STRING_MAX_LEN) + throw SerializationError("String too long for serializeLongString"); + writeU32((u8*)&buf[0], plain.size()); std::string s; s.append(buf, 4); @@ -110,62 +137,88 @@ std::string serializeLongString(const std::string &plain) return s; } -// Reads a string with the length as the first four bytes std::string deSerializeLongString(std::istream &is) { + std::string s; char buf[4]; + is.read(buf, 4); - if(is.gcount() != 4) + if (is.gcount() != 4) throw SerializationError("deSerializeLongString: size not read"); - u32 s_size = readU32((u8*)buf); - std::string s; - if(s_size == 0) + + u32 s_size = readU32((u8 *)buf); + if (s_size == 0) return s; + + // We don't really want a remote attacker to force us to allocate 4GB... + if (s_size > LONG_STRING_MAX_LEN) { + throw SerializationError("deSerializeLongString: " + "string too long: " + itos(s_size) + " bytes"); + } + Buffer<char> buf2(s_size); is.read(&buf2[0], s_size); + if (is.gcount() != s_size) + throw SerializationError("deSerializeLongString: couldn't read all chars"); + s.reserve(s_size); s.append(&buf2[0], s_size); return s; } -// Creates a string encoded in JSON format (almost equivalent to a C string literal) +//// +//// JSON +//// + std::string serializeJsonString(const std::string &plain) { std::ostringstream os(std::ios::binary); - os<<"\""; - for(size_t i = 0; i < plain.size(); i++) - { + os << "\""; + + for (size_t i = 0; i < plain.size(); i++) { char c = plain[i]; - switch(c) - { - case '"': os<<"\\\""; break; - case '\\': os<<"\\\\"; break; - case '/': os<<"\\/"; break; - case '\b': os<<"\\b"; break; - case '\f': os<<"\\f"; break; - case '\n': os<<"\\n"; break; - case '\r': os<<"\\r"; break; - case '\t': os<<"\\t"; break; - default: - { - if(c >= 32 && c <= 126) - { - os<<c; - } - else - { - u32 cnum = (u32) (u8) c; - os<<"\\u"<<std::hex<<std::setw(4)<<std::setfill('0')<<cnum; + switch (c) { + case '"': + os << "\\\""; + break; + case '\\': + os << "\\\\"; + break; + case '/': + os << "\\/"; + break; + case '\b': + os << "\\b"; + break; + case '\f': + os << "\\f"; + break; + case '\n': + os << "\\n"; + break; + case '\r': + os << "\\r"; + break; + case '\t': + os << "\\t"; + break; + default: { + if (c >= 32 && c <= 126) { + os << c; + } else { + u32 cnum = (u8)c; + os << "\\u" << std::hex << std::setw(4) + << std::setfill('0') << cnum; } break; } } } - os<<"\""; + + os << "\""; return os.str(); } -// Reads a string encoded in JSON format std::string deSerializeJsonString(std::istream &is) { std::ostringstream os(std::ios::binary); @@ -173,55 +226,66 @@ std::string deSerializeJsonString(std::istream &is) // Parse initial doublequote is >> c; - if(c != '"') + if (c != '"') throw SerializationError("JSON string must start with doublequote"); // Parse characters - for(;;) - { + for (;;) { c = is.get(); - if(is.eof()) + if (is.eof()) throw SerializationError("JSON string ended prematurely"); - if(c == '"') - { + + if (c == '"') { return os.str(); - } - else if(c == '\\') - { + } else if (c == '\\') { c2 = is.get(); - if(is.eof()) + if (is.eof()) throw SerializationError("JSON string ended prematurely"); - switch(c2) - { - default: os<<c2; break; - case 'b': os<<'\b'; break; - case 'f': os<<'\f'; break; - case 'n': os<<'\n'; break; - case 'r': os<<'\r'; break; - case 't': os<<'\t'; break; - case 'u': - { - char hexdigits[4+1]; + switch (c2) { + case 'b': + os << '\b'; + break; + case 'f': + os << '\f'; + break; + case 'n': + os << '\n'; + break; + case 'r': + os << '\r'; + break; + case 't': + os << '\t'; + break; + case 'u': { + int hexnumber; + char hexdigits[4 + 1]; + is.read(hexdigits, 4); - if(is.eof()) + if (is.eof()) throw SerializationError("JSON string ended prematurely"); hexdigits[4] = 0; + std::istringstream tmp_is(hexdigits, std::ios::binary); - int hexnumber; tmp_is >> std::hex >> hexnumber; - os<<((char)hexnumber); + os << (char)hexnumber; break; } + default: + os << c2; + break; } - } - else - { - os<<c; + } else { + os << c; } } + return os.str(); } +//// +//// String/Struct conversions +//// bool deSerializeStringToStruct(std::string valstr, std::string format, void *out, size_t olen) @@ -384,7 +448,6 @@ fail: return true; } - // Casts *buf to a signed or unsigned fixed-width integer of 'w' width #define SIGN_CAST(w, buf) (is_unsigned ? *((u##w *) buf) : *((s##w *) buf)) @@ -471,11 +534,33 @@ bool serializeStructToString(std::string *out, *out = os.str(); // Trim off the trailing comma and space - if (out->size() >= 2) { + if (out->size() >= 2) out->resize(out->size() - 2); - } return true; } #undef SIGN_CAST + +//// +//// Other +//// + +std::string serializeHexString(const std::string &data, bool insert_spaces) +{ + std::string result; + result.reserve(data.size() * (2 + insert_spaces)); + + static const char hex_chars[] = "0123456789abcdef"; + + const size_t len = data.size(); + for (size_t i = 0; i != len; i++) { + u8 byte = data[i]; + result.push_back(hex_chars[(byte >> 4) & 0x0F]); + result.push_back(hex_chars[(byte >> 0) & 0x0F]); + if (insert_spaces && i != len - 1) + result.push_back(' '); + } + + return result; +} diff --git a/src/util/serialize.h b/src/util/serialize.h index 79907799f..bf0d9c863 100644 --- a/src/util/serialize.h +++ b/src/util/serialize.h @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define UTIL_SERIALIZE_HEADER #include "../irrlichttypes_bloated.h" +#include "../debug.h" // for assert #include "config.h" #if HAVE_ENDIAN_H #include <endian.h> @@ -30,185 +31,155 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #define FIXEDPOINT_FACTOR 1000.0f -#define FIXEDPOINT_INVFACTOR (1.0f/FIXEDPOINT_FACTOR) + +// 0x7FFFFFFF / 1000.0f is not serializable. +// The limited float precision at this magnitude may cause the result to round +// to a greater value than can be represented by a 32 bit integer when increased +// by a factor of FIXEDPOINT_FACTOR. As a result, [F1000_MIN..F1000_MAX] does +// not represent the full range, but rather the largest safe range, of values on +// all supported architectures. Note: This definition makes assumptions on +// platform float-to-int conversion behavior. +#define F1000_MIN ((float)(s32)((-0x7FFFFFFF - 1) / FIXEDPOINT_FACTOR)) +#define F1000_MAX ((float)(s32)((0x7FFFFFFF) / FIXEDPOINT_FACTOR)) + +#define STRING_MAX_LEN 0xFFFF +#define WIDE_STRING_MAX_LEN 0xFFFF +// 64 MB ought to be enough for anybody - Billy G. +#define LONG_STRING_MAX_LEN (64 * 1024 * 1024) + #if HAVE_ENDIAN_H // use machine native byte swapping routines // Note: memcpy below is optimized out by modern compilers -inline void writeU64(u8* data, u64 i) -{ - u64 val = htobe64(i); - memcpy(data, &val, 8); -} - -inline void writeU32(u8* data, u32 i) +inline u16 readU16(const u8 *data) { - u32 val = htobe32(i); - memcpy(data, &val, 4); + u16 val; + memcpy(&val, data, 2); + return be16toh(val); } -inline void writeU16(u8* data, u16 i) +inline u32 readU32(const u8 *data) { - u16 val = htobe16(i); - memcpy(data, &val, 2); + u32 val; + memcpy(&val, data, 4); + return be32toh(val); } -inline u64 readU64(const u8* data) +inline u64 readU64(const u8 *data) { u64 val; memcpy(&val, data, 8); return be64toh(val); } -inline u32 readU32(const u8* data) +inline void writeU16(u8 *data, u16 i) { - u32 val; - memcpy(&val, data, 4); - return be32toh(val); + u16 val = htobe16(i); + memcpy(data, &val, 2); } -inline u16 readU16(const u8* data) +inline void writeU32(u8 *data, u32 i) { - u16 val; - memcpy(&val, data, 2); - return be16toh(val); + u32 val = htobe32(i); + memcpy(data, &val, 4); } -#else -// generic byte-swapping implementation - inline void writeU64(u8 *data, u64 i) { - data[0] = ((i>>56)&0xff); - data[1] = ((i>>48)&0xff); - data[2] = ((i>>40)&0xff); - data[3] = ((i>>32)&0xff); - data[4] = ((i>>24)&0xff); - data[5] = ((i>>16)&0xff); - data[6] = ((i>> 8)&0xff); - data[7] = ((i>> 0)&0xff); + u64 val = htobe64(i); + memcpy(data, &val, 8); } -inline void writeU32(u8 *data, u32 i) +#else +// generic byte-swapping implementation + +inline u16 readU16(const u8 *data) { - data[0] = ((i>>24)&0xff); - data[1] = ((i>>16)&0xff); - data[2] = ((i>> 8)&0xff); - data[3] = ((i>> 0)&0xff); + return + ((u16)data[0] << 8) | ((u16)data[1] << 0); } -inline void writeU16(u8 *data, u16 i) +inline u32 readU32(const u8 *data) { - data[0] = ((i>> 8)&0xff); - data[1] = ((i>> 0)&0xff); + return + ((u32)data[0] << 24) | ((u32)data[1] << 16) | + ((u32)data[2] << 8) | ((u32)data[3] << 0); } inline u64 readU64(const u8 *data) { - return ((u64)data[0]<<56) | ((u64)data[1]<<48) - | ((u64)data[2]<<40) | ((u64)data[3]<<32) - | ((u64)data[4]<<24) | ((u64)data[5]<<16) - | ((u64)data[6]<<8) | ((u64)data[7]<<0); + return + ((u64)data[0] << 56) | ((u64)data[1] << 48) | + ((u64)data[2] << 40) | ((u64)data[3] << 32) | + ((u64)data[4] << 24) | ((u64)data[5] << 16) | + ((u64)data[6] << 8) | ((u64)data[7] << 0); } -inline u32 readU32(const u8 *data) +inline void writeU16(u8 *data, u16 i) { - return (data[0]<<24) | (data[1]<<16) | (data[2]<<8) | (data[3]<<0); + data[0] = (i >> 8) & 0xFF; + data[1] = (i >> 0) & 0xFF; } -inline u16 readU16(const u8 *data) +inline void writeU32(u8 *data, u32 i) { - return (data[0]<<8) | (data[1]<<0); + data[0] = (i >> 24) & 0xFF; + data[1] = (i >> 16) & 0xFF; + data[2] = (i >> 8) & 0xFF; + data[3] = (i >> 0) & 0xFF; } -#endif - -inline void writeU8(u8 *data, u8 i) +inline void writeU64(u8 *data, u64 i) { - data[0] = ((i>> 0)&0xff); + data[0] = (i >> 56) & 0xFF; + data[1] = (i >> 48) & 0xFF; + data[2] = (i >> 40) & 0xFF; + data[3] = (i >> 32) & 0xFF; + data[4] = (i >> 24) & 0xFF; + data[5] = (i >> 16) & 0xFF; + data[6] = (i >> 8) & 0xFF; + data[7] = (i >> 0) & 0xFF; } -inline u8 readU8(const u8 *data) -{ - return (data[0]<<0); -} +#endif // HAVE_ENDIAN_H -inline void writeS32(u8 *data, s32 i){ - writeU32(data, (u32)i); -} -inline s32 readS32(const u8 *data){ - return (s32)readU32(data); -} +//////////////// read routines //////////////// -inline void writeS16(u8 *data, s16 i){ - writeU16(data, (u16)i); -} -inline s16 readS16(const u8 *data){ - return (s16)readU16(data); +inline u8 readU8(const u8 *data) +{ + return ((u8)data[0] << 0); } -inline void writeS8(u8 *data, s8 i){ - writeU8(data, (u8)i); -} -inline s8 readS8(const u8 *data){ +inline s8 readS8(const u8 *data) +{ return (s8)readU8(data); } -inline void writeF1000(u8 *data, f32 i){ - writeS32(data, i*FIXEDPOINT_FACTOR); -} -inline f32 readF1000(const u8 *data){ - return (f32)readS32(data)*FIXEDPOINT_INVFACTOR; -} - -inline void writeV3S32(u8 *data, v3s32 p) +inline s16 readS16(const u8 *data) { - writeS32(&data[0], p.X); - writeS32(&data[4], p.Y); - writeS32(&data[8], p.Z); -} -inline v3s32 readV3S32(const u8 *data) -{ - v3s32 p; - p.X = readS32(&data[0]); - p.Y = readS32(&data[4]); - p.Z = readS32(&data[8]); - return p; + return (s16)readU16(data); } -inline void writeV3F1000(u8 *data, v3f p) +inline s32 readS32(const u8 *data) { - writeF1000(&data[0], p.X); - writeF1000(&data[4], p.Y); - writeF1000(&data[8], p.Z); -} -inline v3f readV3F1000(const u8 *data) -{ - v3f p; - p.X = (float)readF1000(&data[0]); - p.Y = (float)readF1000(&data[4]); - p.Z = (float)readF1000(&data[8]); - return p; + return (s32)readU32(data); } -inline void writeV2F1000(u8 *data, v2f p) +inline s64 readS64(const u8 *data) { - writeF1000(&data[0], p.X); - writeF1000(&data[4], p.Y); + return (s64)readU64(data); } -inline v2f readV2F1000(const u8 *data) + +inline f32 readF1000(const u8 *data) { - v2f p; - p.X = (float)readF1000(&data[0]); - p.Y = (float)readF1000(&data[4]); - return p; + return (f32)readS32(data) / FIXEDPOINT_FACTOR; } -inline void writeV2S16(u8 *data, v2s16 p) +inline video::SColor readARGB8(const u8 *data) { - writeS16(&data[0], p.X); - writeS16(&data[2], p.Y); + video::SColor p(readU32(data)); + return p; } inline v2s16 readV2S16(const u8 *data) @@ -219,10 +190,13 @@ inline v2s16 readV2S16(const u8 *data) return p; } -inline void writeV2S32(u8 *data, v2s32 p) +inline v3s16 readV3S16(const u8 *data) { - writeS32(&data[0], p.X); - writeS32(&data[4], p.Y); + v3s16 p; + p.X = readS16(&data[0]); + p.Y = readS16(&data[2]); + p.Z = readS16(&data[4]); + return p; } inline v2s32 readV2S32(const u8 *data) @@ -233,198 +207,166 @@ inline v2s32 readV2S32(const u8 *data) return p; } -inline void writeV3S16(u8 *data, v3s16 p) -{ - writeS16(&data[0], p.X); - writeS16(&data[2], p.Y); - writeS16(&data[4], p.Z); -} - -inline v3s16 readV3S16(const u8 *data) +inline v3s32 readV3S32(const u8 *data) { - v3s16 p; - p.X = readS16(&data[0]); - p.Y = readS16(&data[2]); - p.Z = readS16(&data[4]); + v3s32 p; + p.X = readS32(&data[0]); + p.Y = readS32(&data[4]); + p.Z = readS32(&data[8]); return p; } -inline void writeARGB8(u8 *data, video::SColor p) +inline v2f readV2F1000(const u8 *data) { - writeU32(data, p.color); + v2f p; + p.X = (float)readF1000(&data[0]); + p.Y = (float)readF1000(&data[4]); + return p; } -inline video::SColor readARGB8(const u8 *data) +inline v3f readV3F1000(const u8 *data) { - video::SColor p(readU32(data)); + v3f p; + p.X = (float)readF1000(&data[0]); + p.Y = (float)readF1000(&data[4]); + p.Z = (float)readF1000(&data[8]); return p; } -/* - The above stuff directly interfaced to iostream -*/ - -inline void writeU8(std::ostream &os, u8 p) -{ - char buf[1]; - writeU8((u8*)buf, p); - os.write(buf, 1); -} -inline u8 readU8(std::istream &is) -{ - char buf[1] = {0}; - is.read(buf, 1); - return readU8((u8*)buf); -} +/////////////// write routines //////////////// -inline void writeU16(std::ostream &os, u16 p) -{ - char buf[2]; - writeU16((u8*)buf, p); - os.write(buf, 2); -} -inline u16 readU16(std::istream &is) +inline void writeU8(u8 *data, u8 i) { - char buf[2] = {0}; - is.read(buf, 2); - return readU16((u8*)buf); + data[0] = (i >> 0) & 0xFF; } -inline void writeU32(std::ostream &os, u32 p) +inline void writeS8(u8 *data, s8 i) { - char buf[4]; - writeU32((u8*)buf, p); - os.write(buf, 4); -} -inline u32 readU32(std::istream &is) -{ - char buf[4] = {0}; - is.read(buf, 4); - return readU32((u8*)buf); + writeU8(data, (u8)i); } -inline void writeS32(std::ostream &os, s32 p) -{ - writeU32(os, (u32) p); -} -inline s32 readS32(std::istream &is) +inline void writeS16(u8 *data, s16 i) { - return (s32)readU32(is); + writeU16(data, (u16)i); } -inline void writeS16(std::ostream &os, s16 p) -{ - writeU16(os, (u16) p); -} -inline s16 readS16(std::istream &is) +inline void writeS32(u8 *data, s32 i) { - return (s16)readU16(is); + writeU32(data, (u32)i); } -inline void writeS8(std::ostream &os, s8 p) -{ - writeU8(os, (u8) p); -} -inline s8 readS8(std::istream &is) +inline void writeS64(u8 *data, s64 i) { - return (s8)readU8(is); + writeU64(data, (u64)i); } -inline void writeF1000(std::ostream &os, f32 p) +inline void writeF1000(u8 *data, f32 i) { - char buf[4]; - writeF1000((u8*)buf, p); - os.write(buf, 4); -} -inline f32 readF1000(std::istream &is) -{ - char buf[4] = {0}; - is.read(buf, 4); - return readF1000((u8*)buf); + assert(i >= F1000_MIN && i <= F1000_MAX); + writeS32(data, i * FIXEDPOINT_FACTOR); } -inline void writeV3F1000(std::ostream &os, v3f p) -{ - char buf[12]; - writeV3F1000((u8*)buf, p); - os.write(buf, 12); -} -inline v3f readV3F1000(std::istream &is) +inline void writeARGB8(u8 *data, video::SColor p) { - char buf[12]; - is.read(buf, 12); - return readV3F1000((u8*)buf); + writeU32(data, p.color); } -inline void writeV2F1000(std::ostream &os, v2f p) -{ - char buf[8]; - writeV2F1000((u8*)buf, p); - os.write(buf, 8); -} -inline v2f readV2F1000(std::istream &is) +inline void writeV2S16(u8 *data, v2s16 p) { - char buf[8] = {0}; - is.read(buf, 8); - return readV2F1000((u8*)buf); + writeS16(&data[0], p.X); + writeS16(&data[2], p.Y); } -inline void writeV2S16(std::ostream &os, v2s16 p) -{ - char buf[4]; - writeV2S16((u8*)buf, p); - os.write(buf, 4); -} -inline v2s16 readV2S16(std::istream &is) +inline void writeV3S16(u8 *data, v3s16 p) { - char buf[4] = {0}; - is.read(buf, 4); - return readV2S16((u8*)buf); + writeS16(&data[0], p.X); + writeS16(&data[2], p.Y); + writeS16(&data[4], p.Z); } -inline void writeV2S32(std::ostream &os, v2s32 p) -{ - char buf[8]; - writeV2S32((u8*)buf, p); - os.write(buf, 8); -} -inline v2s32 readV2S32(std::istream &is) +inline void writeV2S32(u8 *data, v2s32 p) { - char buf[8] = {0}; - is.read(buf, 8); - return readV2S32((u8*)buf); + writeS32(&data[0], p.X); + writeS32(&data[4], p.Y); } -inline void writeV3S16(std::ostream &os, v3s16 p) -{ - char buf[6]; - writeV3S16((u8*)buf, p); - os.write(buf, 6); -} -inline v3s16 readV3S16(std::istream &is) +inline void writeV3S32(u8 *data, v3s32 p) { - char buf[6] = {0}; - is.read(buf, 6); - return readV3S16((u8*)buf); + writeS32(&data[0], p.X); + writeS32(&data[4], p.Y); + writeS32(&data[8], p.Z); } -inline void writeARGB8(std::ostream &os, video::SColor p) +inline void writeV2F1000(u8 *data, v2f p) { - char buf[4]; - writeARGB8((u8*)buf, p); - os.write(buf, 4); + writeF1000(&data[0], p.X); + writeF1000(&data[4], p.Y); } -inline video::SColor readARGB8(std::istream &is) +inline void writeV3F1000(u8 *data, v3f p) { - char buf[4] = {0}; - is.read(buf, 4); - return readARGB8((u8*)buf); + writeF1000(&data[0], p.X); + writeF1000(&data[4], p.Y); + writeF1000(&data[8], p.Z); } -/* - More serialization stuff -*/ +//// +//// Iostream wrapper for data read/write +//// + +#define MAKE_STREAM_READ_FXN(T, N, S) \ + inline T read ## N(std::istream &is) \ + { \ + char buf[S] = {0}; \ + is.read(buf, sizeof(buf)); \ + return read ## N((u8 *)buf); \ + } + +#define MAKE_STREAM_WRITE_FXN(T, N, S) \ + inline void write ## N(std::ostream &os, T val) \ + { \ + char buf[S]; \ + write ## N((u8 *)buf, val); \ + os.write(buf, sizeof(buf)); \ + } + +MAKE_STREAM_READ_FXN(u8, U8, 1); +MAKE_STREAM_READ_FXN(u16, U16, 2); +MAKE_STREAM_READ_FXN(u32, U32, 4); +MAKE_STREAM_READ_FXN(u64, U64, 8); +MAKE_STREAM_READ_FXN(s8, S8, 1); +MAKE_STREAM_READ_FXN(s16, S16, 2); +MAKE_STREAM_READ_FXN(s32, S32, 4); +MAKE_STREAM_READ_FXN(s64, S64, 8); +MAKE_STREAM_READ_FXN(f32, F1000, 4); +MAKE_STREAM_READ_FXN(v2s16, V2S16, 4); +MAKE_STREAM_READ_FXN(v3s16, V3S16, 6); +MAKE_STREAM_READ_FXN(v2s32, V2S32, 8); +MAKE_STREAM_READ_FXN(v3s32, V3S32, 12); +MAKE_STREAM_READ_FXN(v2f, V2F1000, 8); +MAKE_STREAM_READ_FXN(v3f, V3F1000, 12); +MAKE_STREAM_READ_FXN(video::SColor, ARGB8, 4); + +MAKE_STREAM_WRITE_FXN(u8, U8, 1); +MAKE_STREAM_WRITE_FXN(u16, U16, 2); +MAKE_STREAM_WRITE_FXN(u32, U32, 4); +MAKE_STREAM_WRITE_FXN(u64, U64, 8); +MAKE_STREAM_WRITE_FXN(s8, S8, 1); +MAKE_STREAM_WRITE_FXN(s16, S16, 2); +MAKE_STREAM_WRITE_FXN(s32, S32, 4); +MAKE_STREAM_WRITE_FXN(s64, S64, 8); +MAKE_STREAM_WRITE_FXN(f32, F1000, 4); +MAKE_STREAM_WRITE_FXN(v2s16, V2S16, 4); +MAKE_STREAM_WRITE_FXN(v3s16, V3S16, 6); +MAKE_STREAM_WRITE_FXN(v2s32, V2S32, 8); +MAKE_STREAM_WRITE_FXN(v3s32, V3S32, 12); +MAKE_STREAM_WRITE_FXN(v2f, V2F1000, 8); +MAKE_STREAM_WRITE_FXN(v3f, V3F1000, 12); +MAKE_STREAM_WRITE_FXN(video::SColor, ARGB8, 4); + +//// +//// More serialization stuff +//// // Creates a string with the length as the first two bytes std::string serializeString(const std::string &plain); @@ -450,6 +392,9 @@ std::string serializeJsonString(const std::string &plain); // Reads a string encoded in JSON format std::string deSerializeJsonString(std::istream &is); +// Creates a string consisting of the hexadecimal representation of `data` +std::string serializeHexString(const std::string &data, bool insert_spaces=false); + // Creates a string containing comma delimited values of a struct whose layout is // described by the parameter format bool serializeStructToString(std::string *out, @@ -461,4 +406,3 @@ bool deSerializeStringToStruct(std::string valstr, std::string format, void *out, size_t olen); #endif - diff --git a/src/sha1.cpp b/src/util/sha1.cpp index 6ed7385d5..6ed7385d5 100644 --- a/src/sha1.cpp +++ b/src/util/sha1.cpp diff --git a/src/sha1.h b/src/util/sha1.h index c04947373..c04947373 100644 --- a/src/sha1.h +++ b/src/util/sha1.h diff --git a/src/util/sha2.h b/src/util/sha2.h new file mode 100644 index 000000000..6ac045feb --- /dev/null +++ b/src/util/sha2.h @@ -0,0 +1,154 @@ +/* crypto/sha/sha.h */ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +#ifndef HEADER_SHA_H +# define HEADER_SHA_H + +# include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +# if defined(OPENSSL_NO_SHA) || (defined(OPENSSL_NO_SHA0) && defined(OPENSSL_NO_SHA1)) +# error SHA is disabled. +# endif + +# if defined(OPENSSL_FIPS) +# define FIPS_SHA_SIZE_T size_t +# endif + +/* + Compat stuff from OpenSSL land + */ + +/* crypto.h */ + +# define fips_md_init(alg) fips_md_init_ctx(alg, alg) + +# define fips_md_init_ctx(alg, cx) \ + int alg##_Init(cx##_CTX *c) +# define fips_cipher_abort(alg) while(0) + +/*- + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * ! SHA_LONG has to be at least 32 bits wide. If it's wider, then ! + * ! SHA_LONG_LOG2 has to be defined along. ! + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + */ + +# if defined(__LP32__) +# define SHA_LONG unsigned long +# elif defined(__ILP64__) +# define SHA_LONG unsigned long +# define SHA_LONG_LOG2 3 +# else +# define SHA_LONG unsigned int +# endif + +# define SHA_LBLOCK 16 +# define SHA_CBLOCK (SHA_LBLOCK*4)/* SHA treats input data as a + * contiguous array of 32 bit wide + * big-endian values. */ +# define SHA_LAST_BLOCK (SHA_CBLOCK-8) +# define SHA_DIGEST_LENGTH 20 + +typedef struct SHAstate_st { + SHA_LONG h0, h1, h2, h3, h4; + SHA_LONG Nl, Nh; + SHA_LONG data[SHA_LBLOCK]; + unsigned int num; +} SHA_CTX; + +# define SHA256_CBLOCK (SHA_LBLOCK*4)/* SHA-256 treats input data as a + * contiguous array of 32 bit wide + * big-endian values. */ +# define SHA224_DIGEST_LENGTH 28 +# define SHA256_DIGEST_LENGTH 32 + +typedef struct SHA256state_st { + SHA_LONG h[8]; + SHA_LONG Nl, Nh; + SHA_LONG data[SHA_LBLOCK]; + unsigned int num, md_len; +} SHA256_CTX; + +# ifndef OPENSSL_NO_SHA256 +# ifdef OPENSSL_FIPS +int private_SHA224_Init(SHA256_CTX *c); +int private_SHA256_Init(SHA256_CTX *c); +# endif +int SHA224_Init(SHA256_CTX *c); +int SHA224_Update(SHA256_CTX *c, const void *data, size_t len); +int SHA224_Final(unsigned char *md, SHA256_CTX *c); +unsigned char *SHA224(const unsigned char *d, size_t n, unsigned char *md); +int SHA256_Init(SHA256_CTX *c); +int SHA256_Update(SHA256_CTX *c, const void *data, size_t len); +int SHA256_Final(unsigned char *md, SHA256_CTX *c); +unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md); +void SHA256_Transform(SHA256_CTX *c, const unsigned char *data); +# endif + +# define SHA384_DIGEST_LENGTH 48 +# define SHA512_DIGEST_LENGTH 64 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/util/sha256.c b/src/util/sha256.c new file mode 100644 index 000000000..4c2bb71a8 --- /dev/null +++ b/src/util/sha256.c @@ -0,0 +1,404 @@ +/* crypto/sha/sha256.c */ +/* ==================================================================== + * Copyright (c) 2004 The OpenSSL Project. All rights reserved + * according to the OpenSSL license [found in ../../LICENSE]. + * ==================================================================== + */ +# include <stdlib.h> +# include <string.h> + +# include <util/sha2.h> + +# define OPENSSL_VERSION_TEXT "OpenSSL 1.0.2a 19 Mar 2015" +# define OPENSSL_VERSION_PTEXT " part of " OPENSSL_VERSION_TEXT + +const char SHA256_version[] = "SHA-256" OPENSSL_VERSION_PTEXT; + +/* mem_clr.c */ +unsigned static char cleanse_ctr = 0; +static void OPENSSL_cleanse(void *ptr, size_t len) +{ + unsigned char *p = ptr; + size_t loop = len, ctr = cleanse_ctr; + while (loop--) { + *(p++) = (unsigned char)ctr; + ctr += (17 + ((size_t)p & 0xF)); + } + p = memchr(ptr, (unsigned char)ctr, len); + if (p) + ctr += (63 + (size_t)p); + cleanse_ctr = (unsigned char)ctr; +} + +# define fips_md_init(alg) fips_md_init_ctx(alg, alg) +# define fips_md_init_ctx(alg, cx) \ + int alg##_Init(cx##_CTX *c) +# define fips_cipher_abort(alg) while(0) + +fips_md_init_ctx(SHA224, SHA256) +{ + memset(c, 0, sizeof(*c)); + c->h[0] = 0xc1059ed8UL; + c->h[1] = 0x367cd507UL; + c->h[2] = 0x3070dd17UL; + c->h[3] = 0xf70e5939UL; + c->h[4] = 0xffc00b31UL; + c->h[5] = 0x68581511UL; + c->h[6] = 0x64f98fa7UL; + c->h[7] = 0xbefa4fa4UL; + c->md_len = SHA224_DIGEST_LENGTH; + return 1; +} + +fips_md_init(SHA256) +{ + memset(c, 0, sizeof(*c)); + c->h[0] = 0x6a09e667UL; + c->h[1] = 0xbb67ae85UL; + c->h[2] = 0x3c6ef372UL; + c->h[3] = 0xa54ff53aUL; + c->h[4] = 0x510e527fUL; + c->h[5] = 0x9b05688cUL; + c->h[6] = 0x1f83d9abUL; + c->h[7] = 0x5be0cd19UL; + c->md_len = SHA256_DIGEST_LENGTH; + return 1; +} + +unsigned char *SHA224(const unsigned char *d, size_t n, unsigned char *md) +{ + SHA256_CTX c; + static unsigned char m[SHA224_DIGEST_LENGTH]; + + if (md == NULL) + md = m; + SHA224_Init(&c); + SHA256_Update(&c, d, n); + SHA256_Final(md, &c); + OPENSSL_cleanse(&c, sizeof(c)); + return (md); +} + +unsigned char *SHA256(const unsigned char *d, size_t n, unsigned char *md) +{ + SHA256_CTX c; + static unsigned char m[SHA256_DIGEST_LENGTH]; + + if (md == NULL) + md = m; + SHA256_Init(&c); + SHA256_Update(&c, d, n); + SHA256_Final(md, &c); + OPENSSL_cleanse(&c, sizeof(c)); + return (md); +} + +int SHA224_Update(SHA256_CTX *c, const void *data, size_t len) +{ + return SHA256_Update(c, data, len); +} + +int SHA224_Final(unsigned char *md, SHA256_CTX *c) +{ + return SHA256_Final(md, c); +} + +# define DATA_ORDER_IS_BIG_ENDIAN + +# define HASH_LONG SHA_LONG +# define HASH_CTX SHA256_CTX +# define HASH_CBLOCK SHA_CBLOCK +/* + * Note that FIPS180-2 discusses "Truncation of the Hash Function Output." + * default: case below covers for it. It's not clear however if it's + * permitted to truncate to amount of bytes not divisible by 4. I bet not, + * but if it is, then default: case shall be extended. For reference. + * Idea behind separate cases for pre-defined lenghts is to let the + * compiler decide if it's appropriate to unroll small loops. + */ +# define HASH_MAKE_STRING(c,s) do { \ + unsigned long ll; \ + unsigned int nn; \ + switch ((c)->md_len) \ + { case SHA224_DIGEST_LENGTH: \ + for (nn=0;nn<SHA224_DIGEST_LENGTH/4;nn++) \ + { ll=(c)->h[nn]; (void)HOST_l2c(ll,(s)); } \ + break; \ + case SHA256_DIGEST_LENGTH: \ + for (nn=0;nn<SHA256_DIGEST_LENGTH/4;nn++) \ + { ll=(c)->h[nn]; (void)HOST_l2c(ll,(s)); } \ + break; \ + default: \ + if ((c)->md_len > SHA256_DIGEST_LENGTH) \ + return 0; \ + for (nn=0;nn<(c)->md_len/4;nn++) \ + { ll=(c)->h[nn]; (void)HOST_l2c(ll,(s)); } \ + break; \ + } \ + } while (0) + +# define HASH_UPDATE SHA256_Update +# define HASH_TRANSFORM SHA256_Transform +# define HASH_FINAL SHA256_Final +# define HASH_BLOCK_DATA_ORDER sha256_block_data_order +# ifndef SHA256_ASM +static +# endif +void sha256_block_data_order(SHA256_CTX *ctx, const void *in, size_t num); + +# include "md32_common.h" + +# ifndef SHA256_ASM +static const SHA_LONG K256[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, + 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, + 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, + 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, + 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, + 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, + 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, + 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL +}; + +/* + * FIPS specification refers to right rotations, while our ROTATE macro + * is left one. This is why you might notice that rotation coefficients + * differ from those observed in FIPS document by 32-N... + */ +# define Sigma0(x) (ROTATE((x),30) ^ ROTATE((x),19) ^ ROTATE((x),10)) +# define Sigma1(x) (ROTATE((x),26) ^ ROTATE((x),21) ^ ROTATE((x),7)) +# define sigma0(x) (ROTATE((x),25) ^ ROTATE((x),14) ^ ((x)>>3)) +# define sigma1(x) (ROTATE((x),15) ^ ROTATE((x),13) ^ ((x)>>10)) + +# define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) +# define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) + +# ifdef OPENSSL_SMALL_FOOTPRINT + +static void sha256_block_data_order(SHA256_CTX *ctx, const void *in, + size_t num) +{ + unsigned MD32_REG_T a, b, c, d, e, f, g, h, s0, s1, T1, T2; + SHA_LONG X[16], l; + int i; + const unsigned char *data = in; + + while (num--) { + + a = ctx->h[0]; + b = ctx->h[1]; + c = ctx->h[2]; + d = ctx->h[3]; + e = ctx->h[4]; + f = ctx->h[5]; + g = ctx->h[6]; + h = ctx->h[7]; + + for (i = 0; i < 16; i++) { + HOST_c2l(data, l); + T1 = X[i] = l; + T1 += h + Sigma1(e) + Ch(e, f, g) + K256[i]; + T2 = Sigma0(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + for (; i < 64; i++) { + s0 = X[(i + 1) & 0x0f]; + s0 = sigma0(s0); + s1 = X[(i + 14) & 0x0f]; + s1 = sigma1(s1); + + T1 = X[i & 0xf] += s0 + s1 + X[(i + 9) & 0xf]; + T1 += h + Sigma1(e) + Ch(e, f, g) + K256[i]; + T2 = Sigma0(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + ctx->h[0] += a; + ctx->h[1] += b; + ctx->h[2] += c; + ctx->h[3] += d; + ctx->h[4] += e; + ctx->h[5] += f; + ctx->h[6] += g; + ctx->h[7] += h; + + } +} + +# else + +# define ROUND_00_15(i,a,b,c,d,e,f,g,h) do { \ + T1 += h + Sigma1(e) + Ch(e,f,g) + K256[i]; \ + h = Sigma0(a) + Maj(a,b,c); \ + d += T1; h += T1; } while (0) + +# define ROUND_16_63(i,a,b,c,d,e,f,g,h,X) do { \ + s0 = X[(i+1)&0x0f]; s0 = sigma0(s0); \ + s1 = X[(i+14)&0x0f]; s1 = sigma1(s1); \ + T1 = X[(i)&0x0f] += s0 + s1 + X[(i+9)&0x0f]; \ + ROUND_00_15(i,a,b,c,d,e,f,g,h); } while (0) + +static void sha256_block_data_order(SHA256_CTX *ctx, const void *in, + size_t num) +{ + unsigned MD32_REG_T a, b, c, d, e, f, g, h, s0, s1, T1; + SHA_LONG X[16]; + int i; + const unsigned char *data = in; + const union { + long one; + char little; + } is_endian = { + 1 + }; + + while (num--) { + + a = ctx->h[0]; + b = ctx->h[1]; + c = ctx->h[2]; + d = ctx->h[3]; + e = ctx->h[4]; + f = ctx->h[5]; + g = ctx->h[6]; + h = ctx->h[7]; + + if (!is_endian.little && sizeof(SHA_LONG) == 4 + && ((size_t)in % 4) == 0) { + const SHA_LONG *W = (const SHA_LONG *)data; + + T1 = X[0] = W[0]; + ROUND_00_15(0, a, b, c, d, e, f, g, h); + T1 = X[1] = W[1]; + ROUND_00_15(1, h, a, b, c, d, e, f, g); + T1 = X[2] = W[2]; + ROUND_00_15(2, g, h, a, b, c, d, e, f); + T1 = X[3] = W[3]; + ROUND_00_15(3, f, g, h, a, b, c, d, e); + T1 = X[4] = W[4]; + ROUND_00_15(4, e, f, g, h, a, b, c, d); + T1 = X[5] = W[5]; + ROUND_00_15(5, d, e, f, g, h, a, b, c); + T1 = X[6] = W[6]; + ROUND_00_15(6, c, d, e, f, g, h, a, b); + T1 = X[7] = W[7]; + ROUND_00_15(7, b, c, d, e, f, g, h, a); + T1 = X[8] = W[8]; + ROUND_00_15(8, a, b, c, d, e, f, g, h); + T1 = X[9] = W[9]; + ROUND_00_15(9, h, a, b, c, d, e, f, g); + T1 = X[10] = W[10]; + ROUND_00_15(10, g, h, a, b, c, d, e, f); + T1 = X[11] = W[11]; + ROUND_00_15(11, f, g, h, a, b, c, d, e); + T1 = X[12] = W[12]; + ROUND_00_15(12, e, f, g, h, a, b, c, d); + T1 = X[13] = W[13]; + ROUND_00_15(13, d, e, f, g, h, a, b, c); + T1 = X[14] = W[14]; + ROUND_00_15(14, c, d, e, f, g, h, a, b); + T1 = X[15] = W[15]; + ROUND_00_15(15, b, c, d, e, f, g, h, a); + + data += SHA256_CBLOCK; + } else { + SHA_LONG l; + + HOST_c2l(data, l); + T1 = X[0] = l; + ROUND_00_15(0, a, b, c, d, e, f, g, h); + HOST_c2l(data, l); + T1 = X[1] = l; + ROUND_00_15(1, h, a, b, c, d, e, f, g); + HOST_c2l(data, l); + T1 = X[2] = l; + ROUND_00_15(2, g, h, a, b, c, d, e, f); + HOST_c2l(data, l); + T1 = X[3] = l; + ROUND_00_15(3, f, g, h, a, b, c, d, e); + HOST_c2l(data, l); + T1 = X[4] = l; + ROUND_00_15(4, e, f, g, h, a, b, c, d); + HOST_c2l(data, l); + T1 = X[5] = l; + ROUND_00_15(5, d, e, f, g, h, a, b, c); + HOST_c2l(data, l); + T1 = X[6] = l; + ROUND_00_15(6, c, d, e, f, g, h, a, b); + HOST_c2l(data, l); + T1 = X[7] = l; + ROUND_00_15(7, b, c, d, e, f, g, h, a); + HOST_c2l(data, l); + T1 = X[8] = l; + ROUND_00_15(8, a, b, c, d, e, f, g, h); + HOST_c2l(data, l); + T1 = X[9] = l; + ROUND_00_15(9, h, a, b, c, d, e, f, g); + HOST_c2l(data, l); + T1 = X[10] = l; + ROUND_00_15(10, g, h, a, b, c, d, e, f); + HOST_c2l(data, l); + T1 = X[11] = l; + ROUND_00_15(11, f, g, h, a, b, c, d, e); + HOST_c2l(data, l); + T1 = X[12] = l; + ROUND_00_15(12, e, f, g, h, a, b, c, d); + HOST_c2l(data, l); + T1 = X[13] = l; + ROUND_00_15(13, d, e, f, g, h, a, b, c); + HOST_c2l(data, l); + T1 = X[14] = l; + ROUND_00_15(14, c, d, e, f, g, h, a, b); + HOST_c2l(data, l); + T1 = X[15] = l; + ROUND_00_15(15, b, c, d, e, f, g, h, a); + } + + for (i = 16; i < 64; i += 8) { + ROUND_16_63(i + 0, a, b, c, d, e, f, g, h, X); + ROUND_16_63(i + 1, h, a, b, c, d, e, f, g, X); + ROUND_16_63(i + 2, g, h, a, b, c, d, e, f, X); + ROUND_16_63(i + 3, f, g, h, a, b, c, d, e, X); + ROUND_16_63(i + 4, e, f, g, h, a, b, c, d, X); + ROUND_16_63(i + 5, d, e, f, g, h, a, b, c, X); + ROUND_16_63(i + 6, c, d, e, f, g, h, a, b, X); + ROUND_16_63(i + 7, b, c, d, e, f, g, h, a, X); + } + + ctx->h[0] += a; + ctx->h[1] += b; + ctx->h[2] += c; + ctx->h[3] += d; + ctx->h[4] += e; + ctx->h[5] += f; + ctx->h[6] += g; + ctx->h[7] += h; + + } +} + +# endif +# endif /* SHA256_ASM */ diff --git a/src/util/srp.cpp b/src/util/srp.cpp new file mode 100644 index 000000000..94426db92 --- /dev/null +++ b/src/util/srp.cpp @@ -0,0 +1,1038 @@ +/* + * Secure Remote Password 6a implementation + * https://github.com/est31/csrp-gmp + * + * The MIT License (MIT) + * + * Copyright (c) 2010, 2013 Tom Cocagne, 2015 est31 <MTest31@outlook.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#ifdef WIN32 + #include <windows.h> + #include <wincrypt.h> +#else + #include <time.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include <config.h> + +#if USE_SYSTEM_GMP || defined (__ANDROID__) || defined (ANDROID) + #include <gmp.h> +#else + #include <gmp/mini-gmp.h> +#endif + +#include <util/sha2.h> + +#include "srp.h" +//#define CSRP_USE_SHA1 +#define CSRP_USE_SHA256 + +#define srp_dbg_data(data, datalen, prevtext) ; +/*void srp_dbg_data(unsigned char * data, size_t datalen, char * prevtext) +{ + printf(prevtext); + size_t i; + for (i = 0; i < datalen; i++) + { + printf("%02X", data[i]); + } + printf("\n"); +}*/ + +static int g_initialized = 0; + +#define RAND_BUFF_MAX 128 +static unsigned int g_rand_idx; +static unsigned char g_rand_buff[RAND_BUFF_MAX]; + +typedef struct +{ + mpz_t N; + mpz_t g; +} NGConstant; + +struct NGHex +{ + const char* n_hex; + const char* g_hex; +}; + +/* All constants here were pulled from Appendix A of RFC 5054 */ +static struct NGHex global_Ng_constants[] = { + { /* 1024 */ + "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496" + "EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8E" + "F4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA" + "9AFD5138FE8376435B9FC61D2FC0EB06E3", + "2" + }, + { /* 2048 */ + "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4" + "A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF60" + "95179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF" + "747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B907" + "8717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB37861" + "60279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DB" + "FBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73", + "2" + }, + { /* 4096 */ + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + "FFFFFFFFFFFFFFFF", + "5" + }, + { /* 8192 */ + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + "6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA" + "3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C" + "5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886" + "2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6" + "6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5" + "0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268" + "359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6" + "FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", + "13" + }, + {0,0} /* null sentinel */ +}; + + +static void delete_ng(NGConstant *ng) +{ + if (ng) { + mpz_clear(ng->N); + mpz_clear(ng->g); + free(ng); + } +} + +static NGConstant *new_ng( SRP_NGType ng_type, const char *n_hex, const char *g_hex ) +{ + NGConstant *ng = (NGConstant *) malloc(sizeof(NGConstant)); + mpz_init(ng->N); + mpz_init(ng->g); + + if (!ng) + return 0; + + if (ng_type != SRP_NG_CUSTOM) { + n_hex = global_Ng_constants[ ng_type ].n_hex; + g_hex = global_Ng_constants[ ng_type ].g_hex; + } + + int rv = 0; + rv = mpz_set_str(ng->N, n_hex, 16); + rv = rv | mpz_set_str(ng->g, g_hex, 16); + + if (rv) { + delete_ng(ng); + return 0; + } + + return ng; +} + + +typedef union +{ + SHA_CTX sha; + SHA256_CTX sha256; + //SHA512_CTX sha512; +} HashCTX; + + +struct SRPVerifier +{ + SRP_HashAlgorithm hash_alg; + NGConstant *ng; + + char *username; + unsigned char *bytes_B; + int authenticated; + + unsigned char M[SHA512_DIGEST_LENGTH]; + unsigned char H_AMK[SHA512_DIGEST_LENGTH]; + unsigned char session_key[SHA512_DIGEST_LENGTH]; +}; + + +struct SRPUser +{ + SRP_HashAlgorithm hash_alg; + NGConstant *ng; + + mpz_t a; + mpz_t A; + mpz_t S; + + unsigned char *bytes_A; + int authenticated; + + char *username; + char *username_verifier; + unsigned char *password; + size_t password_len; + + unsigned char M[SHA512_DIGEST_LENGTH]; + unsigned char H_AMK[SHA512_DIGEST_LENGTH]; + unsigned char session_key[SHA512_DIGEST_LENGTH]; +}; + + +static int hash_init(SRP_HashAlgorithm alg, HashCTX *c) +{ + switch (alg) { +#ifdef CSRP_USE_SHA1 + case SRP_SHA1: return SHA1_Init(&c->sha); +#endif + /*case SRP_SHA224: return SHA224_Init(&c->sha256);*/ +#ifdef CSRP_USE_SHA256 + case SRP_SHA256: return SHA256_Init(&c->sha256); +#endif + /*case SRP_SHA384: return SHA384_Init(&c->sha512); + case SRP_SHA512: return SHA512_Init(&c->sha512);*/ + default: return -1; + }; +} +static int hash_update( SRP_HashAlgorithm alg, HashCTX *c, const void *data, size_t len ) +{ + switch (alg) { +#ifdef CSRP_USE_SHA1 + case SRP_SHA1: return SHA1_Update(&c->sha, data, len); +#endif + /*case SRP_SHA224: return SHA224_Update(&c->sha256, data, len);*/ +#ifdef CSRP_USE_SHA256 + case SRP_SHA256: return SHA256_Update(&c->sha256, data, len); +#endif + /*case SRP_SHA384: return SHA384_Update( &c->sha512, data, len ); + case SRP_SHA512: return SHA512_Update( &c->sha512, data, len );*/ + default: return -1; + }; +} +static int hash_final( SRP_HashAlgorithm alg, HashCTX *c, unsigned char *md ) +{ + switch (alg) { +#ifdef CSRP_USE_SHA1 + case SRP_SHA1: return SHA1_Final(md, &c->sha); +#endif + /*case SRP_SHA224: return SHA224_Final(md, &c->sha256);*/ +#ifdef CSRP_USE_SHA256 + case SRP_SHA256: return SHA256_Final(md, &c->sha256); +#endif + /*case SRP_SHA384: return SHA384_Final(md, &c->sha512); + case SRP_SHA512: return SHA512_Final(md, &c->sha512);*/ + default: return -1; + }; +} +static unsigned char *hash(SRP_HashAlgorithm alg, const unsigned char *d, size_t n, unsigned char *md) +{ + switch (alg) { +#ifdef CSRP_USE_SHA1 + case SRP_SHA1: return SHA1(d, n, md); +#endif + /*case SRP_SHA224: return SHA224( d, n, md );*/ +#ifdef CSRP_USE_SHA256 + case SRP_SHA256: return SHA256(d, n, md); +#endif + /*case SRP_SHA384: return SHA384( d, n, md ); + case SRP_SHA512: return SHA512( d, n, md );*/ + default: return 0; + }; +} +static size_t hash_length(SRP_HashAlgorithm alg) +{ + switch (alg) { +#ifdef CSRP_USE_SHA1 + case SRP_SHA1: return SHA_DIGEST_LENGTH; +#endif + /*case SRP_SHA224: return SHA224_DIGEST_LENGTH;*/ +#ifdef CSRP_USE_SHA256 + case SRP_SHA256: return SHA256_DIGEST_LENGTH; +#endif + /*case SRP_SHA384: return SHA384_DIGEST_LENGTH; + case SRP_SHA512: return SHA512_DIGEST_LENGTH;*/ + default: return -1; + }; +} + +inline static int mpz_num_bytes(const mpz_t op) +{ + return (mpz_sizeinbase (op, 2) + 7) / 8; +} + +inline static void mpz_to_bin(const mpz_t op, unsigned char *to) +{ + mpz_export(to, NULL, 1, 1, 1, 0, op); +} + +inline static void mpz_from_bin(const unsigned char *s, size_t len, mpz_t ret) +{ + mpz_import(ret, len, 1, 1, 1, 0, s); +} + +// set op to (op1 * op2) mod d, using tmp for the calculation +inline static void mpz_mulm(mpz_t op, const mpz_t op1, const mpz_t op2, const mpz_t d, mpz_t tmp) +{ + mpz_mul(tmp, op1, op2); + mpz_mod(op, tmp, d); +} + +// set op to (op1 + op2) mod d, using tmp for the calculation +inline static void mpz_addm( mpz_t op, const mpz_t op1, const mpz_t op2, const mpz_t d, mpz_t tmp ) +{ + mpz_add(tmp, op1, op2); + mpz_mod(op, tmp, d); +} + +// set op to (op1 - op2) mod d, using tmp for the calculation +inline static void mpz_subm(mpz_t op, const mpz_t op1, const mpz_t op2, const mpz_t d, mpz_t tmp) +{ + mpz_sub(tmp, op1, op2); + mpz_mod(op, tmp, d); +} + +static int H_nn(mpz_t result, SRP_HashAlgorithm alg, const mpz_t N, const mpz_t n1, const mpz_t n2) +{ + unsigned char buff[SHA512_DIGEST_LENGTH]; + size_t len_N = mpz_num_bytes(N); + size_t len_n1 = mpz_num_bytes(n1); + size_t len_n2 = mpz_num_bytes(n2); + size_t nbytes = len_N + len_N; + unsigned char *bin = (unsigned char *) malloc(nbytes); + if (!bin) + return 0; + if (len_n1 > len_N || len_n2 > len_N) { + free(bin); + return 0; + } + memset(bin, 0, nbytes); + mpz_to_bin(n1, bin + (len_N - len_n1)); + mpz_to_bin(n2, bin + (len_N + len_N - len_n2)); + hash( alg, bin, nbytes, buff ); + free(bin); + mpz_from_bin(buff, hash_length(alg), result); + return 1; +} + +static int H_ns(mpz_t result, SRP_HashAlgorithm alg, const unsigned char *n, size_t len_n, const unsigned char *bytes, size_t len_bytes) +{ + unsigned char buff[SHA512_DIGEST_LENGTH]; + size_t nbytes = len_n + len_bytes; + unsigned char *bin = (unsigned char *) malloc(nbytes); + if (!bin) + return 0; + memcpy(bin, n, len_n); + memcpy(bin + len_n, bytes, len_bytes); + hash(alg, bin, nbytes, buff); + free(bin); + mpz_from_bin(buff, hash_length(alg), result); + return 1; +} + +static int calculate_x(mpz_t result, SRP_HashAlgorithm alg, const unsigned char *salt, size_t salt_len, const char *username, const unsigned char *password, size_t password_len) +{ + unsigned char ucp_hash[SHA512_DIGEST_LENGTH]; + HashCTX ctx; + hash_init(alg, &ctx); + + srp_dbg_data((char*) username, strlen(username), "Username for x: "); + srp_dbg_data((char*) password, password_len, "Password for x: "); + hash_update(alg, &ctx, username, strlen(username)); + hash_update(alg, &ctx, ":", 1); + hash_update(alg, &ctx, password, password_len); + + hash_final(alg, &ctx, ucp_hash); + + return H_ns(result, alg, salt, salt_len, ucp_hash, hash_length(alg)); +} + +static void update_hash_n(SRP_HashAlgorithm alg, HashCTX *ctx, const mpz_t n) +{ + size_t len = mpz_num_bytes(n); + unsigned char* n_bytes = (unsigned char *) malloc(len); + if (!n_bytes) + return; + mpz_to_bin(n, n_bytes); + hash_update(alg, ctx, n_bytes, len); + free(n_bytes); +} + +static void hash_num( SRP_HashAlgorithm alg, const mpz_t n, unsigned char *dest ) +{ + int nbytes = mpz_num_bytes(n); + unsigned char *bin = (unsigned char *) malloc(nbytes); + if(!bin) + return; + mpz_to_bin(n, bin); + hash(alg, bin, nbytes, dest); + free(bin); +} + +static void calculate_M(SRP_HashAlgorithm alg, NGConstant *ng, unsigned char *dest, + const char *I, const unsigned char *s_bytes, size_t s_len, + const mpz_t A, const mpz_t B, const unsigned char *K) +{ + unsigned char H_N[SHA512_DIGEST_LENGTH]; + unsigned char H_g[SHA512_DIGEST_LENGTH]; + unsigned char H_I[SHA512_DIGEST_LENGTH]; + unsigned char H_xor[SHA512_DIGEST_LENGTH]; + HashCTX ctx; + size_t i = 0; + size_t hash_len = hash_length(alg); + + hash_num(alg, ng->N, H_N); + hash_num(alg, ng->g, H_g); + + hash(alg, (const unsigned char *)I, strlen(I), H_I); + + + for (i = 0; i < hash_len; i++ ) + H_xor[i] = H_N[i] ^ H_g[i]; + + hash_init(alg, &ctx); + + hash_update(alg, &ctx, H_xor, hash_len); + hash_update(alg, &ctx, H_I, hash_len); + hash_update(alg, &ctx, s_bytes, s_len); + update_hash_n(alg, &ctx, A); + update_hash_n(alg, &ctx, B); + hash_update(alg, &ctx, K, hash_len); + + hash_final(alg, &ctx, dest); +} + +static void calculate_H_AMK(SRP_HashAlgorithm alg, unsigned char *dest, const mpz_t A, const unsigned char *M, const unsigned char *K) +{ + HashCTX ctx; + + hash_init(alg, &ctx); + + update_hash_n(alg, &ctx, A); + hash_update(alg, &ctx, M, hash_length(alg)); + hash_update(alg, &ctx, K, hash_length(alg)); + + hash_final(alg, &ctx, dest); +} + + +struct srp_pcgrandom { + unsigned long long int m_state; + unsigned long long int m_inc; +}; typedef struct srp_pcgrandom srp_pcgrandom; + +static unsigned long int srp_pcgrandom_next(srp_pcgrandom *r) +{ + unsigned long long int oldstate = r->m_state; + r->m_state = oldstate * 6364136223846793005ULL + r->m_inc; + + unsigned long int xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + unsigned long int rot = oldstate >> 59u; + return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); +} + +static void srp_pcgrandom_seed(srp_pcgrandom *r, unsigned long long int state, + unsigned long long int seq) +{ + r->m_state = 0U; + r->m_inc = (seq << 1u) | 1u; + srp_pcgrandom_next(r); + r->m_state += state; + srp_pcgrandom_next(r); +} + + +static int fill_buff() +{ + g_rand_idx = 0; + +#ifdef WIN32 + HCRYPTPROV wctx; +#else + FILE *fp = 0; +#endif + +#ifdef WIN32 + + CryptAcquireContext(&wctx, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + CryptGenRandom(wctx, sizeof(g_rand_buff), (BYTE*) g_rand_buff); + CryptReleaseContext(wctx, 0); + + return 1; + +#else + fp = fopen("/dev/urandom", "r"); + + if (fp) { + fread(g_rand_buff, sizeof(g_rand_buff), 1, fp); + fclose(fp); + } else { + srp_pcgrandom *r = (srp_pcgrandom *) malloc(sizeof(srp_pcgrandom)); + srp_pcgrandom_seed(r, time(NULL) ^ clock(), 0xda3e39cb94b95bdbULL); + size_t i = 0; + for (i = 0; i < RAND_BUFF_MAX; i++) { + g_rand_buff[i] = srp_pcgrandom_next(r); + } + } +#endif + return 1; +} + +static void mpz_fill_random(mpz_t num) +{ + // was call: BN_rand(num, 256, -1, 0); + if (RAND_BUFF_MAX - g_rand_idx < 32) + fill_buff(); + mpz_from_bin((const unsigned char *) (&g_rand_buff[g_rand_idx]), 32, num); + g_rand_idx += 32; +} + +static void init_random() +{ + if (g_initialized) + return; + g_initialized = fill_buff(); +} + +#define srp_dbg_num(num, text) ; +/*void srp_dbg_num(mpz_t num, char * prevtext) +{ + int len_num = mpz_num_bytes(num); + char *bytes_num = (char*) malloc(len_num); + mpz_to_bin(num, (unsigned char *) bytes_num); + srp_dbg_data(bytes_num, len_num, prevtext); + free(bytes_num); + +}*/ + +/*********************************************************************************************************** + * + * Exported Functions + * + ***********************************************************************************************************/ + +void srp_create_salted_verification_key( SRP_HashAlgorithm alg, + SRP_NGType ng_type, const char *username_for_verifier, + const unsigned char *password, size_t len_password, + unsigned char **bytes_s, size_t *len_s, + unsigned char **bytes_v, size_t *len_v, + const char *n_hex, const char *g_hex ) +{ + mpz_t v; mpz_init(v); + mpz_t x; mpz_init(x); + NGConstant *ng = new_ng(ng_type, n_hex, g_hex); + + if(!ng) + goto cleanup_and_exit; + + init_random(); /* Only happens once */ + + if (*bytes_s == NULL) { + *len_s = 16; + if (RAND_BUFF_MAX - g_rand_idx < 16) + fill_buff(); + *bytes_s = (unsigned char*)malloc(sizeof(char) * 16); + memcpy(*bytes_s, &g_rand_buff + g_rand_idx, sizeof(char) * 16); + g_rand_idx += 16; + } + + + if (!calculate_x(x, alg, *bytes_s, *len_s, username_for_verifier, + password, len_password)) + goto cleanup_and_exit; + + srp_dbg_num(x, "Server calculated x: "); + + mpz_powm(v, ng->g, x, ng->N); + + *len_v = mpz_num_bytes(v); + + *bytes_v = (unsigned char*)malloc(*len_v); + + if (!bytes_v) + goto cleanup_and_exit; + + mpz_to_bin(v, *bytes_v); + +cleanup_and_exit: + delete_ng( ng ); + mpz_clear(v); + mpz_clear(x); +} + + + +/* Out: bytes_B, len_B. + * + * On failure, bytes_B will be set to NULL and len_B will be set to 0 + */ +struct SRPVerifier *srp_verifier_new(SRP_HashAlgorithm alg, + SRP_NGType ng_type, const char *username, + const unsigned char *bytes_s, size_t len_s, + const unsigned char *bytes_v, size_t len_v, + const unsigned char *bytes_A, size_t len_A, + const unsigned char *bytes_b, size_t len_b, + unsigned char **bytes_B, size_t *len_B, + const char *n_hex, const char *g_hex ) +{ + mpz_t v; mpz_init(v); mpz_from_bin(bytes_v, len_v, v); + mpz_t A; mpz_init(A); mpz_from_bin(bytes_A, len_A, A); + mpz_t u; mpz_init(u); + mpz_t B; mpz_init(B); + mpz_t S; mpz_init(S); + mpz_t b; mpz_init(b); + mpz_t k; mpz_init(k); + mpz_t tmp1; mpz_init(tmp1); + mpz_t tmp2; mpz_init(tmp2); + mpz_t tmp3; mpz_init(tmp3); + size_t ulen = strlen(username) + 1; + NGConstant *ng = new_ng(ng_type, n_hex, g_hex); + struct SRPVerifier *ver = 0; + + *len_B = 0; + *bytes_B = 0; + + if (!ng) + goto cleanup_and_exit; + + ver = (struct SRPVerifier *) malloc( sizeof(struct SRPVerifier) ); + + if (!ver) + goto cleanup_and_exit; + + init_random(); /* Only happens once */ + + ver->username = (char *) malloc(ulen); + ver->hash_alg = alg; + ver->ng = ng; + + if (!ver->username) { + free(ver); + ver = 0; + goto cleanup_and_exit; + } + + memcpy((char*)ver->username, username, ulen); + + ver->authenticated = 0; + + /* SRP-6a safety check */ + mpz_mod(tmp1, A, ng->N); + if (mpz_sgn(tmp1) != 0) { + if (bytes_b) { + mpz_from_bin(bytes_b, len_b, b); + } else { + mpz_fill_random(b); + } + + if (!H_nn(k, alg, ng->N, ng->N, ng->g)) { + free(ver); + ver = 0; + goto cleanup_and_exit; + } + + /* B = kv + g^b */ + mpz_mulm(tmp1, k, v, ng->N, tmp3); + mpz_powm(tmp2, ng->g, b, ng->N); + mpz_addm(B, tmp1, tmp2, ng->N, tmp3); + + if (!H_nn(u, alg, ng->N, A, B)) { + free(ver); + ver = 0; + goto cleanup_and_exit; + } + + srp_dbg_num(u, "Server calculated u: "); + + /* S = (A *(v^u)) ^ b */ + mpz_powm(tmp1, v, u, ng->N); + mpz_mulm(tmp2, A, tmp1, ng->N, tmp3); + mpz_powm(S, tmp2, b, ng->N); + + hash_num(alg, S, ver->session_key); + + calculate_M(alg, ng, ver->M, username, bytes_s, len_s, A, B, ver->session_key); + calculate_H_AMK(alg, ver->H_AMK, A, ver->M, ver->session_key); + + *len_B = mpz_num_bytes(B); + *bytes_B = (unsigned char*)malloc(*len_B); + + if (!*bytes_B) { + free(ver->username); + free(ver); + ver = 0; + *len_B = 0; + goto cleanup_and_exit; + } + + mpz_to_bin(B, *bytes_B); + + ver->bytes_B = *bytes_B; + } else { + free(ver); + ver = 0; + } + +cleanup_and_exit: + mpz_clear(v); + mpz_clear(A); + mpz_clear(u); + mpz_clear(k); + mpz_clear(B); + mpz_clear(S); + mpz_clear(b); + mpz_clear(tmp1); + mpz_clear(tmp2); + mpz_clear(tmp3); + return ver; +} + + + + +void srp_verifier_delete(struct SRPVerifier *ver) +{ + if (ver) { + delete_ng(ver->ng); + free(ver->username); + free(ver->bytes_B); + memset(ver, 0, sizeof(*ver)); + free(ver); + } +} + + + +int srp_verifier_is_authenticated(struct SRPVerifier *ver) +{ + return ver->authenticated; +} + + +const char *srp_verifier_get_username(struct SRPVerifier *ver) +{ + return ver->username; +} + + +const unsigned char *srp_verifier_get_session_key(struct SRPVerifier *ver, size_t *key_length) +{ + if (key_length) + *key_length = hash_length(ver->hash_alg); + return ver->session_key; +} + + +size_t srp_verifier_get_session_key_length(struct SRPVerifier *ver) +{ + return hash_length(ver->hash_alg); +} + + +/* user_M must be exactly SHA512_DIGEST_LENGTH bytes in size */ +void srp_verifier_verify_session(struct SRPVerifier *ver, const unsigned char *user_M, unsigned char **bytes_HAMK) +{ + if (memcmp(ver->M, user_M, hash_length(ver->hash_alg)) == 0) { + ver->authenticated = 1; + *bytes_HAMK = ver->H_AMK; + } else + *bytes_HAMK = NULL; +} + +/*******************************************************************************/ + +struct SRPUser *srp_user_new(SRP_HashAlgorithm alg, SRP_NGType ng_type, + const char *username, const char *username_for_verifier, + const unsigned char *bytes_password, size_t len_password, + const char *n_hex, const char *g_hex) +{ + struct SRPUser *usr = (struct SRPUser *) malloc(sizeof(struct SRPUser)); + size_t ulen = strlen(username) + 1; + size_t uvlen = strlen(username_for_verifier) + 1; + + if (!usr) + goto err_exit; + + init_random(); /* Only happens once */ + + usr->hash_alg = alg; + usr->ng = new_ng(ng_type, n_hex, g_hex); + + mpz_init(usr->a); + mpz_init(usr->A); + mpz_init(usr->S); + + if (!usr->ng) + goto err_exit; + + usr->username = (char*)malloc(ulen); + usr->username_verifier = (char*)malloc(uvlen); + usr->password = (unsigned char*)malloc(len_password); + usr->password_len = len_password; + + if (!usr->username || !usr->password) + goto err_exit; + + memcpy(usr->username, username, ulen); + memcpy(usr->username_verifier, username_for_verifier, uvlen); + memcpy(usr->password, bytes_password, len_password); + + usr->authenticated = 0; + + usr->bytes_A = 0; + + return usr; + +err_exit: + if (usr) { + mpz_clear(usr->a); + mpz_clear(usr->A); + mpz_clear(usr->S); + if (usr->ng) + delete_ng(usr->ng); + if (usr->username) + free(usr->username); + if (usr->username_verifier) + free(usr->username_verifier); + if (usr->password) { + memset(usr->password, 0, usr->password_len); + free(usr->password); + } + free(usr); + } + + return 0; +} + + + +void srp_user_delete(struct SRPUser *usr) +{ + if(usr) { + mpz_clear(usr->a); + mpz_clear(usr->A); + mpz_clear(usr->S); + + delete_ng(usr->ng); + + memset(usr->password, 0, usr->password_len); + + free(usr->username); + free(usr->username_verifier); + free(usr->password); + + if (usr->bytes_A) + free(usr->bytes_A); + + memset(usr, 0, sizeof(*usr)); + free(usr); + } +} + + + +int srp_user_is_authenticated(struct SRPUser *usr) +{ + return usr->authenticated; +} + + +const char *srp_user_get_username(struct SRPUser *usr) +{ + return usr->username; +} + + +const unsigned char* srp_user_get_session_key(struct SRPUser* usr, size_t* key_length) +{ + if (key_length) + *key_length = hash_length(usr->hash_alg); + return usr->session_key; +} + + +size_t srp_user_get_session_key_length(struct SRPUser *usr) +{ + return hash_length(usr->hash_alg); +} + + +/* Output: username, bytes_A, len_A */ +void srp_user_start_authentication(struct SRPUser *usr, char **username, + const unsigned char *bytes_a, size_t len_a, + unsigned char **bytes_A, size_t *len_A) +{ + if (bytes_a) { + mpz_from_bin(bytes_a, len_a, usr->a); + } else { + mpz_fill_random(usr->a); + } + + mpz_powm(usr->A, usr->ng->g, usr->a, usr->ng->N); + + *len_A = mpz_num_bytes(usr->A); + *bytes_A = (unsigned char*)malloc(*len_A); + + if (!*bytes_A) { + *len_A = 0; + *bytes_A = 0; + *username = 0; + return; + } + + mpz_to_bin(usr->A, *bytes_A); + + usr->bytes_A = *bytes_A; + if (username) + *username = usr->username; +} + + +/* Output: bytes_M. Buffer length is SHA512_DIGEST_LENGTH */ +void srp_user_process_challenge(struct SRPUser *usr, + const unsigned char *bytes_s, size_t len_s, + const unsigned char *bytes_B, size_t len_B, + unsigned char **bytes_M, size_t *len_M) +{ + mpz_t B; mpz_init(B); mpz_from_bin(bytes_B, len_B, B); + mpz_t u; mpz_init(u); + mpz_t x; mpz_init(x); + mpz_t k; mpz_init(k); + mpz_t v; mpz_init(v); + mpz_t tmp1; mpz_init(tmp1); + mpz_t tmp2; mpz_init(tmp2); + mpz_t tmp3; mpz_init(tmp3); + mpz_t tmp4; mpz_init(tmp4); + + *len_M = 0; + *bytes_M = 0; + + if (!H_nn(u, usr->hash_alg, usr->ng->N, usr->A, B)) + goto cleanup_and_exit; + + srp_dbg_num(u, "Client calculated u: "); + + if (!calculate_x(x, usr->hash_alg, bytes_s, len_s, + usr->username_verifier, usr->password, usr->password_len)) + goto cleanup_and_exit; + + srp_dbg_num(x, "Client calculated x: "); + + if (!H_nn(k, usr->hash_alg, usr->ng->N, usr->ng->N, usr->ng->g)) + goto cleanup_and_exit; + + /* SRP-6a safety check */ + if ( mpz_sgn(B) != 0 && mpz_sgn(u) != 0 ) { + mpz_powm(v, usr->ng->g, x, usr->ng->N); + + srp_dbg_num(v, "Client calculated v: "); + + /* S = (B - k*(g^x)) ^ (a + ux) */ + mpz_mul(tmp1, u, x); + mpz_add(tmp2, usr->a, tmp1); /* tmp2 = (a + ux) */ + mpz_powm(tmp1, usr->ng->g, x, usr->ng->N); /* tmp1 = g^x */ + mpz_mulm(tmp3, k, tmp1, usr->ng->N, tmp4); /* tmp3 = k*(g^x) */ + mpz_subm(tmp1, B, tmp3, usr->ng->N, tmp4); /* tmp1 = (B - K*(g^x)) */ + mpz_powm(usr->S, tmp1, tmp2, usr->ng->N); + + hash_num(usr->hash_alg, usr->S, usr->session_key); + + calculate_M( usr->hash_alg, usr->ng, usr->M, usr->username, bytes_s, len_s, usr->A,B, usr->session_key ); + calculate_H_AMK( usr->hash_alg, usr->H_AMK, usr->A, usr->M, usr->session_key ); + + *bytes_M = usr->M; + if (len_M) + *len_M = hash_length( usr->hash_alg ); + } else { + *bytes_M = NULL; + if (len_M) + *len_M = 0; + } + +cleanup_and_exit: + + mpz_clear(B); + mpz_clear(u); + mpz_clear(x); + mpz_clear(k); + mpz_clear(v); + mpz_clear(tmp1); + mpz_clear(tmp2); + mpz_clear(tmp3); + mpz_clear(tmp4); +} + + +void srp_user_verify_session(struct SRPUser *usr, const unsigned char *bytes_HAMK) +{ + if (memcmp(usr->H_AMK, bytes_HAMK, hash_length(usr->hash_alg)) == 0) + usr->authenticated = 1; +} diff --git a/src/util/srp.h b/src/util/srp.h new file mode 100644 index 000000000..15a2b8a68 --- /dev/null +++ b/src/util/srp.h @@ -0,0 +1,171 @@ +/* + * Secure Remote Password 6a implementation + * https://github.com/est31/csrp-gmp + * + * The MIT License (MIT) + * + * Copyright (c) 2010, 2013 Tom Cocagne, 2015 est31 <MTest31@outlook.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +/* + * + * Purpose: This is a direct implementation of the Secure Remote Password + * Protocol version 6a as described by + * http://srp.stanford.edu/design.html + * + * Author: tom.cocagne@gmail.com (Tom Cocagne) + * + * Dependencies: LibGMP + * + * Usage: Refer to test_srp.c for a demonstration + * + * Notes: + * This library allows multiple combinations of hashing algorithms and + * prime number constants. For authentication to succeed, the hash and + * prime number constants must match between + * srp_create_salted_verification_key(), srp_user_new(), + * and srp_verifier_new(). A recommended approach is to determine the + * desired level of security for an application and globally define the + * hash and prime number constants to the predetermined values. + * + * As one might suspect, more bits means more security. As one might also + * suspect, more bits also means more processing time. The test_srp.c + * program can be easily modified to profile various combinations of + * hash & prime number pairings. + */ + +#ifndef SRP_H +#define SRP_H + + +struct SRPVerifier; +struct SRPUser; + +typedef enum +{ + SRP_NG_1024, + SRP_NG_2048, + SRP_NG_4096, + SRP_NG_8192, + SRP_NG_CUSTOM +} SRP_NGType; + +typedef enum +{ + /*SRP_SHA1,*/ + /*SRP_SHA224,*/ + SRP_SHA256, + /*SRP_SHA384, + SRP_SHA512*/ +} SRP_HashAlgorithm; + +/* Out: bytes_v, len_v + * + * The caller is responsible for freeing the memory allocated for bytes_v + * + * The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type. + * If provided, they must contain ASCII text of the hexidecimal notation. + * + * If bytes_s == NULL, it is filled with random data. The caller is responsible for freeing. + */ +void srp_create_salted_verification_key( SRP_HashAlgorithm alg, + SRP_NGType ng_type, const char *username_for_verifier, + const unsigned char *password, size_t len_password, + unsigned char **bytes_s, size_t *len_s, + unsigned char **bytes_v, size_t *len_v, + const char * n_hex, const char *g_hex ); + +/* Out: bytes_B, len_B. + * + * On failure, bytes_B will be set to NULL and len_B will be set to 0 + * + * The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type + * + * If bytes_b == NULL, random data is used for b. + */ +struct SRPVerifier* srp_verifier_new(SRP_HashAlgorithm alg, SRP_NGType ng_type, + const char *username, + const unsigned char *bytes_s, size_t len_s, + const unsigned char *bytes_v, size_t len_v, + const unsigned char *bytes_A, size_t len_A, + const unsigned char *bytes_b, size_t len_b, + unsigned char** bytes_B, size_t *len_B, + const char* n_hex, const char* g_hex); + + +void srp_verifier_delete( struct SRPVerifier* ver ); + + +int srp_verifier_is_authenticated( struct SRPVerifier* ver ); + + +const char * srp_verifier_get_username( struct SRPVerifier* ver ); + +/* key_length may be null */ +const unsigned char* srp_verifier_get_session_key( struct SRPVerifier* ver, + size_t *key_length ); + + +size_t srp_verifier_get_session_key_length(struct SRPVerifier* ver); + + +/* user_M must be exactly srp_verifier_get_session_key_length() bytes in size */ +void srp_verifier_verify_session( struct SRPVerifier* ver, + const unsigned char* user_M, unsigned char** bytes_HAMK ); + +/*******************************************************************************/ + +/* The n_hex and g_hex parameters should be 0 unless SRP_NG_CUSTOM is used for ng_type */ +struct SRPUser *srp_user_new(SRP_HashAlgorithm alg, SRP_NGType ng_type, + const char *username, const char *username_for_verifier, + const unsigned char *bytes_password, size_t len_password, + const char *n_hex, const char *g_hex); + +void srp_user_delete(struct SRPUser * usr); + +int srp_user_is_authenticated(struct SRPUser * usr); + + +const char* srp_user_get_username(struct SRPUser * usr); + +/* key_length may be null */ +const unsigned char* srp_user_get_session_key(struct SRPUser* usr, size_t* key_length); + +size_t srp_user_get_session_key_length(struct SRPUser* usr); + +/* Output: username, bytes_A, len_A. If you don't want it get written, set username to NULL. + * If bytes_a == NULL, random data is used for a. */ +void srp_user_start_authentication(struct SRPUser* usr, char** username, + const unsigned char* bytes_a, size_t len_a, + unsigned char** bytes_A, size_t* len_A); + +/* Output: bytes_M, len_M (len_M may be null and will always be + * srp_user_get_session_key_length() bytes in size) */ +void srp_user_process_challenge(struct SRPUser *usr, + const unsigned char *bytes_s, size_t len_s, + const unsigned char *bytes_B, size_t len_B, + unsigned char **bytes_M, size_t *len_M); + +/* bytes_HAMK must be exactly srp_user_get_session_key_length() bytes in size */ +void srp_user_verify_session(struct SRPUser* usr, const unsigned char* bytes_HAMK); + +#endif /* Include Guard */ diff --git a/src/util/string.cpp b/src/util/string.cpp index 956a1ac44..2c4143c76 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -22,25 +22,163 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "numeric.h" #include "log.h" -#include "sha1.h" -#include "base64.h" #include "hex.h" #include "../porting.h" -#include <algorithm> #include <sstream> #include <iomanip> #include <map> +#ifndef _WIN32 + #include <iconv.h> +#else + #define _WIN32_WINNT 0x0501 + #include <windows.h> +#endif + +#if defined(_ICONV_H_) && (defined(__FreeBSD__) || defined(__NetBSD__) || \ + defined(__OpenBSD__) || defined(__DragonFly__)) + #define BSD_ICONV_USED +#endif + static bool parseHexColorString(const std::string &value, video::SColor &color); static bool parseNamedColorString(const std::string &value, video::SColor &color); +#ifndef _WIN32 + +bool convert(const char *to, const char *from, char *outbuf, + size_t outbuf_size, char *inbuf, size_t inbuf_size) +{ + iconv_t cd = iconv_open(to, from); + +#ifdef BSD_ICONV_USED + const char *inbuf_ptr = inbuf; +#else + char *inbuf_ptr = inbuf; +#endif + + char *outbuf_ptr = outbuf; + + size_t *inbuf_left_ptr = &inbuf_size; + size_t *outbuf_left_ptr = &outbuf_size; + + size_t old_size = inbuf_size; + while (inbuf_size > 0) { + iconv(cd, &inbuf_ptr, inbuf_left_ptr, &outbuf_ptr, outbuf_left_ptr); + if (inbuf_size == old_size) { + iconv_close(cd); + return false; + } + old_size = inbuf_size; + } + + iconv_close(cd); + return true; +} + +std::wstring utf8_to_wide(const std::string &input) +{ + size_t inbuf_size = input.length() + 1; + // maximum possible size, every character is sizeof(wchar_t) bytes + size_t outbuf_size = (input.length() + 1) * sizeof(wchar_t); + + char *inbuf = new char[inbuf_size]; + memcpy(inbuf, input.c_str(), inbuf_size); + char *outbuf = new char[outbuf_size]; + memset(outbuf, 0, outbuf_size); + + if (!convert("WCHAR_T", "UTF-8", outbuf, outbuf_size, inbuf, inbuf_size)) { + infostream << "Couldn't convert UTF-8 string 0x" << hex_encode(input) + << " into wstring" << std::endl; + delete[] inbuf; + delete[] outbuf; + return L"<invalid UTF-8 string>"; + } + std::wstring out((wchar_t *)outbuf); + + delete[] inbuf; + delete[] outbuf; + + return out; +} + +#ifdef __ANDROID__ +// TODO: this is an ugly fix for wide_to_utf8 somehow not working on android +std::string wide_to_utf8(const std::wstring &input) +{ + return wide_to_narrow(input); +} +#else +std::string wide_to_utf8(const std::wstring &input) +{ + size_t inbuf_size = (input.length() + 1) * sizeof(wchar_t); + // maximum possible size: utf-8 encodes codepoints using 1 up to 6 bytes + size_t outbuf_size = (input.length() + 1) * 6; + + char *inbuf = new char[inbuf_size]; + memcpy(inbuf, input.c_str(), inbuf_size); + char *outbuf = new char[outbuf_size]; + memset(outbuf, 0, outbuf_size); + + if (!convert("UTF-8", "WCHAR_T", outbuf, outbuf_size, inbuf, inbuf_size)) { + infostream << "Couldn't convert wstring 0x" << hex_encode(inbuf, inbuf_size) + << " into UTF-8 string" << std::endl; + delete[] inbuf; + delete[] outbuf; + return "<invalid wstring>"; + } + std::string out(outbuf); + + delete[] inbuf; + delete[] outbuf; + + return out; +} + +#endif +#else // _WIN32 + +std::wstring utf8_to_wide(const std::string &input) +{ + size_t outbuf_size = input.size() + 1; + wchar_t *outbuf = new wchar_t[outbuf_size]; + memset(outbuf, 0, outbuf_size * sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, input.c_str(), input.size(), + outbuf, outbuf_size); + std::wstring out(outbuf); + delete[] outbuf; + return out; +} + +std::string wide_to_utf8(const std::wstring &input) +{ + size_t outbuf_size = (input.size() + 1) * 6; + char *outbuf = new char[outbuf_size]; + memset(outbuf, 0, outbuf_size); + WideCharToMultiByte(CP_UTF8, 0, input.c_str(), input.size(), + outbuf, outbuf_size, NULL, NULL); + std::string out(outbuf); + delete[] outbuf; + return out; +} + +#endif // _WIN32 + +wchar_t *utf8_to_wide_c(const char *str) +{ + std::wstring ret = utf8_to_wide(std::string(str)).c_str(); + size_t len = ret.length(); + wchar_t *ret_c = new wchar_t[len + 1]; + memset(ret_c, 0, (len + 1) * sizeof(wchar_t)); + memcpy(ret_c, ret.c_str(), len * sizeof(wchar_t)); + return ret_c; +} // You must free the returned string! // The returned string is allocated using new wchar_t *narrow_to_wide_c(const char *str) { - wchar_t* nstr = 0; + wchar_t *nstr = NULL; #if defined(_WIN32) int nResult = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR) str, -1, 0, 0); if (nResult == 0) { @@ -51,7 +189,7 @@ wchar_t *narrow_to_wide_c(const char *str) } #else size_t len = strlen(str); - nstr = new wchar_t[len+1]; + nstr = new wchar_t[len + 1]; std::wstring intermediate = narrow_to_wide(str); memset(nstr, 0, (len + 1) * sizeof(wchar_t)); @@ -63,6 +201,7 @@ wchar_t *narrow_to_wide_c(const char *str) #ifdef __ANDROID__ + const wchar_t* wide_chars = L" !\"#$%&'()*+,-./0123456789:;<=>?@" L"ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" @@ -175,25 +314,6 @@ std::string wide_to_narrow(const std::wstring &wcs) #endif -// Get an sha-1 hash of the player's name combined with -// the password entered. That's what the server uses as -// their password. (Exception : if the password field is -// blank, we send a blank password - this is for backwards -// compatibility with password-less players). -std::string translatePassword(std::string playername, std::wstring password) -{ - if (password.length() == 0) - return ""; - - std::string slt = playername + wide_to_narrow(password); - SHA1 sha1; - sha1.addBytes(slt.c_str(), slt.length()); - unsigned char *digest = sha1.getDigest(); - std::string pwd = base64_encode(digest, 20); - free(digest); - return pwd; -} - std::string urlencode(std::string str) { // Encodes non-unreserved URI characters by a percent sign @@ -319,7 +439,7 @@ char *mystrtok_r(char *s, const char *sep, char **lasts) } t++; } - + *lasts = t; return s; } @@ -328,15 +448,15 @@ u64 read_seed(const char *str) { char *endptr; u64 num; - + if (str[0] == '0' && str[1] == 'x') num = strtoull(str, &endptr, 16); else num = strtoull(str, &endptr, 10); - + if (*endptr) num = murmur_hash_64_ua(str, (int)strlen(str), 0x1337); - + return num; } @@ -467,8 +587,8 @@ ColorContainer::ColorContainer() colors["greenyellow"] = 0xadff2f; colors["honeydew"] = 0xf0fff0; colors["hotpink"] = 0xff69b4; - colors["indianred "] = 0xcd5c5c; - colors["indigo "] = 0x4b0082; + colors["indianred"] = 0xcd5c5c; + colors["indigo"] = 0x4b0082; colors["ivory"] = 0xfffff0; colors["khaki"] = 0xf0e68c; colors["lavender"] = 0xe6e6fa; @@ -613,4 +733,3 @@ void str_replace(std::string &str, char from, char to) { std::replace(str.begin(), str.end(), from, to); } - diff --git a/src/util/string.h b/src/util/string.h index dc520e3a8..793baad0e 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -25,25 +25,40 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <string> #include <cstring> #include <vector> +#include <map> #include <sstream> #include <cctype> #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) +// Checks whether a byte is an inner byte for an utf-8 multibyte sequence +#define IS_UTF8_MULTB_INNER(x) (((unsigned char)x >= 0x80) && ((unsigned char)x < 0xc0)) + +typedef std::map<std::string, std::string> StringMap; + struct FlagDesc { const char *name; u32 flag; }; +// try not to convert between wide/utf8 encodings; this can result in data loss +// try to only convert between them when you need to input/output stuff via Irrlicht +std::wstring utf8_to_wide(const std::string &input); +std::string wide_to_utf8(const std::wstring &input); + +wchar_t *utf8_to_wide_c(const char *str); + +// NEVER use those two functions unless you have a VERY GOOD reason to +// they just convert between wide and multibyte encoding +// multibyte encoding depends on current locale, this is no good, especially on Windows // You must free the returned string! // The returned string is allocated using new wchar_t *narrow_to_wide_c(const char *str); - std::wstring narrow_to_wide(const std::string &mbs); std::string wide_to_narrow(const std::wstring &wcs); -std::string translatePassword(std::string playername, std::wstring password); + std::string urlencode(std::string str); std::string urldecode(std::string str); u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask); @@ -150,6 +165,24 @@ inline bool str_starts_with(const std::basic_string<T> &str, return true; } +/** + * Check whether \p str begins with the string prefix. If \p case_insensitive + * is true then the check is case insensitve (default is false; i.e. case is + * significant). + * + * @param str + * @param prefix + * @param case_insensitive + * @return true if the str begins with prefix + */ +template <typename T> +inline bool str_starts_with(const std::basic_string<T> &str, + const T *prefix, + bool case_insensitive = false) +{ + return str_starts_with(str, std::basic_string<T>(prefix), + case_insensitive); +} /** * Splits a string into its component parts separated by the character @@ -383,7 +416,10 @@ inline bool string_allowed_blacklist(const std::string &str, * every \p row_len characters whether it breaks a word or not. It is * intended to be used for, for example, showing paths in the GUI. * - * @param from The string to be wrapped into rows. + * @note This function doesn't wrap inside utf-8 multibyte sequences and also + * counts multibyte sequences correcly as single characters. + * + * @param from The (utf-8) string to be wrapped into rows. * @param row_len The row length (in characters). * @return A new string with the wrapping applied. */ @@ -392,9 +428,14 @@ inline std::string wrap_rows(const std::string &from, { std::string to; + size_t character_idx = 0; for (size_t i = 0; i < from.size(); i++) { - if (i != 0 && i % row_len == 0) - to += '\n'; + if (!IS_UTF8_MULTB_INNER(from[i])) { + // Wrap string after last inner byte of char + if (character_idx > 0 && character_idx % row_len == 0) + to += '\n'; + character_idx++; + } to += from[i]; } diff --git a/src/util/thread.h b/src/util/thread.h index 8b3c33621..b3a5e68a2 100644 --- a/src/util/thread.h +++ b/src/util/thread.h @@ -25,15 +25,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "../jthread/jmutex.h" #include "../jthread/jmutexautolock.h" #include "porting.h" +#include "log.h" template<typename T> -class MutexedVariable -{ +class MutexedVariable { public: MutexedVariable(T value): m_value(value) - { - } + {} T get() { @@ -46,13 +45,13 @@ public: JMutexAutoLock lock(m_mutex); m_value = value; } - + // You'll want to grab this in a SharedPtr - JMutexAutoLock * getLock() + JMutexAutoLock *getLock() { return new JMutexAutoLock(m_mutex); } - + // You pretty surely want to grab the lock when accessing this T m_value; @@ -64,8 +63,7 @@ private: A single worker thread - multiple client threads queue framework. */ template<typename Key, typename T, typename Caller, typename CallerData> -class GetResult -{ +class GetResult { public: Key key; T item; @@ -73,34 +71,27 @@ public: }; template<typename Key, typename T, typename Caller, typename CallerData> -class ResultQueue: public MutexedQueue< GetResult<Key, T, Caller, CallerData> > -{ +class ResultQueue : public MutexedQueue<GetResult<Key, T, Caller, CallerData> > { }; template<typename Caller, typename Data, typename Key, typename T> -class CallerInfo -{ +class CallerInfo { public: Caller caller; Data data; - ResultQueue< Key, T, Caller, Data>* dest; + ResultQueue<Key, T, Caller, Data> *dest; }; template<typename Key, typename T, typename Caller, typename CallerData> -class GetRequest -{ +class GetRequest { public: - GetRequest() - { - } - GetRequest(Key a_key) - { + GetRequest() {} + ~GetRequest() {} + + GetRequest(Key a_key) { key = a_key; } - ~GetRequest() - { - } - + Key key; std::list<CallerInfo<Caller, CallerData, Key, T> > callers; }; @@ -113,8 +104,7 @@ public: * @param CallerData data passed back to caller */ template<typename Key, typename T, typename Caller, typename CallerData> -class RequestQueue -{ +class RequestQueue { public: bool empty() { @@ -122,40 +112,36 @@ public: } void add(Key key, Caller caller, CallerData callerdata, - ResultQueue<Key, T, Caller, CallerData> *dest) + ResultQueue<Key, T, Caller, CallerData> *dest) { + typename std::deque<GetRequest<Key, T, Caller, CallerData> >::iterator i; + typename std::list<CallerInfo<Caller, CallerData, Key, T> >::iterator j; + { JMutexAutoLock lock(m_queue.getMutex()); /* If the caller is already on the list, only update CallerData */ - for(typename std::list< GetRequest<Key, T, Caller, CallerData> >::iterator - i = m_queue.getList().begin(); - i != m_queue.getList().end(); ++i) - { + for (i = m_queue.getQueue().begin(); i != m_queue.getQueue().end(); ++i) { GetRequest<Key, T, Caller, CallerData> &request = *i; - - if(request.key == key) - { - for(typename std::list< CallerInfo<Caller, CallerData, Key, T> >::iterator - i = request.callers.begin(); - i != request.callers.end(); ++i) - { - CallerInfo<Caller, CallerData, Key, T> &ca = *i; - if(ca.caller == caller) - { - ca.data = callerdata; - return; - } + if (request.key != key) + continue; + + for (j = request.callers.begin(); j != request.callers.end(); ++j) { + CallerInfo<Caller, CallerData, Key, T> &ca = *j; + if (ca.caller == caller) { + ca.data = callerdata; + return; } - CallerInfo<Caller, CallerData, Key, T> ca; - ca.caller = caller; - ca.data = callerdata; - ca.dest = dest; - request.callers.push_back(ca); - return; } + + CallerInfo<Caller, CallerData, Key, T> ca; + ca.caller = caller; + ca.data = callerdata; + ca.dest = dest; + request.callers.push_back(ca); + return; } } @@ -170,7 +156,7 @@ public: ca.data = callerdata; ca.dest = dest; request.callers.push_back(ca); - + m_queue.push_back(request); } @@ -184,13 +170,11 @@ public: return m_queue.pop_frontNoEx(); } - void pushResult(GetRequest<Key, T, Caller, CallerData> req, - T res) { - - for(typename std::list< CallerInfo<Caller, CallerData, Key, T> >::iterator + void pushResult(GetRequest<Key, T, Caller, CallerData> req, T res) + { + for (typename std::list<CallerInfo<Caller, CallerData, Key, T> >::iterator i = req.callers.begin(); - i != req.callers.end(); ++i) - { + i != req.callers.end(); ++i) { CallerInfo<Caller, CallerData, Key, T> &ca = *i; GetResult<Key,T,Caller,CallerData> result; @@ -205,7 +189,62 @@ public: } private: - MutexedQueue< GetRequest<Key, T, Caller, CallerData> > m_queue; + MutexedQueue<GetRequest<Key, T, Caller, CallerData> > m_queue; +}; + +class UpdateThread : public JThread { +public: + UpdateThread() {} + virtual ~UpdateThread() {} + + void deferUpdate() + { + m_update_sem.Post(); + } + + void Stop() + { + JThread::Stop(); + + // give us a nudge + m_update_sem.Post(); + } + + void *Thread() + { + ThreadStarted(); + + const char *thread_name = getName(); + log_register_thread(thread_name); + porting::setThreadName(thread_name); + + DSTACK(__FUNCTION_NAME); + BEGIN_DEBUG_EXCEPTION_HANDLER + + while (!StopRequested()) { + m_update_sem.Wait(); + + // Empty the queue, just in case doUpdate() is expensive + while (m_update_sem.GetValue()) + m_update_sem.Wait(); + + if (StopRequested()) + break; + + doUpdate(); + } + + END_DEBUG_EXCEPTION_HANDLER(errorstream) + + return NULL; + } + +protected: + virtual void doUpdate() = 0; + virtual const char *getName() = 0; + +private: + JSemaphore m_update_sem; }; #endif diff --git a/src/version.cpp b/src/version.cpp index ecfeb95f8..ca206bded 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -20,27 +20,19 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "config.h" -#ifdef __ANDROID__ +#if defined(__ANDROID__) #include "android_version.h" + #include "android_version_githash.h" #elif defined(USE_CMAKE_CONFIG_H) #include "cmake_config_githash.h" #endif -#ifdef CMAKE_VERSION_GITHASH - #define VERSION_GITHASH CMAKE_VERSION_GITHASH -#else +#ifndef VERSION_GITHASH #define VERSION_GITHASH VERSION_STRING #endif -const char *minetest_version_simple = VERSION_STRING; -const char *minetest_version_hash = VERSION_GITHASH; -#ifdef USE_CMAKE_CONFIG_H -const char *minetest_build_info = - "VER=" VERSION_GITHASH " " CMAKE_BUILD_INFO; -#elif defined(ANDROID) -const char *minetest_build_info = "android jni"; -#else -const char *minetest_build_info = "non-cmake"; -#endif +const char *g_version_string = VERSION_STRING; +const char *g_version_hash = VERSION_GITHASH; +const char *g_build_info = "VER=" VERSION_GITHASH " " BUILD_INFO; diff --git a/src/version.h b/src/version.h index b23e770aa..0b803b82c 100644 --- a/src/version.h +++ b/src/version.h @@ -20,9 +20,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef VERSION_HEADER #define VERSION_HEADER -extern const char *minetest_version_simple; -extern const char *minetest_version_hash; -extern const char *minetest_build_info; +extern const char *g_version_string; +extern const char *g_version_hash; +extern const char *g_build_info; #endif diff --git a/src/voxel.cpp b/src/voxel.cpp index 8ac786aab..87773b240 100644 --- a/src/voxel.cpp +++ b/src/voxel.cpp @@ -390,7 +390,6 @@ void VoxelManipulator::unspreadLight(enum LightBank bank, v3s16 p, u8 oldlight, } } -#if 1 /* Goes recursively through the neighbours of the node. @@ -421,115 +420,6 @@ void VoxelManipulator::unspreadLight(enum LightBank bank, unspreadLight(bank, j->first, j->second, light_sources, nodemgr); } } -#endif - -#if 0 -/* - Goes recursively through the neighbours of the node. - - Alters only transparent nodes. - - If the lighting of the neighbour is lower than the lighting of - the node was (before changing it to 0 at the step before), the - lighting of the neighbour is set to 0 and then the same stuff - repeats for the neighbour. - - The ending nodes of the routine are stored in light_sources. - This is useful when a light is removed. In such case, this - routine can be called for the light node and then again for - light_sources to re-light the area without the removed light. - - values of from_nodes are lighting values. -*/ -void VoxelManipulator::unspreadLight(enum LightBank bank, - core::map<v3s16, u8> & from_nodes, - core::map<v3s16, bool> & light_sources) -{ - v3s16 dirs[6] = { - v3s16(0,0,1), // back - v3s16(0,1,0), // top - v3s16(1,0,0), // right - v3s16(0,0,-1), // front - v3s16(0,-1,0), // bottom - v3s16(-1,0,0), // left - }; - - if(from_nodes.size() == 0) - return; - - core::map<v3s16, u8> unlighted_nodes; - core::map<v3s16, u8>::Iterator j; - j = from_nodes.getIterator(); - - for(; j.atEnd() == false; j++) - { - v3s16 pos = j.getNode()->getKey(); - - addArea(VoxelArea(pos - v3s16(1,1,1), pos + v3s16(1,1,1))); - - //MapNode &n = m_data[m_area.index(pos)]; - - u8 oldlight = j.getNode()->getValue(); - - // Loop through 6 neighbors - for(u16 i=0; i<6; i++) - { - // Get the position of the neighbor node - v3s16 n2pos = pos + dirs[i]; - - u32 n2i = m_area.index(n2pos); - - if(m_flags[n2i] & VOXELFLAG_NO_DATA) - continue; - - MapNode &n2 = m_data[n2i]; - - /* - If the neighbor is dimmer than what was specified - as oldlight (the light of the previous node) - */ - if(n2.getLight(bank, nodemgr) < oldlight) - { - /* - And the neighbor is transparent and it has some light - */ - if(nodemgr->get(n2).light_propagates && n2.getLight(bank, nodemgr) != 0) - { - /* - Set light to 0 and add to queue - */ - - u8 current_light = n2.getLight(bank, nodemgr); - n2.setLight(bank, 0); - - unlighted_nodes.insert(n2pos, current_light); - - /* - Remove from light_sources if it is there - NOTE: This doesn't happen nearly at all - */ - /*if(light_sources.find(n2pos)) - { - std::cout<<"Removed from light_sources"<<std::endl; - light_sources.remove(n2pos); - }*/ - } - } - else{ - light_sources.insert(n2pos, true); - } - } - } - - /*dstream<<"unspreadLight(): Changed block " - <<blockchangecount<<" times" - <<" for "<<from_nodes.size()<<" nodes" - <<std::endl;*/ - - if(unlighted_nodes.size() > 0) - unspreadLight(bank, unlighted_nodes, light_sources); -} -#endif void VoxelManipulator::spreadLight(enum LightBank bank, v3s16 p, INodeDefManager *nodemgr) @@ -594,34 +484,9 @@ void VoxelManipulator::spreadLight(enum LightBank bank, v3s16 p, } } -#if 0 -/* - Lights neighbors of from_nodes, collects all them and then - goes on recursively. - - NOTE: This is faster on small areas but will overflow the - stack on large areas. Thus it is not used. -*/ -void VoxelManipulator::spreadLight(enum LightBank bank, - core::map<v3s16, bool> & from_nodes) -{ - if(from_nodes.size() == 0) - return; - - core::map<v3s16, bool> lighted_nodes; - core::map<v3s16, bool>::Iterator j; - j = from_nodes.getIterator(); - - for(; j.atEnd() == false; j++) - { - v3s16 pos = j.getNode()->getKey(); - spreadLight(bank, pos); - } -} -#endif +const MapNode VoxelManipulator::ContentIgnoreNode = MapNode(CONTENT_IGNORE); -#if 1 /* Lights neighbors of from_nodes, collects all them and then goes on recursively. @@ -714,6 +579,5 @@ void VoxelManipulator::spreadLight(enum LightBank bank, if(!lighted_nodes.empty()) spreadLight(bank, lighted_nodes, nodemgr); } -#endif //END diff --git a/src/voxel.h b/src/voxel.h index 52274ac19..58ad39be4 100644 --- a/src/voxel.h +++ b/src/voxel.h @@ -213,7 +213,7 @@ public: return; } - assert(contains(a)); + assert(contains(a)); // pre-condition // Take back area, XY inclusive { @@ -413,10 +413,21 @@ public: } // Stuff explodes if non-emerged area is touched with this. // Emerge first, and check VOXELFLAG_NO_DATA if appropriate. - MapNode & getNodeRefUnsafe(v3s16 p) + MapNode & getNodeRefUnsafe(const v3s16 &p) { return m_data[m_area.index(p)]; } + + const MapNode & getNodeRefUnsafeCheckFlags(const v3s16 &p) + { + s32 index = m_area.index(p); + + if (m_flags[index] & VOXELFLAG_NO_DATA) + return ContentIgnoreNode; + + return m_data[index]; + } + u8 & getFlagsRefUnsafe(v3s16 p) { return m_flags[m_area.index(p)]; @@ -569,6 +580,8 @@ public: */ u8 *m_flags; + static const MapNode ContentIgnoreNode; + //TODO: Use these or remove them //TODO: Would these make any speed improvement? //bool m_pressure_route_valid; diff --git a/src/wieldmesh.cpp b/src/wieldmesh.cpp index 4ddae36d4..bc2977a0e 100644 --- a/src/wieldmesh.cpp +++ b/src/wieldmesh.cpp @@ -17,7 +17,6 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "main.h" #include "settings.h" #include "wieldmesh.h" #include "inventory.h" @@ -26,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "nodedef.h" #include "mesh.h" #include "mapblock_mesh.h" -#include "tile.h" +#include "client/tile.h" #include "log.h" #include "util/numeric.h" #include <map> @@ -35,10 +34,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #define WIELD_SCALE_FACTOR 30.0 #define WIELD_SCALE_FACTOR_EXTRUDED 40.0 -#define MIN_EXTRUSION_MESH_RESOLUTION 32 // not 16: causes too many "holes" +#define MIN_EXTRUSION_MESH_RESOLUTION 16 #define MAX_EXTRUSION_MESH_RESOLUTION 512 -static scene::IMesh* createExtrusionMesh(int resolution_x, int resolution_y) +static scene::IMesh *createExtrusionMesh(int resolution_x, int resolution_y) { const f32 r = 0.5; @@ -115,7 +114,9 @@ static scene::IMesh* createExtrusionMesh(int resolution_x, int resolution_y) mesh->addMeshBuffer(buf); buf->drop(); scaleMesh(mesh, scale); // also recalculates bounding box - return mesh; + scene::IMesh *newmesh = createForsythOptimizedMesh(mesh); + mesh->drop(); + return newmesh; } /* @@ -170,7 +171,7 @@ public: if (it == m_extrusion_meshes.end()) { // no viable resolution found; use largest one it = m_extrusion_meshes.find(MAX_EXTRUSION_MESH_RESOLUTION); - assert(it != m_extrusion_meshes.end()); + sanity_check(it != m_extrusion_meshes.end()); } scene::IMesh *mesh = it->second; @@ -231,7 +232,7 @@ WieldMeshSceneNode::WieldMeshSceneNode( WieldMeshSceneNode::~WieldMeshSceneNode() { - assert(g_extrusion_mesh_cache); + sanity_check(g_extrusion_mesh_cache); if (g_extrusion_mesh_cache->drop()) g_extrusion_mesh_cache = NULL; } @@ -260,14 +261,20 @@ void WieldMeshSceneNode::setCube(const TileSpec tiles[6], } void WieldMeshSceneNode::setExtruded(const std::string &imagename, - v3f wield_scale, ITextureSource *tsrc) + v3f wield_scale, ITextureSource *tsrc, u8 num_frames) { video::ITexture *texture = tsrc->getTexture(imagename); if (!texture) { changeToMesh(NULL); return; } + core::dimension2d<u32> dim = texture->getSize(); + // Detect animation texture and pull off top frame instead of using entire thing + if (num_frames > 1) { + u32 frame_height = dim.Height / num_frames; + dim = core::dimension2d<u32>(dim.Width, frame_height); + } scene::IMesh *mesh = g_extrusion_mesh_cache->create(dim); changeToMesh(mesh); mesh->drop(); @@ -276,7 +283,9 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, // Customize material video::SMaterial &material = m_meshnode->getMaterial(0); - material.setTexture(0, texture); + material.setTexture(0, tsrc->getTexture(imagename)); + material.TextureLayer[0].TextureWrapU = video::ETC_CLAMP_TO_EDGE; + material.TextureLayer[0].TextureWrapV = video::ETC_CLAMP_TO_EDGE; material.MaterialType = m_material_type; material.setFlag(video::EMF_BACK_FACE_CULLING, true); // Enable bi/trilinear filtering only for high resolution textures @@ -292,35 +301,29 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, #if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2 material.setFlag(video::EMF_USE_MIP_MAPS, false); #endif - -#if 0 -//// TODO(RealBadAngel): Reactivate when shader is added for wield items - if (m_enable_shaders) - material.setTexture(2, tsrc->getTexture("disable_img.png")); -#endif + if (m_enable_shaders) { + material.setTexture(2, tsrc->getShaderFlagsTexture(false)); + } } void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef) { ITextureSource *tsrc = gamedef->getTextureSource(); IItemDefManager *idef = gamedef->getItemDefManager(); - //IShaderSource *shdrsrc = gamedef->getShaderSource(); + IShaderSource *shdrsrc = gamedef->getShaderSource(); INodeDefManager *ndef = gamedef->getNodeDefManager(); const ItemDefinition &def = item.getDefinition(idef); const ContentFeatures &f = ndef->get(def.name); content_t id = ndef->getId(def.name); -#if 0 -//// TODO(RealBadAngel): Reactivate when shader is added for wield items if (m_enable_shaders) { - u32 shader_id = shdrsrc->getShader("nodes_shader", TILE_MATERIAL_BASIC, NDT_NORMAL); + u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL); m_material_type = shdrsrc->getShaderInfo(shader_id).material; } -#endif // If wield_image is defined, it overrides everything else if (def.wield_image != "") { - setExtruded(def.wield_image, def.wield_scale, tsrc); + setExtruded(def.wield_image, def.wield_scale, tsrc, 1); return; } // Handle nodes @@ -336,11 +339,11 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef) } else if (f.drawtype == NDT_AIRLIKE) { changeToMesh(NULL); } else if (f.drawtype == NDT_PLANTLIKE) { - setExtruded(tsrc->getTextureName(f.tiles[0].texture_id), def.wield_scale, tsrc); + setExtruded(tsrc->getTextureName(f.tiles[0].texture_id), def.wield_scale, tsrc, f.tiles[0].animation_frame_count); } else if (f.drawtype == NDT_NORMAL || f.drawtype == NDT_ALLFACES) { setCube(f.tiles, def.wield_scale, tsrc); } else { - MeshMakeData mesh_make_data(gamedef); + MeshMakeData mesh_make_data(gamedef, false); MapNode mesh_make_node(id, 255, 0); mesh_make_data.fillSingleNode(&mesh_make_node); MapBlockMesh mapblock_mesh(&mesh_make_data, v3s16(0, 0, 0)); @@ -350,8 +353,13 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef) def.wield_scale * WIELD_SCALE_FACTOR / (BS * f.visual_scale)); } - for (u32 i = 0; i < m_meshnode->getMaterialCount(); ++i) { - assert(i < 6); + u32 material_count = m_meshnode->getMaterialCount(); + if (material_count > 6) { + errorstream << "WieldMeshSceneNode::setItem: Invalid material " + "count " << material_count << ", truncating to 6" << std::endl; + material_count = 6; + } + for (u32 i = 0; i < material_count; ++i) { video::SMaterial &material = m_meshnode->getMaterial(i); material.setFlag(video::EMF_BACK_FACE_CULLING, true); material.setFlag(video::EMF_BILINEAR_FILTER, m_bilinear_filter); @@ -364,8 +372,6 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef) material.setTexture(0, f.tiles[i].texture); } material.MaterialType = m_material_type; -#if 0 -//// TODO(RealBadAngel): Reactivate when shader is added for wield items if (m_enable_shaders) { if (f.tiles[i].normal_texture) { if (animated) { @@ -374,17 +380,14 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, IGameDef *gamedef) } else { material.setTexture(1, f.tiles[i].normal_texture); } - material.setTexture(2, tsrc->getTexture("enable_img.png")); - } else { - material.setTexture(2, tsrc->getTexture("disable_img.png")); } + material.setTexture(2, f.tiles[i].flags_texture); } -#endif } return; } else if (def.inventory_image != "") { - setExtruded(def.inventory_image, def.wield_scale, tsrc); + setExtruded(def.inventory_image, def.wield_scale, tsrc, 1); return; } @@ -396,6 +399,7 @@ void WieldMeshSceneNode::setColor(video::SColor color) { assert(!m_lighting); setMeshColor(m_meshnode->getMesh(), color); + shadeMeshFaces(m_meshnode->getMesh()); } void WieldMeshSceneNode::render() diff --git a/src/wieldmesh.h b/src/wieldmesh.h index b7739f18c..3f4f4fc04 100644 --- a/src/wieldmesh.h +++ b/src/wieldmesh.h @@ -41,13 +41,16 @@ public: void setCube(const TileSpec tiles[6], v3f wield_scale, ITextureSource *tsrc); void setExtruded(const std::string &imagename, - v3f wield_scale, ITextureSource *tsrc); + v3f wield_scale, ITextureSource *tsrc, u8 num_frames); void setItem(const ItemStack &item, IGameDef *gamedef); // Sets the vertex color of the wield mesh. // Must only be used if the constructor was called with lighting = false void setColor(video::SColor color); + scene::IMesh *getMesh() + { return m_meshnode->getMesh(); } + virtual void render(); virtual const core::aabbox3d<f32>& getBoundingBox() const |